|
Bogdan Timofte
authored
2 weeks ago
|
1
|
# Meter Entity
|
|
|
2
|
|
|
|
3
|
Reprezentarea unui contor Bluetooth conectat (UM25C, UM34C, TC66C).
|
|
|
4
|
|
|
|
5
|
## Responsabilități
|
|
|
6
|
|
|
|
7
|
- Modelarea unui dispozitiv USB power meter BT
|
|
|
8
|
- Gestionarea stării de conectare şi comunicare
|
|
|
9
|
- Citirea măsurătorilor de la dispozitiv
|
|
|
10
|
- Persistență metadate conector (device, timestamp, expiry)
|
|
|
11
|
|
|
|
12
|
## Invarianți
|
|
|
13
|
|
|
|
14
|
- **MUST**: Un Meter are un `id` (UUID) unic în aplicaţie
|
|
|
15
|
- **MUST**: Un Meter cu `macAddress` nu poate fi duplicat în Core Data (Cloud-Kit safe)
|
|
|
16
|
- **MUST**: Starea `OperationalState` este monoton crescătoare: `notPresent → ... → dataIsAvailable`
|
|
|
17
|
- **MUST**: Dacă `OperationalState` este `peripheralConnected` sau mai mare, trebuie să existe o conexiune BT activă
|
|
|
18
|
- **SHOULD**: Un Meter inactiv (fără măsurători > 2 ore) ar trebui să se reconecteze automat
|
|
|
19
|
- **MAY**: Temperature unit preference (Celsius/Fahrenheit) poate fi schimbat oricând
|
|
|
20
|
|
|
|
21
|
## Estados operaţionale
|
|
|
22
|
|
|
|
23
|
```
|
|
|
24
|
notPresent
|
|
|
25
|
↓
|
|
|
26
|
peripheralNotConnected
|
|
|
27
|
↓
|
|
|
28
|
peripheralConnectionPending
|
|
|
29
|
↓
|
|
|
30
|
peripheralConnected
|
|
|
31
|
↓
|
|
|
32
|
peripheralReady
|
|
|
33
|
↓
|
|
|
34
|
comunicating ↔ dataIsAvailable
|
|
|
35
|
```
|
|
|
36
|
|
|
|
37
|
## API Public
|
|
|
38
|
|
|
|
39
|
### Proprietăţi
|
|
|
40
|
|
|
|
41
|
| Proprietate | Tip | Descriere | Observaţii |
|
|
|
42
|
|---|---|---|---|
|
|
|
43
|
| `id` | UUID | Identificator unic | Generat la creare |
|
|
|
44
|
| `macAddress` | String | Adresa MAC BT | De ex: "AA:BB:CC:DD:EE:FF" |
|
|
|
45
|
| `meter` | Model | Tipul: UM25C, UM34C, TC66C | Immutable după creare |
|
|
|
46
|
| `meterName` | String | Nume ales de utilizator | Persistent în Core Data |
|
|
|
47
|
| `operationalState` | OperationalState | Starea curentă | Published, triggers UI updates |
|
|
|
48
|
| `lastDataPoint` | Measurement? | Ultima măsurătoare | Nullable |
|
|
|
49
|
| `connectionMetadata` | ConnectionMetadata? | Info conexiune curentă | Device, timestamp, expiry |
|
|
|
50
|
| `discoveryMetadata` | DiscoveryMetadata? | Info descoperire BT | Last seen, seen by |
|
|
|
51
|
| `temperatureUnit` | TemperatureUnitPreference | Unitatei temperatură | Celsius/Fahrenheit |
|
|
|
52
|
|
|
|
53
|
### Metode
|
|
|
54
|
|
|
|
55
|
```swift
|
|
|
56
|
// Conectare
|
|
|
57
|
func connect()
|
|
|
58
|
// MUST: tranzițe starea la peripheralConnectionPending
|
|
|
59
|
// SHOULD: se conectează în max 5s
|
|
|
60
|
|
|
|
61
|
func disconnect()
|
|
|
62
|
// MUST: tranzițe starea la peripheralNotConnected
|
|
|
63
|
// MUST: eliberează resurse BT
|
|
|
64
|
|
|
|
65
|
// Citire măsurători
|
|
|
66
|
func requestMeasurement() -> Bool
|
|
|
67
|
// SHOULD: retur true dacă cererea a fost trimisă
|
|
|
68
|
// SHOULD: se așteaptă răspuns în max 3s (timeout)
|
|
|
69
|
// MUST: nu trimite dacă starea este < comunicating
|
|
|
70
|
|
|
|
71
|
func processMeasurementData(_ data: Data) -> Measurement?
|
|
|
72
|
// Parsează payload-ul de la dispozitiv
|
|
|
73
|
// MUST: retur nil dacă payload-ul e invalid
|
|
|
74
|
// SHOULD: loghează checksum errors
|
|
|
75
|
|
|
|
76
|
// Gestionare sesiuni
|
|
|
77
|
func startChargeRecord(for device: ChargedDevice) -> ChargeRecord
|
|
|
78
|
// Inițiază o nouă sesiune de încărcare
|
|
|
79
|
|
|
|
80
|
func endChargeRecord(_ record: ChargeRecord)
|
|
|
81
|
// Finalizează sesiunea
|
|
|
82
|
|
|
|
83
|
// Naming
|
|
|
84
|
func renameToMeter(_ newName: String)
|
|
|
85
|
// MUST: actualizează proprietatea şi persistă în Core Data
|
|
|
86
|
```
|
|
|
87
|
|
|
|
88
|
## Comportamente critice
|
|
|
89
|
|
|
|
90
|
### Reconexiune automată
|
|
|
91
|
|
|
|
92
|
- **MUST**: Dacă `OperationalState < peripheralConnected`, retry-ează conectarea
|
|
|
93
|
- **SHOULD**: Backoff exponential: 1s, 2s, 4s, 8s, max 60s
|
|
|
94
|
- **SHOULD**: Anulează retry-urile dacă utilizatorul deconectează manual
|
|
|
95
|
|
|
|
96
|
### Timeout pe măsurători
|
|
|
97
|
|
|
|
98
|
- **MUST**: Dacă nu primim răspuns în 3s după cerere, timeout-ul măsurătorii
|
|
|
99
|
- **SHOULD**: Loghează timeout-urile pentru debugging
|
|
|
100
|
- **MAY**: Incrementează contorul de failed requests
|
|
|
101
|
|
|
|
102
|
### Stare după disconnecţie accidentală
|
|
|
103
|
|
|
|
104
|
- **MUST**: Dacă BT drop-ă accidental, starea revine la `peripheralNotConnected`
|
|
|
105
|
- **SHOULD**: Încearcă reconexiune automată (backoff)
|
|
|
106
|
- **MAY**: Notifică UI-ul cu banner "Meter disconnected"
|
|
|
107
|
|
|
|
108
|
### Validare MAC address
|
|
|
109
|
|
|
|
110
|
- **MUST**: MAC address trebuie să fie format valid (XX:XX:XX:XX:XX:XX)
|
|
|
111
|
- **MUST**: Dacă MAC e invalid, Meter nu poate fi creat
|
|
|
112
|
|
|
|
113
|
## Testare
|
|
|
114
|
|
|
|
115
|
### Unit tests
|
|
|
116
|
|
|
|
117
|
```swift
|
|
|
118
|
// Stări operaţionale
|
|
|
119
|
test_operationalStateTransition()
|
|
|
120
|
test_stateMonotonicity()
|
|
|
121
|
|
|
|
122
|
// Conectare
|
|
|
123
|
test_connectInitiatesPeripheralConnectionPending()
|
|
|
124
|
test_disconnectCleansUpResources()
|
|
|
125
|
test_reconnectWithBackoff()
|
|
|
126
|
|
|
|
127
|
// Măsurători
|
|
|
128
|
test_requestMeasurementFailsIfStateInvalid()
|
|
|
129
|
test_processMeasurementDataWithValidPayload()
|
|
|
130
|
test_measurementTimeout()
|
|
|
131
|
|
|
|
132
|
// Naming
|
|
|
133
|
test_renameUpdatesPersistence()
|
|
|
134
|
```
|
|
|
135
|
|
|
|
136
|
### Integration tests
|
|
|
137
|
|
|
|
138
|
- [ ] Meter apare în Sidebar după scan
|
|
|
139
|
- [ ] Meter se reconectează după BT drop
|
|
|
140
|
- [ ] Măsurătorile se salvează pentru ChargedDevice
|
|
|
141
|
- [ ] Temperature unit e persistent între app restarts
|
|
|
142
|
|
|
|
143
|
## Dependenţe
|
|
|
144
|
|
|
|
145
|
- `BluetoothManager`: gestionează Core Bluetooth
|
|
|
146
|
- `ChargeInsightsStore`: salvează măsurătorile
|
|
|
147
|
- `AppData`: CloudKit sync
|
|
|
148
|
|
|
|
149
|
## Note
|
|
|
150
|
|
|
|
151
|
- Legat: [CloudKit Sync](./CloudKitSync.md) (persistență MAC, name)
|
|
|
152
|
- Legat: [Bluetooth Discovery](./BluetoothDiscovery.md) (scan, advertisement)
|