Repository navigation
Support OTA updates for devices exposing OTA as a server cluster - #1915
TheJulianJES wants to merge 3 commits into
Conversation
Devices that expose their OTA client as a server cluster still expect OTA server commands, so the direction of sent frames can't be derived from the cluster type.
The coordinator's own endpoint lists OTA as a server cluster, so falling back to it during initialization made every coordinator try to read OTA attributes from itself.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #1915 +/- ##
=======================================
Coverage 99.51% 99.52%
=======================================
Files 61 61
Lines 12777 12790 +13
=======================================
+ Hits 12715 12729 +14
+ Misses 62 61 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@zigpy-review-bot Alternatively, think about if we should just always swap a server |
zigpy-review-bot
left a comment
There was a problem hiding this comment.
No blocking findings. The server-cluster fallback and the Ota.send_direction override are correct and narrowly scoped (unchanged for every non-OTA cluster and for client OTA clusters), and the two regression tests (test_update_device_firmware_server_ota_cluster, test_initialize_coordinator_no_ota_traffic) fail when their fix is reverted. What's left is the hardware test the description already lists, so this is a comment rather than an approval while the PR is a draft.
Notes
- The direction-flip fallback now carries OTA for these devices. On a server-only OTA device, every inbound
query_next_image/image_block/upgrade_endreaches the serverOtacluster, and so the OTA manager's callbacks, only through the fallback in_find_zcl_cluster(zigpy/device.py:1073). Without it,_match_packet_endpoint_clusterdrops the frame as an unknown cluster. That fallback's comment still says "This will be removed in 2025.9.0." Optional: reword it so it says OTA on these devices depends on it, so whoever removes it later doesn't silently break these installs (inline note onfind_ota_cluster). - Overlap with draft #1849. It rewrites the post-OTA block in
update_firmwarethat this PR touches and keepsfind_cluster(..., ClusterType.Client)there. Whichever lands second needsfind_ota_cluster()in the rewritten block, otherwise server-only devices hit the sameValueErrorafter a successful transfer. - For the hardware test: the post-OTA
read_attributes(..., retries=10)atzigpy/device.py:1359is not guarded. If the 3RSS007Z doesn't answer a server-to-client read on the OTA cluster it lists as input,update_firmwareraises after the transfer has completed, and theimage_notifyandreinterview()are skipped. This is existing behaviour for every device, but it is the untested part of the new direction on these devices, so it's worth watching (confirmed with a variant of the new integration test whose device fails the read:DeliveryErrorpropagates, the cache-cleared event has fired,reinterviewis not called). - ZHA follow-up, besides
_read_current_firmware_version:OtaCurrentFileVersionCache(zha/application/platforms/virtual.py) only matches client OTA clusters, so on these devicescurrent_file_versionisn't refreshed fromquery_next_image. The update entity still gets its installed version fromOtaImageAvailableEvent, so this only affects the attribute cache.
Verified (7 checks)
- zigpy suite at 237a01a: 1362 passed.
- zha suite (dev 0ea5e1ad) with this branch installed editable: 1458 passed.
send_directionis the oldis_clientexpression for every cluster exceptOta, andOtaalready sent server-to-client from a client cluster, so only serverOtaclusters change. No zha / zha-quirks code subclassesOtaor overridesrequest/reply.- Response matching (
device.requestkeys on the flipped request direction) lines up with the new direction: a server-to-client read expects the client-to-server response the device's OTA client sends. Automatic default responses on a serverOtacluster now also go server-to-client, which is correct for frames from the device's client. - Reverting only the
Ota.send_directionoverride makestest_update_device_firmware_server_ota_clusterfail; switching_discovertofind_ota_cluster()makestest_initialize_coordinator_no_ota_trafficfail. Both pin the behaviour the description claims. - Z2M has no
ota: truefor the 3RSS007Z (zigbee-herdsman-convertersthird_reality.ts), and herdsman's OTA picks endpoints viasupportsOutputCluster("genOta"), so there's no prior art for this device either way. - Copilot second opinion (two models): no findings.
| """Find the OTA cluster, falling back to a server cluster. | ||
|
|
||
| OTA is a client cluster on the device, but some devices expose it as a server | ||
| cluster instead. Runtime routing also falls back to the server cluster for |
There was a problem hiding this comment.
Optional: this makes the direction-flip fallback in _find_zcl_cluster (zigpy/device.py:1080-1081) load-bearing for OTA on these devices, but its comment still says "This will be removed in 2025.9.0." Consider rewording that comment so the dependency is visible to whoever eventually removes it.
|
I looked into it. In short, a blanket swap wouldn't be a simpler replacement for this PR. The descriptor it would change is what quirks match on, so it needs quirk-side care and more zigpy code than What a swap would gain
What it breaks or needs
If you want it anyway, the minimal safe version is a post-resolution normalizer for non-coordinator devices. It would only touch server-only OTA endpoints and create a fresh client What I checked
|
DRAFT.
Proposed change
Some devices (e.g. the Third Reality 3RSS007Z) list the OTA cluster as an input (server) cluster in their simple descriptor, even though they run a normal OTA client and send spec-correct client-to-server
query_next_imagecommands. zigpy already routes those frames to the server cluster via the direction-flip fallback (see #1640), and ZHA creates an update entity for them (FirmwareUpdateServerEntity, zigpy/zha#425), so updates are offered. Installing one has never worked, though:OTAManager.__init__and the post-OTA cleanup inDevice.update_firmwarelook up the OTA cluster withfind_cluster(..., ClusterType.Client), which raisesValueError: Cluster 0x0019 not foundon these devices. The install fails before any transfer starts.Cluster.request()/reply()derive the frame direction from the cluster type, so everything sent through a server OTA cluster went out client-to-server. The device's OTA client doesn't recognize those frames, e.g.image_notify(client command 0x00) arrives as an undefined server command. This also affected the existingNO_IMAGE_AVAILABLEreply zigpy sends to these devices.This PR:
Device.find_ota_cluster(), which prefers a client OTA cluster on any endpoint and falls back to a server one, and uses it inOTAManagerand the post-OTA cleanup.Cluster.send_directionproperty (unchanged behavior for every cluster) thatrequest()/reply()use for the frame direction and thedisable_default_responsedefault.Otaoverrides it to always send server-to-client, since zigpy only implements the OTA server role and all OTA attributes are client-side (ZCL R8 §11.10)._discoverclient-only. The coordinator's own endpoint lists OTA as a server cluster (register_endpoints), and bellows/zigpy-znp initialize the coordinator device on every startup, so falling back there would make every coordinator read OTA attributes from itself and fail init after a timeout. A regression test covers this.Known limitation
After a successful update,
update_firmware()re-interviews the device, and_device_reinterviewed()does not carry over the OTA attribute cache orlast_query_cmd. Since_discoverstays client-only, a server-cluster OTA device shows no installed version after the update until it sends its nextquery_next_image. Handling the join-time path for these devices safely (coordinator guard, guarded attribute read) is left for a follow-up.Related
FirmwareUpdateServerEntityfor devices exposing OTA as a server cluster.last_query_cmdwhen loading device diagnostics (both cluster directions).last_query_cmdfor server OTA clusters in diagnostics (uses the 3RSS007Z as the server-only case).Cluster ... has incorrect directionwarning #1640: tracks the incoming direction-flip fallback that already routes these devices' OTA frames to the server cluster._read_current_firmware_version(zha/quirks.py) could usefind_ota_cluster()as well.Testing
find_ota_cluster()lookup order incl. multi-endpoint,send_directionfor request/reply on regular and OTA clusters, and coordinator startup sending no OTA traffic. They fail without the fix; the integration test also fails with only the cluster fallback, since the direction fix is needed as well.