Follow-ups from the first hardware acceptance run - #10
Merged
Conversation
Two things stopped step [3] from ever answering Q3 on real hardware. It only looked in the root. A box in use keeps its programs in a subfolder - the one this was written against lists exactly two directories at the root and no files at all - so the check skipped, and the report came back saying Q3 was unanswered on the very run that was supposed to answer it. It now falls back to one level down, taking the largest file it finds, and only reports "no file" if the box really has none anywhere. A folder that refuses to list is stepped over rather than ending the search. It also called download() without the size, and then compared the result against the size from the listing. That is the hazardous path the parameter exists to avoid: on a box whose largest file happens to be an exact multiple of 512, the read would time out and the report would print "block accounting is off" about a client that had done nothing wrong. The size is now passed. That does not weaken the check - a device sending more raises, a device sending fewer still trips the comparison - and inferring the end without a size still has a home, in the 512-byte boundary check, which is deliberately left as it is. Evidence: 4 unit tests for the search (folder fallback, root preferred so no needless round-trip, unreadable folder skipped, genuinely empty box). And a real run: against a Micro DNC 2 whose root holds only directories, step [3] now reports '0:program\3D.NC' (in 0:program): 259373 bytes, matches the listing - where the previous code reported SKIP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"No valid reply for block 2 after 10 attempts" sends you to check power, cabling and the local-port-69 rule. On this firmware the likeliest cause is none of those: the file ended, the device stayed quiet instead of sending an empty final block, and nobody passed the size. That is fixable from the message, so the message should carry it. It deliberately does NOT diagnose. A timeout always lands on a block boundary - a short block ends the loop instead of timing out - so "the file ended here" and "a packet was lost mid-file" are indistinguishable from inside download(). That indistinguishability is the whole reason silence is never read as end of file, and a message claiming to know which one happened would be worse than the bare timeout it replaces. So it names both, and names the parameter that makes the first one stop being a failure. Nothing is added when no bytes arrived at all: that is a box that never answered, and pointing at a missing parameter there would send someone editing code while the real problem is the cable. Evidence: 2 tests - the message names expected_size and list_dir() and still admits a lost packet is possible; and a box that answers nothing produces the plain timeout with no mention of the size. 130 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q1/Q2/Q3 were posed as open questions because nothing had been run against hardware. An acceptance run has now settled all three, and leaving the README asking them buries the one thing this repository has that a reimplementation would not: measurements off the real device. Recorded as what one unit did, not as a property of the protocol. The box reports no firmware revision, so there is no version to pin the claims to, and someone else's unit may differ - which is exactly why the script ships rather than just its output. The finding worth putting in the protocol summary is the asymmetry: the device never sends the empty final block, it just goes quiet past the end, while happily accepting an empty final block when receiving. That is what makes expected_size a correctness requirement rather than an optimisation on a file whose length is an exact multiple of 512, and it is not something a reader could infer from the opcode table. Evidence: hardware_check.py against a MICRO DNC2 over WiFi - Q1 from a 104-byte canary and a 259,373-byte program carrying 14,141 CRLF pairs, both returned byte for byte identical; Q2 from listings in the root and a subfolder; Q3 from 507 blocks matching the listing, and from the 512-byte boundary check timing out, which is the measurement behind the asymmetry above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Everything here comes out of running
hardware_check.pyagainst a real Micro DNC 2 (see #3). Three commits, independently reviewable.hardware_checkstep [3] never answered Q3 on real hardware. It only looked in the root, and a box in use keeps programs in a subfolder — so it skipped on exactly the run meant to answer Q3. It also calleddownload()without the size and then compared against the listing, which is the hazardous path: on a box whose largest file is an exact multiple of 512 it would time out and blame "block accounting" on a correct client. Now searches one level down and passes the size. Inferring the end without a size still has a home — the 512-byte boundary check, left as is.The download timeout named the symptom, not the fix. On this firmware the likeliest cause of "no valid reply for block N" is that the file ended, the device stayed quiet, and no size was passed. It deliberately does not diagnose: a timeout always lands on a block boundary, so end-of-file and lost-packet are indistinguishable from inside
download()— that being the whole reason silence is never read as EOF. It names both, plus the parameter that fixes the first. Nothing is added when no bytes arrived at all.README states the answers. Recorded as what one unit did, not as a protocol guarantee — the box reports no firmware revision. The finding worth the protocol summary is the asymmetry: it never sends an empty final block, but happily accepts one.
Tests: 130 pass (124 + 6 new). Real-hardware evidence for the search fix: with the root holding only directories, step [3] now reports
'0:program\3D.NC' (in 0:program): 259373 bytes, matches the listingwhere it previously reported SKIP.Not addressed here: the 512-boundary check still reports
FAILfor what is a discovered firmware behaviour the client already handles correctly. Making a clean run all-green needs a fourth outcome alongside PASS/SKIP/FAIL, which changes the script's result semantics — left for a separate decision.