Skip to content

fix: don't drop image block requests that arrive while the response is being sent - #1846

Open
mattlathrop wants to merge 2 commits into
Koenkk:masterfrom
mattlathrop:fix/ota-block-request-race
Open

fix: don't drop image block requests that arrive while the response is being sent#1846
mattlathrop wants to merge 2 commits into
Koenkk:masterfrom
mattlathrop:fix/ota-block-request-race

Conversation

@mattlathrop

Copy link
Copy Markdown

Was having issues with OTA updates on Hue bulbs with the SLZB-06. Fable came up with the below which seems to have great results.

OtaSession.commandStream() only armed the imageBlockRequest/imagePageRequest waiters at the top of its loop, i.e. after the consumer had fully sent the previous imageBlockResponse and resumed the generator. Fast devices (e.g. Philips Hue, which request with minimumBlockPeriod=0) send their next request within milliseconds of receiving a response — inside that unarmed window — so every follow-up request was dropped and the transfer only advanced on the device's ~10s retry timeout: ~9,200 blocks x ~10s = ~23 hours per Hue bulb.

Arm the next pair of waiters before yielding each request so an early request is captured by an already-armed waiter, keep passive rejection handlers on the armed promises (they can settle while the generator is suspended at yield), and cancel the armed pair in a finally block when the generator closes.

The OTA test harness previously fabricated the device's next request at waiter-arm time — encoding the same arm-then-request assumption this change removes. It now produces requests lazily, in arm order, gated on completion of the responses owed for the previously produced request (1 per block request, blocks-per-page per page request, 0 for frames that elicit no response), which models a real device regardless of when the controller arms its waiters.

Measured on a zstack coordinator (SLZB-06) with Hue BR30 bulbs: block cadence went from 1 block per ~10.9s to ~12 blocks/s; a 458KB image completed in 10m37s instead of a projected ~23h. A subsequent 13-bulb rollout completed back-to-back with zero failures.

…s being sent

OtaSession.commandStream() only armed the imageBlockRequest/imagePageRequest
waiters at the top of its loop, i.e. after the consumer had fully sent the
previous imageBlockResponse and resumed the generator. Fast devices (e.g.
Philips Hue, which request with minimumBlockPeriod=0) send their next request
within milliseconds of receiving a response — inside that unarmed window — so
every follow-up request was dropped and the transfer only advanced on the
device's ~10s retry timeout: ~9,200 blocks x ~10s = ~23 hours per Hue bulb.

Arm the next pair of waiters before yielding each request so an early request
is captured by an already-armed waiter, keep passive rejection handlers on the
armed promises (they can settle while the generator is suspended at yield),
and cancel the armed pair in a finally block when the generator closes.

The OTA test harness previously fabricated the device's next request at
waiter-arm time — encoding the same arm-then-request assumption this change
removes. It now produces requests lazily, in arm order, gated on completion of
the responses owed for the previously produced request (1 per block request,
blocks-per-page per page request, 0 for frames that elicit no response), which
models a real device regardless of when the controller arms its waiters.

Measured on a zstack coordinator (SLZB-06) with Hue BR30 bulbs: block cadence
went from 1 block per ~10.9s to ~12 blocks/s; a 458KB image completed in
10m37s instead of a projected ~23h. A subsequent 13-bulb rollout completed
back-to-back with zero failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Nerivec

Nerivec commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

We haven't heard of this behavior during testing after last big OTA refactoring, nor since. I'm not really seeing how the device could end up re-requesting before we're back in the loop; there is no logic after the "send response", so we're back in the loop immediately (far faster than serial+network come-back). Only scenario I can see this happening is if the device requests blocks without first getting the response to the previous.
Is the device using pages?
Can you provide some debug logs of the "before this"?

Was this tested against a real failure scenario (e.g. yank the device off the network during update)? Seems to be a lot error swallowing in the refactored code.

I haven't dug into the behavior changes, but I think upgradeEndRequest needs to be cancelled in the finally as well.
Might as well merge the two Promise.race into one while at it (missed that last time - excessive copy/pasting 😅).
Also should condense the AI's comments - drastically.

By the amount of changes in the tests, this needs extensive real testing for possible regressions.

CC: @andrei-lazarov @burmistrzak

@mattlathrop

Copy link
Copy Markdown
Author

Below is an excerpt from the log before I made the change and after

Before (note the 10s gap between packets)

[2026-08-13 13:41:20] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330042,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
[2026-08-13 13:41:20] debug:    zh:controller:ota: Request offsets: fileOffset=330042 pageOffset=0 maximumDataSize=64
[2026-08-13 13:41:20] debug:    zh:controller:endpoint: CommandResponse 0x0017880106dba796/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":274,"fileVersion":16788992,"fileOffset":330042,"dataSize":50,"data":{"type":"Buffer","data":[148,0,9,7,28,71,29,218,168,70,136,253,39,176,103,31,104,19,198,141,77,156,131,112,150,194,99,60,245,146,176,218,126,87,128,132,135,48,142,255,88,73,182,202,27,115,63,227,49,156]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
[2026-08-13 13:41:20] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330092,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
[2026-08-13 13:41:30] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330092,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
[2026-08-13 13:41:30] debug:    zh:controller:ota: Request offsets: fileOffset=330092 pageOffset=0 maximumDataSize=64
[2026-08-13 13:41:30] debug:    zh:controller:endpoint: CommandResponse 0x0017880106dba796/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":274,"fileVersion":16788992,"fileOffset":330092,"dataSize":50,"data":{"type":"Buffer","data":[195,18,63,141,96,105,101,212,183,104,94,245,222,1,69,17,16,108,108,187,238,89,89,183,241,220,96,220,196,131,195,171,234,86,95,232,194,111,85,221,131,124,241,254,26,23,33,117,12,178]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
[2026-08-13 13:41:30] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330142,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
[2026-08-13 13:41:40] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330142,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
[2026-08-13 13:41:40] debug:    zh:controller:ota: Request offsets: fileOffset=330142 pageOffset=0 maximumDataSize=64
[2026-08-13 13:41:40] debug:    zh:controller:endpoint: CommandResponse 0x0017880106dba796/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":274,"fileVersion":16788992,"fileOffset":330142,"dataSize":50,"data":{"type":"Buffer","data":[229,33,248,77,118,26,11,32,52,118,134,1,40,42,219,96,98,20,79,148,119,208,64,55,44,22,111,103,79,74,150,230,205,96,97,182,17,244,71,237,127,18,48,104,230,165,232,251,219,118]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
[2026-08-13 13:41:40] debug:    z2m: Received Zigbee message from 'Downstairs Hallway Entry', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":330192,"fileVersion":16788992,"imageType":274,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0

After (no gap)

Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335232,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335232 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335232,"dataSize":50,"data":{"type":"Buffer","data":[236,47,23,229,58,92,140,48,18,241,176,98,222,90,136,209,231,155,121,82,105,167,50,158,11,226,83,56,124,65,19,7,182,108,39,108,191,43,173,112,70,127,6,18,77,176,41,137,8,110]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335282,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335282 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335282,"dataSize":50,"data":{"type":"Buffer","data":[232,179,99,19,116,138,239,170,137,174,166,63,130,201,91,146,143,213,227,77,237,125,67,144,188,127,114,133,220,105,44,134,122,139,140,74,59,65,64,214,62,18,183,104,93,131,252,94,237,42]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335332,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335332 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335332,"dataSize":50,"data":{"type":"Buffer","data":[14,251,243,99,60,113,104,76,128,241,93,27,118,248,28,164,117,221,4,246,207,156,24,72,22,194,189,176,191,250,106,169,82,162,63,208,32,1,173,83,141,10,178,43,252,30,49,129,150,109]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335382,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335382 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335382,"dataSize":50,"data":{"type":"Buffer","data":[152,228,74,61,171,35,104,219,17,169,152,70,176,203,124,153,82,133,83,251,134,250,159,159,53,108,148,59,219,191,226,56,225,88,167,246,13,12,188,14,37,29,85,34,60,132,220,24,102,198]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335432,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335432 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335432,"dataSize":50,"data":{"type":"Buffer","data":[197,9,35,138,185,206,127,50,93,216,21,73,126,191,130,85,224,44,59,37,35,38,218,34,3,128,171,20,18,41,122,14,71,106,91,234,112,222,200,141,209,79,51,217,1,75,73,13,104,61]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335482,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335482 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335482,"dataSize":50,"data":{"type":"Buffer","data":[65,157,72,94,61,5,50,149,52,149,106,13,123,22,196,233,202,142,115,90,39,14,46,233,98,185,191,166,157,106,80,65,126,37,166,194,124,79,250,36,88,176,183,48,155,75,189,181,3,107]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335532,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335532 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335532,"dataSize":50,"data":{"type":"Buffer","data":[79,56,85,214,223,248,77,191,81,194,244,127,16,232,122,100,61,18,90,47,35,73,252,81,202,176,130,94,230,5,106,115,149,83,165,3,160,45,31,157,136,66,164,252,161,247,14,71,80,203]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         z2m: Received Zigbee message from 'Server Closet', type 'commandImageBlockRequest', cluster 'genOta', data '{"fieldControl":2,"fileOffset":335582,"fileVersion":16787968,"imageType":276,"manufacturerCode":4107,"maximumDataSize":64,"minimumBlockPeriod":0}' from endpoint 11 with groupID 0
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:ota: Request offsets: fileOffset=335582 pageOffset=0 maximumDataSize=64
Aug 13 14:38:56 zigbee2mqtt pnpm[12466]: [2026-08-13 14:38:56] debug:         zh:controller:endpoint: CommandResponse 0x001788010de75c81/11 genOta.imageBlockResponse({"status":0,"manufacturerCode":4107,"imageType":276,"fileVersion":16787968,"fileOffset":335582,"dataSize":50,"data":{"type":"Buffer","data":[46,167,59,220,50,239,62,135,35,183,21,8,5,186,41,54,160,70,42,16,130,84,192,184,83,193,200,40,173,236,148,50,94,204,120,249,226,125,52,161,44,106,149,95,169,147,235,186,193,181]}}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":1,"reservedBits":0,"writeUndiv":false})

…y, condensed comments

Also fixes two latent issues in the OTA test mock surfaced by the review changes:
the mock waitFor was missing the defaultRspCommandId parameter (so its timeout
argument received the response command id), and the mock now emulates the adapter
waiter's timeout so device-silence scenarios fail the same way they do live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Nerivec

Nerivec commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Do you have the full logs (with the ZH low level stuff) so we can actually see what is happening?

@burmistrzak

Copy link
Copy Markdown
Contributor

I'd like to see full debug-level logs as well.

IIRC, I never had issues updating Hue bulbs.
However, it's somewhat known that (older) Hue accessories can be a bit difficult to OTAU.

One thing we could try is pairing a misbehaving Hue Dimmer Switch to a Hue Bridge and capture the OTAU with Wireshark. Maybe Hue is doing something interesting?

@mattlathrop

Copy link
Copy Markdown
Author

Do you have the full logs (with the ZH low level stuff) so we can actually see what is happening?

Are you wanting more depth in the given time range (enabling lower level logging) or a larger time range. Just want to make sure I get you what you are asking for.

@Nerivec

Nerivec commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Mostly want to see what is going on in these 10 seconds at the lower layer. It is a curious behavior, so, would be good to know what the device is doing exactly.
So, just a minute or so capturing a few exchanges during OTA should be fine, but with no filtering of the logs at all, and level set to debug.

@mattlathrop

Copy link
Copy Markdown
Author

ct158-before-10s-unfiltered.txt

I'm hoping this is fully unfiltered. Captured with z2m set to debug level.

@Nerivec

Nerivec commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Sample is fairly limited (there's just one occurrence in the file), and no millis in the timestamps to be sure, but looks like the device may not be waiting for the response before requesting another block (as predicted)... so, race conditions ensue (sometimes it's fine, sometimes it's not, exactly as in the logs). Dangerous behavior, I'd say likely a bug in the firmware.

Logic of the PR looks fine from what I can tell.
Didn't check the test changes closely though. I did glance at a defineProperty call in there, which we should get rid of - need to find some other way to do the same (that's the kind of thing that can mess up other tests - a nightmare to debug). And a few "pending promises" being redeclared on-sites (there's already a pendingPromise, should use it).

Someone needs to test a real/live error (forcing error through device power off or something), to ensure errors are still propagated upstream and cleanup happens properly.
I don't have a device I can test this with at the moment. @andrei-lazarov maybe, if you have some time? 😬

@andrei-lazarov

andrei-lazarov commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

I've seen reports of slow Hue updates, but never had the issue myself.
I can only help with some sanity tests 🙂

Network with only one device (Telink), right next to coordinator (MG21). OTA options:

I ran the test twice. First time the OTA was ~30% faster with this fix (241s -> 177s).
But the time estimate glitched a little

Second time it was identical (244s). But again I don't see any of those gaps in my logs..

@Nerivec

Nerivec commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

The estimate is always unreliable indeed (at least comparing between two runs), too many factors can affect it; differences likely have nothing to do with this specific PR.

Are you able to yank a device off during update to check the error path and state afterwards are still correct?

@andrei-lazarov

Copy link
Copy Markdown
Contributor

Ah I forgot, will do

@Nerivec

Nerivec commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

@mattlathrop meantime, can you look at the refactors I mentioned about the tests?

@andrei-lazarov

Copy link
Copy Markdown
Contributor

Yes I powered off the device during the update. Got the timeout error in Z2M after 150s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants