Repository navigation
Serialize BLE access, stop replaced command loops, release the adapter when idle - #174
Conversation
…r when idle Only one BLE connection may be open at a time, and the adapter may only be released when none is. Neither was guaranteed: - body_controller_state and SendKeysToVehicle opened their own connections next to the command loop. body_controller_state now goes through the queue (VCSEC only, so it still never wakes the car) and is answered right away on an open connection. SendKeysToVehicle and the loop share bleMu, held from dial to disconnect. A command that needs Infotainment on a VCSEC-only connection reconnects with the usual wake-up logic. - The active BleControl was a plain global that a key change set to nil and replaced while handlers read it twice. It is now an atomic pointer read once per request. SetupBleControl stops the previous loop instead of leaving it running; Stop closes its connection and answers queued and later commands with an error instead of leaving them hanging. From #161: connectionTimeout makes the connection hold configurable (default 29 s as before), and releaseAdapterWhenIdle (off by default) calls ble.CloseAdapter whenever the loop is idle, under bleMu, so other BlueZ services on the host can use the adapter (#158). Errors that are not retried are now reported instead of returned as success. Missing or invalid bodies for set_charging_amps, set_charge_limit and set_sentry_mode are InvalidInputErrors, checked before queuing, so they answer with an error and never contact the car. Co-authored-by: AloisKlingler <11095760+AloisKlingler@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Environment variable docs, a README troubleshooting section on sharing the Bluetooth adapter, and CHANGELOG entries under Unreleased. Co-authored-by: AloisKlingler <11095760+AloisKlingler@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🟡 Merge after the to-dosGood fix for the double-connection and nil-pointer problems, and the new options are off or unchanged by default. Two behaviour changes can still be noticed by evcc and Home Assistant, so please check them on a real car first. Impact on existing users: To-do for the author
Test before merging: On a Pi with a car: run evcc polling plus a Technical details
|
…ection Review follow-up for #174: - Push held the read lock while sending into a full queue, so Stop (and with it a key change) waited until the loop drained it, and RWMutex then blocked every further push too. Push no longer blocks: a full queue (50 requests, the car has been unreachable for a while) is answered with an error. - SendKeysToVehicle asks the loop to close its connection (RequestClose) instead of waiting up to connectionTimeout for it, and gives up after 30 s with a clear message. - Test that a caller who gave up (body_controller_state after 15 s) is never sent to the car: the loop checks the request context before dialing. - Document the queue behaviour of body_controller_state and the stricter error reporting under "Changed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wimaha/ble_BleConnectFix#1 (by @AloisKlingler) issues HCIDEVUP when the HCI user channel socket is closed. Without it the adapter stays down after ble.CloseAdapter and BlueZ never sees it again, so releaseAdapterWhenIdle had no effect for other services (#158). The module diff is only that change in linux/hci/socket/socket.go. Co-authored-by: AloisKlingler <11095760+AloisKlingler@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🟡 Merge after the to-dosGood fix: one BLE connection at a time, and old loops are stopped after a key change. Defaults stay the same (29 s, adapter kept). One real problem: commands sent without Impact on existing users: Some errors that used to return To-do for the author
Test before merging: On a real Pi with evcc: poll Technical details
|
Second review follow-up for #174: - A command sent without wait=true that could not be queued (queue full, loop stopped by a key change) was dropped while the API answered "will be processed shortly". Push now returns the error and the handler reports it as result=false. Commands dropped later (queued during a key change) are logged as not executed. - Stop ended an open connection but not a running connection attempt, so after a key change with the car out of range the old loop kept Bluetooth busy through its scans and retries, and SendKeysToVehicle could time out with "Bluetooth is busy". Stop now cancels the attempt as well. - CHANGELOG lists the stricter error reporting, the async rejection and the body_controller_state queue limit under "Breaking" as possibly breaking for integrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Should fix: #158 |
Why
#161 by @AloisKlingler lets the proxy share the Bluetooth adapter with other BlueZ services (#158) by calling
ble.CloseAdapter()whenever the command loop is idle. That is only safe if no BLE connection is open at that moment, and today that isn't guaranteed:body_controller_stateandSendKeysToVehicleopen their own connections next to the command loop.CloseAdapterstops the device without closing existing connections, so a parallel request would lose its adapter.BleControlwas a plain global. A key change set it toniland replaced it while handlers read it twice (checkBleControl, thenPushCommand), which could cause a nil dereference. EverySetupBleControl()also started a new loop without stopping the old one.This PR fixes those first and then adds #161 on top. It replaces #161, which no longer applies after the refactoring in #169. Credit goes to @AloisKlingler as co-author.
What
One connection at a time
body_controller_stategoes through the command queue (VCSEC only, so it still never wakes the car). If a connection is already open, it is answered on it right away without a new scan. The 15 s limit stays.SendKeysToVehicleand the loop sharebleMu, held from dial to disconnect.Key changes
atomic.Pointer. Handlers read it once per request (control.Instance()).SetupBleControlstops the previous loop.Stop()closes its connection and answers queued and later commands with an error instead of leaving them hanging.From #161
connectionTimeout: how long a connection stays open after a command. The default stays at 29 s, so behaviour is unchanged.releaseAdapterWhenIdle(off by default): callsble.CloseAdapter()when the loop is idle, underbleMu.SendKeysToVehiclealso releases the adapter afterwards.Errors no longer reported as success
result: true, for exampleset_charging_ampswithout a body. They are now reported.set_charging_amps,set_charge_limitandset_sentry_modeareInvalidInputErrors and are checked before queuing, so they never contact the car.Docs: env var docs, a README troubleshooting section on sharing the adapter, and CHANGELOG entries. All new sections are marked
*(unreleased)*.Behaviour changes to note
body_controller_state, the connection now stays open forconnectionTimeoutlike after any command, instead of closing at once. A following command reuses it.BLE library for #158
wimaha/ble_BleConnectFix#1 (HCIDEVUP in
Close()) is merged, andgo.modnow uses it (b933489). The module diff is only that change inlinux/hci/socket/socket.go. WithreleaseAdapterWhenIdle=true, BlueZ sees the adapter again after each release.Review follow-up
Pushnever blocks. A full queue (50 requests) is answered with an error right away, soStop()(and a key change) can no longer be held up by a waiting push.body_controller_statein the queue: documented in the README API section and under CHANGELOG "Changed". A caller that gave up after 15 s is never sent to the car: the loop checks the request context before dialing or sending. A test covers this.SendKeysToVehicleasks the loop to close its idle connection (RequestClose) instead of waiting up toconnectionTimeout, and gives up after 30 s with a clear message.Second round
wait=true) that cannot be queued (queue full, key change) now return 503 with the reason instead of "will be processed shortly". Commands dropped later during a key change are logged as not executed.Stop()now also cancels a running connection attempt (scan, retry waits), so the old loop frees Bluetooth at once andSendKeysToVehicledoes not run into "Bluetooth is busy". A test covers this; without the fix it fails.result: false, the async rejection and the 15 sbody_controller_statelimit are listed as possibly breaking for integrations.GOARM=6).ble.CloseAdapter()is a no-op when no adapter is open (if device != nilin vehicle-command), so calling it at loop start is harmless.Testing
go vet ./...andgo test -race ./.... The new tests ran 10× under-racewithout failure.control_lifecycle_test.go:Stopanswers queued and later commands, and closes an open connection.body-controller-statereuses the open connection, and the following Infotainment command reconnects and wakes.Stopdoes not hang.RequestCloseends an idle connection.lockBLEgives up after its timeout.bleMu, theStopcase or the Infotainment check makes the matching test fail.releaseAdapterWhenIdle. @AloisKlingler, could you check this branch in your shared-adapter setup?🤖 Generated with Claude Code