Initial clean code

This commit is contained in:
pe1hvh
2026-03-09 17:53:29 +01:00
commit d8a7947c6b
111 changed files with 24373 additions and 0 deletions
Binary file not shown.
+494
View File
@@ -0,0 +1,494 @@
# MeshCore GUI — BLE Architecture
## Overzicht
Dit document beschrijft hoe MeshCore GUI communiceert met een MeshCore T1000-E device via Bluetooth Low Energy (BLE), welke libraries daarbij betrokken zijn, en hoe de volledige stack van hardware tot applicatielogica in elkaar zit.
---
## 1. De BLE Stack
De communicatie loopt door 7 lagen, van hardware tot GUI:
```
┌─────────────────────────────────────────────────────┐
│ 7. meshcore_gui (applicatie) │
│ BLEWorker, EventHandler, CommandHandler │
├─────────────────────────────────────────────────────┤
│ 6. meshcore (meshcore_py) (protocol) │
│ MeshCore.connect(), commands.*, event callbacks │
├─────────────────────────────────────────────────────┤
│ 5. bleak (BLE abstractie) │
│ BleakClient.connect(), start_notify(), write() │
├─────────────────────────────────────────────────────┤
│ 4. dbus_fast (D-Bus async client) │
│ MessageBus, ServiceInterface, method calls │
├─────────────────────────────────────────────────────┤
│ 3. D-Bus system bus (IPC) │
│ /org/bluez/hci0, org.bluez.Device1, Agent1 │
├─────────────────────────────────────────────────────┤
│ 2. BlueZ (bluetoothd) (Bluetooth daemon) │
│ GATT, pairing, bonding, device management │
├─────────────────────────────────────────────────────┤
│ 1. Linux Kernel + Hardware (HCI driver + radio) │
│ hci0, Bluetooth 5.0 chip (RPi5 built-in / USB) │
└─────────────────────────────────────────────────────┘
```
---
## 2. Libraries en hun rol
### 2.1 bleak (Bluetooth Low Energy platform Agnostic Klient)
**Doel:** Cross-platform Python BLE library. Abstracteert de platform-specifieke BLE backends achter één API.
| Platform | Backend | Communicatie |
|----------|---------|-------------|
| Linux | BlueZ via D-Bus | `dbus_fast``bluetoothd` |
| macOS | CoreBluetooth | Objective-C bridge via `pyobjc` |
| Windows | WinRT | Windows Runtime BLE API |
**Hoe bleak werkt op Linux:**
Bleak praat *niet* rechtstreeks met de Bluetooth hardware. In plaats daarvan stuurt bleak D-Bus berichten naar de BlueZ daemon (`bluetoothd`), die op zijn beurt de kernel HCI driver aanstuurt. Elk bleak-commando wordt vertaald naar een D-Bus method call:
| bleak API | D-Bus call naar BlueZ |
|-----------|----------------------|
| `BleakClient.connect()` | `org.bluez.Device1.Connect()` |
| `BleakClient.disconnect()` | `org.bluez.Device1.Disconnect()` |
| `BleakClient.start_notify(uuid, callback)` | `org.bluez.GattCharacteristic1.StartNotify()` |
| `BleakClient.write_gatt_char(uuid, data)` | `org.bluez.GattCharacteristic1.WriteValue()` |
| `BleakScanner.discover()` | `org.bluez.Adapter1.StartDiscovery()` |
Bleak installeert automatisch `dbus_fast` als dependency.
### 2.2 dbus_fast
**Doel:** Async Python D-Bus library. Biedt twee functies:
1. **Client** — Bleak gebruikt `dbus_fast.aio.MessageBus` om D-Bus method calls naar BlueZ te sturen (connect, read, write, notify). Dit is intern aan bleak; onze code raakt dit niet direct aan.
2. **Server** — Onze `ble_agent.py` gebruikt `dbus_fast.service.ServiceInterface` om een D-Bus service te *exporteren*: de PIN agent die BlueZ aanroept wanneer het device pairing nodig heeft.
Doordat `dbus_fast` al een dependency van `bleak` is, hoeven we geen extra packages te installeren.
### 2.3 meshcore (meshcore_py)
**Doel:** MeshCore protocol implementatie. Vertaalt hoge-niveau commando's naar BLE GATT read/write operaties.
**GATT Service:** MeshCore devices gebruiken de **Nordic UART Service (NUS)** voor communicatie:
| Characteristic | UUID | Richting | Functie |
|---------------|------|----------|---------|
| RX | `6e400002-b5a3-f393-e0a9-e50e24dcca9e` | Host → Device | Commando's schrijven |
| TX | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | Device → Host | Responses/events ontvangen (notify) |
**Protocol:** De meshcore library:
- Serialiseert commando's (appstart, device_query, get_contacts, send_msg, etc.) naar binaire packets
- Schrijft deze naar de NUS RX characteristic via `bleak.write_gatt_char()`
- Luistert op de NUS TX characteristic via `bleak.start_notify()` voor responses en async events
- Deserialiseert binaire responses terug naar Python dicts met event types
**Communicatiepatroon:** Request-response met async events:
```
meshcore_gui → meshcore → bleak → D-Bus → BlueZ → HCI → Radio → T1000-E
meshcore_gui ← meshcore ← bleak ← D-Bus ← BlueZ ← HCI ← Radio ←──────┘
```
Commando's zijn *subscribe-before-send*: meshcore registreert eerst een notify handler op de TX characteristic, stuurt dan het commando via de RX characteristic, en wacht op de response via de notify callback. Dit voorkomt race conditions waarbij de response arriveert voordat de listener klaar is (gefixt in meshcore_py PR #52).
### 2.4 meshcoredecoder
**Doel:** Decodering van ruwe LoRa packets die via de RX log binnenkomen. Decrypts packets met channel keys en extraheert route-informatie (path hashes, hop data). Gebruikt door `PacketDecoder` in de BLE events layer.
### 2.5 Onze eigen BLE modules
| Module | Library | Functie |
|--------|---------|---------|
| `ble_agent.py` | `dbus_fast` (server) | Exporteert `org.bluez.Agent1` interface op D-Bus; beantwoordt PIN requests |
| `ble_reconnect.py` | `dbus_fast` (client) | `remove_bond()`: roept `org.bluez.Adapter1.RemoveDevice()` aan via D-Bus |
| `worker.py` | `meshcore` + `bleak` (indirect) | `MeshCore.connect()`, command loop, disconnect detection |
| `commands.py` | `meshcore` | `mc.commands.send_msg()`, `send_advert()`, etc. |
| `events.py` | `meshcore` | Callbacks: `CHANNEL_MSG_RECV`, `RX_LOG_DATA`, etc. |
---
## 3. De drie D-Bus gesprekken
Onze applicatie voert drie soorten D-Bus communicatie uit, elk met een ander doel:
### 3.1 PIN Agent (dbus_fast — server mode)
**Probleem:** Wanneer BlueZ een BLE device wil pairen dat een PIN vereist, zoekt het op de D-Bus naar een geregistreerde Agent die de PIN kan leveren. Zonder agent faalt de pairing met "failed to discover services".
**Oplossing:** `ble_agent.py` exporteert een `org.bluez.Agent1` service op D-Bus path `/meshcore/ble_agent`. BlueZ roept methodes aan op onze agent:
```
BlueZ (bluetoothd) Onze Agent (ble_agent.py)
│ │
│── RegisterAgent(/meshcore/ble_agent) ──→│ (bij startup)
│← OK ──────────────────────────────────│
│ │
│── RequestDefaultAgent() ──────────────→│
│← OK ──────────────────────────────────│
│ │
│ ... device wil pairen ... │
│ │
│── RequestPinCode(/org/bluez/.../dev) ─→│
│← "123456" ───────────────────────────│
│ │
│ ... pairing succesvol ... │
```
### 3.2 Bond Cleanup (dbus_fast — client mode)
**Probleem:** Na een disconnect slaat BlueZ de pairing keys op (een "bond"). Bij reconnectie gebruikt BlueZ deze oude keys, maar het device heeft ze verworpen → "PIN or Key Missing" error.
**Oplossing:** `ble_reconnect.py` stuurt een D-Bus method call naar BlueZ:
```python
# Equivalent van: bluetoothctl remove FF:05:D6:71:83:8D
bus.call(
destination="org.bluez",
path="/org/bluez/hci0", # Adapter
interface="org.bluez.Adapter1",
member="RemoveDevice",
signature="o",
body=["/org/bluez/hci0/dev_FF_05_D6_71_83_8D"] # Device object path
)
```
### 3.3 BLE Communicatie (bleak → dbus_fast — client mode)
Bleak stuurt intern D-Bus berichten voor alle BLE operaties. Dit is transparant voor onze code — wij roepen alleen de bleak API aan, bleak vertaalt naar D-Bus:
```python
# Onze code (via meshcore):
await mc.connect(ble_address)
# Wat bleak intern doet:
await bus.call("org.bluez.Device1.Connect()")
await bus.call("org.bluez.GattCharacteristic1.StartNotify()") # TX char
await bus.call("org.bluez.GattCharacteristic1.WriteValue()") # RX char
```
---
## 4. Sequence Diagram — Volledige BLE Lifecycle
Het onderstaande diagram toont de complete levenscyclus van een BLE sessie, van startup tot disconnect en reconnect.
```mermaid
sequenceDiagram
autonumber
participant GUI as GUI Thread<br/>(NiceGUI)
participant Worker as BLEWorker<br/>(asyncio thread)
participant Agent as BleAgentManager<br/>(ble_agent.py)
participant Reconnect as ble_reconnect.py
participant MC as meshcore<br/>(MeshCore)
participant Bleak as bleak<br/>(BleakClient)
participant DBus as D-Bus<br/>(system bus)
participant BZ as BlueZ<br/>(bluetoothd)
participant Dev as T1000-E<br/>(BLE device)
Note over Worker,Dev: ═══ FASE 1: PIN Agent Registratie ═══
Worker->>Agent: start(pin="123456")
Agent->>DBus: connect to system bus
Agent->>DBus: export /meshcore/ble_agent<br/>(org.bluez.Agent1)
Agent->>DBus: RegisterAgent(/meshcore/ble_agent, "KeyboardOnly")
DBus->>BZ: RegisterAgent
BZ-->>DBus: OK
Agent->>DBus: RequestDefaultAgent(/meshcore/ble_agent)
DBus->>BZ: RequestDefaultAgent
BZ-->>DBus: OK
Agent-->>Worker: Agent ready
Note over Worker,Dev: ═══ FASE 2: Bond Cleanup ═══
Worker->>Reconnect: remove_bond("FF:05:...")
Reconnect->>DBus: Adapter1.RemoveDevice(/org/bluez/hci0/dev_FF_05_...)
DBus->>BZ: RemoveDevice
BZ-->>DBus: OK (of "Does Not Exist" → genegeerd)
Reconnect-->>Worker: Bond removed
Note over Worker,Dev: ═══ FASE 3: Verbinding + GATT Discovery ═══
Worker->>MC: MeshCore.connect("FF:05:...")
MC->>Bleak: BleakClient.connect()
Bleak->>DBus: Device1.Connect()
DBus->>BZ: Connect
BZ->>Dev: BLE Connection Request
Dev-->>BZ: Connection Accepted
Note over BZ,Dev: Pairing vereist (PIN)
BZ->>DBus: Agent1.RequestPinCode(device_path)
DBus->>Agent: RequestPinCode()
Agent-->>DBus: "123456"
DBus-->>BZ: PIN
BZ->>Dev: Pairing met PIN
Dev-->>BZ: Pairing OK + Encryption active
BZ->>BZ: GATT Service Discovery
BZ-->>Bleak: Services resolved (NUS: 6e400001-...)
Bleak-->>MC: Connected
MC->>Bleak: start_notify(TX: 6e400003-...)
Bleak->>DBus: GattCharacteristic1.StartNotify()
DBus->>BZ: StartNotify
BZ-->>Bleak: Notifications enabled
MC->>Bleak: write(RX: 6e400002-..., appstart_cmd)
Bleak->>DBus: GattCharacteristic1.WriteValue(data)
DBus->>BZ: WriteValue
BZ->>Dev: BLE Write (appstart)
Dev-->>BZ: BLE Notify (response)
BZ-->>Bleak: Notification callback
Bleak-->>MC: Event: SELF_INFO
MC-->>Worker: self_info = {name, pubkey, freq, ...}
Note over Worker,Dev: ═══ FASE 4: Data Laden ═══
Worker->>MC: commands.send_device_query()
MC->>Bleak: write(RX, device_query_cmd)
Bleak->>DBus: WriteValue
DBus->>BZ: WriteValue
BZ->>Dev: device_query
Dev-->>BZ: notify(response)
BZ-->>Bleak: callback
Bleak-->>MC: Event: DEVICE_QUERY
MC-->>Worker: {firmware, tx_power, ...}
Worker->>MC: commands.get_channel(0..N)
MC-->>Worker: {name, channel_secret}
Worker->>MC: commands.get_contacts()
MC-->>Worker: [{pubkey, name, type, lat, lon}, ...]
Worker->>GUI: SharedData.set_channels(), set_contacts(), ...
GUI->>GUI: Timer 500ms → update UI
Note over Worker,Dev: ═══ FASE 5: Operationele Loop ═══
loop Elke 500ms
GUI->>GUI: _update_ui() → lees SharedData snapshot
end
loop Command Queue
GUI->>Worker: put_command("send_msg", {text, channel})
Worker->>MC: commands.send_msg(channel, text)
MC->>Bleak: write(RX, send_msg_packet)
Bleak->>DBus: WriteValue
DBus->>BZ: WriteValue
BZ->>Dev: BLE Write
end
loop Async Events (continu)
Dev-->>BZ: BLE Notify (incoming mesh message)
BZ-->>Bleak: Notification callback
Bleak-->>MC: raw data
MC-->>Worker: Event: CHANNEL_MSG_RECV
Worker->>Worker: EventHandler → dedup → SharedData.add_message()
Worker->>GUI: message_updated = True
end
Note over Worker,Dev: ═══ FASE 6: Disconnect + Auto-Reconnect ═══
Dev--xBZ: BLE link lost (~2 uur timeout)
BZ-->>Bleak: Disconnected callback
Bleak-->>MC: Connection lost
MC-->>Worker: Exception: "not connected" / "disconnected"
Worker->>Worker: Disconnect gedetecteerd
loop Reconnect (max 5 pogingen, lineaire backoff)
Worker->>Reconnect: remove_bond("FF:05:...")
Reconnect->>DBus: Adapter1.RemoveDevice
DBus->>BZ: RemoveDevice
BZ-->>Reconnect: OK
Worker->>Worker: wait(attempt × 5s)
Worker->>MC: MeshCore.connect("FF:05:...")
MC->>Bleak: BleakClient.connect()
Bleak->>DBus: Device1.Connect()
DBus->>BZ: Connect
BZ->>Dev: BLE Connection Request
alt Verbinding succesvol
Dev-->>BZ: Connected + Paired (PIN via Agent)
BZ-->>Bleak: Connected
Worker->>Worker: Re-wire event handlers + reload data
Worker->>GUI: set_status("✅ Reconnected")
else Verbinding mislukt
BZ-->>Bleak: Error
Worker->>Worker: Volgende poging...
end
end
Note over Worker,Dev: ═══ FASE 7: Cleanup ═══
Worker->>Agent: stop()
Agent->>DBus: UnregisterAgent(/meshcore/ble_agent)
Agent->>DBus: disconnect()
```
---
## 5. GATT Communicatie in Detail
### 5.1 Nordic UART Service (NUS)
Het MeshCore device adverteert één primaire BLE service: de **Nordic UART Service**. Dit is een de-facto standaard voor seriële communicatie over BLE, oorspronkelijk ontworpen door Nordic Semiconductor.
```
Service: Nordic UART Service
UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e
├── RX Characteristic (Write Without Response)
│ UUID: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
│ Richting: Host → Device
│ Gebruik: Commando's sturen naar het T1000-E
│ Max grootte: 20 bytes per write (MTU-afhankelijk)
└── TX Characteristic (Notify)
UUID: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
Richting: Device → Host
Gebruik: Responses en async events ontvangen
Activatie: bleak.start_notify() → BlueZ StartNotify
```
### 5.2 Dataflow per commando
Een typisch commando (bijv. "stuur een mesh bericht") doorloopt deze stappen:
```
1. GUI: gebruiker typt bericht, klikt Send
2. GUI → SharedData: put_command("send_msg", {channel: 0, text: "Hello"})
3. BLEWorker: haalt command uit queue
4. meshcore: serialiseert naar binary packet
→ [header][cmd_type][channel_idx][payload_len][utf8_text]
5. bleak: write_gatt_char(NUS_RX_UUID, packet)
6. dbus_fast: GattCharacteristic1.WriteValue(packet_bytes, {})
7. BlueZ: schrijft naar HCI controller
8. HCI: stuurt BLE PDU via radio
9. T1000-E: ontvangt, verwerkt, stuurt via LoRa mesh
```
De response (of een inkomend mesh bericht) loopt de omgekeerde route:
```
1. T1000-E: ontvangt mesh bericht via LoRa
2. T1000-E → HCI: BLE notification met data
3. BlueZ: ontvangt notification, stuurt via D-Bus
4. dbus_fast: roept de notify callback in bleak aan
5. bleak: roept de registered callback in meshcore aan
6. meshcore: deserialiseert binary → Event(type, payload)
7. BLEWorker: EventHandler verwerkt het event
→ dedup check → naam resolutie → path hash extractie
8. SharedData: add_message(Message.incoming(...))
9. GUI: ziet message_updated flag bij volgende 500ms poll
```
### 5.3 Waarom subscribe-before-send?
BLE notifications zijn asynchroon. Als meshcore eerst het commando schrijft en *daarna* `start_notify()` aanroept, kan de response al verloren zijn gegaan voordat de listener klaar is. Dit was een bug in de originele meshcore_py die leidde tot ~2 minuten startup delay:
```
❌ Oud (race condition):
write(RX, command) → device antwoordt direct
start_notify(TX) → te laat, response is al weg
✅ Nieuw (PR #52):
start_notify(TX) → listener actief
write(RX, command) → device antwoordt
callback fired → response ontvangen
```
---
## 6. Pairing en Bonding
### 6.1 Waarom PIN pairing?
Het T1000-E device is geconfigureerd met BLE PIN `123456` (instelbaar via firmware). Dit voorkomt dat willekeurige BLE clients verbinden. BlueZ ondersteunt PIN pairing via het **Agent** mechanisme.
### 6.2 Agent interface
BlueZ definieert de `org.bluez.Agent1` D-Bus interface. Onze `BluezAgent` class implementeert deze callbacks:
| Methode | D-Bus Signature | Wanneer aangeroepen | Ons antwoord |
|---------|----------------|--------------------:|-------------|
| `RequestPinCode` | `o → s` | Device vraagt PIN | `"123456"` |
| `RequestPasskey` | `o → u` | Device vraagt numeriek passkey | `123456` (uint32) |
| `DisplayPasskey` | `oqu → ` | Passkey tonen (info only) | (log only) |
| `RequestConfirmation` | `ou → ` | Bevestig passkey match | (accept) |
| `AuthorizeService` | `os → ` | Service autorisatie | (accept) |
| `Cancel` | ` → ` | Pairing geannuleerd | (log only) |
| `Release` | ` → ` | Agent niet meer nodig | (cleanup) |
### 6.3 Het bonding probleem
Na succesvolle pairing slaat BlueZ de encryption keys op in `/var/lib/bluetooth/<adapter>/<device>/info`. Dit heet een "bond". Bij de volgende connectie probeert BlueZ deze keys te hergebruiken.
**Het probleem:** Het T1000-E verwerpt na ~2 uur de BLE verbinding (firmware timeout). BlueZ heeft nog de oude bond keys, maar het device heeft ze verworpen. Resultaat:
```
BlueZ: "Ik heb keys voor dit device, gebruik die"
T1000-E: "Ik ken deze keys niet → Reject (PIN or Key Missing)"
BlueZ: "Pairing failed"
```
**De oplossing:** Vóór elke reconnectie verwijderen we de bond:
```
remove_bond() → Adapter1.RemoveDevice() → BlueZ wist keys
connect() → BlueZ: "Geen keys, start verse pairing"
Agent → levert PIN → verse pairing succesvol
```
---
## 7. D-Bus Policy
Normale gebruikers mogen standaard niet alle BlueZ D-Bus interfaces aanspreken. De D-Bus policy file (`/etc/dbus-1/system.d/meshcore-ble.conf`) geeft de gebruiker die de service draait toestemming:
```xml
<busconfig>
<policy user="hans">
<allow send_destination="org.bluez"/>
<allow send_interface="org.bluez.Agent1"/>
<allow send_interface="org.bluez.AgentManager1"/>
</policy>
</busconfig>
```
Zonder deze policy:
- `bleak` kan nog steeds verbinden (bleak gebruikt een standaard D-Bus policy die al met BlueZ meekomt)
- Onze **agent** kan zich niet registreren → PIN pairing faalt
- Onze **bond cleanup** kan `RemoveDevice` niet aanroepen
---
## 8. Samenvatting Dependencies
```
meshcore-gui
├── nicegui → Web UI framework (onze GUI)
├── meshcore → MeshCore protocol (commando's, events)
│ └── bleak → BLE abstractie (connect, notify, write)
│ └── dbus_fast → D-Bus communicatie (naar BlueZ)
├── meshcoredecoder → LoRa packet decryptie + route extractie
└── (geen extra) → ble_agent.py en ble_reconnect.py
gebruiken dbus_fast die al via bleak
geïnstalleerd is
```
Alle BLE-gerelateerde functionaliteit draait op precies **vier Python packages**: `bleak`, `dbus_fast`, `meshcore`, en `meshcoredecoder`. Er zijn geen system-level dependencies meer nodig buiten `bluez` zelf (geen `bluez-tools`, geen `bt-agent`).
# Legacy BLE Document
> **Note:** This document describes the BLE architecture and is retained for historical reference. The current GUI uses USB serial.
+491
View File
@@ -0,0 +1,491 @@
# MeshCore GUI — BLE Architecture
## Overzicht
Dit document beschrijft hoe MeshCore GUI communiceert met een MeshCore T1000-E device via Bluetooth Low Energy (BLE), welke libraries daarbij betrokken zijn, en hoe de volledige stack van hardware tot applicatielogica in elkaar zit.
---
## 1. De BLE Stack
De communicatie loopt door 7 lagen, van hardware tot GUI:
```
┌─────────────────────────────────────────────────────┐
│ 7. meshcore_gui (applicatie) │
│ BLEWorker, EventHandler, CommandHandler │
├─────────────────────────────────────────────────────┤
│ 6. meshcore (meshcore_py) (protocol) │
│ MeshCore.connect(), commands.*, event callbacks │
├─────────────────────────────────────────────────────┤
│ 5. bleak (BLE abstractie) │
│ BleakClient.connect(), start_notify(), write() │
├─────────────────────────────────────────────────────┤
│ 4. dbus_fast (D-Bus async client) │
│ MessageBus, ServiceInterface, method calls │
├─────────────────────────────────────────────────────┤
│ 3. D-Bus system bus (IPC) │
│ /org/bluez/hci0, org.bluez.Device1, Agent1 │
├─────────────────────────────────────────────────────┤
│ 2. BlueZ (bluetoothd) (Bluetooth daemon) │
│ GATT, pairing, bonding, device management │
├─────────────────────────────────────────────────────┤
│ 1. Linux Kernel + Hardware (HCI driver + radio) │
│ hci0, Bluetooth 5.0 chip (RPi5 built-in / USB) │
└─────────────────────────────────────────────────────┘
```
---
## 2. Libraries en hun rol
### 2.1 bleak (Bluetooth Low Energy platform Agnostic Klient)
**Doel:** Cross-platform Python BLE library. Abstracteert de platform-specifieke BLE backends achter één API.
| Platform | Backend | Communicatie |
|----------|---------|-------------|
| Linux | BlueZ via D-Bus | `dbus_fast` → `bluetoothd` |
| macOS | CoreBluetooth | Objective-C bridge via `pyobjc` |
| Windows | WinRT | Windows Runtime BLE API |
**Hoe bleak werkt op Linux:**
Bleak praat *niet* rechtstreeks met de Bluetooth hardware. In plaats daarvan stuurt bleak D-Bus berichten naar de BlueZ daemon (`bluetoothd`), die op zijn beurt de kernel HCI driver aanstuurt. Elk bleak-commando wordt vertaald naar een D-Bus method call:
| bleak API | D-Bus call naar BlueZ |
|-----------|----------------------|
| `BleakClient.connect()` | `org.bluez.Device1.Connect()` |
| `BleakClient.disconnect()` | `org.bluez.Device1.Disconnect()` |
| `BleakClient.start_notify(uuid, callback)` | `org.bluez.GattCharacteristic1.StartNotify()` |
| `BleakClient.write_gatt_char(uuid, data)` | `org.bluez.GattCharacteristic1.WriteValue()` |
| `BleakScanner.discover()` | `org.bluez.Adapter1.StartDiscovery()` |
Bleak installeert automatisch `dbus_fast` als dependency.
### 2.2 dbus_fast
**Doel:** Async Python D-Bus library. Biedt twee functies:
1. **Client** — Bleak gebruikt `dbus_fast.aio.MessageBus` om D-Bus method calls naar BlueZ te sturen (connect, read, write, notify). Dit is intern aan bleak; onze code raakt dit niet direct aan.
2. **Server** — Onze `ble_agent.py` gebruikt `dbus_fast.service.ServiceInterface` om een D-Bus service te *exporteren*: de PIN agent die BlueZ aanroept wanneer het device pairing nodig heeft.
Doordat `dbus_fast` al een dependency van `bleak` is, hoeven we geen extra packages te installeren.
### 2.3 meshcore (meshcore_py)
**Doel:** MeshCore protocol implementatie. Vertaalt hoge-niveau commando's naar BLE GATT read/write operaties.
**GATT Service:** MeshCore devices gebruiken de **Nordic UART Service (NUS)** voor communicatie:
| Characteristic | UUID | Richting | Functie |
|---------------|------|----------|---------|
| RX | `6e400002-b5a3-f393-e0a9-e50e24dcca9e` | Host → Device | Commando's schrijven |
| TX | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | Device → Host | Responses/events ontvangen (notify) |
**Protocol:** De meshcore library:
- Serialiseert commando's (appstart, device_query, get_contacts, send_msg, etc.) naar binaire packets
- Schrijft deze naar de NUS RX characteristic via `bleak.write_gatt_char()`
- Luistert op de NUS TX characteristic via `bleak.start_notify()` voor responses en async events
- Deserialiseert binaire responses terug naar Python dicts met event types
**Communicatiepatroon:** Request-response met async events:
```
meshcore_gui → meshcore → bleak → D-Bus → BlueZ → HCI → Radio → T1000-E
meshcore_gui ← meshcore ← bleak ← D-Bus ← BlueZ ← HCI ← Radio ←──────┘
```
Commando's zijn *subscribe-before-send*: meshcore registreert eerst een notify handler op de TX characteristic, stuurt dan het commando via de RX characteristic, en wacht op de response via de notify callback. Dit voorkomt race conditions waarbij de response arriveert voordat de listener klaar is (gefixt in meshcore_py PR #52).
### 2.4 meshcoredecoder
**Doel:** Decodering van ruwe LoRa packets die via de RX log binnenkomen. Decrypts packets met channel keys en extraheert route-informatie (path hashes, hop data). Gebruikt door `PacketDecoder` in de BLE events layer.
### 2.5 Onze eigen BLE modules
| Module | Library | Functie |
|--------|---------|---------|
| `ble_agent.py` | `dbus_fast` (server) | Exporteert `org.bluez.Agent1` interface op D-Bus; beantwoordt PIN requests |
| `ble_reconnect.py` | `dbus_fast` (client) | `remove_bond()`: roept `org.bluez.Adapter1.RemoveDevice()` aan via D-Bus |
| `worker.py` | `meshcore` + `bleak` (indirect) | `MeshCore.connect()`, command loop, disconnect detection |
| `commands.py` | `meshcore` | `mc.commands.send_msg()`, `send_advert()`, etc. |
| `events.py` | `meshcore` | Callbacks: `CHANNEL_MSG_RECV`, `RX_LOG_DATA`, etc. |
---
## 3. De drie D-Bus gesprekken
Onze applicatie voert drie soorten D-Bus communicatie uit, elk met een ander doel:
### 3.1 PIN Agent (dbus_fast — server mode)
**Probleem:** Wanneer BlueZ een BLE device wil pairen dat een PIN vereist, zoekt het op de D-Bus naar een geregistreerde Agent die de PIN kan leveren. Zonder agent faalt de pairing met "failed to discover services".
**Oplossing:** `ble_agent.py` exporteert een `org.bluez.Agent1` service op D-Bus path `/meshcore/ble_agent`. BlueZ roept methodes aan op onze agent:
```
BlueZ (bluetoothd) Onze Agent (ble_agent.py)
│ │
│── RegisterAgent(/meshcore/ble_agent) ──→│ (bij startup)
│← OK ──────────────────────────────────│
│ │
│── RequestDefaultAgent() ──────────────→│
│← OK ──────────────────────────────────│
│ │
│ ... device wil pairen ... │
│ │
│── RequestPinCode(/org/bluez/.../dev) ─→│
│← "123456" ───────────────────────────│
│ │
│ ... pairing succesvol ... │
```
### 3.2 Bond Cleanup (dbus_fast — client mode)
**Probleem:** Na een disconnect slaat BlueZ de pairing keys op (een "bond"). Bij reconnectie gebruikt BlueZ deze oude keys, maar het device heeft ze verworpen → "PIN or Key Missing" error.
**Oplossing:** `ble_reconnect.py` stuurt een D-Bus method call naar BlueZ:
```python
# Equivalent van: bluetoothctl remove FF:05:D6:71:83:8D
bus.call(
destination="org.bluez",
path="/org/bluez/hci0", # Adapter
interface="org.bluez.Adapter1",
member="RemoveDevice",
signature="o",
body=["/org/bluez/hci0/dev_FF_05_D6_71_83_8D"] # Device object path
)
```
### 3.3 BLE Communicatie (bleak → dbus_fast — client mode)
Bleak stuurt intern D-Bus berichten voor alle BLE operaties. Dit is transparant voor onze code — wij roepen alleen de bleak API aan, bleak vertaalt naar D-Bus:
```python
# Onze code (via meshcore):
await mc.connect(ble_address)
# Wat bleak intern doet:
await bus.call("org.bluez.Device1.Connect()")
await bus.call("org.bluez.GattCharacteristic1.StartNotify()") # TX char
await bus.call("org.bluez.GattCharacteristic1.WriteValue()") # RX char
```
---
## 4. Sequence Diagram — Volledige BLE Lifecycle
Het onderstaande diagram toont de complete levenscyclus van een BLE sessie, van startup tot disconnect en reconnect.
```mermaid
sequenceDiagram
autonumber
participant GUI as GUI Thread<br/>(NiceGUI)
participant Worker as BLEWorker<br/>(asyncio thread)
participant Agent as BleAgentManager<br/>(ble_agent.py)
participant Reconnect as ble_reconnect.py
participant MC as meshcore<br/>(MeshCore)
participant Bleak as bleak<br/>(BleakClient)
participant DBus as D-Bus<br/>(system bus)
participant BZ as BlueZ<br/>(bluetoothd)
participant Dev as T1000-E<br/>(BLE device)
Note over Worker,Dev: ═══ FASE 1: PIN Agent Registratie ═══
Worker->>Agent: start(pin="123456")
Agent->>DBus: connect to system bus
Agent->>DBus: export /meshcore/ble_agent<br/>(org.bluez.Agent1)
Agent->>DBus: RegisterAgent(/meshcore/ble_agent, "KeyboardOnly")
DBus->>BZ: RegisterAgent
BZ-->>DBus: OK
Agent->>DBus: RequestDefaultAgent(/meshcore/ble_agent)
DBus->>BZ: RequestDefaultAgent
BZ-->>DBus: OK
Agent-->>Worker: Agent ready
Note over Worker,Dev: ═══ FASE 2: Bond Cleanup ═══
Worker->>Reconnect: remove_bond("FF:05:...")
Reconnect->>DBus: Adapter1.RemoveDevice(/org/bluez/hci0/dev_FF_05_...)
DBus->>BZ: RemoveDevice
BZ-->>DBus: OK (of "Does Not Exist" → genegeerd)
Reconnect-->>Worker: Bond removed
Note over Worker,Dev: ═══ FASE 3: Verbinding + GATT Discovery ═══
Worker->>MC: MeshCore.connect("FF:05:...")
MC->>Bleak: BleakClient.connect()
Bleak->>DBus: Device1.Connect()
DBus->>BZ: Connect
BZ->>Dev: BLE Connection Request
Dev-->>BZ: Connection Accepted
Note over BZ,Dev: Pairing vereist (PIN)
BZ->>DBus: Agent1.RequestPinCode(device_path)
DBus->>Agent: RequestPinCode()
Agent-->>DBus: "123456"
DBus-->>BZ: PIN
BZ->>Dev: Pairing met PIN
Dev-->>BZ: Pairing OK + Encryption active
BZ->>BZ: GATT Service Discovery
BZ-->>Bleak: Services resolved (NUS: 6e400001-...)
Bleak-->>MC: Connected
MC->>Bleak: start_notify(TX: 6e400003-...)
Bleak->>DBus: GattCharacteristic1.StartNotify()
DBus->>BZ: StartNotify
BZ-->>Bleak: Notifications enabled
MC->>Bleak: write(RX: 6e400002-..., appstart_cmd)
Bleak->>DBus: GattCharacteristic1.WriteValue(data)
DBus->>BZ: WriteValue
BZ->>Dev: BLE Write (appstart)
Dev-->>BZ: BLE Notify (response)
BZ-->>Bleak: Notification callback
Bleak-->>MC: Event: SELF_INFO
MC-->>Worker: self_info = {name, pubkey, freq, ...}
Note over Worker,Dev: ═══ FASE 4: Data Laden ═══
Worker->>MC: commands.send_device_query()
MC->>Bleak: write(RX, device_query_cmd)
Bleak->>DBus: WriteValue
DBus->>BZ: WriteValue
BZ->>Dev: device_query
Dev-->>BZ: notify(response)
BZ-->>Bleak: callback
Bleak-->>MC: Event: DEVICE_QUERY
MC-->>Worker: {firmware, tx_power, ...}
Worker->>MC: commands.get_channel(0..N)
MC-->>Worker: {name, channel_secret}
Worker->>MC: commands.get_contacts()
MC-->>Worker: [{pubkey, name, type, lat, lon}, ...]
Worker->>GUI: SharedData.set_channels(), set_contacts(), ...
GUI->>GUI: Timer 500ms → update UI
Note over Worker,Dev: ═══ FASE 5: Operationele Loop ═══
loop Elke 500ms
GUI->>GUI: _update_ui() → lees SharedData snapshot
end
loop Command Queue
GUI->>Worker: put_command("send_msg", {text, channel})
Worker->>MC: commands.send_msg(channel, text)
MC->>Bleak: write(RX, send_msg_packet)
Bleak->>DBus: WriteValue
DBus->>BZ: WriteValue
BZ->>Dev: BLE Write
end
loop Async Events (continu)
Dev-->>BZ: BLE Notify (incoming mesh message)
BZ-->>Bleak: Notification callback
Bleak-->>MC: raw data
MC-->>Worker: Event: CHANNEL_MSG_RECV
Worker->>Worker: EventHandler → dedup → SharedData.add_message()
Worker->>GUI: message_updated = True
end
Note over Worker,Dev: ═══ FASE 6: Disconnect + Auto-Reconnect ═══
Dev--xBZ: BLE link lost (~2 uur timeout)
BZ-->>Bleak: Disconnected callback
Bleak-->>MC: Connection lost
MC-->>Worker: Exception: "not connected" / "disconnected"
Worker->>Worker: Disconnect gedetecteerd
loop Reconnect (max 5 pogingen, lineaire backoff)
Worker->>Reconnect: remove_bond("FF:05:...")
Reconnect->>DBus: Adapter1.RemoveDevice
DBus->>BZ: RemoveDevice
BZ-->>Reconnect: OK
Worker->>Worker: wait(attempt × 5s)
Worker->>MC: MeshCore.connect("FF:05:...")
MC->>Bleak: BleakClient.connect()
Bleak->>DBus: Device1.Connect()
DBus->>BZ: Connect
BZ->>Dev: BLE Connection Request
alt Verbinding succesvol
Dev-->>BZ: Connected + Paired (PIN via Agent)
BZ-->>Bleak: Connected
Worker->>Worker: Re-wire event handlers + reload data
Worker->>GUI: set_status("✅ Reconnected")
else Verbinding mislukt
BZ-->>Bleak: Error
Worker->>Worker: Volgende poging...
end
end
Note over Worker,Dev: ═══ FASE 7: Cleanup ═══
Worker->>Agent: stop()
Agent->>DBus: UnregisterAgent(/meshcore/ble_agent)
Agent->>DBus: disconnect()
```
---
## 5. GATT Communicatie in Detail
### 5.1 Nordic UART Service (NUS)
Het MeshCore device adverteert één primaire BLE service: de **Nordic UART Service**. Dit is een de-facto standaard voor seriële communicatie over BLE, oorspronkelijk ontworpen door Nordic Semiconductor.
```
Service: Nordic UART Service
UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e
├── RX Characteristic (Write Without Response)
│ UUID: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
│ Richting: Host → Device
│ Gebruik: Commando's sturen naar het T1000-E
│ Max grootte: 20 bytes per write (MTU-afhankelijk)
└── TX Characteristic (Notify)
UUID: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
Richting: Device → Host
Gebruik: Responses en async events ontvangen
Activatie: bleak.start_notify() → BlueZ StartNotify
```
### 5.2 Dataflow per commando
Een typisch commando (bijv. "stuur een mesh bericht") doorloopt deze stappen:
```
1. GUI: gebruiker typt bericht, klikt Send
2. GUI → SharedData: put_command("send_msg", {channel: 0, text: "Hello"})
3. BLEWorker: haalt command uit queue
4. meshcore: serialiseert naar binary packet
→ [header][cmd_type][channel_idx][payload_len][utf8_text]
5. bleak: write_gatt_char(NUS_RX_UUID, packet)
6. dbus_fast: GattCharacteristic1.WriteValue(packet_bytes, {})
7. BlueZ: schrijft naar HCI controller
8. HCI: stuurt BLE PDU via radio
9. T1000-E: ontvangt, verwerkt, stuurt via LoRa mesh
```
De response (of een inkomend mesh bericht) loopt de omgekeerde route:
```
1. T1000-E: ontvangt mesh bericht via LoRa
2. T1000-E → HCI: BLE notification met data
3. BlueZ: ontvangt notification, stuurt via D-Bus
4. dbus_fast: roept de notify callback in bleak aan
5. bleak: roept de registered callback in meshcore aan
6. meshcore: deserialiseert binary → Event(type, payload)
7. BLEWorker: EventHandler verwerkt het event
→ dedup check → naam resolutie → path hash extractie
8. SharedData: add_message(Message.incoming(...))
9. GUI: ziet message_updated flag bij volgende 500ms poll
```
### 5.3 Waarom subscribe-before-send?
BLE notifications zijn asynchroon. Als meshcore eerst het commando schrijft en *daarna* `start_notify()` aanroept, kan de response al verloren zijn gegaan voordat de listener klaar is. Dit was een bug in de originele meshcore_py die leidde tot ~2 minuten startup delay:
```
❌ Oud (race condition):
write(RX, command) → device antwoordt direct
start_notify(TX) → te laat, response is al weg
✅ Nieuw (PR #52):
start_notify(TX) → listener actief
write(RX, command) → device antwoordt
callback fired → response ontvangen
```
---
## 6. Pairing en Bonding
### 6.1 Waarom PIN pairing?
Het T1000-E device is geconfigureerd met BLE PIN `123456` (instelbaar via firmware). Dit voorkomt dat willekeurige BLE clients verbinden. BlueZ ondersteunt PIN pairing via het **Agent** mechanisme.
### 6.2 Agent interface
BlueZ definieert de `org.bluez.Agent1` D-Bus interface. Onze `BluezAgent` class implementeert deze callbacks:
| Methode | D-Bus Signature | Wanneer aangeroepen | Ons antwoord |
|---------|----------------|--------------------:|-------------|
| `RequestPinCode` | `o → s` | Device vraagt PIN | `"123456"` |
| `RequestPasskey` | `o → u` | Device vraagt numeriek passkey | `123456` (uint32) |
| `DisplayPasskey` | `oqu → ` | Passkey tonen (info only) | (log only) |
| `RequestConfirmation` | `ou → ` | Bevestig passkey match | (accept) |
| `AuthorizeService` | `os → ` | Service autorisatie | (accept) |
| `Cancel` | ` → ` | Pairing geannuleerd | (log only) |
| `Release` | ` → ` | Agent niet meer nodig | (cleanup) |
### 6.3 Het bonding probleem
Na succesvolle pairing slaat BlueZ de encryption keys op in `/var/lib/bluetooth/<adapter>/<device>/info`. Dit heet een "bond". Bij de volgende connectie probeert BlueZ deze keys te hergebruiken.
**Het probleem:** Het T1000-E verwerpt na ~2 uur de BLE verbinding (firmware timeout). BlueZ heeft nog de oude bond keys, maar het device heeft ze verworpen. Resultaat:
```
BlueZ: "Ik heb keys voor dit device, gebruik die"
T1000-E: "Ik ken deze keys niet → Reject (PIN or Key Missing)"
BlueZ: "Pairing failed"
```
**De oplossing:** Vóór elke reconnectie verwijderen we de bond:
```
remove_bond() → Adapter1.RemoveDevice() → BlueZ wist keys
connect() → BlueZ: "Geen keys, start verse pairing"
Agent → levert PIN → verse pairing succesvol
```
---
## 7. D-Bus Policy
Normale gebruikers mogen standaard niet alle BlueZ D-Bus interfaces aanspreken. De D-Bus policy file (`/etc/dbus-1/system.d/meshcore-ble.conf`) geeft de gebruiker die de service draait toestemming:
```xml
<busconfig>
<policy user="hans">
<allow send_destination="org.bluez"/>
<allow send_interface="org.bluez.Agent1"/>
<allow send_interface="org.bluez.AgentManager1"/>
</policy>
</busconfig>
```
Zonder deze policy:
- `bleak` kan nog steeds verbinden (bleak gebruikt een standaard D-Bus policy die al met BlueZ meekomt)
- Onze **agent** kan zich niet registreren → PIN pairing faalt
- Onze **bond cleanup** kan `RemoveDevice` niet aanroepen
---
## 8. Samenvatting Dependencies
```
meshcore-gui
├── nicegui → Web UI framework (onze GUI)
├── meshcore → MeshCore protocol (commando's, events)
│ └── bleak → BLE abstractie (connect, notify, write)
│ └── dbus_fast → D-Bus communicatie (naar BlueZ)
├── meshcoredecoder → LoRa packet decryptie + route extractie
└── (geen extra) → ble_agent.py en ble_reconnect.py
gebruiken dbus_fast die al via bleak
geïnstalleerd is
```
Alle BLE-gerelateerde functionaliteit draait op precies **vier Python packages**: `bleak`, `dbus_fast`, `meshcore`, en `meshcoredecoder`. Er zijn geen system-level dependencies meer nodig buiten `bluez` zelf (geen `bluez-tools`, geen `bt-agent`).
@@ -0,0 +1,639 @@
# BLE Capture Workflow T1000-e — Explanation & Background
> **Note:** This document is BLE-specific and kept for historical reference. The current GUI uses USB serial.
> **Source:** `ble_capture_workflow_t_1000_e.md`
>
> This document is a **companion guide** to the original technical working document. It provides:
> - Didactic explanation of BLE concepts and terminology
> - Background knowledge about GATT services and how they work
> - Context for better understanding future BLE projects
>
> **Intended audience:** Myself, as a long-term reference.
---
## 1. What is this document about?
This document explains the BLE concepts and terminology behind communicating with a **MeshCore T1000-e** radio from a Linux computer. It covers:
- How BLE connections work and how they differ from Classic Bluetooth
- The GATT service model and the Nordic UART Service (NUS) used by MeshCore
- Why BLE session ownership matters and how it can cause connection failures
**The key message in one sentence:**
> Only **one BLE client at a time** can be connected to the T1000-e. If something else is already connected, your connection will fail.
---
## 2. Terms and abbreviations explained
### 2.1 BLE — Bluetooth Low Energy
BLE is an **energy-efficient variant of Bluetooth**, designed for devices that need to run on a battery for months or years.
| Property | Classic Bluetooth | BLE |
|----------|-------------------|-----|
| Power consumption | High | Very low |
| Data rate | High | Low |
| Typical use | Audio, file transfer | Sensors, IoT, MeshCore |
**Analogy:** Classic Bluetooth is like a phone call (constantly connected, high energy). BLE is like sending text messages (brief contact when needed, low energy).
---
### 2.2 GATT — Generic Attribute Profile
GATT is the **structure** through which BLE devices expose their data. Think of it as a **digital bulletin board** with a fixed layout:
```
Service (category)
└── Characteristic (specific data point)
└── Descriptor (additional configuration)
```
**Example for MeshCore:**
```
Nordic UART Service (NUS)
├── RX Characteristic → messages from radio to computer
└── TX Characteristic → messages from computer to radio
```
---
### 2.3 NUS — Nordic UART Service
NUS is a **standard BLE service** developed by Nordic Semiconductor. It simulates an old-fashioned serial port (UART) over Bluetooth.
- **RX** (Receive): Data you **receive** from the device
- **TX** (Transmit): Data you **send** to the device
Note: RX/TX are from the computer's perspective, not the radio's.
#### Is NUS a protocol?
**No.** NUS is a **service specification**, not a protocol. This is an important distinction:
| Level | What is it | Example |
|-------|-----------|---------|
| **Protocol** | Rules for communication | BLE, ATT, GATT |
| **Service** | Collection of related characteristics | NUS, Heart Rate Service |
| **Characteristic** | Specific data point within a service | RX, TX |
**Restaurant analogy:**
| Concept | Restaurant analogy |
|---------|--------------------|
| **Protocol (GATT)** | The rules: you order from the waiter, food comes from the kitchen |
| **Service (NUS)** | A specific menu (e.g. "breakfast menu") |
| **Characteristics** | The individual dishes on that menu |
People often say "we're using the NUS protocol", but strictly speaking **GATT** is the protocol and **NUS** is a service offered via GATT.
---
### 2.4 Other GATT services (official and custom)
NUS is just one of many BLE services. The **Bluetooth SIG** (the organisation behind Bluetooth) defines dozens of official services. In addition, manufacturers can create their own (custom) services.
#### Official services (Bluetooth SIG)
These services have a **16-bit UUID** and are standardised for interoperability:
| Service | UUID | Application |
|---------|------|-------------|
| **Heart Rate Service** | 0x180D | Heart rate monitors, fitness devices |
| **Battery Service** | 0x180F | Reporting battery level |
| **Device Information** | 0x180A | Manufacturer, model number, firmware version |
| **Blood Pressure** | 0x1810 | Blood pressure monitors |
| **Health Thermometer** | 0x1809 | Medical thermometers |
| **Cycling Speed and Cadence** | 0x1816 | Bicycle sensors |
| **Environmental Sensing** | 0x181A | Temperature, humidity, pressure |
| **Glucose** | 0x1808 | Blood glucose meters |
| **HID over GATT** | 0x1812 | Keyboards, mice, gamepads |
| **Proximity** | 0x1802 | "Find My" functionality |
| **Generic Access** | 0x1800 | **Mandatory** — device name and appearance |
#### Custom/vendor-specific services
Manufacturers can define their own services with a **128-bit UUID**. Examples:
| Service | Manufacturer | Application |
|---------|--------------|-------------|
| **Nordic UART Service (NUS)** | Nordic Semiconductor | Serial port over BLE |
| **Apple Notification Center** | Apple | iPhone notifications to wearables |
| **Xiaomi Mi Band Service** | Xiaomi | Fitness tracker communication |
| **MeshCore Companion** | MeshCore | Radio communication (uses NUS) |
#### The difference: 16-bit vs. 128-bit UUID
| Type | Length | Example | Who can create it? |
|------|--------|---------|--------------------|
| **Official (SIG)** | 16-bit | `0x180D` | Bluetooth SIG only |
| **Custom** | 128-bit | `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | Anyone |
The NUS service uses this 128-bit UUID:
```
6e400001-b5a3-f393-e0a9-e50e24dcca9e
```
#### Why this matters
In the MeshCore project we use **NUS** (a custom service) for communication. But when working with other BLE devices — such as a heart rate monitor or a smart thermostat — they typically use **official SIG services**.
The principle remains the same:
1. Discover which services the device offers
2. Find the right characteristic
3. Read, write, or subscribe to notify
---
### 2.5 Notify vs. Read
There are two ways to get data from a BLE device:
| Method | How it works | When to use |
|--------|-------------|-------------|
| **Read** | You actively request data | One-off values (e.g. battery status) |
| **Notify** | Device sends automatically when new data is available | Continuous data stream (e.g. messages) |
**Analogy:**
- **Read** = You call someone and ask "how are you?"
- **Notify** = You automatically receive a WhatsApp message when there's news
For MeshCore captures you use **Notify** — after all, you want to know when a message arrives.
---
### 2.6 CCCD — Client Characteristic Configuration Descriptor
The CCCD is the **on/off switch for Notify**. Technically:
1. Your computer writes a `1` to the CCCD
2. The device now knows: "this client wants notifications"
3. When new data arrives, the device automatically sends a message
**The crucial point:** Only **one client at a time** can activate the CCCD. A second client will receive the error:
```
Notify acquired
```
This means: "someone else has already enabled notify."
---
### 2.7 Pairing, Bonding and Trust
These are three separate steps in the BLE security process:
| Step | What happens | Analogy |
|------|-------------|---------|
| **Pairing** | Devices exchange cryptographic keys | You meet someone and exchange phone numbers |
| **Bonding** | The keys are stored permanently | You save the number in your contacts |
| **Trust** | The system trusts the device automatically | You add someone to your favourites |
After these three steps, you no longer need to enter the PIN code each time.
**Verification on Linux:**
```bash
bluetoothctl info AA:BB:CC:DD:EE:FF | egrep -i "Paired|Bonded|Trusted"
```
Expected output:
```
Paired: yes
Bonded: yes
Trusted: yes
```
---
### 2.8 Ownership — The core problem
**Ownership** is an informal term indicating: "which client currently holds the active GATT session with notify?"
**Analogy:** Think of a walkie-talkie where only one person can listen at a time:
- If GNOME Bluetooth Manager is already connected → it is the "owner"
- If your Python script then tries to connect → it won't get access
**Typical "owners" that cause problems:**
- GNOME Bluetooth GUI (often runs in the background)
- `bluetoothctl connect` (makes bluetoothctl the owner)
- Phone with Bluetooth enabled
- Other BLE apps
---
### 2.9 BlueZ
**BlueZ** is the official Bluetooth stack for Linux. It is the software that handles all Bluetooth communication between your applications and the hardware.
---
### 2.10 Bleak
**Bleak** is a Python library for BLE communication. It builds on top of BlueZ (Linux), Core Bluetooth (macOS) or WinRT (Windows).
---
## 3. BLE versus Classic Bluetooth
A common question: are BLE and "regular" Bluetooth the same thing? The answer is **no** — they are different technologies that happen to share the same name and frequency band.
### 3.1 Two flavours of Bluetooth
Since Bluetooth 4.0 (2010) there are **two separate radio systems** within the Bluetooth standard:
| Name | Technical term | Characteristics |
|------|---------------|-----------------|
| **Classic Bluetooth** | BR/EDR (Basic Rate / Enhanced Data Rate) | High data rate, continuous connection, more power |
| **Bluetooth Low Energy** | BLE (also: Bluetooth Smart) | Low data rate, short bursts, very efficient |
**Crucially:** These are **different radio protocols** that cannot communicate directly with each other.
### 3.2 Protocol and hardware
Bluetooth (both Classic and BLE) encompasses **multiple layers** — it is not just a protocol, but also hardware:
```
┌─────────────────────────────────────────┐
│ SOFTWARE │
│ ┌───────────────────────────────────┐ │
│ │ Application (your code) │ │
│ ├───────────────────────────────────┤ │
│ │ Profiles / GATT Services │ │
│ ├───────────────────────────────────┤ │
│ │ Protocols (ATT, L2CAP, etc.) │ │
│ ├───────────────────────────────────┤ │
│ │ Host Controller Interface (HCI) │ │ ← Software/firmware boundary
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ FIRMWARE │
│ ┌───────────────────────────────────┐ │
│ │ Link Layer / Controller │ │
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ HARDWARE │
│ ┌───────────────────────────────────┐ │
│ │ Radio (2.4 GHz transceiver) │ │
│ ├───────────────────────────────────┤ │
│ │ Antenna │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
### 3.3 Where is the difference?
The difference exists across **multiple layers**, not just the protocol:
| Layer | Classic (BR/EDR) | BLE | Hardware difference? |
|-------|------------------|-----|---------------------|
| **Radio** | GFSK, π/4-DQPSK, 8DPSK | GFSK | **Yes** — different modulation |
| **Channels** | 79 channels, 1 MHz wide | 40 channels, 2 MHz wide | **Yes** — different layout |
| **Link Layer** | LMP (Link Manager Protocol) | LL (Link Layer) | **Yes** — different state machine |
| **Protocols** | L2CAP, RFCOMM, SDP | L2CAP, ATT, GATT | No — software |
### 3.4 Dual-mode devices
The overlap lies in devices that support **both**:
| Device type | Supports | Example |
|-------------|----------|---------|
| **Classic-only** | BR/EDR only | Old headsets, car audio |
| **BLE-only** (Bluetooth Smart) | BLE only | Fitness trackers, sensors, T1000-e |
| **Dual-Mode** (Bluetooth Smart Ready) | Both | Smartphones, laptops, ESP32 |
**Your smartphone** is dual-mode: it can talk to your classic Bluetooth headphones (BR/EDR) and to your MeshCore T1000-e (BLE).
### 3.5 Practical examples
| Scenario | What is used |
|----------|-------------|
| Music to your headphones | **Classic** (A2DP profile) |
| Heart rate from your smartwatch | **BLE** (Heart Rate Service) |
| Sending a file to a laptop | **Classic** (OBEX/FTP profile) |
| Reading the MeshCore T1000-e | **BLE** (NUS service) |
| Hands-free calling in the car | **Classic** (HFP profile) |
| Controlling a smart light | **BLE** (custom GATT service) |
---
## 4. BLE channel layout and frequency hopping
### 4.1 The 40 BLE channels
The 2.4 GHz ISM band runs from **2400 MHz to 2483.5 MHz** (83.5 MHz wide).
BLE divides this into **40 channels of 2 MHz each**:
```
2400 MHz 2480 MHz
│ │
▼ ▼
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│00│01│02│03│04│05│06│07│08│09│10│11│12│13│14│15│16│17│18│19│...→ 39
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
└──────────────────────────────────────────────────────┘
2 MHz per channel
```
**Total:** 40 × 2 MHz = **80 MHz** used
### 4.2 Advertising vs. data channels
The 40 channels are not all equal:
| Type | Channels | Function |
|------|----------|----------|
| **Advertising** | 3 (nos. 37, 38, 39) | Device discovery, initiating connections |
| **Data** | 37 (nos. 0-36) | Actual communication after connection |
The advertising channels are strategically chosen to **avoid Wi-Fi interference**:
```
Wi-Fi channel 1 Wi-Fi channel 6 Wi-Fi channel 11
│ │ │
▼ ▼ ▼
────████████─────────────█████████────────────████████────
BLE: ▲ ▲ ▲
Ch.37 Ch.38 Ch.39
(advertising channels sit between the Wi-Fi channels)
```
### 4.3 Comparison with Classic Bluetooth
| Aspect | Classic (BR/EDR) | BLE |
|--------|------------------|-----|
| **Number of channels** | 79 | 40 |
| **Channel width** | 1 MHz | 2 MHz |
| **Total bandwidth** | 79 MHz | 80 MHz |
| **Frequency hopping** | Yes, all 79 | Yes, 37 data channels |
Classic has **more but narrower** channels, BLE has **fewer but wider** channels.
### 4.4 Frequency hopping: one channel at a time
**Key insight:** You only ever use **one channel at a time**. The 40 channels exist for **frequency hopping** — alternately switching channels to avoid interference:
```
Time →
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 12 │ ▓ │ │ │ │ │ │ ▓ │
└───┘ └───┘ └───┘ └───┘
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 07 │ │ │ ▓ │ │ │ │ │
└───┘ └───┘ └───┘ └───┘
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 31 │ │ │ │ │ ▓ │ │ │
└───┘ └───┘ └───┘ └───┘
↑ ↑ ↑ ↑
Packet 1 Packet 2 Packet 3 Packet 4
```
This is **not parallel communication** — it is serial with alternating frequencies.
---
## 5. Two meanings of "serial"
When we say "NUS is serial", this can cause confusion. The word "serial" has **two different meanings** in this context.
### 5.1 Radio level: always serial
**All** wireless communication is serial at the physical level — you only have **one radio channel at a time** and bits go into the air **one after another**:
```
Radio wave: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Bits: 0 1 1 0 1 0 0 1 1 1 0 1 0 0 1 0 → one by one
```
The 40 channels are for **frequency hopping**, not for parallel transmission. This applies to **all** BLE services — NUS, Heart Rate, Battery, all of them.
### 5.2 Data level: NUS simulates a serial port
When we say "NUS is a serial service", we mean something different:
**NUS simulates an old serial port (RS-232/UART):**
```
Historical (1980s-2000s):
Computer Device
┌──────┐ Serial cable ┌──────┐
│ COM1 │←────────────────→│ UART │
└──────┘ (RS-232) └──────┘
Bytes: 0x48 0x65 0x6C 0x6C 0x6F ("Hello")
└─────────────────────┘
No structure, just a stream of bytes
```
**NUS mimics this over BLE:**
```
Today:
Computer Device
┌──────┐ BLE (NUS) ┌──────┐
│ App │←~~~~~~~~~~~~~~~~~~~~→│ MCU │
└──────┘ (wireless) └──────┘
Behaves as if there were a serial cable
```
### 5.3 Comparison: serial vs. structured
| Aspect | NUS (serial) | Heart Rate (structured) |
|--------|-------------|------------------------|
| **Radio** | Serial, frequency hopping | Serial, frequency hopping |
| **Data** | Unstructured byte stream | Fixed fields with meaning |
| **Who determines the format?** | You (custom protocol) | Bluetooth SIG (specification) |
### 5.4 Analogy: motorway with lanes
Think of a **motorway with 40 lanes** (the channels):
- You may only use **one lane at a time**
- You regularly switch lanes (frequency hopping, to avoid collisions)
- The **cargo** you transport can differ:
| Service | Cargo analogy |
|---------|--------------|
| **NUS** | Loose items mixed together (flexible, but you need to figure out what's what) |
| **Heart Rate** | Standardised pallets (everyone knows what goes where) |
The **motorway works the same** — the difference lies in how you organise the cargo.
---
## 6. Serial vs. structured services (deep dive)
An important distinction that is often overlooked: **not all BLE services work the same way**. There are fundamentally two approaches.
### 6.1 Serial services (stream-based)
**NUS (Nordic UART Service)** is designed to **simulate a serial port**:
- Continuous stream of raw bytes
- No imposed structure
- You determine the format and meaning yourself
**Analogy:** A serial service is like a **phone line** — you can say whatever you want, in any language, without fixed rules.
```
Example NUS data (MeshCore):
0x01 0x0A 0x48 0x65 0x6C 0x6C 0x6F ...
└── Meaning determined by MeshCore protocol, not by BLE
```
### 6.2 Structured services (field-based)
Most official SIG services work **differently** — they define **exactly** which bytes mean what:
**Analogy:** A structured service is like a **tax form** — each field has a fixed meaning and a prescribed format.
#### Example: Heart Rate Measurement
```
Byte 0: Flags (bitfield)
├── Bit 0: 0 = heart rate in 1 byte, 1 = heart rate in 2 bytes
├── Bit 1-2: Sensor contact status
├── Bit 3: Energy expended present?
└── Bit 4: RR-interval present?
Byte 1(-2): Heart rate value
Byte N...: Optional additional fields (depending on flags)
```
**Concrete example:**
```
Received bytes: 0x00 0x73
0x00 = Flags: 8-bit format, no additional fields
0x73 = 115 decimal → heart rate is 115 bpm
```
So you don't receive the text "115", but a binary packet that you need to **parse** according to the specification.
#### Example: Battery Level
Simpler — just **1 byte**:
```
Received byte: 0x5A
0x5A = 90 decimal → battery is 90%
```
#### Example: Environmental Sensing (temperature)
```
Received bytes: 0x9C 0x08
Little-endian 16-bit signed integer: 0x089C = 2204
Resolution: 0.01°C
Temperature: 2204 × 0.01 = 22.04°C
```
### 6.3 Comparison table
| Aspect | Serial (NUS) | Structured (SIG) |
|--------|-------------|------------------|
| **Data format** | Free, self-determined | Fixed, by specification |
| **Who defines the format?** | You / the manufacturer | Bluetooth SIG |
| **Where to find the spec?** | Own documentation / source code | bluetooth.com/specifications |
| **Parsing** | Build your own parser | Standard parser possible |
| **Interoperability** | Own software only | Any conformant app/device |
| **Flexibility** | Maximum | Limited to spec |
| **Complexity** | Easy to get started | Reading the spec required |
### 6.4 Examples of structured services
| Service | Characteristic | Data format |
|---------|----------------|-------------|
| **Battery Service** | Battery Level | 1 byte: 0-100 (percentage) |
| **Heart Rate** | Heart Rate Measurement | Flags + 8/16-bit HR + optional fields |
| **Health Thermometer** | Temperature Measurement | IEEE-11073 FLOAT (4 bytes) |
| **Blood Pressure** | Blood Pressure Measurement | Compound: systolic, diastolic, MAP, pulse |
| **Cycling Speed & Cadence** | CSC Measurement | 32-bit counters + 16-bit time |
| **Environmental Sensing** | Temperature | 16-bit signed, resolution 0.01°C |
| **Environmental Sensing** | Humidity | 16-bit unsigned, resolution 0.01% |
| **Environmental Sensing** | Pressure | 32-bit unsigned, resolution 0.1 Pa |
### 6.5 When to use which approach?
| Situation | Recommended approach |
|-----------|---------------------|
| Custom protocol (MeshCore, custom IoT) | **Serial** (NUS or custom service) |
| Standard use case (heart rate, battery) | **Structured** (SIG service) |
| Interoperability with existing apps required | **Structured** (SIG service) |
| Complex, variable data structures | **Serial** with custom protocol |
| Quick prototype without studying specs | **Serial** (NUS) |
### 6.6 Why MeshCore uses NUS
MeshCore chose NUS (serial) because:
1. **Flexibility** — The Companion Protocol requires its own framing
2. **No suitable SIG service** — There is no "Mesh Radio Service" standard
3. **Bidirectional communication** — NUS offers both RX and TX characteristics
4. **Simplicity** — No need to implement a complex SIG specification
The downside: you can't just use any arbitrary BLE app to talk to MeshCore — you need software that understands the MeshCore Companion Protocol.
---
## 7. The OSI model in context
The document places the problem in a **layer model**. This helps understand *where* the problem lies:
| Layer | Name | In this project | Problem here? |
|-------|------|-----------------|---------------|
| 7 | Application | MeshCore Companion Protocol | No |
| 6 | Presentation | Frame encoding (hex) | No |
| **5** | **Session** | **GATT client ↔ server session** | **★ YES** |
| 4 | Transport | ATT / GATT | No |
| 2 | Data Link | BLE Link Layer | No |
| 1 | Physical | 2.4 GHz radio | No |
**Conclusion:** The ownership problem sits at **layer 5 (session)**. The firmware and protocol are not the problem — it's about who "owns" the session.
---
## 8. Conclusion
The key takeaways from this document:
- ✅ MeshCore BLE companion **works correctly** on Linux
- ✅ The firmware **does not block notify**
- ✅ The only requirement is: **exactly one active BLE client per radio**
Understanding the ownership model and BLE fundamentals described here is essential for working with any BLE-connected MeshCore device.
---
## 9. References
- MeshCore Companion Radio Protocol: [GitHub Wiki](https://github.com/meshcore-dev/MeshCore/wiki/Companion-Radio-Protocol)
- Bluetooth SIG Assigned Numbers (official services): [bluetooth.com/specifications/assigned-numbers](https://www.bluetooth.com/specifications/assigned-numbers/)
- Bluetooth SIG GATT Specifications: [bluetooth.com/specifications/specs](https://www.bluetooth.com/specifications/specs/)
- Nordic Bluetooth Numbers Database: [GitHub](https://github.com/NordicSemiconductor/bluetooth-numbers-database)
- GATT Explanation (Adafruit): [learn.adafruit.com](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt)
- Bleak documentation: [bleak.readthedocs.io](https://bleak.readthedocs.io/)
- BlueZ: [bluez.org](http://www.bluez.org/)
---
> **Document:** `ble_capture_workflow_t_1000_e_explanation.md`
> **Based on:** `ble_capture_workflow_t_1000_e.md`
@@ -0,0 +1,639 @@
# BLE Capture Workflow T1000-e — Uitleg & Achtergrond
> **Note:** Dit document is BLE-specifiek en wordt bewaard als referentie. De huidige GUI gebruikt USB-serieel.
> **Bron:** `ble_capture_workflow_t_1000_e.md`
>
> Dit document is een **verdiepingsdocument** bij het originele technische werkdocument. Het biedt:
> - Didactische uitleg van BLE-concepten en terminologie
> - Achtergrondkennis over GATT-services en hun werking
> - Context om toekomstige BLE-projecten beter te begrijpen
>
> **Doelgroep:** Mezelf, als referentie voor de lange termijn.
---
## 1. Waar gaat dit document over?
Dit document legt de BLE-concepten en terminologie uit achter de communicatie met een **MeshCore T1000-e** radio vanaf een Linux-computer. Het behandelt:
- Hoe BLE-verbindingen werken en hoe ze verschillen van Classic Bluetooth
- Het GATT-servicemodel en de Nordic UART Service (NUS) die MeshCore gebruikt
- Waarom BLE-sessie-ownership belangrijk is en hoe het verbindingsproblemen kan veroorzaken
**De kernboodschap in één zin:**
> Er mag maar **één BLE-client tegelijk** verbonden zijn met de T1000-e. Als iets anders al verbonden is, faalt jouw verbinding.
---
## 2. Begrippen en afkortingen uitgelegd
### 2.1 BLE — Bluetooth Low Energy
BLE is een **zuinige variant van Bluetooth**, ontworpen voor apparaten die maanden of jaren op een batterij moeten werken.
| Eigenschap | Klassiek Bluetooth | BLE |
|------------|-------------------|-----|
| Stroomverbruik | Hoog | Zeer laag |
| Datasnelheid | Hoog | Laag |
| Typisch gebruik | Audio, bestanden | Sensoren, IoT, MeshCore |
**Analogie:** Klassiek Bluetooth is als een telefoongesprek (constant verbonden, veel energie). BLE is als SMS'jes sturen (kort contact wanneer nodig, weinig energie).
---
### 2.2 GATT — Generic Attribute Profile
GATT is de **structuur** waarmee BLE-apparaten hun data aanbieden. Zie het als een **digitaal prikbord** met een vaste indeling:
```
Service (categorie)
└── Characteristic (specifiek datapunt)
└── Descriptor (extra configuratie)
```
**Voorbeeld voor MeshCore:**
```
Nordic UART Service (NUS)
├── RX Characteristic → berichten van radio naar computer
└── TX Characteristic → berichten van computer naar radio
```
---
### 2.3 NUS — Nordic UART Service
NUS is een **standaard BLE-service** ontwikkeld door Nordic Semiconductor. Het simuleert een ouderwetse seriële poort (UART) over Bluetooth.
- **RX** (Receive): Data die je **ontvangt** van het apparaat
- **TX** (Transmit): Data die je **verstuurt** naar het apparaat
Let op: RX/TX zijn vanuit het perspectief van de computer, niet van de radio.
#### Is NUS een protocol?
**Nee.** NUS is een **servicespecificatie**, geen protocol. Dit is een belangrijk onderscheid:
| Niveau | Wat is het | Voorbeeld |
|--------|-----------|-----------|
| **Protocol** | Regels voor communicatie | BLE, ATT, GATT |
| **Service** | Verzameling van gerelateerde characteristics | NUS, Heart Rate Service |
| **Characteristic** | Specifiek datapunt binnen een service | RX, TX |
**Analogie met een restaurant:**
| Concept | Restaurant-analogie |
|---------|---------------------|
| **Protocol (GATT)** | De regels: je bestelt bij de ober, eten komt uit de keuken |
| **Service (NUS)** | Een specifieke menukaart (bijv. "ontbijtmenu") |
| **Characteristics** | De individuele gerechten op dat menu |
Mensen zeggen vaak "we gebruiken het NUS-protocol", maar strikt genomen is **GATT** het protocol en is **NUS** een service die via GATT wordt aangeboden.
---
### 2.4 Andere GATT-services (officieel en custom)
NUS is slechts één van vele BLE-services. De **Bluetooth SIG** (de organisatie achter Bluetooth) definieert tientallen officiële services. Daarnaast kunnen fabrikanten eigen (custom) services maken.
#### Officiële services (Bluetooth SIG)
Deze services hebben een **16-bit UUID** en zijn gestandaardiseerd voor interoperabiliteit:
| Service | UUID | Toepassing |
|---------|------|------------|
| **Heart Rate Service** | 0x180D | Hartslagmeters, fitnessapparaten |
| **Battery Service** | 0x180F | Batterijniveau rapporteren |
| **Device Information** | 0x180A | Fabrikant, modelnummer, firmwareversie |
| **Blood Pressure** | 0x1810 | Bloeddrukmeters |
| **Health Thermometer** | 0x1809 | Medische thermometers |
| **Cycling Speed and Cadence** | 0x1816 | Fietssensoren |
| **Environmental Sensing** | 0x181A | Temperatuur, luchtvochtigheid, druk |
| **Glucose** | 0x1808 | Bloedglucosemeters |
| **HID over GATT** | 0x1812 | Toetsenborden, muizen, gamepads |
| **Proximity** | 0x1802 | "Find My"-functionaliteit |
| **Generic Access** | 0x1800 | **Verplicht** — apparaatnaam en uiterlijk |
#### Custom/vendor-specific services
Fabrikanten kunnen eigen services definiëren met een **128-bit UUID**. Voorbeelden:
| Service | Fabrikant | Toepassing |
|---------|-----------|------------|
| **Nordic UART Service (NUS)** | Nordic Semiconductor | Seriële poort over BLE |
| **Apple Notification Center** | Apple | iPhone notificaties naar wearables |
| **Xiaomi Mi Band Service** | Xiaomi | Fitnesstracker communicatie |
| **MeshCore Companion** | MeshCore | Radio-communicatie (gebruikt NUS) |
#### Het verschil: 16-bit vs. 128-bit UUID
| Type | Lengte | Voorbeeld | Wie mag het maken? |
|------|--------|-----------|-------------------|
| **Officieel (SIG)** | 16-bit | `0x180D` | Alleen Bluetooth SIG |
| **Custom** | 128-bit | `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | Iedereen |
De NUS-service gebruikt bijvoorbeeld deze 128-bit UUID:
```
6e400001-b5a3-f393-e0a9-e50e24dcca9e
```
#### Waarom dit relevant is
In het MeshCore-project gebruiken we **NUS** (een custom service) voor de communicatie. Maar als je met andere BLE-apparaten werkt — zoals een hartslagmeter of een slimme thermostaat — dan gebruiken die vaak **officiële SIG-services**.
Het principe blijft hetzelfde:
1. Ontdek welke services het apparaat aanbiedt
2. Zoek de juiste characteristic
3. Lees, schrijf, of abonneer op notify
---
### 2.5 Notify vs. Read
Er zijn twee manieren om data van een BLE-apparaat te krijgen:
| Methode | Werking | Wanneer gebruiken |
|---------|---------|-------------------|
| **Read** | Jij vraagt actief om data | Eenmalige waarden (bijv. batterijstatus) |
| **Notify** | Apparaat stuurt automatisch bij nieuwe data | Continue datastroom (bijv. berichten) |
**Analogie:**
- **Read** = Je belt iemand en vraagt "hoe gaat het?"
- **Notify** = Je krijgt automatisch een WhatsApp-bericht als er nieuws is
Voor MeshCore-captures gebruik je **Notify** — je wilt immers weten wanneer er een bericht binnenkomt.
---
### 2.6 CCCD — Client Characteristic Configuration Descriptor
De CCCD is de **aan/uit-schakelaar voor Notify**. Technisch gezien:
1. Jouw computer schrijft een `1` naar de CCCD
2. Het apparaat weet nu: "deze client wil notificaties"
3. Bij nieuwe data stuurt het apparaat automatisch een bericht
**Het cruciale punt:** Slechts **één client tegelijk** kan de CCCD activeren. Een tweede client krijgt de foutmelding:
```
Notify acquired
```
Dit betekent: "iemand anders heeft notify al ingeschakeld."
---
### 2.7 Pairing, Bonding en Trust
Dit zijn drie afzonderlijke stappen in het BLE-beveiligingsproces:
| Stap | Wat gebeurt er | Analogie |
|------|----------------|----------|
| **Pairing** | Apparaten wisselen cryptografische sleutels uit | Je maakt kennis en wisselt telefoonnummers |
| **Bonding** | De sleutels worden permanent opgeslagen | Je slaat het nummer op in je contacten |
| **Trust** | Het systeem vertrouwt het apparaat automatisch | Je zet iemand in je favorieten |
Na deze drie stappen hoef je niet elke keer opnieuw de pincode in te voeren.
**Controle in Linux:**
```bash
bluetoothctl info literal:AA:BB:CC:DD:EE:FF | egrep -i "Paired|Bonded|Trusted"
```
Verwachte output:
```
Paired: yes
Bonded: yes
Trusted: yes
```
---
### 2.8 Ownership — Het kernprobleem
**Ownership** is een informele term die aangeeft: "welke client heeft op dit moment de actieve GATT-sessie met notify?"
**Analogie:** Denk aan een walkietalkie waarbij maar één persoon tegelijk kan luisteren:
- Als GNOME Bluetooth Manager al verbonden is → die is de "eigenaar"
- Als jouw Python-script daarna probeert te verbinden → krijgt het geen toegang
**Typische "eigenaren" die problemen veroorzaken:**
- GNOME Bluetooth GUI (draait vaak op de achtergrond)
- `bluetoothctl connect` (maakt bluetoothctl de eigenaar)
- Telefoon met Bluetooth aan
- Andere BLE-apps
---
### 2.9 BlueZ
**BlueZ** is de officiële Bluetooth-stack voor Linux. Het is de software die alle Bluetooth-communicatie afhandelt tussen je applicaties en de hardware.
---
### 2.10 Bleak
**Bleak** is een Python-bibliotheek voor BLE-communicatie. Het bouwt voort op BlueZ (Linux), Core Bluetooth (macOS) of WinRT (Windows).
---
## 3. BLE versus Classic Bluetooth
Een veelvoorkomende vraag: zijn BLE en "gewone" Bluetooth hetzelfde? Het antwoord is **nee** — het zijn verschillende technologieën die wel dezelfde naam en frequentieband delen.
### 3.1 Twee smaken van Bluetooth
Sinds Bluetooth 4.0 (2010) zijn er **twee afzonderlijke radiosystemen** binnen de Bluetooth-standaard:
| Naam | Technische term | Kenmerken |
|------|-----------------|-----------|
| **Classic Bluetooth** | BR/EDR (Basic Rate / Enhanced Data Rate) | Hoge datasnelheid, continue verbinding, meer stroom |
| **Bluetooth Low Energy** | BLE (ook: Bluetooth Smart) | Lage datasnelheid, korte bursts, zeer zuinig |
**Cruciaal:** Dit zijn **verschillende radioprotocollen** die niet rechtstreeks met elkaar kunnen communiceren.
### 3.2 Protocol én hardware
Bluetooth (zowel Classic als BLE) omvat **meerdere lagen** — het is niet alleen een protocol, maar ook hardware:
```
┌─────────────────────────────────────────┐
│ SOFTWARE │
│ ┌───────────────────────────────────┐ │
│ │ Applicatie (jouw code) │ │
│ ├───────────────────────────────────┤ │
│ │ Profielen / GATT Services │ │
│ ├───────────────────────────────────┤ │
│ │ Protocollen (ATT, L2CAP, etc.) │ │
│ ├───────────────────────────────────┤ │
│ │ Host Controller Interface (HCI) │ │ ← Grens software/firmware
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ FIRMWARE │
│ ┌───────────────────────────────────┐ │
│ │ Link Layer / Controller │ │
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ HARDWARE │
│ ┌───────────────────────────────────┐ │
│ │ Radio (2.4 GHz transceiver) │ │
│ ├───────────────────────────────────┤ │
│ │ Antenne │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
### 3.3 Waar zit het verschil?
Het verschil zit op **meerdere lagen**, niet alleen protocol:
| Laag | Classic (BR/EDR) | BLE | Verschil in hardware? |
|------|------------------|-----|----------------------|
| **Radio** | GFSK, π/4-DQPSK, 8DPSK | GFSK | **Ja** — andere modulatie |
| **Kanalen** | 79 kanalen, 1 MHz breed | 40 kanalen, 2 MHz breed | **Ja** — andere indeling |
| **Link Layer** | LMP (Link Manager Protocol) | LL (Link Layer) | **Ja** — andere state machine |
| **Protocollen** | L2CAP, RFCOMM, SDP | L2CAP, ATT, GATT | Nee — software |
### 3.4 Dual-mode apparaten
De overlap zit in apparaten die **beide** ondersteunen:
| Apparaattype | Ondersteunt | Voorbeeld |
|--------------|-------------|-----------|
| **Classic-only** | Alleen BR/EDR | Oude headsets, auto-audio |
| **BLE-only** (Bluetooth Smart) | Alleen BLE | Fitnesstrackers, sensoren, T1000-e |
| **Dual-Mode** (Bluetooth Smart Ready) | Beide | Smartphones, laptops, ESP32 |
**Jouw smartphone** is dual-mode: hij kan praten met je klassieke Bluetooth-koptelefoon (BR/EDR) én met je MeshCore T1000-e (BLE).
### 3.5 Praktijkvoorbeelden
| Scenario | Wat wordt gebruikt |
|----------|-------------------|
| Muziek naar je koptelefoon | **Classic** (A2DP profiel) |
| Hartslag van je smartwatch | **BLE** (Heart Rate Service) |
| Bestand naar laptop sturen | **Classic** (OBEX/FTP profiel) |
| MeshCore T1000-e uitlezen | **BLE** (NUS service) |
| Handsfree bellen in auto | **Classic** (HFP profiel) |
| Slimme lamp bedienen | **BLE** (eigen GATT service) |
---
## 4. BLE kanaalindeling en frequency hopping
### 4.1 De 40 BLE-kanalen
De 2.4 GHz ISM-band loopt van **2400 MHz tot 2483.5 MHz** (83.5 MHz breed).
BLE verdeelt dit in **40 kanalen van elk 2 MHz**:
```
2400 MHz 2480 MHz
│ │
▼ ▼
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│00│01│02│03│04│05│06│07│08│09│10│11│12│13│14│15│16│17│18│19│...→ 39
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
└──────────────────────────────────────────────────────┘
2 MHz per kanaal
```
**Totaal:** 40 × 2 MHz = **80 MHz** gebruikt
### 4.2 Advertising vs. data kanalen
De 40 kanalen zijn niet allemaal gelijk:
| Type | Kanalen | Functie |
|------|---------|---------|
| **Advertising** | 3 (nrs. 37, 38, 39) | Apparaten vinden, verbinding starten |
| **Data** | 37 (nrs. 0-36) | Daadwerkelijke communicatie na verbinding |
De advertising-kanalen zijn strategisch gekozen om **Wi-Fi-interferentie** te vermijden:
```
Wi-Fi kanaal 1 Wi-Fi kanaal 6 Wi-Fi kanaal 11
│ │ │
▼ ▼ ▼
────████████─────────────█████████────────────████████────
BLE: ▲ ▲ ▲
Ch.37 Ch.38 Ch.39
(advertising kanalen zitten tússen de Wi-Fi kanalen)
```
### 4.3 Vergelijking met Classic Bluetooth
| Aspect | Classic (BR/EDR) | BLE |
|--------|------------------|-----|
| **Aantal kanalen** | 79 | 40 |
| **Kanaalbreedte** | 1 MHz | 2 MHz |
| **Totale bandbreedte** | 79 MHz | 80 MHz |
| **Frequency hopping** | Ja, alle 79 | Ja, 37 datakanalen |
Classic heeft **meer maar smallere** kanalen, BLE heeft **minder maar bredere** kanalen.
### 4.4 Frequency hopping: één kanaal tegelijk
**Belangrijk inzicht:** Je gebruikt altijd maar **één kanaal tegelijk**. De 40 kanalen zijn er voor **frequency hopping** — het afwisselend wisselen van kanaal om interferentie te vermijden:
```
Tijd →
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 12 │ ▓ │ │ │ │ │ │ ▓ │
└───┘ └───┘ └───┘ └───┘
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 07 │ │ │ ▓ │ │ │ │ │
└───┘ └───┘ └───┘ └───┘
┌───┐ ┌───┐ ┌───┐ ┌───┐
Ch. 31 │ │ │ │ │ ▓ │ │ │
└───┘ └───┘ └───┘ └───┘
↑ ↑ ↑ ↑
Pakket 1 Pakket 2 Pakket 3 Pakket 4
```
Dit is **geen parallelle communicatie** — het is serieel met wisselende frequentie.
---
## 5. Twee betekenissen van "serieel"
Wanneer we zeggen "NUS is serieel", kan dit verwarring veroorzaken. Het woord "serieel" heeft namelijk **twee verschillende betekenissen** in deze context.
### 5.1 Radio-niveau: altijd serieel
**Alle** draadloze communicatie is serieel op fysiek niveau — je hebt maar **één radiokanaal tegelijk** en bits gaan **na elkaar** de lucht in:
```
Radiogolf: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Bits: 0 1 1 0 1 0 0 1 1 1 0 1 0 0 1 0 → één voor één
```
De 40 kanalen zijn voor **frequency hopping**, niet voor parallel versturen. Dit geldt voor **alle** BLE-services — NUS, Heart Rate, Battery, allemaal.
### 5.2 Data-niveau: NUS simuleert een seriële poort
Wanneer we zeggen "NUS is een seriële service", bedoelen we iets anders:
**NUS simuleert een oude seriële poort (RS-232/UART):**
```
Historisch (jaren '80-'00):
Computer Apparaat
┌──────┐ Seriële kabel ┌──────┐
│ COM1 │←────────────────→│ UART │
└──────┘ (RS-232) └──────┘
Bytes: 0x48 0x65 0x6C 0x6C 0x6F ("Hello")
└─────────────────────┘
Geen structuur, gewoon een stroom bytes
```
**NUS bootst dit na over BLE:**
```
Vandaag:
Computer Apparaat
┌──────┐ BLE (NUS) ┌──────┐
│ App │←~~~~~~~~~~~~~~~~~~~~→│ MCU │
└──────┘ (draadloos) └──────┘
Gedraagt zich alsof er een seriële kabel zit
```
### 5.3 Vergelijking: serieel vs. gestructureerd
| Aspect | NUS (serieel) | Heart Rate (gestructureerd) |
|--------|---------------|----------------------------|
| **Radio** | Serieel, frequency hopping | Serieel, frequency hopping |
| **Data** | Ongestructureerde bytestroom | Vaste velden met betekenis |
| **Wie bepaalt formaat?** | Jij (eigen protocol) | Bluetooth SIG (specificatie) |
### 5.4 Analogie: snelweg met rijstroken
Denk aan een **snelweg met 40 rijstroken** (de kanalen):
- Je mag maar **één rijstrook tegelijk** gebruiken
- Je wisselt regelmatig van rijstrook (frequency hopping, om botsingen te vermijden)
- De **vracht** die je vervoert kan verschillen:
| Service | Vracht-analogie |
|---------|-----------------|
| **NUS** | Losse spullen door elkaar (flexibel, maar jij moet uitzoeken wat wat is) |
| **Heart Rate** | Gestandaardiseerde pallets (iedereen weet wat waar zit) |
De **snelweg werkt hetzelfde** — het verschil zit in hoe je de vracht organiseert.
---
## 6. Seriële vs. gestructureerde services (verdieping)
Een belangrijk onderscheid dat vaak over het hoofd wordt gezien: **niet alle BLE-services werken hetzelfde**. Er zijn fundamenteel twee benaderingen.
### 6.1 Seriële services (stream-gebaseerd)
**NUS (Nordic UART Service)** is ontworpen om een **seriële poort te simuleren**:
- Continue datastroom van ruwe bytes
- Geen opgelegde structuur
- Jij bepaalt zelf het formaat en de betekenis
**Analogie:** Een seriële service is als een **telefoonlijn** — je kunt alles zeggen wat je wilt, in elke taal, zonder vaste regels.
```
Voorbeeld NUS-data (MeshCore):
0x01 0x0A 0x48 0x65 0x6C 0x6C 0x6F ...
└── Betekenis bepaald door MeshCore protocol, niet door BLE
```
### 6.2 Gestructureerde services (veld-gebaseerd)
De meeste officiële SIG-services werken **anders** — ze definiëren **exact** welke bytes wat betekenen:
**Analogie:** Een gestructureerde service is als een **belastingformulier** — elk vakje heeft een vaste betekenis en een voorgeschreven formaat.
#### Voorbeeld: Heart Rate Measurement
```
Byte 0: Flags (bitfield)
├── Bit 0: 0 = hartslag in 1 byte, 1 = hartslag in 2 bytes
├── Bit 1-2: Sensor contact status
├── Bit 3: Energy expended aanwezig?
└── Bit 4: RR-interval aanwezig?
Byte 1(-2): Heart rate waarde
Byte N...: Optionele extra velden (afhankelijk van flags)
```
**Concreet voorbeeld:**
```
Ontvangen bytes: 0x00 0x73
0x00 = Flags: 8-bit formaat, geen extra velden
0x73 = 115 decimaal → hartslag is 115 bpm
```
Je krijgt dus niet de tekst "115", maar een binair pakket dat je moet **parsen** volgens de specificatie.
#### Voorbeeld: Battery Level
Eenvoudiger — slechts **1 byte**:
```
Ontvangen byte: 0x5A
0x5A = 90 decimaal → batterij is 90%
```
#### Voorbeeld: Environmental Sensing (temperatuur)
```
Ontvangen bytes: 0x9C 0x08
Little-endian 16-bit signed integer: 0x089C = 2204
Resolutie: 0.01°C
Temperatuur: 2204 × 0.01 = 22.04°C
```
### 6.3 Vergelijkingstabel
| Aspect | Serieel (NUS) | Gestructureerd (SIG) |
|--------|---------------|----------------------|
| **Data-indeling** | Vrij, zelf bepalen | Vast, door specificatie |
| **Wie definieert het formaat?** | Jij / de fabrikant | Bluetooth SIG |
| **Waar vind je de specificatie?** | Eigen documentatie / broncode | bluetooth.com/specifications |
| **Parsing** | Eigen parser bouwen | Standaard parser mogelijk |
| **Interoperabiliteit** | Alleen eigen software | Elke conforme app/device |
| **Flexibiliteit** | Maximaal | Beperkt tot spec |
| **Complexiteit** | Eenvoudig te starten | Spec lezen vereist |
### 6.4 Voorbeelden van gestructureerde services
| Service | Characteristic | Data-formaat |
|---------|----------------|--------------|
| **Battery Service** | Battery Level | 1 byte: 0-100 (percentage) |
| **Heart Rate** | Heart Rate Measurement | Flags + 8/16-bit HR + optionele velden |
| **Health Thermometer** | Temperature Measurement | IEEE-11073 FLOAT (4 bytes) |
| **Blood Pressure** | Blood Pressure Measurement | Compound: systolisch, diastolisch, MAP, pulse |
| **Cycling Speed & Cadence** | CSC Measurement | 32-bit tellers + 16-bit tijd |
| **Environmental Sensing** | Temperature | 16-bit signed, resolutie 0.01°C |
| **Environmental Sensing** | Humidity | 16-bit unsigned, resolutie 0.01% |
| **Environmental Sensing** | Pressure | 32-bit unsigned, resolutie 0.1 Pa |
### 6.5 Wanneer welke aanpak?
| Situatie | Aanbevolen aanpak |
|----------|-------------------|
| Eigen protocol (MeshCore, custom IoT) | **Serieel** (NUS of eigen service) |
| Standaard use-case (hartslag, batterij) | **Gestructureerd** (SIG-service) |
| Interoperabiliteit met bestaande apps vereist | **Gestructureerd** (SIG-service) |
| Complexe, variabele datastructuren | **Serieel** met eigen protocol |
| Snel prototype zonder spec-studie | **Serieel** (NUS) |
### 6.6 Waarom MeshCore NUS gebruikt
MeshCore koos voor NUS (serieel) omdat:
1. **Flexibiliteit** — Het Companion Protocol heeft eigen framing nodig
2. **Geen passende SIG-service** — Er is geen "Mesh Radio Service" standaard
3. **Bidirectionele communicatie** — NUS biedt RX én TX characteristics
4. **Eenvoud** — Geen complexe SIG-specificatie implementeren
Het nadeel: je kunt niet zomaar een willekeurige BLE-app gebruiken om met MeshCore te praten — je hebt software nodig die het MeshCore Companion Protocol begrijpt.
---
## 7. Het OSI-model in context
Het document plaatst het probleem in een **lagenmodel**. Dit helpt begrijpen *waar* het probleem zit:
| Laag | Naam | In dit project | Probleem hier? |
|------|------|----------------|----------------|
| 7 | Applicatie | MeshCore Companion Protocol | Nee |
| 6 | Presentatie | Frame-encoding (hex) | Nee |
| **5** | **Sessie** | **GATT client ↔ server sessie** | **★ JA** |
| 4 | Transport | ATT / GATT | Nee |
| 2 | Data Link | BLE Link Layer | Nee |
| 1 | Fysiek | 2.4 GHz radio | Nee |
**Conclusie:** Het ownership-probleem zit op **laag 5 (sessie)**. De firmware en het protocol zijn niet het probleem — het gaat om wie de sessie "bezit".
---
## 8. Conclusie
De belangrijkste inzichten uit dit document:
- ✅ MeshCore BLE companion **werkt correct** op Linux
- ✅ De firmware **blokkeert notify niet**
- ✅ Het enige vereiste is: **exact één actieve BLE-client per radio**
Het begrijpen van het ownership-model en de BLE-fundamenten uit dit document is essentieel voor het werken met elk BLE-verbonden MeshCore-apparaat.
---
## 9. Referenties
- MeshCore Companion Radio Protocol: [GitHub Wiki](https://github.com/meshcore-dev/MeshCore/wiki/Companion-Radio-Protocol)
- Bluetooth SIG Assigned Numbers (officiële services): [bluetooth.com/specifications/assigned-numbers](https://www.bluetooth.com/specifications/assigned-numbers/)
- Bluetooth SIG GATT Specifications: [bluetooth.com/specifications/specs](https://www.bluetooth.com/specifications/specs/)
- Nordic Bluetooth Numbers Database: [GitHub](https://github.com/NordicSemiconductor/bluetooth-numbers-database)
- GATT Uitleg (Adafruit): [learn.adafruit.com](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt)
- Bleak documentatie: [bleak.readthedocs.io](https://bleak.readthedocs.io/)
- BlueZ: [bluez.org](http://www.bluez.org/)
---
> **Document:** `ble_capture_workflow_t_1000_e_uitleg.md`
> **Gebaseerd op:** `ble_capture_workflow_t_1000_e.md`