Newer Older
249 lines | 6.533kb
Bogdan Timofte authored 2 weeks ago
1
# Core Operations
2

            
3
Operaţii principale care orchestrează aplicaţia.
4

            
5
## AppData Lifecycle
6

            
7
`AppData` este singleton-ul global care orchestrează toate subsistemele.
8

            
9
### Inițializare
10

            
11
```swift
12
let appData = AppData()
13
```
14

            
15
- **MUST**: Se instanțiază în `AppDelegate.application(_:didFinishLaunchingWithOptions:)`
16
- **MUST**: Se accesează din `SceneDelegate.scene(_:willConnectTo:options:)`
17
- **MUST**: Inițializează `NSPersistentCloudKitContainer` cu name `"CKModel"`
18
- **MUST**: Inițializează `CloudDeviceSettingsStore` (wraps NSManagedObjectContext)
19

            
20
### Startup sequence
21

            
22
1. `AppDelegate.application(_:didFinishLaunchingWithOptions:)`:
23
   - Crează `AppData()`
24
   - Inițializează Core Data stack
25
   - Încarcă migrări din `cloudStoreRebuildVersion`
26

            
27
2. `SceneDelegate.scene(_:willConnectTo:options:)`:
28
   - Apelează `appData.activateCloudDeviceSync(context: sceneContext)`
29
   - Inițiază BT scanning
30
   - Inițiază CloudKit sync
31

            
32
### Shutdown sequence
33

            
34
- **SHOULD**: Finalizează sesiuni active la app terminate
35
- **MUST**: Salvează Core Data context (`saveIfNeeded()`)
36
- **SHOULD**: Deconectează BT graceful
37

            
38
## Bluetooth Connection Management
39

            
40
### Connection state machine
41

            
42
```
43
notPresent
44
  ↓
45
peripheralNotConnected ← (user disconnect)
46
  ↓
47
peripheralConnectionPending
48
  ↓
49
peripheralConnected
50
  ↓
51
peripheralReady
52
  ↓
53
comunicating ↔ dataIsAvailable
54
```
55

            
56
### Operations
57

            
58
```swift
59
// Conectare inițială
60
blutooth.connect(to peripheralUUID: UUID, type: Model)
61
// MUST: inițiază CBCentralManager scan
62
// MUST: se conectează la UUID specific
63
// SHOULD: timeout = 5s
64
// MUST: pe succes, tranzițe la peripheralConnected
65

            
66
// Deconectare
67
bluetooth.disconnect(from meter: Meter)
68
// MUST: anulează reconnect logic
69
// MUST: eliberează resurse
70
// MUST: marchez ca manual disconnect
71

            
72
// Auto-reconnect
73
bluetooth.autoReconnect(meter: Meter, backoff: Backoff)
74
// SHOULD: exponential backoff: 1s, 2s, 4s, 8s, max 60s
75
// MUST: anulează dacă utilizator disconnect manual
76
// MUST: max 3 retry-uri consecutive
77
```
78

            
79
## Measurement Recording
80

            
81
### Session lifecycle
82

            
83
```swift
84
let session = meter.startChargeRecord(for: device)
85
```
86

            
87
1. **Start**: Crează `ChargeRecord(sessionID: UUID(), startTime: now())`
88
2. **Measure**: La fiecare 1-2s:
89
   ```swift
90
   let measurement = meter.lastDataPoint
91
   session.addMeasurement(measurement)
92
   ```
93
3. **End**:
94
   ```swift
95
   meter.endChargeRecord(session)
96
   ```
97
   - Calculează `totalEnergy = ∑(V * A * Δt)`
98
   - Marchez ca `completed`
99
   - Salvează în Core Data
100

            
101
### Invarianţi
102

            
103
- **MUST**: O sesiune activă per meter
104
- **MUST**: Măsurătorile sunt cronologice
105
- **MUST**: `startTime <= now() <= (endTime || ∞)`
106
- **MUST**: Energia ≥ 0
107

            
108
### Recording frequency
109

            
110
- **SHOULD**: Măsurători la ~1Hz (1000ms interval)
111
- **MAY**: Reduce frequency dacă battery low
112
- **SHOULD**: Drop măsurători dacă queue > 100 items
113

            
114
## Cloud Sync
115

            
116
### Main concepts
117

            
118
- **DeviceSettings**: entitate Core Data persistă MAC, name, temperature unit
119
- **CloudDeviceSettingsStore**: wrapper pe NSManagedObjectContext
120
- **Rebuild version**: `cloudStoreRebuildVersion` (curent: 3)
121

            
122
### Sync flow
123

            
124
1. **Upload**: Locale changes → Core Data → CloudKit
125
   ```swift
126
   cloudStore.upsertDeviceSettings(
127
       macAddress: "AA:BB:CC:DD:EE:FF",
128
       meterName: "Kitchen Meter",
129
       tc66TemperatureUnit: .celsius,
130
       connectionMetadata: ...
131
   )
132
   ```
133
   - MUST: salvează în Core Data sync
134
   - MUST: CloudKit container replica-ază automat
135

            
136
2. **Download**: CloudKit changes → Core Data → UI
137
   - **MUST**: NSPersistentCloudKitContainer sincronizează automat
138
   - **SHOULD**: Refresh UI după fetch
139

            
140
3. **Conflict resolution**: Dacă două modificări simultane
141
   - **MUST**: Mergi pe "last write wins" dacă timestamps diferă
142
   - **SHOULD**: Loghează conflictul pentru debugging
143

            
144
### Discovery throttling
145

            
146
- **MUST**: Max 1 discovery per device per 120s
147
- **SHOULD**: Previne CloudKit thrashing din repeat BT advertisements
148
- **MUST**: Reseta timer dacă device disconnect-ează
149

            
150
## Device Settings Persistence
151

            
152
### MAC address mapping
153

            
154
```swift
155
// CloudDeviceSettingsStore.swift
156
func upsertDeviceSettings(
157
    macAddress: String,
158
    meterName: String,
159
    tc66TemperatureUnit: TemperatureUnitPreference?,
160
    connectionMetadata: ConnectionMetadata?,
161
    discoveryMetadata: DiscoveryMetadata?
162
)
163
```
164

            
165
- **MUST**: macAddress optional (datorită migration)
166
- **MUST**: meterName unic pe meter (no duplicates)
167
- **SHOULD**: connectionMetadata.expiresAt = now() + 24h
168

            
169
### Legacy KV Store
170

            
171
`NSUbiquitousKeyValueStore` sincronizează:
172
- `MeterNames`: {macAddress → meterName}
173
- `TC66TemperatureUnits`: {macAddress → unit}
174

            
175
- **MUST**: Menţinere backward compat
176
- **SHOULD**: Migrate pe Core Data la first sync
177
- **MAY**: Drop la următoarea major version
178

            
179
## Error handling
180

            
181
### Bluetooth errors
182

            
183
```
184
Error: BT peripheral not found
185
→ Retry connect cu backoff
186
→ Max 3 retries, apoi fallback offline mode
187

            
188
Error: Characteristic not found
189
→ Log error
190
→ Mark meter incompatible (UI warning)
191

            
192
Error: Read timeout
193
→ Retry measurement request
194
→ Increment timeout counter
195
```
196

            
197
### CloudKit errors
198

            
199
```
200
Error: Network unavailable
201
→ Queue pending changes
202
→ Retry la next network change
203

            
204
Error: Conflict detected
205
→ Merge data (last write wins)
206
→ Retry sync
207

            
208
Error: Quota exceeded
209
→ Log error
210
→ Notify user (prune old data?)
211
```
212

            
213
## Testare
214

            
215
### Unit tests
216

            
217
```swift
218
test_appDataInitializes_CoreDataAndCloudKit()
219
test_bluetoothConnectInitiatesProperStateTransition()
220
test_bluetoothDisconnect_CleansUp()
221
test_autoReconnectBackoff_ExponentialScaling()
222
test_sessionStartCreatesValidRecord()
223
test_sessionEndCalculatesEnergy()
224
test_cloudSyncUpsert_SavesToCoreData()
225
test_discoveryThrottling_RespectsTiming()
226
test_conflictResolution_LastWriteWins()
227
```
228

            
229
### Integration tests
230

            
231
- [ ] Full app startup (BT + CloudKit)
232
- [ ] Connect meter → start session → record measurements → end session
233
- [ ] Disconnect meter → reconnect avec backoff
234
- [ ] Device settings sync CloudKit → other device
235
- [ ] Offline mode (queue changes, sync later)
236

            
237
## Dependenţe
238

            
239
- `AppData`: orchestrează tot
240
- `BluetoothManager`: gestionează Core Bluetooth
241
- `CloudDeviceSettingsStore`: Core Data + CloudKit
242
- `ChargeInsightsStore`: sesiuni persistente
243
- `ConsumptionMonitorStore`: monitorizare consum
244

            
245
## Notes
246

            
247
- Legat: [CloudKit Sync](./CloudKitSync.md)
248
- Legat: [Bluetooth Discovery](./BluetoothDiscovery.md)
249
- Referință: AppDelegate.swift, SceneDelegate.swift