Expromo EPS power sequencer (Keyestudio KS0304: ATmega328P + WIZnet W5500), firmware v2.2 vs v3.0, v3.1, v3.2 and v3.3
Bench work 2026-10-08 13:00 – 2026-10-12 morning · Repo daveleo/EPS · Status as of 2026-10-12 · v3.3 is recommended as the official firmware (v3.2 + the gaps a code analysis found; see §5.25–5.26)
Units in the field drop off the network at random: no ping, no web page, no TCP control, until power is cycled. No failed unit has been examined in its failed state. Field units are usually set to a static IP; some AV networks have a DHCP server, sometimes not.
On the bench (two bare KS0304 boards on USB power, and since 2026-10-09 afternoon a real EPS box with its 10 V supply, six SSRs and the 22 µF reset capacitor; NETGEAR M4250 switch, a model with field failures):
Expromo_EPS_v2_2/), built on the Arduino Ethernet library 2.0.2. Older: v2.1.0 (6 ch) and v1.9.1 (6 ch, also a 10-ch variant in the field whose source isn't available).8C:1F:64:C3:9 (8C:1F:64:C3:90:00–9F:FF) and assigns one MAC per produced unit, never reused.companion-eps), eXcontrol v0.3.1, Crestron, Q-SYS, the web page.| Unit 1 | Unit 2 | |
|---|---|---|
| Origin | the original showroom test unit (was 172.22.40.21, label EPS_Test) |
new, unflashed on arrival |
| USB (programming, serial, reset) | COM5 | COM6 |
| IP now | 192.168.0.94 static |
192.168.0.93 static |
| MAC now | 8C:1F:64:C3:90:24 (assigned; the MAC in the v2.2 sketch, assumed to be this unit's) |
02:45:50:A3:4E:D9 (temporary, generated) |
| Label | EPS_Test |
EPS_Test_2 |
| Firmware now | v3.0 production | v3.0 production |
| M4250 switch port | 0/6 | 0/12 |
Both boards are USB-powered from the PC; nothing is connected to the outputs.
Unit 3 (from 2026-10-09 ~12:30): a real EPS box as built for the field (photos in photos/EPS 6 channel workshop built/): KS0304 with v2.2, 10 V DIN PSU (1.8 A) soldered to the barrel jack, six KANE KSSR-14840DA SSRs on A0–A5, illuminated push-button on D2–D4 (RGB) and D6, 22 µF between RESET and GND (added because the 10 V PSU didn't restart the board reliably; it also blocks USB flashing). MAC 8C:1F:64:C3:90:58, M4250 port 0/9, USB unplugged, mains switched by a Netio PowerBOX 4KF (output 4, URL API). No loads on the outputs. Afternoon changes: unit 2 moved to 192.168.0.95; units 1–2 ran the v2.2 diagnostic builds, unit 1 v3.0 from ~15:15.
172.22.40.66, later 192.168.0.83.192.168.137.0/24), then removed.192.168.0.23, 2026-10-09 evening and 2026-10-10): v3.1 tests and the conformance suite, unit 3 only.192.168.0.66), units on 0/6 and 0/12, switch management 192.168.0.250. Isolated. EEE and Energy-Detect off on all ports, RSTP (802.1w) on, BPDU guard off, clock not set (shows May 2023).| Build | Source / flags | Purpose |
|---|---|---|
| v2.2 | Expromo_EPS_v2_2/ |
field firmware (MAC compiled in) |
| v2.2, unit 2 | v2.2 with only the MAC changed | v2.2 on unit 2 without a duplicate MAC |
| v2.2 diagnostic | tools/v2_2_diag/ (-DBENCH_UNIT2 for unit 2's MAC) |
v2.2 + start-up report on USB serial (W5500 found, link, IP, MAC, gateway, mask, socket states every 2 s for 60 s); no behaviour change |
| v2.2 fault | scratch copy of v2.2 + hidden TEST_W5500_RESET |
imitate a W5500 glitch |
| Bare library | tools/lib_minimal_test/ |
Ethernet 2.0.2 with two servers, no EPS code |
| v3.0 | Expromo_EPS_v3_0/ |
the rewrite (production build: no flags); tagged v3.0-baseline |
| v3.1 (final) | Expromo_EPS_v3_1/, branch v3.1-standards, commit 5284db6, per unit -DEPS_MAC_SUFFIX=0x058 |
v3.0 + standards work (§5.22); 31,120 B flash, 1,364 B RAM; same test flags as v3.0 |
| EEPROM wipe | tools/eeprom_wipe/ |
all EEPROM to 0xFF, so the firmware starts from defaults (conformance suite) |
| v3.3 | Expromo_EPS_v3_3/, per unit -DEPS_MAC_SUFFIX=0x058 |
v3.2 + G1–G5 (§5.26); 32,094 B flash, 1,407 B RAM; test builds v33_poke (TEST_POKE_BUILD, TEST_SLIM), v33_poke_nog1 (+NO_PHY_RST_CHECK), v33_fault, v33_rollover |
| v3.0 test flags | -DNET_DEBUG=1 (socket/link log on serial), -DFAULT_INJECTION=1 (TEST_W5500_RESET, TEST_HANG, TEST_STACK), -DMILLIS_ROLLOVER_TEST=1 (clock wraps 60 s after boot), -DMAC_GEN_TEST, -DEPS_MAC_SUFFIX=0x024 (per-unit MAC) |
tests |
| H1 / H2 firmware | tools/w5500_spi_stress/, tools/w5500_phy_cycle/ |
built, not run yet |
tools/)eps_loadtest.py (read-only load test), idle_dose_test.py, eps_power_test.py (switches outputs), dhcp_test_server.py, run_comparison.ps1 + analyze_comparison.py, soak_monitor.py, reset_startup_test.py, hang_harness.py (H1/H2, not run), persistent_client_test.py, junk_input_test.py, m4250.py (switch CLI over SSH); added 2026-10-09: event_check.py (single-event checks, Netio control), repeat_flap.py, flap_capture.py, serial_logger.py (with a command file), decode_diag2.py, novahub_check.py, dhcp_watch.py, link_flap_test.py; diagnostic build v2_2_diag2/ (W5500 registers once a second); added 2026-10-10: v31_dhcp_test.py, v31_http_check.py, v31_fault_check.py, v31_link10_check.py, v31_rollover_check.py, v31_stack_run.py, udp_services.py, conformance_suite.py + conformance_report.py. PC tools: Python 3.12, pyserial, Wireshark (tshark, dumpcap), nmap, arduino-cli (from Arduino IDE 2), PuTTY plink, Scapy (installed, not used for traffic injection).
docs/comparison-protocol.md 2026-10-08 17:59; docs/w5500-hang-tests.md 2026-10-09 00:15, amendments 08:04 and 08:22; docs/v3.1-standards.md test table 2026-10-09; docs/conformance-suite.md 2026-10-10, before either firmware was run).POWER_STATUS, PING, GET /, idle connections; the power test (outputs switched) ran only with nothing connected. No crafted or spoofed traffic was sent to the units.EthernetServer::available() hands over only connections that have sent data; silent connections are never closed. The W5500 keepalive is off, so half-open connections stay forever.print(F()) sends every byte as its own TCP segment.W5100Class::init() runs only once per boot, so the network can't be re-initialised without a CPU reset.EEPROM.update).Unit 1 cabled straight to the NovaHub adapter:
| Condition | Result |
|---|---|
| 100 Mbps full (auto) | deaf: sends its keepalive ARP every 30.08 s, answers no ARP, no unicast ping, no TCP SYN (not even RST) |
| after RESET button / power cycle / adapter EEE off | still deaf |
| adapter forced to 10 Mbps full | works (ping, TCP, web) |
| back to 100 Mbps | deaf |
| ASIX adapter at 100 Mbps | works at once |
Same signature as §5.12 below.
eps_loadtest.py, unit 1, direct ASIX link.
| Phase | Port 5000 OK |
|---|---|
| baseline / 1 idle web connection | 20/20 · 20/20 |
| 2 idle web connections | 1/30 |
| + idle control connection (refused) | 3/86 |
| released | 20/20 |
| mixed load: Companion-like (1 Hz, 700 ms timeout) | 44/60, failures every 3 s with the browser |
| mixed load: eXcontrol-like (4 Hz, 4 s timeout) | 67/70 |
Port 5000 was unreachable 107 s, ping 100 % throughout. Web page: 3,598 TCP segments for 3,696 bytes (3,519 single-byte), 890–938 ms.
ciaddr); server gone → fallback to static after lease expiry. The scenario was cut short (background task stopped under low memory), so "server returns" wasn't run with the bench server.eps_power_test.py, nothing connected): 21/21: OUT1 at 1.03 s, steps 0.281–0.313 s, fully on at 2.52 s, order 1→6; POWER_ON when on: no-op; OUTx during the sequence stops it and POWER_ON when partly on re-sequences (v2.2 behaviour kept); web commands; restore after reboot.| Step | Result |
|---|---|
Unit in DHCP mode on fallback .93, sharing turned on 16:59:17 |
lease 192.168.137.24 at 16:59:34, no reboot |
| Load test on the DHCP address | 1,086/1,086, control ~12 ms, page ~43 ms |
| Reboots | first build: new IP each time (.24 → .137 → .223) → fixed (last lease stored, requested again): 3 reboots → .223 each time, bound ~5 s after boot |
PHYRESET=1 after sharing was enabled |
caused by link bounces (adapter reconfigured) making announcement probes count as failures → fixed |
| Cable out 27 s, back | LINKDOWNS=1, same IP, bound again 4 s after replug, PHYRESET=0, no reboot |
The temporary MAC was derived from the ATmega's serial bytes, which were identical on both boards: both units came up as 02:45:50:2F:69:FD. Reachability: 12/20 and 8/20; after giving unit 2 a different MAC: 20/20 each. Fixed: the temporary MAC is random on first boot (watchdog-oscillator jitter) and then stored; 20 distinct values on 2 boards × 2 boots.
Unit 2 ran the bare library sketch, and unit 1 ran v3.0 alongside it, both polled by eXcontrol. idle_dose_test.py:
| Idle connections held | Bare library :5000 / :80 | v3.0 |
|---|---|---|
| 0–2 | 20/20 / 20/20 | 100 % |
| 3 | 16/20 (idle on :80), 15/20 (idle on :5000) / 20/20 | 100 % |
| 4 (5th refused) | 0/10 / 0/10 | 100 % |
| held 60 s more | 0/30 / 0/30 | 60/60 |
| released | 20/20 / 20/20 | 100 % |
The library also sent its replies one byte per packet.
Pre-registered (§4). 3 load-test runs + 1 dose test per firmware per phase; phase A unit 1 = v3.0, unit 2 = v2.2; phase B swapped.
| Criterion | v2.2 (6 runs, 2 boards) | v3.0 (6 runs, 2 boards) |
|---|---|---|
| C1 no lost requests | FAIL (127–162 lost per run) | FAIL (0 in 5 runs, 1 ping in 1 run) |
| C2 availability ≥ 99.9 % | FAIL (84.2–86.6 %) | PASS (99.91–100 %) |
| C3 port 5000 outage ≤ 5 s | FAIL (79–121 s) | PASS (0 s) |
| C4 control p99 ≤ 100 ms | FAIL (1,031–1,532 ms) | PASS (31–47 ms) |
| C5 web p95 ≤ 500 ms | FAIL (906–1,375 ms) | PASS (47–62 ms) |
| C6 dose ≥ 99 % at every step | FAIL (0 %) | PASS (100 %) |
The same on both boards. Seen in the dose tests: with 3 sockets stuck, v2.2's stall watchdog reboots the unit after ~60 s; a v2.2 reboot switches the outputs off unless "restore after reboot" is on. Full table: docs/comparison-results.md.
| v2.2 | v3.0 | |
|---|---|---|
| After one W5500 soft reset | no ping, no ARP, no TCP, no web | the same for ~3 s |
| Recovered by itself | 0 / 4 (watched 60–150 s) | 5 / 5 (3.0, 3.5, 4.1, 4.1, 6.0 s; REINIT counted) |
| Brought back by | USB reset (2.8 s) or power cycle | — |
Observed on the dead v2.2 unit (user, at the bench): RGB blue; holding D9 blinked the red LED (drawn by the main loop, so the CPU was running); one D6 press gave no visible reaction (unexplained; procedure not verified); unplugging the USB power brought it back.
A 96-event ring in EEPROM (survives power cuts; ~20 events/hour max). It logs boots with their reason, link up/down with speed/duplex, W5500 re-inits with the wrong registers, PHY resets, gateway silent/back, IP conflicts, DHCP lease/fallback, settings, factory reset, and every power/output command with its source. Bench test: all events logged correctly; the injected glitch was recorded as W5500_REINIT version=0x04 wrong=GATEWAY,MASK,MAC,IP,ALL_ZERO(chip_reset); the log survived a reset and is readable via NET_LOG and on the web page.
v2.2 (unit 1) and v3.0 with recorder (unit 2), Ubiquiti switch, eXcontrol polling; soak_monitor.py: ping and TCP once a second, web every 30 s.
| 5 h 08 min | v2.2 | v3.0 |
|---|---|---|
| Ping | 18,374 / 18,374 | 18,412 / 18,412 |
| TCP | 18,374 / 18,374 | 18,412 / 18,412 |
| Web | 613 / 613 | 614 / 614 |
| Worst TCP reply per minute: median / max | 188 / 625 ms | 16 / 16 ms |
| v3.0 counters | — | all 0, 22,535 connections |
Under normal traffic neither firmware failed. (The packet capture of this run stopped writing after 1 min; no lost pings, so nothing was lost.)
USB reset (DTR). Correction (2026-10-09, §5.17): this also resets the W5500 (its link renegotiates); it is not a CPU-only reset. A reset fails if there's no answer to POWER_STATUS within 15 s; failures were watched 60 s.
| Run | Unit 1 | Unit 2 |
|---|---|---|
| H4 first run, v2.2 (00:2x, stopped early) | not run (USB port busy) | reset 36 failed (≥ 36 resets) |
| H4 re-run, v2.2 diagnostic (08:05–08:21) | 200/200, median 2.23 s | 198/200 (resets 129, 191 failed) |
| H4b-1, fuller diagnostic + capture (08:25–08:42) | 300/300 | 299/300 (reset 275 failed) |
| H4b-2 (08:44–09:09) | v2.2 diagnostic: 300/300 | v3.0: 300/300, median 3.73 s (it renegotiates the link at start-up) |
What a failed start-up looks like (unit 2; diagnostics every 2 s for 60 s):
hw=3 link=1 ip=192.168.0.93 mac=02:45:50:A3:4E:D9 gw=192.168.0.1 mask=255.255.255.0 sock=14,14,22,0: W5500 found, link up, all registers correct, both servers listening.Totals (v2.2): unit 2 4 deaf start-ups in ≥ 536 resets = 0.75 % (95 % CI 0.20–1.90 %); unit 1 0 in 800 (≤ 0.37 %). Board/port difference: one-sided Fisher p = 0.026, confounded with the switch port (port swap pending). v3.0 on unit 2: 0 in 300 (≤ 0.99 %); against v2.2's rate, P(0 in 300) = 0.106.
| Test | Result |
|---|---|
| Controllers that keep connections open (change: idle control connections kept 5 min instead of 5 s; oldest idle one evicted when all four are taken; still closed after each answer) | quiet 90 s then command: answered; 4 quiet clients hold all sockets, a 5th is served in 0.02 s; oldest evicted, newest works (6/6); dose test 100 % |
Per-unit MAC build -DEPS_MAC_SUFFIX=0x024, then generic build |
MAC 8C:1F:64:C3:90:24, MAC_ASSIGNED=YES, kept by the generic build |
| Fixed 10 Mbps half duplex | negotiated 10HALF with an Auto switch; 30/30 commands, page 63 ms; back to 100FULL |
Junk input (junk_input_test.py) |
16/16; settings unchanged |
| millis() rollover (test build) | power test 21/21 and short load test across it (sequence not timed to the exact wrap) |
| Stack high-water mark (test build) | ≥ 464 B free at the lowest point |
| Hung main loop (test build) | restart after 19 s, RESTART=WATCHDOG, logged |
| IP conflict (both units on one IP) | both detected it within ~75 s (2 and 4 events) |
| Companion module live test and parser | correct answers; parsed as protocol 2.x with all fields |
Upgrade note: when a unit's MAC changes, a Windows PC kept the old MAC in its ARP cache until cleared; with per-unit builds the MAC does not change on upgrade.
Ports 0/6 and 0/12 at 100 Mbps full (auto), 0 receive/transmit errors, no link flaps; EEE and Energy-Detect off on all ports; RSTP on; LLDP active; log shows link-ups and SSH sessions. Port mapping confirmed from the MAC table.
Pre-registered single events (docs/m4250-stress-tests.md, amendment 2), 3 tries each; a unit counts as unresponsive with no answer for 60 s after the event.
| Event | Unit-tries | Unresponsive | Time to answer |
|---|---|---|---|
| Unit 3 mains off 10 s (Netio) | 3 | 0 | 5.6–6.7 s |
| Unit 3 mains off 0.2 / 0.5 / 0.9 / 1.5 / 3 s | 15 | 0 | 5.6–7.2 s |
| POWER_ON → POWER_OFF; OUT1–6 on/off (all units) | 18 | 0 | ≤ 0.8 s |
| Settings saved unchanged (v2.2 restarts through its watchdog) | 9 | 0 | 0.8 s |
The real box's supply holds the board ~0.9 s after a mains cut.
| Run | Flaps | Unit-tries | Unresponsive |
|---|---|---|---|
| All three ports down 0.5 s / 10 s, 3 each | 6 | 18 | 1 (unit 2, 0.5 s, 13:05) |
| Replication, all ports 0.5 s | 20 | 60 | 0 |
| v2.2 watchdog restart, then 60 s later the flap | 10 | 30 | 0 |
| Port 0/12 only, 0.5 s, mirrored and instrumented | 98 | 98 | 0 |
The 13:05 event: ports down 13:05:02–04, up 13:05:05–06; the switch logged Link Up on 0/12 at 13:05:08. Units 1 and 3 answered by 13:05:11–14. Unit 2: no ping, no ARP, no TCP from 13:05:04 to ~13:06:44, then answered again with no link event on 0/12 until the next flap at 13:07:00. Not recorded for this event: capture, serial, switch counters (instrumentation started 13:11). Over 12:49–14:31 the every-second poller saw no other unexplained outage ≥ 10 s on any unit. Unit 2's per-flap rate: 1 in 134 (95 % CI 0.02–4.1 %).
| Reset | Count | Link drops seen by the switch |
|---|---|---|
| USB/DTR reset | 11 per unit | 12 per port (~2 s down each) |
| v2.2 watchdog restart (settings save) | 19 | 0 |
So the W5500's reset follows the board's RESET line (most likely; continuity not checked), and the M4250's ~3 s forwarding delay after a link-up (ports are "auto edge" in RSTP) explains why units answered 5.1 s after a DTR reset here vs 2.2 s on the Ubiquiti switch.
PC: only LargeHUB holds 192.168.0.x, one route, correct neighbour entries for all units. When a unit goes silent, Windows also tries a Hyper-V adapter's default route (vEthernet (Agent) entries appear): a consequence, not a cause. A second NIC with a 192.168.0.x address gave the PC two routes for a while (~13:40–13:55, after the 13:05 event); fixed before further tests. Switch: RSTP 802.1w, unit ports auto-edge, no BPDUs from the units, port security off, broadcast storm control 5 % (no action).
v2_2_diag2) and a library defect — MeasuredThe dump shows on v2.2: MR 0, correct GAR/SUBR/SHAR/SIPR, VERSIONR 4, PHYCFGR as the chip sees its link. It also shows RTR = 0x02D0 (72 ms), RCR = 8, SIMR = 0xD0, although v2.2 sets 200 ms and 2 retries. Cause (source of Ethernet 2.0.2): RTR and RCR are defined at the W5100 addresses 0x0017/0x0019 and used for the W5500 too, where those addresses are SIR/SIMR and the RTR high byte. This explains the "8 ARP tries 73 ms apart" seen on 2026-10-08 and corrects finding #7. Not linked to the silence.
Unit 1 v3.0 (control), unit 2 v2.2 diagnostic, unit 3 v2.2 real box; fallback addresses in another subnet (192.168.137.20/.21/.22). Evidence: DHCP and ARP capture, unit 2's serial log, the switch's binding table.
| Step | Unit 1 (v3.0) | Units 2, 3 (v2.2) |
|---|---|---|
| Server switched on | lease by itself in 17 s | need a restart (no retry after fallback); then lease in 2.6 s / ~15 s |
| 10 min of renewals | kept its lease, 0 restarts | renewals ACKed, then one unanswered → ~10 s blocked in the library's DHCP loop → watchdog restart → same lease; every 3–4 min |
| Server off 5 min | kept its address to the lease end, fell back, asked every 30 s | next renewal unanswered → restart → 60.8 s DHCP wait → static fallback → no DHCP request since |
| Server on again | lease again in 6 s | 0 DHCP packets in 5.5 min: stranded, unreachable from the DHCP subnet |
| Unit 3 mains off 10 s | — | lease again, answering 13 s after power on |
Unit 3 then kept restarting about every 4 min while left on this server (visible as the LED going dark). Why the switch dropped the v2.2 bindings although it ACKed their renewals (broadcast, client address 0.0.0.0) is not established; v3.0's renewals (unicast, with its address) kept its binding.
Units 1 and 2, each cabled straight to the NovaHub USB 10/100 adapter: at 100 Mbps (any duplex) no frame reached the W5500's sockets (UDP receive counter 0, no ping, no ARP, no TCP) while the chip reported link up with the right speed and duplex; at 10 Mbps everything worked (230/230 bytes, 10/10 pings); a PHY reset didn't help. Same on both boards and cables. The adapter has been removed from the bench; this line was set aside as a false lead for the field question.
v3.1 is v3.0 plus changes for network-standards conformance (docs/v3.1-standards.md). IPv6 and security features are out of scope by decision. All tests ran on unit 3 (the real box), cabled straight to the PC.
Changes from v3.0:
Allow for other methods, HEAD without body and without action.Final build 5284db6: 31,120 B flash (1,136 free), 1,364 B RAM. Every non-soak test was re-run on it (2026-10-10 17:51–18:46); all pass:
| Test | Result |
|---|---|
| DHCP sequence (no server → fallback; lease; renewals; expiry; address in use → DECLINE ×4; free address) | pass, every step as RFC 2131 requires |
| Syslog | pass: every event (boot, link, settings, power/output commands with source, DHCP) |
| Static start-up, 3 resets | pass ×3: 3 probes 1.08–1.99 s apart, 2 announcements 2.0 s apart, no false conflict |
| HTTP / TCP checks | 21/21 |
| Persistent control clients | 6/6; with every control socket held idle, a new client is served in 0.02 s |
| Idle-connection dose (0–5 idle on :80, then :5000) | 800/800 probes answered |
| 10 Mbps half duplex | pass; one ping lost under load in each of 2 runs (TCP 1,086/1,086) |
| Stack low-water (test build, every heavy path) | 243 B free |
| W5500 settings loss ×10 | 10/10 recovered in 2.1–6.9 s; outputs stayed on throughout |
| Main-loop hang ×3 | 3/3 watchdog restart, answering ~25 s later |
| millis() rollover | 6/6 |
Found: with a configured gateway that doesn't answer (normal on a closed AV network), the once-a-minute gateway check makes the chip send ~20 ARP requests in 3.8 s; RFC 1122 recommends ≤ 1 per second. Negligible load; listed as a known deviation (§5.23).
Not yet tested on v3.1: D5/D6/D9 inputs and LED colours by hand; real power cycles from the Netio with USB unplugged; real loads.
Fifty-six requirements from ten standards, each with a pass criterion, committed before either firmware was run (docs/conformance-suite.md). The same tool (tools/conformance_suite.py) ran on both firmwares on unit 3, cabled straight to the PC. It covers link (PC NIC forced to 10/100 half), resets, an 8 s link outage, a bench DHCP server (no server, lease, server gone, NAK, address in use), a static address equal to the PC's, and fragmented pings. No forged traffic. A standard passes only if every applicable requirement passes. Results: docs/conformance-results.md.
| Standard | v3.1 | v2.2 |
|---|---|---|
| IEEE 802.3 Ethernet | PASS | PASS |
| IEEE 802 MAC addressing | PASS | PASS |
| RFC 826 ARP | PASS | PASS |
| RFC 1122 host requirements | FAIL (2 MUST, 1 SHOULD) | FAIL (2 MUST, 1 SHOULD) |
| RFC 5227 address conflict detection | PASS (2 not verified) | FAIL (4 MUST, 2 SHOULD) |
| RFC 2131/2132 DHCP client | FAIL (1 MUST) | FAIL (4 MUST, 3 SHOULD) |
| TCP (RFC 9293 / 1122) | FAIL (1 MUST) | FAIL (1 MUST, 1 SHOULD) |
| HTTP (RFC 9110 / 9112) | FAIL (3 MUST) | FAIL (6 MUST, 1 SHOULD) |
| IEEE 802.1D/Q (end station) | PASS | PASS |
| RFC 5424/5426 syslog | PASS | N/A (no syslog) |
| Requirements passed (of 56) | 46 (8 fail, 2 not verified) | 26 (25 fail, 1 not verified, 4 n/a) |
Why the failures happen:
No firmware on this board can fix either.
Host.Corrections after the runs (all listed in the results file): tool errors in the first v3.1 run (C3, L2/L3, H3, D3–D5, T6) were fixed and the phases re-run or re-analysed before v2.2 was run. C7 had a criterion error, flagged as an amendment after data. v2.2's MAC item (M3) was first scored FAIL because the sketch has one constant MAC; in production that constant is set per unit before flashing (Expromo's MA-S block; unit 3 left the workshop with …90:58). It was rescored PASS.
What the suite doesn't measure: recovery from the field mechanisms (§6). Conformance and robustness are separate questions; v3.1's main gain is on the second.
§5.9 reproduced one kind of chip fault: a full W5500 reset (every register zero), which v3.1's 5 s health check repairs. A real SPI glitch or an ESD event is more likely to flip a single register. This test (protocol docs/fault-matrix-tests.md, committed before any data) wrote one fault at a time into unit 3's own chip — the unit's own firmware pokes the register, the PC sends only ping, POWER_STATUS and GET /, no crafted traffic — and watched each service for 120 s, three trials per fault, on v3.1's final build (5284db6) and on v2.2. A trial passes only if every service is back within 60 s with the registers restored and the outputs unmoved; a fault passes only if all three trials pass. Full data and every trial: docs/fault-matrix-results.md.
| Fault (single register) | v3.1 | v2.2 |
|---|---|---|
| Chip soft reset (all zero) | recovered 6–10 s | dead 3/3 |
| IP .93 → .99 | recovered ≤10 s | dead 3/3 |
| MAC …58 → …59 | recovered 5–10 s (one trial on/off ~60 s, then back) | wrong MAC kept 3/3 |
| Gateway .1 → .99 | repaired (no effect on a direct link) | wrong gateway kept 3/3 |
| Mask → /32 | recovered 4–7 s | dead 3/3 |
| Ping block (MR) | dead 3/3 (ping only; TCP/web fine) | dead 3/3 |
| PHY power-down (PHYCFGR) | dead 3/3 | dead 3/3 |
| Forced 10 Mbps half (PHYCFGR) | service back 4 s, stays at 10 half 3/3 | same 3/3 |
| Retry count 0 (RCR) | dead 3/3 (ping ok, TCP/web not) | dead 3/3 |
| Listeners closed / RX buffer 0 | no effect | no effect |
| Control/web listen port moved | no effect | dead (that service lost) |
What it shows. v3.1's health check repairs every register it covers — chip version, gateway, mask, MAC, IP — usually in 4–10 s, with no reset and the outputs untouched; v2.2 needs a power cycle for the chip reset, a wrong IP or a wrong mask (9/9 dead) and keeps a wrong MAC or gateway for good. v3.1 also survives every socket fault, including a listener moved to another port, which v2.2 does not. But four faults fall outside v3.1's check and leave it dead on both firmwares: the mode register's ping block, the retry count at 0, a forced 10-half link, and — the important one — PHY power-down, which produces the exact field symptom (no ping, no web, no TCP until a power cycle). Of the faults tested, PHY power-down (and, on v2.2, the chip reset and wrong IP that v3.1 does repair) is the one that still matches the field failure on v3.1. This says which faults could produce the field symptom, not which one does; the field trigger is still untested (§7).
v3.2 candidate — the fix, and the same matrix re-run (Measured, 2026-10-11). The fix suggested by this result is to widen v3.1's 5 s comparison to the registers it currently ignores: the mode register MR (ping block), the retransmission registers RTR/RCR (retry count) and the PHYCFGR configuration bits (PHY power-down and forced link mode). On a mismatch v3.2 takes the same re-init path it already uses for a bad address, which re-applies all of these and renegotiates the link. Built on branch v3.2-health-check (Expromo_EPS_v3_2, production 31,238 B of 32,256; +118 B over v3.1), flashed to unit 3, first confirmed not to false-trigger a reinit-loop (REINIT=0, PHYRESET=0 over 90 s), then run through the same 14-fault matrix (3 trials each). Result: the four faults v3.1 could not recover — ping block, PHY power-down, forced 10-half and retry count 0 — now recover on v3.2. Forced 10-half and retry-0 pass 3/3; ping block and PHY power-down recover in 2 of 3 trials, the third failing only the end-of-run stray-ping check (which also fired on faults that cannot affect ping, confirming it as a ~0.4 %/ping artefact, not service loss). The field-matching F7 PHY power-down comes back in 11–12 s with no reset. No trial on v3.2 produced a "dead/needs-reset" outcome anywhere in the matrix, no relay moved, and no spurious reinit was seen — so the wider check recovers the field-matching fault without regressing anything or destabilising steady state, at a cost of +118 B flash and three extra SPI reads per 5 s cycle. On these results v3.2 is recommended as the field candidate in place of v3.1. Full per-trial data: docs/fault-matrix-results.md.
Reset start-up (Test 2b). The other planned test asks how often the real box comes up deaf after a fresh start — the deaf start-up seen on unit 2 (§5.12). 200 USB/DTR resets on each firmware (a DTR reset also resets the W5500, §5.17): v3.1 came up 200 of 200, v2.2 200 of 200, no deaf event on either (median time to answer 9.1 s for v3.1, 2.9 s for v2.2 — v3.1 is slower because it runs the RFC 5227 probe/announce and negotiates the PHY twice before using its address). This matches every earlier run on units 1 and 3; the deaf start-up has only ever appeared on unit 2 (4 events). The link-drop half (2a) was set aside: the bench method that drives it (disabling the PC's adapter) stopped being reliable, and a unit-side method was deferred.
All three firmwares and the Arduino Ethernet 2.0.2 source were read in full against the W5500 datasheet v1.1.0 (text extracted from WIZnet's PDF), the RFCs and AVR practice; stack frames were compiled with -fstack-usage. Full text with code references: docs/code-analysis.md.
SIPR, SHAR, SUBR, MR, PHYCFGR); only Ethernet.begin() in setup() does. A static unit can't silence itself by its own logic.begin() returns silently, every later linkStatus() retries the detection with a 560 ms delay, and if a retry succeeds the chip runs on IP 0.0.0.0 / MAC 0 with the LED blue. The only v2.2 recovery, a CPU watchdog reset, doesn't hardware-reset the W5500 (§5.17).SEND: ≈ 340 SPI writes and 82 TCP segments per POWER_STATUS vs ≈ 15 and 1–2 on v3.x. All bench polling of v2.2 (~10⁵ requests) only rules out per-request failure rates above ~3 × 10⁻⁵, while a unit polled once a second makes 2.6 × 10⁶ requests a month.PHYCFGR bit 7; the datasheet says the PHY stays in reset while it is 0); G2 no recovery for a link that stays down; G3 the receiver check needs a gateway that answered since the last link-up, so it never fires on closed networks or for a receiver that goes deaf at link-up; G4 a W5500 that stops answering on SPI needs a hardware reset line the board doesn't have; G5 no supply visibility (Optiboot 4.4 clears the reset-cause flags before the sketch starts).v3.3 = v3.2 + the §5.25 gaps (G1 PHY reset bit in the health check; G2 PHY reset after 30 s without link, backing off; G3 receiver check by one raw ARP request to the gateway or the last client, listening to every frame for 2 s and counting a silent check only if nothing at all was heard, "answered" surviving a link-up; G5 5 V supply via the internal bandgap in NET_STATUS and logged with every re-init; BOD fuse readout; 414 for an over-long target). 32,094 B of 32,256 (162 B free), 1,407 B RAM. Protocol and pass criteria committed before the run (docs/v3.3-tests.md, amendment 1 before any affected data); unattended run on unit 3, direct cable, USB connected, reset capacitor removed. Changes and builds: docs/v3.3.md.
Fault matrix (51 trials on v3.3). All 17 faults recovered by themselves (3–10 s) or had no effect; no dead, latent or slow trial; outputs 111111 throughout; four tail-only trials (stray ping, two on faults that can't affect ping). F15 (PHY held in reset): v3.3 3–6 s; v2.2, v3.1 and v3.2 dead 3/3 each (power cycle needed), as the code analysis predicted (gap G1). F16 (F15 with only the link-down recovery): back at 40 s in 3/3. F17 (PHYCFGR power-down written once): v3.3 recovered 6–7 s; v3.2 recovered 5–26 s; on v3.1 and v2.2 the PHY kept running with the wrong value in the register (latent): a mode written without the RST toggle doesn't take effect until the next PHY reset. The field-matching F7 PHY power-down now recovers in 4–6 s (v3.2: 11–12 s). Background ping loss in recovered trials: v3.3 0.52 %, v3.2 1.09 %, v3.1 1.97 % — the raw-frame listening doesn't add loss.
Receiver check. Gateway present then gone (N6): v3.3 sent one ARP request per check (11 in 12 min to the vanished gateway, never more than one per second) and made no PHY reset (the PC's own frames proved the receiver works); on v2.2, v3.1 and v3.2 the prediction (ARP bursts, PHY resets) was wrong for a reason worth knowing: their check (v2.2: keepalive) is a UDP send, and without MR.FARP the W5500 reuses the gateway MAC it cached at the first send, so after the gateway vanished they sent no ARP request at all in 12 min and their checks kept "succeeding". On v3.1/v3.2 this means the receiver check can never notice a deaf receiver once a gateway has answered once; v3.3's raw ARP request needs a reply every time. Deaf receiver by emulation (N4, TEST_DEAF): exactly one recovery PHY reset after three silent checks, none after; at 6 min instead of the pre-registered 3–5 min, because with a configured gateway that never answers, gateway and client take turns (strict verdict FAIL on timing).
Link down 180 s (N5, v3.3 only, amendment 1): PC adapter disabled for 180 s, twice: service back 8.3 and 8.6 s after it was enabled, 2 PHY resets during each outage (at 30 s and 90 s, as designed), no restart, outputs kept (PASS); the PC adapter survived both cycles.
Regression (production build). Power 21/21; load test 541/541; junk input 16/16; HTTP 21/21 and 414 for a 600-byte target; persistent clients 6/6; idle dose all answered (0–5 idle connections per port); DHCP A–E with syslog (renewals acknowledged while the receiver check ran); 10 Mbps half duplex (load 543/543); 50/50 USB-reset start-ups (median 9.16 s); millis rollover; stack low-water 190 B (FAULT_INJECTION build). NET_STATUS on unit 3: VCC=5096, VCC_MIN=5073 mV (USB supply), BOD=2.7 — the brown-out level the code analysis inferred, now measured.
Settings loss. In the first run (10 trials + 3 watchdog hangs) trial 8 was followed by a ~3.5-min outage that cleared by itself without a restart; three later steps fell in it (10/14). An instrumented re-run of 20 trials (capture, ping, PC address state, unit counters) passed 20/20, as did 20 trials on v3.1. A re-run under the original conditions (50 USB resets, then 10 trials) caught a second episode with the capture running: trial 5 answered only after 47 s; the unit sent its own ARP request with its correct address, then answered none of ~35 ARP requests from the PC for ~38 s with the link up, sent no address-check probes, and recovered by itself with no re-init, PHY reset or link change. A deaf receiver after the re-init's link renegotiation — mechanism (c), until now seen only on unit 2. The 03:48 outage had the same signature (~3.5 min). Tonight: 2 episodes in 40 settings-loss trials on v3.3 (two link negotiations each), 0 in 30 on v3.1; not significantly different, consistent with a chip/board property. Both cleared before v3.3's deaf check acted (~6 min on this bench, where gateway and client take turns). The event log couldn't show this either: each re-init now writes four records, and the hourly budget ran out (a log-economy fix is in §9).
Conformance (N8): same suite and criteria as §5.23, unit 3, 05:44–06:01 (data/conformance/v33_*). 48 of 56 requirements pass (v3.1/v3.2 46, v2.2 26), 6 fail, 2 not verifiable without forged traffic. Newly passing: H6 (one ARP request per check, 1 to the gateway and 1 to the PC in 137 s) and W8 (414). Failing: H2, H3 (W5500 chip limits), D4 (DHCP from the fallback address), T6 (2-s send timeout), W7 (no Host check, kept for control systems that send hand-written HTTP), W9 (GET switching, kept). C7 measured exactly as on v3.1 (3 probes and the first announcement before use) and is scored by the same documented rule (RFC 5227 allows use after the first announcement): pass. Standards: 6 of 10 pass, as v3.1; RFC 1122 now fails only on the chip limits.
What explains the field symptom. The total loss (no ping, no ARP) can only come from the W5500 not answering at all:
| Mechanism | Matches | v2.2 recovers | v3.0 / v3.1 recovers | Trigger |
|---|---|---|---|---|
| DHCP strand: an unanswered renewal → v2.2 restart → no server within 60 s → static fallback for good | exactly (DHCP mode only) | no, until power cycle | yes, by itself (6 s after the server returns) | any DHCP outage or non-answer at renewal time |
| W5500 loses its settings | exactly | no | yes, 3–6 s (v3.1: 2.1–6.9 s, 10/10, outputs kept) | unknown; supply dip / ESD suspected; load switching untested |
| W5500 deaf after a link negotiation (sends, receives nothing) | exactly | unknown beyond 60 s; once cleared by itself after 97 s | v3.0–v3.2: no recovery (their check runs on a cached gateway address, §5.26); v3.3: PHY reset after three silent checks | ~1 in 130 negotiations on unit 2; on unit 3 seen twice after v3.3 re-inits (38 s and ~3.5 min, both cleared by themselves; one captured) |
| Socket starvation | no (ping works) | sometimes, via a watchdog reboot | never starves | idle / half-open connections |
| Duplicate MAC / IP | partly | no | reports IP conflicts | process error |
DHCP sites. The DHCP strand matches the field description exactly and needs no hardware fault: a router or DHCP server that is down, rebooting or slow when a v2.2 unit renews leaves that unit unreachable until someone power-cycles it. It does not apply to units set to static.
Static sites. Two candidates remain: the W5500 losing its settings (trigger untested: load switching), and deafness after a link negotiation (rare, persistence unknown). Withdrawn (2026-10-09): the earlier chain "v2.2's own CPU-only reboots come up deaf"; the deaf start-ups followed DTR resets, which also reset the W5500, and 19+ watchdog restarts never came up deaf.
Why v3.0 had 0/300 deaf start-ups is open: both firmwares got a W5500 hardware reset at each DTR reset; v3.0 additionally resets the PHY in software and negotiates a second time.
The Arduino Ethernet library is responsible for the starvation behaviour and the one-byte-per-packet output (§5.7). v2.2 made it worse (the permanent keepalive socket) and did not compensate.
v3.0 assessment. It fixes the reproduced defects and mechanism (a), and it matches or beats v2.2 everywhere it was measured. Five bugs were found and fixed during two days of testing (0x11 state, DHCP address per reboot, PHY reset on link bounce, PHY recovery assuming a gateway, identical temporary MACs). That is the expected cost of a rewrite; more may remain.
v3.1 assessment. It keeps v3.0's network layer and recovery, and adds standards conformance. On the final build every non-soak test passed, including settings loss with the outputs kept on, and v3.1 passed 46 of 56 conformance requirements against v2.2's 26. Its remaining failures are two chip limits, three small fixable items (ARP rate, Host, 414) and three deliberate trade-offs (DHCP from the fallback address, the 2-s send timeout, GET actions). Two bugs were found and fixed during the v3.1 work (a false conflict from the PC's own probe, and a timer in the split-command fix). It is not field-proven.
v3.2 assessment (recommended as the official firmware). It is v3.1 with the 5 s self-check widened to the mode (MR), retransmission (RTR/RCR) and PHY configuration (PHYCFGR) registers. In the pre-registered single-register fault matrix it recovered from all 14 faults with no "dead/needs-reset" outcome and the outputs never moving — including the four v3.1 died on (ping block, retry count 0, forced 10-half, and the PHY power-down that reproduces the field symptom, back in 11–12 s), and with no regression on the faults v3.1 already handled (§5.24). It scores the same 46 of 56 on the conformance suite (the change does not touch anything the suite measures). Cost: +118 B flash (31,238 of 32,256) and three extra SPI reads per 5 s cycle; the TCP protocol, stored settings and the v2.2 EEPROM downgrade path are unchanged. It is not field-proven; adoption should be followed by the hands-on and real-load checks and a pilot (§7).
v3.3 assessment (recommended as the official firmware). It is v3.2 with the gaps of §5.25 closed. In the overnight run it recovered from all 17 single-register faults (51 trials, 3–10 s, outputs never moved), including the PHY held in reset that leaves v3.2 dead; the link-down recovery alone brings a stuck PHY back in 40 s; the new receiver check sent one ARP request per check and made no false PHY reset when the gateway vanished; the whole regression suite passed. Twice in 40 settings-loss trials the chip came back deaf after the re-init's link renegotiation and cleared by itself (38 s, ~3.5 min; captured once, §5.26): mechanism (c), now seen on unit 3 too; faster detection after a re-init is the next step. Program memory is 99.5 % full (162 B free), so any later feature needs something taken out. It is not field-proven.
Is v2.2 as good as v3.1/v3.2 on standards? No. Several standards show FAIL for both, because one failed item fails a standard, but v2.2 fails 25 requirements against v3.1/v3.2's 8. Only v2.2 lacks conflict detection, gives up on TCP after 18.4 s, deviates from RFC 2131 in its DHCP client and mishandles HTTP methods.
| Conclusion | Grade |
|---|---|
| The field symptom means the W5500 stops answering; socket starvation alone can't cause it | Measured |
| DHCP mode: after a DHCP outage at renewal time, v2.2 stays unreachable until power-cycled; v3.0 recovers by itself | Measured (one server) |
| On v2.2, one unanswered DHCP renewal makes the unit restart itself (outputs off unless "restore") | Measured |
| If the W5500 loses its settings, v2.2 stays dead until reset; v3.0 recovers in 3–6 s | Measured |
| After some link negotiations a W5500 goes deaf (registers correct, receives nothing); ~1 in 130 on one board | Measured (rate preliminary; persistence unknown) |
| A USB/DTR reset also resets the W5500; a v2.2 watchdog restart doesn't | Measured |
| Ethernet 2.0.2 writes RTR/RCR to the wrong W5500 registers | Measured |
| v3.0 avoids the deaf start-up | Preliminary (p = 0.106) |
| v3.0 beats v2.2 on all pre-registered criteria except one lost ping | Measured |
| What makes static-mode units drop off in the field | Unknown |
| v3.1 passes every non-soak bench test on its final build, including settings loss with outputs kept | Measured |
| On 56 standards requirements, v3.1 passes 46 and v2.2 26; two shared failures are W5500 chip limits | Measured (one bench, own tools) |
| v2.2 cannot make a static unit stop answering ping by its own logic; every outside upset of the W5500 is permanent on it | Code-read (§5.25) |
| Unit 3's brown-out level is 2.7 V (Uno default) | Measured (v3.3 BOD=2.7) |
| A supply sag is the static-mode trigger | Hypothesis (untested) |
| A PHY held in reset leaves v3.2 dead; v3.3 recovers in 3–6 s | Measured (§5.26) |
| v3.3 recovers from all 17 single-register faults and passes the full regression | Measured (§5.26; two self-clearing deaf episodes after re-inits) |
| A W5500 can come back deaf after a link renegotiation on unit 3 as well (2 in 40 settings-loss trials on v3.3, cleared by themselves after 38 s and ~3.5 min) | Measured (one episode captured) |
| v3.0–v3.3 prevent the field failure | Unknown (needs a pilot) |
0xFC, ISP), a reset supervisor instead of the 22 µF capacitor, the W5500 reset pin on a free GPIO, the blue LED off D4.-DEPS_MAC_SUFFIX), capacitor removed for the upload; switch the loads with the poller and flight recorder running. Any REINIT/PHYRESET count or logged W5500 re-init is the finding.docs/network-investigation.md (v2.2 analysis and early bench results) · docs/v3.0.md (v3.0 design, changes, test notes) · docs/comparison-protocol.md + docs/comparison-results.md · docs/w5500-hang-tests.md + docs/w5500-hang-results.md · docs/eps-firmware-review.html (an earlier, internal summary page) · docs/v3.1-standards.md (v3.1 changes and tests) · docs/conformance-suite.md + docs/conformance-results.md (standards suite, v3.1 vs v2.2) · docs/fault-matrix-tests.md + docs/fault-matrix-results.md (single-register W5500 faults and link/reset tests, v3.1 vs v2.2) · docs/code-analysis.md (code analysis of v2.2 with the Ethernet library, v3.1 and v3.2, 2026-10-12) · docs/v3.3.md (v3.3 changes and builds) · docs/v3.3-tests.md (v3.3 protocol, amendment and results) · docs/HANDOVER.md (bench state and how to continue).
| When | What |
|---|---|
| 10-08 afternoon | Code review; NovaHub deafness; v2.2 load test; v3.0 written and flashed; 0x11 bug found/fixed |
| 10-08 ~15–17 h | DHCP bench server; power test; Windows-sharing DHCP; sticky-IP and PHY-reset fixes |
| 10-08 ~17–18 h | Second unit; duplicate-MAC bug; library isolation; comparison protocol committed |
| 10-08 18:00–19:05 | A/B phases A and B; fault injection; flight recorder |
| 10-08 19:07 – 10-09 00:14 | Overnight side-by-side run |
| 10-09 00:15 | W5500 hang-test protocol committed; H4 first run (stopped) |
| 10-09 08:05–09:09 | H4 re-run, H4b-1, H4b-2 |
| 10-09 09:30–10:10 | Persistent connections; v3.0 bench batch |
| 10-09 late morning | NETGEAR M4250 connected; access and read-only baseline |
| 10-09 ~12:30 | Unit 3 (real EPS box) and Netio PowerBOX added |
| 10-09 12:50–13:04 | Single events: power cuts, outputs, self-restarts (all fine) |
| 10-09 13:05 | Unit 2 silent ~97 s after a 0.5 s port flap |
| 10-09 13:11–14:31 | Replications (0/30 flaps), instrumented single-port flaps (0/98); DTR resets the W5500; library RTR/RCR defect |
| 10-09 14:41–14:55 | NovaHub at 100 Mbps: deaf on both boards (set aside) |
| 10-09 15:17–15:42 | DHCP tests: v2.2 restarts at renewals and strands after an outage; v3.0 recovers |
| 10-09 17:20–18:40 | v3.1 (standards work) on unit 3: start-up check, HTTP, DHCP, syslog; review fixes |
| 10-10 17:51–18:46 | v3.1 final build: every non-soak test re-run, all pass; ARP rate found |
| 10-10 19:06–19:44 | Conformance suite (pre-registered) on v3.1, then v2.2 |
| 10-10 23:49 – 10-11 02:49 | Fault matrix Test 1 (pre-registered): 14 single-register faults ×3 on v3.1, then v2.2 |
| 10-11 07:19–11:20 | Fault matrix Test 2 re-run: 16 valid v3.1 link drops, then the PC's adapter failed; aborted |
| 10-11 12:49–13:38 | Test 2 part 2b (USB-reset start-up) on v3.1 and v2.2: 200 each, 0 deaf |
| 10-11 13:44–15:12 | v3.2 candidate built; same 14-fault matrix ×3 on v3.2: recovers F6–F9 (incl. PHY power-down), no dead outcome; unit 3 restored to v3.1 production |
| 10-12 ~00:00 | Code analysis of v2.2 (+ Ethernet 2.0.2), v3.1, v3.2 (§5.25) |
| 10-12 00:30–00:56 | v3.3 built (G1–G5, BOD, 414); protocol committed; overnight run started |
| 10-12 00:56–02:52 | v3.3 test build: boot sanity, deaf emulation, 17-fault matrix ×3, F16 |
| 10-12 02:52–04:02 | v3.3 production: gateway-gone test, full regression, fault/stack/rollover builds; the 03:48 outage |
| 10-12 04:02–04:10 | Instrumented settings-loss re-runs on v3.3 and v3.1 (20 each, all pass) |
| 10-12 04:10– | F15/F17 and gateway-gone on v3.2, v3.1, v2.2; link-down on v3.3; conformance v3.3; reproduction run catches a deaf episode (06:10) |