feat: promote Expert 2K-FA Third Series fan profile - #34
Conversation
Attempted the hardware checklist — blocked before step 2, but found the likely root causeBuilt exact commit What blocked step 2
Once I did toggle OPERATE→STANDBY physically, the queued override did fire, navigated Root causeCompared against the LAN-unit's own web client ( Tested the hypothesis live: with ConclusionI don't think this is a bug in this PR's navigation/fan-policy logic — the profile itself ( Given 73, |
Follow-up: checked the manuals — nothing missed, they actually confirm the diagnosisWanted to rule out a documented handshake, session setup, or rate limit before assuming the polling-interleave theory from my last comment was right. Pulled both official docs fresh from spetlc.com: Application Programmer's Guide (the documented Main user manual, §15.1 Remote Control: describes SPE's own official app ( Put together: the display-mirror protocol (the So this looks like a real firmware/protocol limitation rather than a client-side rate or ordering bug Manuals used (fetched fresh):
73, |
Correcting my earlier diagnosis, and the real blockerMy two comments above blamed
|
| condition | status polls sent | status frames back | ACKs back |
|---|---|---|---|
both, 4 min continuous |
670 | 0 | 0 |
status only, no display polls at all |
106 | 0 | 0 |
| fresh LAN-unit session, different transport | 84 | 0 | 0 |
Across every capture, every AA AA AA sequence in the amp→host stream was a display-frame header. Not one AA AA AA 43 status frame, and not one ACK, in any run.
Interleaving is not the cause. In both mode the display path is completely healthy — 109 display polls produced 113 display frames, each arriving 66 ms after its poll, which is exactly 371 bytes of wire time at 57600 baud. Display polling works fine alongside status polling. Status polling simply returns nothing.
The amp is reachable — it's 0x90 specifically
Worth being precise, because it rules out the obvious explanations:
- host→amp works. A serial
OPERATE(0x0D) toggled the amplifier from STANDBY to OPERATE and back, confirmed physically. Commands arrive and execute. - the amp does emit ACKs. I captured
AA AA AA 01 0C 0Con the wire — the documented ACK for aDISPLAYkeystroke. - amp→host works. Display frames flow continuously at ~5.4/s.
So the link is bidirectional and the amp's command parser is reachable. 0x90 alone produces neither the documented STATUS string nor an ACK.
I checked the Programmer's Guide for a missed precondition and there isn't one: §5 documents 55 55 55 01 90 90 exactly as implemented here, describes it as stateless, and says it "can be requested several times every second". No handshake, no session setup.
What that does to this PR's checklist
Both entry points depend on protocol-native status, so both fail closed. Run on exact SHA c6bee25896aefdfa85487067a6addfc1be7d549c, amplifier in STANDBY/RX, automaticFanPolicyEnabled: false, no manual override, overtemperature standby disarmed, fanPolicyFirmwareVersion: Rel.08_06_26_A:
Step 2 — POST /api/v1/fan-policy/override {"mode":"normal"} → HTTP 202
state: unavailable
reason: recent protocol-native temperature status is unavailable
recentContact: false provenance: display-frame
supportedModes: [] actionAvailable: false
Menu Debug arm → HTTP 409 "fresh protocol status is required"
standby false <- needs protocol-native status
rx false <- needs protocol-native status
recentContact false <- needs protocol-native status
standbyHome true
automaticFanPolicyDisabled true
overtemperatureStandbyDisarmed true
actuationLeaseAvailable true
Four of seven prerequisites pass. The three that fail are exactly the three that require a 0x90 reply — while standbyHome being true shows the server can see the amplifier sitting on its standby home screen the whole time.
Both refusals sent zero bytes to the amplifier. The wire during each attempt shows only display polls: 180 polls / 176 frames for the override, 143 / 139 for the arm. The fail-closed behaviour is working exactly as designed — I want to be clear that nothing in this PR's navigation logic misbehaved. It never got the chance to run.
Two client-side findings, unrelated to the above
1. The poll scheduler can silently stop sending one poll type entirely. In serialPollScheduler.advance(), both mode alternates on halfInterval, and the catch-up loop flips phase once per skipped slot. When the read loop settles into a cadence that is an even multiple of halfInterval — which happens naturally when reads time out at serialReadTimeoutMs — the phase returns to the same value every iteration and locks there.
I observed it lock in both directions on different sessions:
session A: 109 display polls, 59 status polls (drifting)
session B: 143 display polls, 0 status polls (locked to display)
session C: 3 display polls, 84 status polls (locked to status)
Session C is the damaging one: display updates dropped to ~3 per 21 s because the entire poll budget went to a command that never answers. This is independent of the amplifier — it's purely the scheduler's arithmetic.
2. A button write landing right after a 0x90 appears to stall the link for ~12.8 s. In six presses under both mode I saw three stalls, and the split was clean: all three stalls were preceded by a 0x90, all three clean presses by a 0x80. Each stall ended precisely when the delayed ACK arrived (silence 24.604 → 37.346 s, ACK at 37.346 s, frames resumed immediately). Two presses under display mode produced no stall.
I'm flagging this one as provisional. A browser tab was open on the LAN unit's own web UI during part of that testing, which puts a second client on the box, and I can't fully exclude its influence. The adjacency correlation is not something a second client would produce, but it deserves a clean re-run before you act on it. If it holds, it would explain the press batching I described in my first comment far better than my interleaving theory did.
I also previously believed the LAN unit free-runs its own display broadcast at 317 ms. That was an artifact of that same open browser tab and I retract it too — with polling off and no other client, the box sends only a null keepalive every ~5 s.
Where this leaves things
I don't think this is a defect in PR #34. The profile, the gates and the fail-closed behaviour all did the right thing. The problem is that the reviewed production path requires protocol-native status within a 5 s window, and on this hardware path that window can never open.
Some options, in the order I'd weigh them:
- Allow display-derived STANDBY/RX evidence for this profile. The checksum-valid LCD flag word decodes
tx,operate,setandtunereliably,standbyHomealready passes, and the Third Series profile is display-verified at every step anyway. This is the smallest change that makes the path reachable. - Treat "status poll unanswered" as a first-class transport state rather than as stale telemetry, so the server can say so plainly instead of reporting a freshness failure that looks like a timing problem.
- Leave it blocked and document that this transport can't run the production fan path. Defensible, but it means the feature is unreachable for anyone on an SPE-LAN-UNIT.
Happy to test whichever direction you prefer, and happy to run the clean re-test of finding 2 on request.
One transport note for context: this amplifier now reaches the server through an SPE-LAN-UNIT over its virtual-COM port rather than direct USB. Status telemetry did work here before 2026-08-18, but the transport moved to the LAN unit and the amplifier was flashed to Rel.08_06_26_A on the same day, so I can't attribute the change to either one from software alone. Separating them needs a direct-USB A/B, which I'll run when I can and report separately.
73,
Justin — AI5OS
|
Thanks for the careful correction and packet captures. The evidence separates two issues cleanly. The scheduler starvation is a real client bug and is being fixed independently under #38. That fix preserves bounded one-poll dispatch and fair status/display alternation after delayed reads; it does not weaken any PR #34 safety gate. PR #34 remains draft and blocked. We will not accept display-derived STANDBY/RX as a substitute for protocol-native status based only on the SPE-LAN-UNIT path. Please run the direct-USB versus LAN-unit The scheduler fix is now merged under #39. This branch will remain unrefreshed and blocked until the transport evidence supports a safe reachable gate; only then will it be brought onto current main, re-reviewed, and given a new hardware checklist. |
Direct-USB vs SPE-LAN-UNIT
|
Retracting the "0x90-then-button stalls ~12.8s" finding — clean re-run, no other client, 0/40 stallsFollow-up to my provisional note above. Re-ran it properly this time: single exclusive raw TCP client to the LAN unit's port 7388 ( Result: 0/40 stalls. Max gap observed was 0.49s, nothing remotely close to 12.8s. The original finding doesn't reproduce — it was the open browser tab I flagged as an uncontrolled variable, not a real bug. Please disregard that part of my earlier comment. One smaller pattern survived the clean re-run and might be worth a note: replies following a Side note for anyone else testing on this hardware: I'd assumed DISPLAY was side-effect-free since it doesn't touch RF/PA, but it's actually a real front-panel button that cycles a 3-screen info carousel (Standby → SN:xxxx → CAL:000 → repeat). 40 rapid presses left my amp's LCD sitting on the SN screen afterward — harmless, but not literally a no-op. Cycled it back to Standby manually after the test. |
|
Thanks again for isolating this so cleanly. The direct-USB A/B gives us enough evidence to keep PR #34's safety gate intact and treat the LAN unit as the transport limitation. A few broader questions, since you may be the first person to run
No need for more amplifier testing from our side right now. We are not going to weaken the protocol-native STANDBY/RX requirement to accommodate this transport. |
|
Hi Ian,
I'm happy to troubleshoot whatever you'd like where I can because I think that expert amp server is an excellent resource. The only reason, and I mean the only reason that I am using the SPE-LAN unit between the Pi and the amplifier as the transport layer is that I find that it is less prone to RF. It could be that I have the wrong kind of USB cable, or something, but for whatever reason I tend to get RF going through my USB connection that goes to the Pi, and so I've had some flakiness in the connection. If you could show me a USB cable that is shielded enough that you've never seen RF get into it, I'd directly put the Pi back to the amplifier. What you're making in software is vastly superior to what they can fit on that board, it's just a little ESP-32 board with some kind of Arduino CPU, you could probably build it for about $25 if you have the mechanical know-how. Let me know what you'd like me to send to the SPE folks, and I will gladly do it.
Thanks,
Justin-ai5os
… On Aug 26, 2026, at 2:06 PM, Ian Ferrell ***@***.***> wrote:
FtlC-ian
left a comment
(FtlC-ian/expert-amp-server#34)
<#34 (comment)>
Thanks again for isolating this so cleanly. The direct-USB A/B gives us enough evidence to keep PR #34 <#34>'s safety gate intact and treat the LAN unit as the transport limitation.
A few broader questions, since you may be the first person to run expert-amp-server through an SPE-LAN-UNIT in earnest:
What motivated you to put the LAN unit between the amplifier and the Pi/server instead of continuing with direct USB? Is there a production advantage in your station, or were you primarily evaluating the newly released unit?
What is your overall impression of the LAN unit and its web software? Are there useful capabilities or interaction patterns it has that you think expert-amp-server should consider adding?
Have you found any indication that its firmware is actively maintained or field-updatable? Since you already offered, would you be willing to send the vendor/SPE the bounded 0x90 A/B results and let us know what they say? The key evidence is simply that the same amp and firmware returned 258/258 valid status frames over direct USB and 0/258 through LAN-unit app version 1.0.3, with malformed six-zero-byte replies on the latter.
No need for more amplifier testing from our side right now. We are not going to weaken the protocol-native STANDBY/RX requirement to accommodate this transport.
—
Reply to this email directly, view it on GitHub <#34?email_source=notifications&email_token=AL6DBBCHIIEUK3AG66QD5MT5L4YKPA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNBSHE4DKMBSGY3KM4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5429850266>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AL6DBBGP7LKBAPU5BDBILTL5L4YKPAVCNFSNUABGKJSXA33TNF2G64TZHMYTEMRQGQYTQMZUHE5US43TOVSTWNJSGM3DGOBVGIZTLILWAI>.
You are receiving this because you commented.
|
|
Justin, I do not think there is one magic bullet, but aggressive common-mode choking made the biggest difference in my shack. Every HF feed line has four to six mix-31 ferrites between the grounded window-entry panel and either my MFJ-998 tuner or the amp. I keep those inside coax runs short and direct, with all coax shields bonded at the entry panel. I also use high-power 1.5–3 kW common-mode chokes outside at the antenna feed points. Before doing that, I had certain band and antenna combinations where transmitting more than about 15 watts would make speakers buzz somewhere in the house. The additional choking drastically reduced the stray RF, although it is not quite perfect. I still need to finish bonding the radio, tuner, and amp chassis to the station ground bus, and I have not tested every band with every antenna combination yet. For comparison, my ANAN G2 is connected to the SPE Expert amp with an ordinary six-foot USB-A-to-USB-B printer cable. It is nothing special and has been completely reliable. I put a couple of generic snap-on ferrites on it, but I do not even know what mix they are. For your USB issue, I would try a shorter, well-shielded cable and known mix-31 snap-ons near both ends. If the cores are large enough, pass the cable through them multiple times. I would not simply coil excess USB cable into an ugly balun without a suitable ferrite core because the choking impedance would be unpredictable. My overall recommendation is known mix-31 choking on the feed lines at the shack entrance, a properly rated choke at each antenna feed point, short inside coax runs, and good single-point chassis bonding. Then test each band and antenna combination individually. At QRO, especially with multiple home-built or experimental antennas, RF control is an ongoing battle. When common-mode RF gets into the shack, practically every USB, Ethernet, audio, power, and control cable can become an antenna. 73, |
The firmware pin now excludes the only 2K-FA on the bench — and the page it protects is byte-identical across both firmwaresI went to capture a fresh menu report for you and found I can't run one at all. Worth reporting, along with the evidence I gathered instead. The blocker
"expert-2k-fa-third-series-fan-normal-quiet-v1": {
capability: CapabilityFan, model: "EXPERT 2K-FA", firmware: "Rel.26_03_24_A",
},and if profile.firmware != "" && profile.firmware != strings.TrimSpace(p.ExpectedFirmware) {
return errors.New("plan does not use the exact reviewed firmware profile")
}I flashed this amplifier to I want to be clear that I am not asking you to relax a safety gate on my say-so, and I did not work around it. The evidence, since I couldn't run your workflowInstead I re-ran my own guarded read-only capture of the Set-mode sub-pages, the same one I used for the 2026-08-13 baseline: only items whose legend reads Comparing raw cell bytes rather than decoded text, across
The two exceptions differ only in row 6, the dynamic context-help line, and only because a different band/antenna is selected today than in August — FAN NOISE — the page this PR's profile actually drives — is byte-for-byte identical: Same title, same two options in the same order, same SAVE, same legend. Row 3's raw cells are One thing that initially looked like a change was not. The decoded text differs between my two captures because your glyph decoder improved between v0.4.6+pr26 and v0.4.7: What I'd suggest, entirely your callThe pin is doing real work for firmwares that genuinely move the menu. But as written it excludes the only 2K-FA available to test this PR, to protect a page that measurably did not change. Some options:
Separately, and unrelated to the pin: I filed #44 for an ABBA deadlock I reproduced on clean Captures are in my own repo and I can attach the raw 73, |
|
Captures are public now if you want to look at the raw evidence rather than take my word for the byte comparison: https://github.com/w9fyi/expert-amp-a11y/tree/main/captures — commit Each sub-page directory has
73, |
|
I reviewed the proposed The implementation is appropriately narrow: it adds the newer firmware as a second exact, case-sensitive allowlisted value for the same model and captured Third Series topology. Empty, mistyped, case-variant, and unlisted values remain blocked before any amplifier command. The original The raw-evidence review is blocked at the moment. No additional amplifier transaction is requested for this evidence check. |
Summary
Verification
Merge gate
Draft only. Do not merge until Justin validates the actual production path on Rel.08_06_26_A while the amplifier remains in STANDBY/RX. OPERATE-to-STANDBY automation remains explicitly out of scope.
Related to #5.