mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-10-31 01:42:04 +01:00
fix(route): default reply channel to message channel, add date to timestamps
WHAT - route_page: _default_reply_channel() derives the reply select default from msg.channel instead of the first entry in the channel list - models: new Message.date field, now_date() and display_timestamp(); format_line() and incoming()/outgoing() use them - message_archive: persist "date" alongside "time" and "timestamp_utc" - room_server_panel + route_page info header use display_timestamp() - VERSION 1.22.2 -> 1.22.3 WHY The route page exists to show one specific message, so that message — not the ordering of the channel list — is the correct source for the reply default. The date could not be fixed at the render site because Message had no date at all: now_timestamp() yields HH:MM:SS and the date lived only as timestamp_utc inside the archive layer, unread by from_dict(). A first-class field plus a single formatting helper fixes the cause and keeps the format in one place. NOTES No breaking changes. The archive schema gains one additive field; pre-1.22.3 entries have their date derived from timestamp_utc (converted to local time) and fall back to time-only when absent. Message.incoming() gained an optional keyword-only date parameter. Message display lines are ~11 characters wider.
This commit is contained in:
1 parent
cced48c10e
commit
af74caf766
22 files changed
+778
-731
No files matched your search
@@ -1,32 +0,0 @@
|
||||
### Map developer rules
|
||||
|
||||
To avoid regressions in the map subsystem, follow these rules:
|
||||
|
||||
**Do**
|
||||
|
||||
- Keep the Leaflet map lifecycle **inside the browser runtime**
|
||||
- Initialize Leaflet **exactly once per DOM container**
|
||||
- Send **compact snapshots only** from Python
|
||||
- Update markers **incrementally by node id**
|
||||
- Keep theme handling in a **dedicated theme channel**
|
||||
- Allow the browser runtime to maintain **viewport state**
|
||||
- Define map/tile `maxZoom` **before** attaching clustering layers
|
||||
|
||||
**Do NOT**
|
||||
|
||||
- Recreate the map inside the 500 ms dashboard update loop
|
||||
- Call `L.map(...)` from snapshot handlers, timers or retry loops
|
||||
- Use `ui.leaflet()` or any NiceGUI map wrapper
|
||||
- Embed theme state inside snapshot payloads
|
||||
- Force map center/zoom during normal refresh cycles
|
||||
- Call Leaflet APIs directly from Python
|
||||
- Place the device marker inside the contact cluster layer
|
||||
|
||||
Breaking these rules will reintroduce:
|
||||
|
||||
- disappearing maps
|
||||
- marker flicker
|
||||
- viewport resets
|
||||
- theme resets
|
||||
- cluster bootstrap failures
|
||||
- `Map container is already initialized` errors
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
This feature implements persistent storage for all incoming messages, RX log entries, and contacts with configurable retention periods. The system uses a dual-layer architecture to balance real-time UI performance with comprehensive data retention.
|
||||
|
||||
The architectural decision (why two layers, why independent locks, lock-ordering rules, schema-evolution policy) is recorded in **`docs/adr/ADR-005-dual-layer-persistence.md`**. This document is the feature reference: storage format, API, performance, testing.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
@@ -29,14 +31,6 @@ This feature implements persistent storage for all incoming messages, RX log ent
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Separation of Concerns**: SharedData handles real-time UI updates, MessageArchive handles persistence
|
||||
2. **Thread Safety**: Independent locks prevent contention between UI and archiving
|
||||
3. **Batch Writes**: Buffered writes reduce disk I/O (flushes every 10 items or 60 seconds)
|
||||
4. **Configurable Retention**: Automatic cleanup based on configurable periods
|
||||
5. **Backward Compatibility**: SharedData API unchanged, archive is optional
|
||||
|
||||
## Storage Format
|
||||
|
||||
### Messages Archive
|
||||
@@ -207,17 +201,14 @@ Cleanup is non-blocking and runs in the background worker thread.
|
||||
|
||||
## Thread Safety
|
||||
|
||||
### Lock Ordering
|
||||
1. SharedData acquires its lock
|
||||
2. SharedData calls MessageArchive methods
|
||||
3. MessageArchive acquires its own lock
|
||||
Lock-ordering rules (SharedData → MessageArchive) and the rationale for
|
||||
two independent locks are documented in
|
||||
**`docs/adr/ADR-005-dual-layer-persistence.md`**.
|
||||
|
||||
This ordering prevents deadlocks.
|
||||
|
||||
### Concurrent Access
|
||||
- SharedData lock: Protects in-memory buffers
|
||||
- MessageArchive lock: Protects file writes and batch buffers
|
||||
- Independent locks prevent contention
|
||||
In short:
|
||||
- SharedData lock protects in-memory buffers
|
||||
- MessageArchive lock protects file writes and batch buffers
|
||||
- Locks are independent — no contention between UI and archiving
|
||||
|
||||
## Error Handling
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
# MeshCore GUI — BLE Stabiliteit: Installatie-instructies (Legacy)
|
||||
|
||||
> **Let op:** Dit document is BLE-specifiek en wordt bewaard als referentie. De huidige GUI gebruikt USB-serieel; gebruik het handmatige systeemd-voorbeeld in de README.
|
||||
|
||||
## Wat is gewijzigd
|
||||
|
||||
### Nieuwe bestanden
|
||||
| Bestand | Doel |
|
||||
|---------|------|
|
||||
| `meshcore_gui/ble/ble_agent.py` | Ingebouwde BlueZ D-Bus PIN agent (vervangt `bt-agent.service`) |
|
||||
| `meshcore_gui/ble/ble_reconnect.py` | Bond-opruiming + automatische reconnect logica |
|
||||
| `install_ble_stable.sh` | Generiek installatiescript (detecteert paden/user automatisch) |
|
||||
|
||||
### Gewijzigde bestanden
|
||||
| Bestand | Wijziging |
|
||||
|---------|-----------|
|
||||
| `meshcore_gui/ble/worker.py` | Agent startup, disconnect detectie, auto-reconnect loop |
|
||||
| `meshcore_gui/config.py` | Nieuwe constanten: `BLE_PIN`, `RECONNECT_MAX_RETRIES`, `RECONNECT_BASE_DELAY` |
|
||||
|
||||
---
|
||||
|
||||
## Snelle installatie (aanbevolen)
|
||||
|
||||
```bash
|
||||
# 1. Verwijder eerst een eventuele kapotte service
|
||||
sudo systemctl stop meshcore-gui 2>/dev/null
|
||||
sudo systemctl disable meshcore-gui 2>/dev/null
|
||||
sudo rm -f /etc/systemd/system/meshcore-gui.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl reset-failed 2>/dev/null
|
||||
|
||||
# 2. Kopieer de nieuwe/gewijzigde bestanden naar je project
|
||||
cp ble_agent.py ~/meshcore-gui/meshcore_gui/ble/
|
||||
cp ble_reconnect.py ~/meshcore-gui/meshcore_gui/ble/
|
||||
cp worker.py ~/meshcore-gui/meshcore_gui/ble/
|
||||
cp config.py ~/meshcore-gui/meshcore_gui/
|
||||
|
||||
# 3. Ga naar je project directory en voer het installatiescript uit
|
||||
cd ~/meshcore-gui
|
||||
BLE_ADDRESS=FF:05:D6:71:83:8D bash install_ble_stable.sh
|
||||
```
|
||||
|
||||
Het script detecteert automatisch:
|
||||
- De juiste project directory (waar je het uitvoert)
|
||||
- De huidige user
|
||||
- Het pad naar de venv Python
|
||||
- Het correcte entry point
|
||||
|
||||
---
|
||||
|
||||
## Handmatige installatie
|
||||
|
||||
Als je het script niet wilt gebruiken:
|
||||
|
||||
### 1. Kopieer Python bestanden
|
||||
```bash
|
||||
# Pas het pad aan naar jouw project directory
|
||||
PROJECT=~/meshcore-gui
|
||||
|
||||
cp ble_agent.py $PROJECT/meshcore_gui/ble/
|
||||
cp ble_reconnect.py $PROJECT/meshcore_gui/ble/
|
||||
cp worker.py $PROJECT/meshcore_gui/ble/
|
||||
cp config.py $PROJECT/meshcore_gui/
|
||||
```
|
||||
|
||||
### 2. Upgrade meshcore library
|
||||
```bash
|
||||
cd $PROJECT
|
||||
source venv/bin/activate
|
||||
pip install --upgrade meshcore
|
||||
```
|
||||
|
||||
### 3. D-Bus policy installeren
|
||||
Maak `/etc/dbus-1/system.d/meshcore-ble.conf` met je eigen username:
|
||||
```bash
|
||||
sudo tee /etc/dbus-1/system.d/meshcore-ble.conf << 'EOF'
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
<policy user="JOUW_USERNAME">
|
||||
<allow send_destination="org.bluez"/>
|
||||
<allow send_interface="org.bluez.Agent1"/>
|
||||
<allow send_interface="org.bluez.AgentManager1"/>
|
||||
</policy>
|
||||
</busconfig>
|
||||
EOF
|
||||
```
|
||||
|
||||
### 4. Systemd service installeren
|
||||
Maak `/etc/systemd/system/meshcore-gui.service` met je eigen paden:
|
||||
```bash
|
||||
sudo tee /etc/systemd/system/meshcore-gui.service << EOF
|
||||
[Unit]
|
||||
Description=MeshCore GUI (BLE)
|
||||
After=bluetooth.target
|
||||
Wants=bluetooth.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$(whoami)
|
||||
WorkingDirectory=$PROJECT
|
||||
ExecStart=$PROJECT/venv/bin/python meshcore_gui.py JOUW_BLE_ADRES --debug-on
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
Environment=DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable meshcore-gui
|
||||
sudo systemctl start meshcore-gui
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verwijderen
|
||||
|
||||
### Via het script
|
||||
```bash
|
||||
cd ~/meshcore-gui
|
||||
bash install_ble_stable.sh --uninstall
|
||||
```
|
||||
|
||||
### Handmatig
|
||||
```bash
|
||||
sudo systemctl stop meshcore-gui
|
||||
sudo systemctl disable meshcore-gui
|
||||
sudo rm -f /etc/systemd/system/meshcore-gui.service
|
||||
sudo rm -f /etc/dbus-1/system.d/meshcore-ble.conf
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl reset-failed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verificatie
|
||||
|
||||
```bash
|
||||
# Service status
|
||||
sudo systemctl status meshcore-gui
|
||||
|
||||
# Live logs
|
||||
journalctl -u meshcore-gui -f
|
||||
|
||||
# Test PIN pairing (vanuit een andere terminal)
|
||||
bluetoothctl remove <BLE_ADRES>
|
||||
sudo systemctl restart meshcore-gui
|
||||
|
||||
# Test disconnect recovery
|
||||
# Zet device uit → wacht 30s → zet weer aan → check logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuratie (config.py)
|
||||
|
||||
```python
|
||||
BLE_PIN = "123456" # T1000e pairing PIN
|
||||
RECONNECT_MAX_RETRIES = 5 # Max pogingen per disconnect
|
||||
RECONNECT_BASE_DELAY = 5.0 # Wachttijd × poging nummer (5s, 10s, 15s...)
|
||||
```
|
||||
|
||||
Pas deze waarden aan in `meshcore_gui/config.py` als je een ander device of andere timing nodig hebt.
|
||||
@@ -6,6 +6,10 @@ Deze wijziging lost het BlueZ 5.82 probleem op door `meshcore-ble-connect`
|
||||
als **persistent subprocess** te draaien dat de D-Bus/BLE connectie openhoudt,
|
||||
terwijl `BleakClient` er alleen GATT service discovery overheen doet.
|
||||
|
||||
De architectuur, de flow en de risico-mitigatie staan in
|
||||
**`docs/adr/ADR-003-subprocess-ble-connect.md`**. Dit document bevat alleen
|
||||
de installatie- en delivery-stappen.
|
||||
|
||||
---
|
||||
|
||||
## Gewijzigde bestanden
|
||||
@@ -28,43 +32,6 @@ terwijl `BleakClient` er alleen GATT service discovery overheen doet.
|
||||
|
||||
---
|
||||
|
||||
## Architectuur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ meshcore-gui (worker.py) │
|
||||
│ │
|
||||
│ 1. ensure_bond() → bond OK │
|
||||
│ 2. start subprocess: │
|
||||
│ meshcore-ble-connect MAC --pin X │
|
||||
│ --connect │
|
||||
│ 3. wait for "READY" op stdout │
|
||||
│ 4. BleakScanner.find_device_by_address() │
|
||||
│ → populeert bleak's interne cache │
|
||||
│ 5. BleakClient(addr).connect() │
|
||||
│ → bleak ziet Connected=True in BlueZ │
|
||||
│ → slaat Device1.Connect() over │
|
||||
│ → doet alleen GATT service discovery │
|
||||
│ 6. MeshCore.create_ble(client=client) │
|
||||
│ 7. Main loop draait │
|
||||
│ 8. subprocess health check elke 100ms │
|
||||
└──────────────┬──────────────────────────────┘
|
||||
│ subprocess (stdout PIPE)
|
||||
│
|
||||
┌──────────────▼──────────────────────────────┐
|
||||
│ meshcore-ble-connect --connect │
|
||||
│ │
|
||||
│ 1. Bond flow (ensure/pair/trust) │
|
||||
│ 2. Device1.Connect() via D-Bus │
|
||||
│ 3. Poll ServicesResolved tot True │
|
||||
│ 4. print("READY") → stdout │
|
||||
│ 5. Monitor Connected property │
|
||||
│ → print("DISCONNECTED") bij verlies │
|
||||
│ 6. Wacht op SIGTERM of disconnect │
|
||||
│ 7. Device1.Disconnect() bij shutdown │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Installatie
|
||||
|
||||
```bash
|
||||
@@ -90,19 +57,3 @@ cd ~/meshcore-gui && python -m meshcore_gui
|
||||
Als het subprocess faalt (bijv. op BlueZ < 5.78 waar het niet nodig is),
|
||||
valt `_connect()` automatisch terug op de directe `MeshCore.create_ble(address)`
|
||||
aanroep. Dit garandeert backwards compatibility.
|
||||
|
||||
## Risico-mitigatie
|
||||
|
||||
Het document noemde het risico dat bleak mogelijk opnieuw `Device1.Connect()`
|
||||
aanroept. Dit is opgelost door:
|
||||
|
||||
1. **`BleakScanner.find_device_by_address()`** vóór `BleakClient.connect()` —
|
||||
dit triggert bleak's `BlueZManager` singleton om bestaande BlueZ device
|
||||
objecten te ontdekken via `GetManagedObjects()`.
|
||||
2. De manager ziet `Connected=True` op het device (gezet door het subprocess)
|
||||
→ `BleakClient.connect()` slaat `Device1.Connect()` over.
|
||||
3. Bleak doet alleen GATT service resolution over de bestaande connectie.
|
||||
|
||||
Als de scanner het device niet vindt (het adverteert mogelijk niet terwijl
|
||||
het connected is), probeert bleak alsnog. De `GetManagedObjects` call bij
|
||||
manager initialisatie vangt dit op in de meeste gevallen.
|
||||
@@ -10,6 +10,8 @@ NiceGUI acts only as a container and data provider.
|
||||
|
||||
This architecture prevents map resets, marker flicker, and viewport jumps during the 500 ms dashboard refresh cycle.
|
||||
|
||||
The rationale and binding rules for this approach are recorded in **`docs/adr/ADR-001-browser-managed-leaflet.md`**.
|
||||
|
||||
---
|
||||
|
||||
# Architecture
|
||||
@@ -174,26 +176,6 @@ This prevents marker flicker during the refresh loop.
|
||||
|
||||
---
|
||||
|
||||
# Important Constraints
|
||||
|
||||
Developers must **not**:
|
||||
|
||||
* recreate the Leaflet map inside the dashboard refresh loop
|
||||
* call `L.map(...)` from snapshot handlers, retry loops or timer callbacks
|
||||
* embed theme state in snapshots
|
||||
* call Leaflet APIs directly from Python
|
||||
* force viewport resets during normal snapshot updates
|
||||
* place the device marker inside the contact cluster layer
|
||||
|
||||
Violating these rules will reintroduce:
|
||||
|
||||
* disappearing maps
|
||||
* marker flicker
|
||||
* viewport resets
|
||||
* theme resets
|
||||
|
||||
---
|
||||
|
||||
# Reconnect Behaviour
|
||||
|
||||
When the NiceGUI connection temporarily drops:
|
||||
@@ -229,3 +211,4 @@ Browser → map lifecycle
|
||||
|
||||
This prevents UI refresh cycles from interfering with map state and ensures smooth rendering even with frequent dashboard updates.
|
||||
|
||||
Binding constraints (what developers must and must not do) are listed in **`docs/adr/ADR-001-browser-managed-leaflet.md`** under "Consequences → Bindende uitvloeisels".
|
||||
@@ -129,3 +129,48 @@ rm ~/.meshcore-gui/logs/_dev_ttyUSB1_meshcore_gui.log
|
||||
rm ~/.meshcore-gui/pins/_dev_ttyUSB1_pins.json
|
||||
rm ~/.meshcore-gui/room_passwords/_dev_ttyUSB1_rooms.json
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port already in use
|
||||
|
||||
If meshcore-gui fails to start with:
|
||||
|
||||
```
|
||||
ERROR: [Errno 98] error while attempting to bind on address ('0.0.0.0', 8081): address already in use
|
||||
```
|
||||
|
||||
a previous instance is still running, or the port hasn't been released yet.
|
||||
|
||||
**Quick fix — kill previous instance and free the port:**
|
||||
|
||||
```bash
|
||||
pkill -9 -f meshcore_gui
|
||||
sleep 3
|
||||
lsof -i :8081
|
||||
```
|
||||
|
||||
If `lsof` returns nothing, the port is free. Start the service again:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart meshcore-gui-device1
|
||||
```
|
||||
|
||||
**If the port is still in use after killing**, sometimes TCP sockets linger
|
||||
in `TIME_WAIT` state. Wait 30 seconds:
|
||||
|
||||
```bash
|
||||
sleep 30
|
||||
lsof -i :8081
|
||||
```
|
||||
|
||||
**Running in background with nohup (for foreground tests, not the systemd
|
||||
flow above):** always redirect output to a log file so you can diagnose:
|
||||
|
||||
```bash
|
||||
# Good - keeps logs for debugging
|
||||
nohup python meshcore_gui.py /dev/ttyUSB0 --debug-on --port=8081 > ~/meshcore.log 2>&1 &
|
||||
|
||||
# Bad - hides all errors
|
||||
nohup python meshcore_gui.py /dev/ttyUSB0 --debug-on --port=8081 > /dev/null 2>&1 &
|
||||
```
|
||||
@@ -1,424 +0,0 @@
|
||||
# MeshCore GUI - Legacy BLE Troubleshooting Guide
|
||||
|
||||
> **Note:** This guide applies to BLE connections only and is kept for historical reference. The current GUI uses USB serial; for serial issues, verify the correct port (e.g. `/dev/ttyUSB0`) and user permissions (e.g. `dialout` on Linux).
|
||||
|
||||
## Problem 1: EOFError during start_notify
|
||||
|
||||
BLE connection to MeshCore device fails with `EOFError` during `start_notify` on the UART TX characteristic. The error originates in `dbus_fast` (the D-Bus library used by `bleak`) and looks like this:
|
||||
|
||||
```
|
||||
File "src/dbus_fast/_private/unmarshaller.py", line 395, in dbus_fast._private.unmarshaller.Unmarshaller._read_sock_with_fds
|
||||
EOFError
|
||||
```
|
||||
|
||||
Basic BLE connect works fine, but subscribing to notifications (`start_notify`) crashes.
|
||||
|
||||
## Problem 2: PIN or Key Missing / Authentication Failure
|
||||
|
||||
BLE connection fails immediately after connecting with `failed to discover services, device disconnected` or `le-connection-abort-by-local`. In `btmon`, the trace shows:
|
||||
|
||||
```
|
||||
Encryption Change - Status: PIN or Key Missing (0x06)
|
||||
Disconnect - Reason: Authentication Failure (0x05)
|
||||
```
|
||||
|
||||
This happens when the MeshCore device requires BLE PIN pairing (e.g., PIN `123456`) but no BlueZ agent is running to handle the passkey exchange. Bleak cannot provide a PIN by itself — it relies on a BlueZ agent to handle pairing.
|
||||
|
||||
**Symptoms:**
|
||||
- `bluetoothctl connect` fails with `le-connection-abort-by-local`
|
||||
- `bluetoothctl pair` asks for a passkey and succeeds
|
||||
- meshcore-gui still fails because bleak creates its own connection without an agent
|
||||
- btmon shows repeated connect → encrypt → `PIN or Key Missing` → disconnect cycles
|
||||
|
||||
## Problem 3: Port already in use
|
||||
|
||||
meshcore-gui fails to start with:
|
||||
|
||||
```
|
||||
ERROR: [Errno 98] error while attempting to bind on address ('0.0.0.0', 8081): address already in use
|
||||
```
|
||||
|
||||
This means a previous meshcore-gui instance is still running (or the port hasn't been released yet).
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic Steps
|
||||
|
||||
### 1. Check adapter status
|
||||
|
||||
```bash
|
||||
hciconfig -a
|
||||
```
|
||||
|
||||
Expected: `UP RUNNING`. If it shows `DOWN`, reset with:
|
||||
|
||||
```bash
|
||||
sudo hciconfig hci0 down
|
||||
sudo hciconfig hci0 up
|
||||
```
|
||||
|
||||
### 2. Check if adapter is detected
|
||||
|
||||
```bash
|
||||
lsusb | grep -i blue
|
||||
```
|
||||
|
||||
### 3. Check power supply (Raspberry Pi)
|
||||
|
||||
```bash
|
||||
vcgencmd get_throttled
|
||||
```
|
||||
|
||||
Expected: `throttled=0x0`. Any other value indicates power issues that can cause BLE instability.
|
||||
|
||||
### 4. Test basic BLE connection (without notify)
|
||||
|
||||
```bash
|
||||
python -c "
|
||||
import asyncio
|
||||
from bleak import BleakClient
|
||||
async def test():
|
||||
async with BleakClient('AA:BB:CC:DD:EE:FF') as c:
|
||||
print('Connected:', c.is_connected)
|
||||
asyncio.run(test())
|
||||
"
|
||||
```
|
||||
|
||||
If this works but meshcli/meshcore_gui fails, the problem is specifically `start_notify`.
|
||||
|
||||
### 5. Test start_notify in isolation
|
||||
|
||||
```bash
|
||||
python -c "
|
||||
import asyncio
|
||||
from bleak import BleakClient
|
||||
UART_TX = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
|
||||
async def test():
|
||||
async with BleakClient('AA:BB:CC:DD:EE:FF') as c:
|
||||
def cb(s, d): print(f'RX: {d.hex()}')
|
||||
await c.start_notify(UART_TX, cb)
|
||||
print('Notify OK!')
|
||||
await asyncio.sleep(2)
|
||||
asyncio.run(test())
|
||||
"
|
||||
```
|
||||
|
||||
If this also fails with `EOFError`, the issue is confirmed at the BlueZ/D-Bus level.
|
||||
|
||||
### 6. Test notifications via bluetoothctl (outside Python)
|
||||
|
||||
```bash
|
||||
bluetoothctl
|
||||
scan on
|
||||
# Wait for device to appear
|
||||
connect AA:BB:CC:DD:EE:FF
|
||||
# Wait for "Connection successful"
|
||||
menu gatt
|
||||
select-attribute 6e400003-b5a3-f393-e0a9-e50e24dcca9e
|
||||
notify on
|
||||
```
|
||||
|
||||
If `connect` fails with `le-connection-abort-by-local`, the problem is at the BlueZ or device level. No Python fix will help.
|
||||
|
||||
### 7. Check if pairing is required (PIN or Key Missing)
|
||||
|
||||
If `bluetoothctl connect` fails with `le-connection-abort-by-local`, try pairing instead:
|
||||
|
||||
```bash
|
||||
bluetoothctl
|
||||
scan on
|
||||
pair AA:BB:CC:DD:EE:FF
|
||||
# If it asks for a passkey, the device requires PIN pairing
|
||||
```
|
||||
|
||||
If pairing succeeds but meshcore-gui still fails, the issue is a missing BlueZ agent (see Solution 2).
|
||||
|
||||
### 8. Use btmon for HCI-level debugging
|
||||
|
||||
```bash
|
||||
sudo btmon
|
||||
```
|
||||
|
||||
In another terminal, start meshcore-gui. Look for:
|
||||
- `Encryption Change - Status: PIN or Key Missing (0x06)` → pairing/agent issue (Solution 2)
|
||||
- Successful encryption but no service discovery → stale bond (Solution 1)
|
||||
|
||||
### 9. Check what is using port 8081
|
||||
|
||||
```bash
|
||||
lsof -i :8081
|
||||
```
|
||||
|
||||
If another process holds the port, see Solution 3.
|
||||
|
||||
---
|
||||
|
||||
## Solution 1: Stale BLE Pairing State (EOFError)
|
||||
|
||||
The root cause is a stale BLE pairing state between the Linux adapter and the MeshCore device. The fix requires a clean reconnect sequence:
|
||||
|
||||
### Step 1 - Remove the device from BlueZ
|
||||
|
||||
```bash
|
||||
bluetoothctl
|
||||
remove AA:BB:CC:DD:EE:FF
|
||||
exit
|
||||
```
|
||||
|
||||
### Step 2 - Hard power cycle the MeshCore device
|
||||
|
||||
Physically power off the T1000-e (not just a software reset). Wait 10 seconds, then power it back on.
|
||||
|
||||
### Step 3 - Scan and reconnect from scratch
|
||||
|
||||
```bash
|
||||
bluetoothctl
|
||||
scan on
|
||||
```
|
||||
|
||||
Wait until the device appears: `[NEW] Device AA:BB:CC:DD:EE:FF MeshCore-...`
|
||||
|
||||
Then immediately connect:
|
||||
|
||||
```
|
||||
connect AA:BB:CC:DD:EE:FF
|
||||
```
|
||||
|
||||
### Step 4 - Verify notifications work
|
||||
|
||||
```
|
||||
menu gatt
|
||||
select-attribute 6e400003-b5a3-f393-e0a9-e50e24dcca9e
|
||||
notify on
|
||||
```
|
||||
|
||||
If this succeeds, disconnect cleanly:
|
||||
|
||||
```
|
||||
notify off
|
||||
back
|
||||
disconnect AA:BB:CC:DD:EE:FF
|
||||
exit
|
||||
```
|
||||
|
||||
### Step 5 - Verify channels with meshcli
|
||||
|
||||
```bash
|
||||
meshcli -d AA:BB:CC:DD:EE:FF
|
||||
> get_channels
|
||||
```
|
||||
|
||||
Confirm output matches `CHANNELS_CONFIG` in `meshcore_gui.py`, then:
|
||||
|
||||
```
|
||||
> exit
|
||||
```
|
||||
|
||||
### Step 6 - Start the GUI
|
||||
|
||||
```bash
|
||||
cd ~/meshcore-gui
|
||||
source venv/bin/activate
|
||||
python meshcore_gui.py AA:BB:CC:DD:EE:FF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Solution 2: Missing BlueZ Agent for PIN Pairing
|
||||
|
||||
When the MeshCore device requires BLE PIN pairing, bleak cannot provide the PIN by itself. BlueZ needs a running agent that responds to pairing requests with the correct passkey.
|
||||
|
||||
**Why this happens:** `bluetoothctl` acts as its own agent (which is why manual pairing works), but when bleak connects independently, there is no agent to handle the passkey exchange. Even if the device was previously paired via `bluetoothctl`, the bond can become invalid when:
|
||||
- The MeshCore device is reset or firmware-updated
|
||||
- Another device (e.g., companion app) pairs with the MeshCore device and overwrites its bond slot
|
||||
- The bond keys get out of sync for any reason
|
||||
|
||||
### Step 1 - Install bluez-tools
|
||||
|
||||
```bash
|
||||
sudo apt install bluez-tools
|
||||
```
|
||||
|
||||
### Step 2 - Create a PIN file
|
||||
|
||||
```bash
|
||||
echo "* 123456" > ~/.meshcore-ble-pin
|
||||
chmod 600 ~/.meshcore-ble-pin
|
||||
```
|
||||
|
||||
The format is `<address-or-wildcard> <pin>`. Use `*` to match any device, or specify a specific address:
|
||||
|
||||
```
|
||||
FF:05:D6:71:83:8D 123456
|
||||
```
|
||||
|
||||
### Step 3 - Remove any existing (corrupt) bond
|
||||
|
||||
```bash
|
||||
bluetoothctl remove AA:BB:CC:DD:EE:FF
|
||||
```
|
||||
|
||||
### Step 4 - Start the agent and meshcore-gui
|
||||
|
||||
```bash
|
||||
bt-agent -c KeyboardOnly -p ~/.meshcore-ble-pin &
|
||||
python meshcore_gui.py AA:BB:CC:DD:EE:FF
|
||||
```
|
||||
|
||||
### Step 5 - Make the agent permanent (systemd service)
|
||||
|
||||
Create the service file:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/systemd/system/bt-agent.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Bluetooth PIN Agent for MeshCore
|
||||
After=bluetooth.service
|
||||
Requires=bluetooth.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/bt-agent -c KeyboardOnly -p /home/hans/.meshcore-ble-pin
|
||||
Restart=always
|
||||
User=hans
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable bt-agent
|
||||
sudo systemctl start bt-agent
|
||||
```
|
||||
|
||||
Verify it is running:
|
||||
|
||||
```bash
|
||||
sudo systemctl status bt-agent
|
||||
```
|
||||
|
||||
Now meshcore-gui can connect at any time without manual pairing. The agent survives reboots.
|
||||
|
||||
**Important:** Only run ONE bt-agent instance. Multiple agents conflict with each other. If you have both a manual `bt-agent &` process and the systemd service running, kill the manual one:
|
||||
|
||||
```bash
|
||||
pkill -f bt-agent
|
||||
sudo systemctl start bt-agent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Solution 3: Port 8081 Already in Use
|
||||
|
||||
This happens when a previous meshcore-gui instance is still running or hasn't fully released the port.
|
||||
|
||||
### Quick fix - Kill previous instance and free the port
|
||||
|
||||
```bash
|
||||
pkill -9 -f meshcore_gui
|
||||
sleep 3
|
||||
```
|
||||
|
||||
Verify the port is free:
|
||||
|
||||
```bash
|
||||
lsof -i :8081
|
||||
```
|
||||
|
||||
If nothing shows up, the port is free. Start meshcore-gui:
|
||||
|
||||
```bash
|
||||
nohup python meshcore_gui.py AA:BB:CC:DD:EE:FF --debug-on > ~/meshcore.log 2>&1 &
|
||||
```
|
||||
|
||||
### If the port is still in use after killing
|
||||
|
||||
Sometimes TCP sockets linger in `TIME_WAIT` state. Wait 30 seconds or force it:
|
||||
|
||||
```bash
|
||||
sleep 30
|
||||
lsof -i :8081
|
||||
```
|
||||
|
||||
### Running in background with nohup
|
||||
|
||||
To run meshcore-gui in the background (survives terminal close):
|
||||
|
||||
```bash
|
||||
nohup python meshcore_gui.py AA:BB:CC:DD:EE:FF --debug-on > ~/meshcore.log 2>&1 &
|
||||
```
|
||||
|
||||
Check if it started successfully:
|
||||
|
||||
```bash
|
||||
sleep 5
|
||||
tail -30 ~/meshcore.log
|
||||
```
|
||||
|
||||
**Tip:** Always redirect output to a log file (not `/dev/null`) so you can diagnose problems:
|
||||
|
||||
```bash
|
||||
# Good - keeps logs for debugging
|
||||
nohup python meshcore_gui.py AA:BB:CC:DD:EE:FF --debug-on > ~/meshcore.log 2>&1 &
|
||||
|
||||
# Bad - hides all errors
|
||||
nohup python meshcore_gui.py AA:BB:CC:DD:EE:FF --debug-on > /dev/null 2>&1 &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Things That Did NOT Help
|
||||
|
||||
| Action | Result |
|
||||
|---|---|
|
||||
| `sudo systemctl restart bluetooth` | No effect |
|
||||
| `sudo hciconfig hci0 down/up` | No effect |
|
||||
| `sudo rmmod btusb && sudo modprobe btusb` | No effect |
|
||||
| `sudo usbreset "8087:0026"` | No effect |
|
||||
| `sudo reboot` | No effect |
|
||||
| Clearing BlueZ cache (`/var/lib/bluetooth/*/cache`) | No effect |
|
||||
| Recreating Python venv | No effect |
|
||||
| Downgrading `dbus_fast` / `bleak` | No effect |
|
||||
| Downgrading `linux-firmware` | No effect |
|
||||
| Adding `pin="123456"` to `MeshCore.create_ble()` | Pairing fails — bleak's `pair()` cannot provide a passkey without a BlueZ agent |
|
||||
| Pre-connecting via `bluetoothctl connect` before meshcore-gui | Bleak creates its own connection and doesn't reuse the existing one |
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
### EOFError / stale bond
|
||||
When `start_notify` fails with `EOFError` but basic BLE connect works, the issue is almost always a stale BLE state between the host adapter and the peripheral device. The fix is:
|
||||
|
||||
1. **Remove** the device from bluetoothctl
|
||||
2. **Hard power cycle** the peripheral device
|
||||
3. **Re-scan** and reconnect from scratch
|
||||
|
||||
### PIN or Key Missing / Authentication Failure
|
||||
When btmon shows `PIN or Key Missing (0x06)` and connections drop immediately after encryption negotiation, the fix is:
|
||||
|
||||
1. **Remove** the corrupt bond from bluetoothctl
|
||||
2. **Run `bt-agent`** with the correct PIN file so BlueZ can handle pairing requests
|
||||
3. **Install as systemd service** for persistence across reboots
|
||||
|
||||
### Port already in use
|
||||
When meshcore-gui fails with `[Errno 98] address already in use`:
|
||||
|
||||
1. **Kill** any existing meshcore-gui process: `pkill -9 -f meshcore_gui`
|
||||
2. **Wait** a few seconds for the port to be released
|
||||
3. **Verify** the port is free: `lsof -i :8081`
|
||||
|
||||
---
|
||||
|
||||
## Recommended Startup Sequence
|
||||
|
||||
For the most reliable BLE connection, always follow this order:
|
||||
|
||||
1. Ensure `bt-agent` is running (if device requires PIN pairing): `sudo systemctl status bt-agent`
|
||||
2. Ensure no other meshcore-gui instance is running: `pkill -f meshcore_gui` and `lsof -i :8081`
|
||||
3. Ensure no other application holds the BLE connection (BT manager, bluetoothctl, meshcli, companion app)
|
||||
4. Verify the device is visible: `bluetoothctl scan on`
|
||||
5. Check channels: `meshcli -d <BLE_ADDRESS>` → `get_channels` → `exit`
|
||||
6. Start the GUI: `python meshcore_gui.py <BLE_ADDRESS>`
|
||||
@@ -0,0 +1,79 @@
|
||||
# ADR-001: Browser-managed Leaflet runtime
|
||||
|
||||
**Status:** Accepted — 2026-02
|
||||
|
||||
## Context
|
||||
|
||||
De MeshCore GUI toont contacten en eigen positie op een Leaflet-kaart in
|
||||
het centrum-paneel van de dashboard. De dashboard-update-loop draait elke
|
||||
500 ms en pusht een snapshot van device- en contact-data naar de UI.
|
||||
|
||||
Eerdere implementaties stuurden de map-lifecycle vanuit Python aan
|
||||
(NiceGUI-wrappers, JS-injection vanuit snapshot-handlers). Dat
|
||||
veroorzaakte:
|
||||
|
||||
- Verdwijnende kaarten omdat `L.map(...)` in de refresh-loop opnieuw
|
||||
werd aangeroepen op een al geïnitialiseerd container-element
|
||||
(`Map container is already initialized`).
|
||||
- Marker-flicker omdat de hele marker-laag elke 500 ms werd vervangen
|
||||
in plaats van incrementeel bijgewerkt.
|
||||
- Viewport-resets en thema-resets omdat thema-state in snapshots zat
|
||||
en queued snapshots gebruikersacties overschreven.
|
||||
- Cluster-bootstrap-fouten wanneer de cluster-laag werd aangehecht
|
||||
voordat `maxZoom` op map of tile-layer bekend was.
|
||||
|
||||
## Decision
|
||||
|
||||
**De Leaflet-map-lifecycle is volledig in handen van de browser.**
|
||||
Python is data-leverancier en nooit aanroeper van Leaflet-API's.
|
||||
|
||||
Concreet:
|
||||
|
||||
- Leaflet wordt **één keer** geïnitialiseerd per DOM-container in
|
||||
`static/leaflet_map_panel.js`.
|
||||
- Python (`MapPanel`, `MapSnapshotService`) stuurt **compacte JSON-
|
||||
snapshots**; de browser past deze incrementeel toe per stabiele
|
||||
node-id.
|
||||
- **Thema-state loopt over een eigen kanaal**, niet door snapshots.
|
||||
- **Viewport-state blijft bij de browser**; snapshots forceren geen
|
||||
center/zoom tijdens normale refresh-cycles.
|
||||
- De **eigen-device-marker zit buiten** de contact-cluster-layer.
|
||||
- `maxZoom` van map/tile-layer wordt gezet **vóór** clustering wordt
|
||||
aangehecht.
|
||||
|
||||
NiceGUI-map-wrappers (`ui.leaflet()` of vergelijkbaar) worden niet
|
||||
gebruikt.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Plus**
|
||||
|
||||
- Geen flicker, geen viewport-resets, geen thema-resets tijdens de
|
||||
refresh-cyclus.
|
||||
- Reconnect na korte NiceGUI-disconnect blijft de map en lokale state
|
||||
intact.
|
||||
- Map-state overleeft snapshot-coalescing: alleen het nieuwste payload
|
||||
wordt toegepast.
|
||||
|
||||
**Min**
|
||||
|
||||
- Leaflet-API's zijn niet vanuit Python aanroepbaar; uitbreidingen
|
||||
(heatmap, route-overlays, tile-switching) moeten als browser-runtime-
|
||||
uitbreiding worden geïmplementeerd.
|
||||
- Debuggen vereist DevTools-inspectie van de runtime, niet alleen
|
||||
Python-logs.
|
||||
|
||||
**Bindende uitvloeisels** (zie `DEV_RULES.md`):
|
||||
|
||||
- Recreate map in 500 ms loop → verboden
|
||||
- `L.map(...)` vanuit Python → verboden
|
||||
- Thema in snapshots → verboden
|
||||
- Device-marker in cluster-layer → verboden
|
||||
|
||||
## References
|
||||
|
||||
- `MAP_ARCHITECTURE.md`
|
||||
- `DEV_RULES.md`
|
||||
- `meshcore_gui/static/leaflet_map_panel.js`
|
||||
- `meshcore_gui/services/map_snapshot_service.py`
|
||||
- `meshcore_gui/gui/panels/map_panel.py`
|
||||
@@ -0,0 +1,86 @@
|
||||
# ADR-002: Protocol-interfaces (`typing.Protocol`) i.p.v. `abc.ABC`
|
||||
|
||||
**Status:** Accepted — 2026-02
|
||||
|
||||
## Context
|
||||
|
||||
De applicatie heeft één centrale, thread-safe data-store (`SharedData`)
|
||||
die door meerdere consumenten wordt gebruikt:
|
||||
|
||||
- `BLEWorker` schrijft device-info, contacten, channels, messages en
|
||||
RX-log entries.
|
||||
- `DashboardPage` leest snapshots en plaatst commands.
|
||||
- `RouteBuilder` zoekt contacten op prefix.
|
||||
- `RoutePage` en `ArchivePage` doen beide read en lookup.
|
||||
- Widget-klassen ontvangen alleen plain dicts en callbacks.
|
||||
|
||||
Vóór de SOLID-refactor importeerden al deze consumenten direct
|
||||
`shared_data.SharedData`. Dat:
|
||||
|
||||
- Verstopte dat de meeste consumenten maar een fractie van de 15+
|
||||
publieke methoden gebruiken (ISP-violation).
|
||||
- Maakte testen lastig — een test-stub moest alle methoden bieden,
|
||||
ook de niet-gebruikte.
|
||||
- Bond consumenten aan de concrete implementatie i.p.v. aan het
|
||||
contract dat ze nodig hebben (DIP-violation).
|
||||
|
||||
## Decision
|
||||
|
||||
**Definieer per consument een smal Protocol-contract in `protocols.py`;**
|
||||
consumenten typen tegen het contract, niet tegen `SharedData`.
|
||||
|
||||
Geïntroduceerde Protocols:
|
||||
|
||||
| Protocol | Consument | Methoden |
|
||||
|----------|-----------|----------|
|
||||
| `SharedDataWriter` | `BLEWorker` | 10–15 (write + lookup) |
|
||||
| `SharedDataReader` | `DashboardPage` | 4–6 (snapshot + commands) |
|
||||
| `ContactLookup` | `RouteBuilder` | 1 |
|
||||
| `SharedDataReadAndLookup` | `RoutePage`, `ArchivePage` | Reader + Lookup |
|
||||
| `CommandSink` | `MeshBot`, GUI-pages | `put_command` |
|
||||
|
||||
`SharedData` blijft één concrete klasse die alle Protocols
|
||||
**structureel** implementeert (zonder `from typing import Protocol`-
|
||||
inheritance). De **composition root** (`meshcore_gui.py`) is de enige
|
||||
plek die de concrete klasse kent.
|
||||
|
||||
`typing.Protocol` is gekozen **boven `abc.ABC`** omdat:
|
||||
|
||||
| Aspect | `abc.ABC` (nominal) | `typing.Protocol` (structural) |
|
||||
|--------|---------------------|-------------------------------|
|
||||
| Subclassing nodig | ja | nee |
|
||||
| Duck typing | nee | ja |
|
||||
| Test-stubs | moeten erven | hoeven alleen methoden te hebben |
|
||||
|
||||
## Consequences
|
||||
|
||||
**Plus**
|
||||
|
||||
- Elke consument ziet alleen wat hij nodig heeft (ISP).
|
||||
- Test-stubs zijn lichtgewicht: alleen de methoden van het smalle
|
||||
contract.
|
||||
- Refactoring van `SharedData`-internen raakt consumenten niet zolang
|
||||
de Protocol-signatures stabiel blijven (DIP).
|
||||
- Widget-klassen hebben **nul** kennis van `SharedData` — ze krijgen
|
||||
plain dicts en callbacks.
|
||||
|
||||
**Min**
|
||||
|
||||
- `typing.Protocol` werkt vanaf Python 3.8 — geen probleem in deze
|
||||
codebase (3.10+).
|
||||
- Vijf interface-classes zijn extra onderhoud bij echt brede signature-
|
||||
wijzigingen. Drempel voor nieuwe Protocols: alleen wanneer een
|
||||
consument een meetbaar smaller contract heeft, niet preventief.
|
||||
|
||||
**Niet introduceren**
|
||||
|
||||
- Geen `abc.ABC` met `@abstractmethod` voor deze use-case. De Protocol-
|
||||
variant levert hetzelfde type-veiligheidsniveau zonder verplichte
|
||||
subclassing.
|
||||
|
||||
## References
|
||||
|
||||
- `SOLID_ANALYSIS.md`
|
||||
- `meshcore_gui/core/protocols.py`
|
||||
- `meshcore_gui/core/shared_data.py`
|
||||
- PEP 544 — Protocols: Structural subtyping
|
||||
@@ -0,0 +1,80 @@
|
||||
# ADR-003: Persistente BLE-connectie via subprocess (`meshcore-ble-connect`)
|
||||
|
||||
**Status:** Accepted — 2026-02 (v5.5)
|
||||
|
||||
## Context
|
||||
|
||||
Vanaf BlueZ 5.82 (en deels 5.78+) lukt het bleak niet meer betrouwbaar
|
||||
om zelf de `Device1.Connect()` D-Bus-aanroep te doen vóór GATT-service-
|
||||
discovery. Resultaat: `failed to discover services, device disconnected`,
|
||||
`le-connection-abort-by-local`, en in `btmon` herhaalde
|
||||
`PIN or Key Missing (0x06) → Authentication Failure (0x05)`-cycli.
|
||||
|
||||
`bluetoothctl` werkt wel, maar zodra bleak een eigen connectie opzet
|
||||
ziet BlueZ deze als een nieuwe sessie zonder geldig agent-context.
|
||||
|
||||
De directe BLE-connect-flow vanuit `BLEWorker` werd daarmee
|
||||
onbetrouwbaar op recente Linux-distributies.
|
||||
|
||||
## Decision
|
||||
|
||||
**Gebruik `meshcore-ble-connect` als persistent subprocess** dat de
|
||||
D-Bus/BLE-connectie openhoudt; bleak doet alleen GATT-service-discovery
|
||||
over de bestaande connectie.
|
||||
|
||||
Flow in `BLEWorker._connect()`:
|
||||
|
||||
1. `ensure_bond()` (subprocess pairing-flow met PIN-agent) — bond OK.
|
||||
2. Start subprocess: `meshcore-ble-connect <MAC> --pin <X> --connect`.
|
||||
3. Wacht op `READY` op subprocess-stdout.
|
||||
4. `BleakScanner.find_device_by_address()` — populeert bleak's
|
||||
`BlueZManager`-singleton zodat hij `Connected=True` ziet via
|
||||
`GetManagedObjects()`.
|
||||
5. `BleakClient(addr).connect()` — bleak slaat `Device1.Connect()`
|
||||
over, doet alleen GATT-discovery.
|
||||
6. `MeshCore.create_ble(client=client)` — normale event-loop.
|
||||
7. Subprocess-health-check elke 100 ms in main loop; cleanup in
|
||||
`finally`-blok.
|
||||
|
||||
Het subprocess monitort `Connected`-property en print `DISCONNECTED`
|
||||
op stdout bij verlies; `BLEWorker` triggert dan reconnect.
|
||||
|
||||
**Fallback:** als het subprocess faalt te starten (bijv. op BlueZ < 5.78
|
||||
waar het niet nodig is), valt `_connect()` terug op directe
|
||||
`MeshCore.create_ble(address)`.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Plus**
|
||||
|
||||
- Werkt op BlueZ 5.82+ zonder workarounds in `bleak`.
|
||||
- Strikte separation of concerns: `meshcore-ble-connect` heeft één
|
||||
taak (D-Bus-connectie openhouden), GUI hoeft geen D-Bus-code te
|
||||
bevatten.
|
||||
- Backwards compatible via fallback-pad.
|
||||
|
||||
**Min**
|
||||
|
||||
- Extra externe dependency (`meshcore-ble-connect` als geïnstalleerd
|
||||
CLI-tool in de venv).
|
||||
- Subprocess-lifecycle moet bewaakt worden (signal handling, cleanup
|
||||
in `finally`, kill bij worker-shutdown).
|
||||
- Potentieel risico dat bleak alsnog `Device1.Connect()` aanroept
|
||||
als de scanner het device niet ziet adverteren terwijl het connected
|
||||
is. In de praktijk vangt `GetManagedObjects()` dit op.
|
||||
|
||||
**Bindende uitvloeisels**
|
||||
|
||||
- Geen pairing- of D-Bus-code in `meshcore_gui/`. Bond-management en
|
||||
agent-registratie horen in de subprocess of in `BleAgentManager`
|
||||
(zie ADR-004).
|
||||
- `meshcore-ble-connect` is een **separaat project** met eigen
|
||||
versionering. Geen systemd-files of install-docs van dat project
|
||||
in deze repo.
|
||||
|
||||
## References
|
||||
|
||||
- `INTEGRATION_GUIDE.md`
|
||||
- ADR-004 — Built-in D-Bus PIN-agent (complementair)
|
||||
- `meshcore_ble_connect/app.py`, `device.py`, `__main__.py`
|
||||
- `meshcore_gui/ble/worker.py`
|
||||
@@ -0,0 +1,75 @@
|
||||
# ADR-004: Ingebouwde D-Bus PIN-agent (vervangt `bt-agent.service`)
|
||||
|
||||
**Status:** Accepted — 2026-02 (v5.11)
|
||||
|
||||
## Context
|
||||
|
||||
MeshCore-devices vereisen BLE PIN-pairing (default `123456`). Bleak kan
|
||||
zelf geen passkey leveren — BlueZ verwacht een geregistreerde
|
||||
`org.bluez.Agent1`-implementatie die op pairing-callbacks reageert.
|
||||
|
||||
De eerste werkende oplossing was het `bluez-tools`-pakket met
|
||||
`bt-agent` als systemd-service, gevoed door een PIN-bestand
|
||||
(`~/.meshcore-ble-pin`). Dat werkte, maar:
|
||||
|
||||
- Vereiste extra OS-pakket (`apt install bluez-tools`).
|
||||
- Vereiste een aparte systemd-unit en D-Bus-policy per host.
|
||||
- Was gevoelig voor "twee agents tegelijk"-conflicten (manueel gestarte
|
||||
`bt-agent &` plus systemd-service).
|
||||
- Splitste pairing-state tussen GUI-config en een los config-bestand.
|
||||
|
||||
## Decision
|
||||
|
||||
**Implementeer de pairing-agent intern**, in `meshcore_gui/ble/ble_agent.py`,
|
||||
gebruikmakend van `dbus_fast` (al een bleak-dependency).
|
||||
|
||||
Componenten:
|
||||
|
||||
- `BluezAgent(ServiceInterface)` — implementeert
|
||||
`org.bluez.Agent1` met callbacks voor `RequestPinCode`,
|
||||
`RequestPasskey`, `DisplayPasskey`, `RequestConfirmation` en
|
||||
`AuthorizeService`. Antwoordt op alle PIN-requests met de geconfigureerde
|
||||
`BLE_PIN` (default `"123456"`).
|
||||
- `BleAgentManager` — `start(pin)` exporteert de agent op D-Bus-pad
|
||||
`/meshcore/ble_agent` en registreert hem als default agent met
|
||||
capability `KeyboardOnly`. `stop()` deregistreert en sluit de
|
||||
D-Bus-verbinding.
|
||||
|
||||
`BLEWorker` start de agent **vóór** elke connect-poging en cleanupt
|
||||
in `finally`. PIN staat in `config.py` (`BLE_PIN`).
|
||||
|
||||
## Consequences
|
||||
|
||||
**Plus**
|
||||
|
||||
- Geen externe dependency op `bluez-tools`.
|
||||
- Geen aparte systemd-service of D-Bus-policy meer nodig voor het
|
||||
agent-pad (system-bus policy voor `org.bluez.Agent1` blijft).
|
||||
- Eén bron voor de PIN: `config.py`.
|
||||
- Lifecycle gebonden aan de Worker — geen verweesde agents.
|
||||
|
||||
**Min**
|
||||
|
||||
- Vereist `dbus_fast` als runtime-dependency (was er al via bleak,
|
||||
maar nu expliciet).
|
||||
- D-Bus-policy in `/etc/dbus-1/system.d/` blijft per-host nodig zodat
|
||||
de niet-root user mag praten met `org.bluez`.
|
||||
- Eén PIN per device-fleet (de agent matcht op `*`). Voor multi-PIN
|
||||
scenario's moet de agent uitgebreid worden.
|
||||
|
||||
**Bindende uitvloeisels**
|
||||
|
||||
- Nieuwe code voor pairing/agent-registratie hoort in
|
||||
`meshcore_gui/ble/ble_agent.py` of in `meshcore-ble-connect`. Niet
|
||||
in `worker.py` of GUI-code.
|
||||
- `INSTALLATIE.md` is daarmee historisch — de daar beschreven
|
||||
`bt-agent`-stappen zijn **niet** meer aanbevolen.
|
||||
|
||||
## References
|
||||
|
||||
- `MeshCore_GUI_Design.docx` §3.18 — `BleAgentManager`
|
||||
- `INSTALLATIE.md` (legacy)
|
||||
- `TROUBLESHOOTING.md` — Solution 2 (legacy bt-agent flow)
|
||||
- ADR-003 — Subprocess-BLE-connect (complementair)
|
||||
- `meshcore_gui/ble/ble_agent.py`
|
||||
- `meshcore_gui/config.py` — `BLE_PIN`
|
||||
@@ -0,0 +1,105 @@
|
||||
# ADR-005: Dual-layer persistence — `SharedData` + `MessageArchive` met onafhankelijke locks
|
||||
|
||||
**Status:** Accepted — 2026-02 (v5.1)
|
||||
|
||||
## Context
|
||||
|
||||
De GUI moet twee verschillende dingen tegelijk aankunnen:
|
||||
|
||||
1. **Real-time UI** — laatste 100 messages, laatste 50 RX-log-entries,
|
||||
met sub-second responsetijd in de 500 ms refresh-loop.
|
||||
2. **Lange-termijn archief** — alle inkomende messages en RX-log-entries
|
||||
bewaren over dagen tot maanden, met retentie en query-mogelijkheden
|
||||
(filter, search, paginatie, hash-lookup voor route-pagina).
|
||||
|
||||
Deze twee belangen botsen in één datastructuur:
|
||||
|
||||
- Eén onbegrensde lijst geeft geheugendruk en trage UI-updates.
|
||||
- Disk-I/O in de UI-thread bevriest NiceGUI.
|
||||
- Gedeelde lock tussen UI-buffer en archive-flush geeft contention —
|
||||
archive-write blokkeert dan een dashboard-refresh.
|
||||
|
||||
## Decision
|
||||
|
||||
**Twee gescheiden lagen met eigen verantwoordelijkheden en eigen locks.**
|
||||
|
||||
```
|
||||
SharedData (in-memory, UI)
|
||||
├── messages: laatste 100
|
||||
├── rx_log: laatste 50
|
||||
├── threading.Lock()
|
||||
└── on add → MessageArchive
|
||||
|
||||
MessageArchive (persistent)
|
||||
├── alle messages / rx-log → JSON
|
||||
├── batch-buffer (10 items / 60 s)
|
||||
├── retentie-cleanup (dagelijks)
|
||||
├── threading.Lock() ← onafhankelijk
|
||||
└── atomic write (temp + rename)
|
||||
```
|
||||
|
||||
**Lock-volgorde** (om deadlock te voorkomen):
|
||||
|
||||
1. `SharedData.lock` acquired
|
||||
2. `SharedData` roept `MessageArchive.add_*()`
|
||||
3. `MessageArchive.lock` acquired binnen die call
|
||||
|
||||
Deze volgorde is consistent in álle write-paden.
|
||||
|
||||
**Storage-format:** één JSON-bestand per data-type per device-identifier:
|
||||
- `~/.meshcore-gui/archive/<ADDRESS>_messages.json`
|
||||
- `~/.meshcore-gui/archive/<ADDRESS>_rxlog.json`
|
||||
|
||||
Velden mogen alleen worden **toegevoegd** (default-waarde),
|
||||
niet verwijderd of hernoemd. Schema-versie staat in elk bestand.
|
||||
|
||||
**Retentie** is configureerbaar in `config.py` (`MESSAGE_RETENTION_DAYS`,
|
||||
`RXLOG_RETENTION_DAYS`, `CONTACT_RETENTION_DAYS`). Cleanup draait
|
||||
dagelijks in de Worker-thread.
|
||||
|
||||
**Backward-compat:** `SharedData()` zonder device-identifier werkt
|
||||
zonder archive (`archive=None`).
|
||||
|
||||
## Consequences
|
||||
|
||||
**Plus**
|
||||
|
||||
- UI blijft responsief: archive-flush blokkeert nooit een snapshot.
|
||||
- Buffered batch-writes (drempel `BATCH_SIZE` of timer) houden disk-I/O
|
||||
laag; ~10 ms voor 1000 messages.
|
||||
- Retentie-cleanup is een eigen periodieke taak, niet verweven met
|
||||
refresh-loop.
|
||||
- Atomic writes (`tempfile + os.replace`) voorkomen halve archives bij
|
||||
crash.
|
||||
- Per-device-isolatie maakt multi-instance triviaal (zie
|
||||
`MULTI_INSTANCE.md`).
|
||||
|
||||
**Min**
|
||||
|
||||
- Twee locks vereisen disciplinaire lock-volgorde. Afwijken
|
||||
introduceert deadlock-risico.
|
||||
- Bij crash tijdens een nog-niet-geflushte batch verlies je tot
|
||||
`BATCH_SIZE` items of de laatste 60 s. Acceptabel voor mesh-
|
||||
observatie; niet acceptabel als dit financiële data was.
|
||||
- JSON-array-append vereist read-modify-write in plaats van streaming
|
||||
append. Bestandsgrootte is voorlopig acceptabel (<10 MB / maand bij
|
||||
100 msg/dag); als het structureel groter wordt, is migratie naar
|
||||
JSONL of SQLite een logische vervolgstap.
|
||||
|
||||
**Bindende uitvloeisels**
|
||||
|
||||
- `MessageArchive` heeft een **eigen lock**. Niet kruislings vergrendelen
|
||||
met `SharedData.lock` buiten de gedefinieerde volgorde.
|
||||
- Schema-velden: alleen toevoegen met default. Verwijderen/hernoemen
|
||||
vereist nieuwe ADR + migratie.
|
||||
- Cleanup-interval staat **niet** afgestemd op de refresh-loop maar
|
||||
draait dagelijks in de Worker.
|
||||
|
||||
## References
|
||||
|
||||
- `FEATURE_MESSAGE_PERSISTENCE.md`
|
||||
- `MeshCore_GUI_Design.docx` §3.13 — `MessageArchive`
|
||||
- `MULTI_INSTANCE.md` — per-device data-isolatie
|
||||
- `meshcore_gui/core/shared_data.py`
|
||||
- `meshcore_gui/persistence/message_archive.py`
|
||||
- `meshcore_gui/config.py` — `MESSAGE_RETENTION_DAYS` etc.
|
||||
Reference in new issue
Block a user