# Bluetooth Discovery

Mecanismul de descoperire şi conectare a dispozitivelor Bluetooth.

## Arhitectură

### Components

1. **BluetoothManager**: coordonează CBCentralManager şi descoperirile
2. **BluetoothRadio**: interfață spre Core Bluetooth low-level
3. **BluetoothSerial**: comunicare pe caracteristici UART
4. **MeterCapabilities**: detectează tip meter (UM25C, UM34C, TC66C)

## Scanning

### Lifecycle

```
User opens app
  ↓
SceneDelegate calls appData.activateCloudDeviceSync()
  ↓
BluetoothManager starts CBCentralManager
  ↓
CBCentralManager begins scanning
  ↓
didDiscoverPeripheral → filter by service UUIDs
  ↓
Check device profile (class, capabilities)
  ↓
Add to discoveredMeters list (UI refresh)
```

### Service UUIDs

```swift
let targetServices = [
    CBUUID(string: "FFF0"),    // UM25C, UM34C
    CBUUID(string: "180D"),    // TC66C (generic service)
]
```

- **MUST**: Scan cu service UUIDs specifice (nu scan generic)
- **SHOULD**: Filter prin manufacturer data dacă posibil
- **REASON**: Reduce energy drain, reduce noise

### Advertisement parsing

Meter advertise-ază:
- Device name: de ex "UM25C-XXXX" sau "TC66-XXXX"
- Services: FFF0 (UM series) sau 180D (TC66)
- Manufacturer data: RDTech identifier

```swift
if let manufacturerData = advertisement[CBAdvertisementDataManufacturerDataKey] as? Data {
    let manufacturerId = manufacturerData.withUnsafeBytes { $0.load(as: UInt16.self) }
    if manufacturerId == 0x5449 { // RDTech
        // Possible UM meter
    }
}
```

- **SHOULD**: Parse manufacturer data pentru identificare rapidă
- **MAY**: Use device name ca fallback (less reliable)

### Device identification

```swift
func identifyMeterType(name: String, services: [CBUUID]) -> Model {
    if name.contains("UM25C") || services.contains(FFF0) {
        return .UM25C
    } else if name.contains("UM34C") || services.contains(FFF0) {
        return .UM34C
    } else if name.contains("TC66") || services.contains(180D) {
        return .TC66C
    }
    return .unknown
}
```

- **MUST**: Trebuie să identific corect tipul de meter
- **SHOULD**: Use name + services (redundant checks)
- **MUST**: Fallback la `.unknown` dacă uncertain

## Connection

### Initiation

```
User taps meter in list
  ↓
AppData calls meter.connect()
  ↓
BluetoothManager calls cbCentralManager.connect(peripheral)
  ↓
didConnect: state → peripheralConnected
  ↓
Discover services & characteristics
```

### Service/Characteristic discovery

```swift
// For UM series (FFF0)
let targetService = CBUUID(string: "FFF0")
let readCharacteristic = CBUUID(string: "FFF1")  // read measurements
let writeCharacteristic = CBUUID(string: "FFF2") // send commands

// For TC66 (180D)
let heartRate = CBUUID(string: "2A37")  // uses standard HRM characteristic
```

- **MUST**: Discover services → discover characteristics (ordered)
- **MUST**: Find expected characteristics, fail if not found
- **SHOULD**: Subscribe to notifications pentru updates
- **TIMEOUT**: 5s max pentru service discovery

### State transitions

```
peripheralConnected
  ↓
discoveringServices (discovering FFF0 / 180D)
  ↓
discoveringCharacteristics (discovering FFF1, FFF2 / 2A37)
  ↓
peripheralReady (services + characteristics found)
  ↓
comunicating ↔ dataIsAvailable (steady state)
```

- **MUST**: Stare monotonă crescătoare (no rollback)
- **SHOULD**: Log state transitions
- **MUST**: Fail gracefully dacă characteristics nu sunt găsite

## Communication

### Measurement requests

**UM series** (UM25C, UM34C):
```swift
// Send command to FFF2 (write characteristic)
let command = UMProtocol.buildMeasurementRequest()
peripheral.writeValue(command, for: writeCharacteristic, type: .withResponse)

// Receive on FFF1 (read characteristic)
// Parse payload (voltage, current, power, temperature, etc.)
let measurement = UMProtocol.parseMeasurement(data)
```

**TC66C**:
```swift
// Uses standard HRM (Heart Rate Measurement) characteristic
// Value format: flags byte + heart_rate_value (2 bytes)
// Repurposed for power data: MSB = watts, LSB = amps (approximation)
let measurement = TC66Protocol.parseMeasurement(data)
```

- **MUST**: Parse protocol-specific payloads
- **MUST**: Validate checksum (if applicable)
- **SHOULD**: Handle invalid/truncated payloads gracefully
- **TIMEOUT**: 3s per measurement request

### Write commands

UM series supports commands:
- Request measurement: `0x00 0xF0 0xA0 0x1B` (+ checksum)
- Set time: `0x02 ...` (timestamp)
- Set calibration: (advanced)

```swift
peripheral.writeValue(
    command,
    for: writeCharacteristic,
    type: .withResponse  // MUST: wait for ACK
)
```

- **MUST**: Use `.withResponse` pentru command-uri critice
- **MAY**: Use `.withoutResponse` pentru bulk writes
- **SHOULD**: Validate response ACK

## Disconnection handling

### Intentional disconnect

```
User taps "Disconnect"
  ↓
meter.disconnect()
  ↓
BluetoothManager calls cbCentralManager.cancelPeripheralConnection()
  ↓
didDisconnect: state → peripheralNotConnected
```

- **MUST**: Anulează pending operations
- **MUST**: Anulează auto-reconnect logic
- **MUST**: Eliberează callbacks

### Unintentional disconnect (BT drop)

```
BT device disconnects (out of range / powered off / interference)
  ↓
didDisconnect event (didDisconnect reason: optional)
  ↓
state → peripheralNotConnected
  ↓
Auto-reconnect logic starts (with backoff)
```

- **MUST**: Detecta unintentional drops (log reason dacă available)
- **SHOULD**: Incepe auto-reconnect cu backoff exponential
- **MUST**: Anulează dacă user disconnect manual (flag)

## Auto-reconnect

### Backoff strategy

```
Attempt 1: 1s delay
Attempt 2: 2s delay
Attempt 3: 4s delay
Attempt 4: 8s delay
Attempt 5: 16s delay
Attempt 6: 32s delay
Attempt 7+: 60s delay (capped)
Max attempts: 3
```

- **MUST**: Exponential backoff (2^n, capped at 60s)
- **MUST**: Max 3 consecutive retry-uri
- **MUST**: Stop retry dacă user disconnect manual
- **SHOULD**: Log fiecare retry attempt

### Trigger conditions

- **MUST**: Activate automatic reconnect doar dacă user conectase anterior
- **MUST**: Disable dacă user disconnect manual
- **MUST**: Disable dacă app goes background > 10 min
- **SHOULD**: Resume reconnect dacă app returns foreground

## Testing

### Unit tests

```swift
test_scanFiltersByServiceUUIDs()
test_deviceIdentification_UM25C()
test_deviceIdentification_UM34C()
test_deviceIdentification_TC66C()
test_connectionStateTransitions()
test_serviceDiscovery_UM25C()
test_characteristicDiscovery_UM25C()
test_measurementParsing_ValidPayload()
test_measurementParsing_InvalidPayload()
test_disconnectCleansUp()
test_unintentionalDropDetected()
test_autoReconnectBackoff_Exponential()
test_autoReconnectStops_OnManualDisconnect()
```

### Integration tests

- [ ] Scan detects available meters
- [ ] Device type identified correctly
- [ ] Connect → service discover → ready (full flow)
- [ ] Measurement received and parsed
- [ ] Unintentional drop detected + reconnect
- [ ] Auto-reconnect respects backoff timing
- [ ] Manual disconnect stops auto-reconnect

## Error handling

### Scan errors

```
Error: CBError.unknown
Error: CBError.managerStatePoweredOff
Error: CBError.invalidParameters
```

Handling:
- Retry scan periodically
- Notify UI: "Bluetooth unavailable"

### Connection errors

```
Error: peripheral not found
→ Retry with backoff

Error: timeout (no services found)
→ Disconnect + retry

Error: security/pairing required
→ Notify user: "Pair device in Settings"
```

### Communication errors

```
Error: write failed
→ Retry measurement request

Error: invalid payload
→ Log error, skip measurement

Error: characteristic not found
→ Disconnect + mark incompatible
```

## Dependencies

- `CoreBluetooth`: CBCentralManager, CBPeripheral
- `UMProtocol`: payload parsing for UM series
- `TC66Protocol`: payload parsing for TC66C
- `MeterCapabilities`: device type detection
- `AppData`: orchestration

## References

- [UMProtocol.swift](../../USB%20Meter/Model/UMProtocol.swift)
- [TC66Protocol.swift](../../USB%20Meter/Model/TC66Protocol.swift)
- [BluetoothManager.swift](../../USB%20Meter/Model/BluetoothManager.swift)
- [External documentation](https://sigrok.org/wiki/RDTech_UM_series)
- [TC66C reverse-engineering notes](../Research%20Resources/Payload%20Notes/TC66C%20Transport%20and%20Payload%20Working%20Note.md)
