Fixes a critical bug where Android devices (acting as BLE centrals) disconnecting
from Pi GATT servers (acting as peripherals) never triggered cleanup, causing stale
peer entries to accumulate until the 7-peer limit was reached and blocked all new
connections.
## Root Cause
- When centrals disconnected from peripheral mode, no cleanup occurred
- `BLEGATTServer._handle_central_disconnected()` method didn't exist
- `on_central_disconnected` callback was never wired to driver
- No D-Bus signal monitoring for device disconnections
- Stale entries remained in `_peers` dict until daemon restart
## Implementation (TDD Approach)
**New Methods:**
- `LinuxBluetoothDriver._handle_peripheral_disconnected()` (line 852)
- Removes peer from `_peers` dictionary
- Notifies on_device_disconnected callback
- Triggers full cleanup chain in BLEInterface
- `BluezeroGATTServer._handle_central_disconnected()` (line 1945)
- Removes from `connected_centrals` dictionary
- Logs connection duration
- Invokes driver callback
- `BluezeroGATTServer._monitor_device_disconnections()` (line 1645)
- Monitors D-Bus PropertiesChanged signals
- Detects when Connected property becomes False
- Runs in separate daemon thread
- Automatically triggers cleanup on disconnect
**Callback Wiring:** (line 1558)
`on_central_disconnected = driver._handle_peripheral_disconnected`
## Testing
- Created comprehensive test suite (9 tests, all passing)
- `tests/test_peripheral_disconnect_cleanup.py`:
- Callback wiring verification
- Peer dictionary cleanup
- D-Bus signal handling simulation
- Edge cases (multiple disconnects, race conditions, shutdown)
- Reproduces real-world bug from production logs
- No regressions in existing tests (test_bluez_state_cleanup.py passes)
## Current Status
✅ Core cleanup logic implemented and tested
✅ Deployed to 4 production devices (10.0.0.80, .242, .39, .246)
⚠️ D-Bus monitoring thread needs debugging (not logging yet)
**Known Issue:** D-Bus signal subscription may need alternative approach.
See PERIPHERAL_DISCONNECT_FIX_SUMMARY.md for troubleshooting steps.
**Fallback Option:** Timeout-based polling can be implemented if D-Bus proves difficult.
Reference: Production logs showed device 4A:87:8C:C7:E3:F3 repeatedly blocked
by "max peers (7) reached" due to uncleaned peripheral disconnections.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>