Let Home Assistant name the updater's CAN interface #75
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/updater-can-interface"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The updater hardcoded
can0. The Pi runs two MCP2515 overlays and talks oncan1, so it opened the wrong interface, reportedconnected: falsefor ever, and answered every installno_link.The interface now comes from the integration, which already holds the channel from its config flow.
POST /api/interface/<name>moves the updater's bus;GET /api/statusreports which one it is on. The integration says it at startup and again every minute, and once more immediately before an install.Why a beat rather than a declaration at setup: either process can restart, in either order, and neither is told when the other does. The updater keeps no interface on disk, deliberately — a copy there would go stale the moment the integration was reconfigured — so being told again is the only thing that recovers it. A minute bounds how long an updater that restarted sits without a bus; the firmware poll's fifteen minutes is far too slow, and the pre-install call closes the last gap so a press in that window does not come back
no_link.Until it is told, the updater has no bus: it guesses nothing, still serves the API and still caches firmware. A changed channel closes the old interface, opens the new one, forgets what the old bus announced, and takes any running session with it.
Listening on can1goes in the log, which the silent failure here did not have.Nothing to configure on the Pi beyond the interface the config flow already asks for. No environment variable, and nothing kept in step by hand.
Tested: the suite covers no interface, being told one, being told the same one again, a change, one that will not open and then does, a session cancelled by a change, and the integration's telling at setup, on the beat and before an install.
rpi-setup.mdand the README say where the interface comes from.Closes CCS-UHA-19.
The exact failure this fixes is still silent on the Home Assistant side at startup, and the comment volume goes back past what the reduction pass just took out. Details inline.
@ -61,6 +62,14 @@ all the same so a press cannot wait for ever.POLL_INTERVAL = timedelta(minutes=15)"""The cache only moves when the updater's own six-hourly check finds something."""CHANNEL_INTERVAL = timedelta(minutes=1)One fact — Home Assistant holds the channel, the updater holds none and is told again — is now written out in nine places: this docstring,
FirmwareUpdates,async_use_channel,link.py's module docstring,BusLink.use_interface,BusLink.run,CanBus,api.py'suse_interface, andUpdaterInterfacein the tests. A comment pass just cut these files back. Say it once, inlink.py's module docstring, and let the rest be one line each.@ -286,0 +349,4 @@except UpdaterError as error:_LOGGER.debug("The updater could not be told which interface: %s", error)listening = Falseif listening != self._listening:_listeningstartsFalse, so the first answer only logs when it isTrue. Van boots with the updater down, or on an interface that will not open, and Home Assistant logs nothing above debug — ever. That is the silent failure mode this PR is about, reproduced on the other side of the loopback. Start_listeningasNoneso the first answer always logs.@ -111,0 +137,4 @@return LinkStatus(None, False, "The updater is not listening yet.")try:return asyncio.run_coroutine_threadsafe(self._use(interface), loop).result()except RuntimeError, CancelledError:except RuntimeError, CancelledError:is PEP 758, valid on 3.14 but the only unparenthesised one in the repo — every other site usesexcept (A, B). Also both new branches here (137 and 140-141) are uncovered, and 137's message is wrong in the only case that reaches it:__init__now takes the loop, soloop is Nonemeans stopping, not "not listening yet".@ -171,0 +211,4 @@"No conversation with the nodes: %s. The updater runs without one.",failure,)return LinkStatus(interface, False, str(failure))After a failed open
self._bus.interfaceisNone(close()cleared it), soPOST /api/interface/can9answers{"interface": "can9", "connected": false}whileGET /api/statusanswers{"interface": null}. Returnself._bus.interfacehere so the two agree.641633bclears the three that mattered. Withdrawing theexceptparentheses point:ruff formatstrips them on 3.14 when there is noas, which is why every other tuple-except in the repo keeps theirs — the unparenthesised form here is forced, not a slip.One thing left, low priority.
@ -72,3 +77,4 @@def close(self) -> None:"""Drop it again."""if self.connected:LinkBus.close()clearsconnectedbut leavesinterfaceset, where the realCanBus.close()clears both. So the interesting half of the fix — open oncan0, then told acan9that will not open — would reportcan0through this fake while production reportsNone, and no test covers it. Clearself.interfacehere and the case becomes testable.