Wait for real firmware config-write acks (0x41/0x42)#19
Open
balloob wants to merge 1 commit into
Open
Conversation
The config-write completion handler only matched 0x00 0xCE / 0x00 0xCF, which the firmware never sends, so it was dead code. The real acks (0x41 for the single/first chunk, 0x42 for subsequent/final chunks) fell through to the generic handler and were merely logged, and writeConfig resolved as soon as the bytes hit GATT -- callers logged "Config written successfully" even when the device rejected or failed to save. Rewrite handleConfigWriteNotification to match the firmware protocol (communication.cpp): 0x00/0xFF 0x41, 0x00/0xFF 0x42, and the 3-byte 0x00 0x41/0x42 0xFE auth-required replies. Drop the never-sent 0xCE/0xCF cases. writeConfig / writeConfigChunked now pace on and settle from these acks: arm an ack waiter before sending each chunk (avoiding a dropped-ack race), send the first chunk (0x0041 + LE total size + data) and await its 0x41 ack, then send each subsequent chunk (0x0042 + data) and await its 0x42 ack -- the final 0x42 ack is the save result. The blind delay(150) is replaced by this ack pacing. Each wait has an 8s timeout so a dead connection rejects instead of hanging, and write-state is always cleared on completion/failure/timeout. The promise resolves on success and rejects with a descriptive Error on failure or auth-required (message contains "Authentication required (0xFE)", matched by the toolbox isAuthError re-auth path); the optional onComplete callback still fires with null on success / Error on failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TEs4tsAWupf2DXFM4faECC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The config-write completion path in
httpdocs/js/ble-common.jsonly matched0x00 0xCE(success) /0x00 0xCF(failure), which the firmware never sends — so it was dead code. The real acks (0x41for the single/first chunk,0x42for subsequent/final chunks) fell through to the generic command handler and were merely logged. AndwriteConfigresolved as soon as the bytes were written to GATT, so callers like the firmware toolbox logged "Config written successfully" even when the device rejected the write or failed to save it, and theonCompletecallback never fired.What changed
handleConfigWriteNotificationis rewritten to match the actual firmware protocol (verified againstcommunication.cpp):0x00/0xFF 0x41,0x00/0xFF 0x42, and the 3-byte0x00 0x41/0x42 0xFEauth-required replies. It settles the pending per-chunk ack waiter instead of firing a callback directly. I dropped the never-sent0xCE/0xCFcases rather than keeping them as fallback, since the handler was fully rewritten and they can never match real firmware.writeConfig/writeConfigChunkednow pace on and settle from those acks:sendHexCommands own await is still in flight is not dropped.0x0041+ 2-byte LE total size + data) and await its0x41ack, then send each subsequent chunk (0x0042+ data) and await its0x42ack. The final0x42ack is the overall save result. Chunk size stays 200.await delay(150)between chunks is replaced by this ack pacing (the ack is the flow control now).activeflag is always cleared on completion / failure / timeout so stale state cannot swallow later notifications.The returned promise now resolves only on the device final ack and rejects with a descriptive
Erroron0xFFfailure or0xFEauth-required. The auth-required error message contains "Authentication required (0xFE)", which the toolboxisAuthError()helper recognizes to trigger its re-auth-and-retry path (mirroring the existing config-READ0x40 0xFEhandling). The optionalonComplete(err)callback still works — called withnullon success and anErroron failure, matching the previous convention.The existing
await bleLib.writeConfig(...)callers infirmware/toolbox/index.html(config write and the SD-card write path) are unchanged in shape — theyawaitand then log success, which is exactly what this fix makes truthful.Verification
node --check httpdocs/js/ble-common.jspasses.sendHexCommand,log, connection) and drives the new code with simulated firmware notifications. 16/16 assertions pass: single-write success resolves and callsonComplete(null);0xFF 0x42on the final chunk rejects with "Config write failed" and callsonComplete(Error);0x00 0x41 0xFErejects with an error that the toolboxsisAuthErrorrecognizes; a silent firmware times out and rejects (fast); and for a 4-chunk config the sends and acks strictly interleave (send 0041, ack 0041, send 0042, ack 0042, ...), proving chunk N+1 is only sent after chunk Ns ack. All state-teardown assertions confirmconfigWriteState.activeis cleared in every path.🤖 Generated with Claude Code
https://claude.ai/code/session_01TEs4tsAWupf2DXFM4faECC