Add Hermes-Lite 2 IO board (N2ADR) support - #4
Open
randal007 wants to merge 1 commit into
Open
Conversation
The HL2 IO board (jimahlstrom/HL2IOBoard) is a Pico daughter board on the filter-board header. It has no HPSDR knowledge: the PC pushes the transmit frequency, RF-input routing and receive frequency codes into its I2C register file, and Pico firmware drives amplifier, antenna, transverter, fan and AH-4 tuner lines from them. Zeus had no I2C path at all, so this adds one. Wire layer (Zeus.Protocol1): - Hl2IoBoard.cs — register map mirroring the firmware's i2c_registers.h, the tunnelled-transaction encoding (0x7A write / 0xFA read on I2C-2 at 0x1D), and a pure scheduler: detect via the hard-wired PCA9536D at 0x41, then cycle TX frequency, RF inputs and the RX frequency codes. - ControlFrame — a raw I2C control block may displace one rotation slot in the odd USB frame, so no extra EP2 packet is introduced and the TX FIFO cadence the transmitter depends on is untouched. - PacketParser.TryExtractHl2I2cReply — decode replies, which the HL2 echoes on C&C address 0x3D. - Protocol1Client ticks the scheduler on the EP2 loop and feeds replies back, gated on BoardKind and the operator's switch. Hosting: - PreferredRadioStore / RadioService persist the switch alongside Band Volts and re-push it into each fresh client on connect. - /api/radio/hl2-options gains `ioBoard` plus a read-only `ioBoardPresent` so the UI can tell "switched on" from "switched on and answering". `IoBoard` on the PUT request is nullable — a client that sends only BandVolts must not switch the board off. - BoardCapabilities.HasHl2IoBoard gates the frontend control, HL2 only. Off by default. With it off the EP2 stream is byte-identical to before. With it on but no board fitted the cost is one read every 2 seconds, and no writes ever happen because writes only start after detection. Two details that matter on real hardware: - The five TX-frequency bytes are latched per turnaround and written LSB-last, matching the firmware's commit-on-byte-0 behaviour, so a retune mid-burst cannot leave a torn frequency on the board. - REG_RF_INPUTS is sent as 0 and is never inferred. Modes 1 and 2 switch the receiver onto the board's J9 input, and mode 2 also drives INTTR — the HL2's T/R relay — on receive. They are only correct when a separate receive antenna is wired to J9, and per N2ADR the register "is a user setting". An explicit operator control is the right way to expose it. Wire encoding and cadence follow deskHPSDR's old_protocol.c case 11 and the softerhardware/Hermes-Lite2 protocol documentation. Verified against a real IO board on an HL2: detection, the frequency round-trip and the 0x3D reply path all confirmed on air.
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.
What this adds
Support for the N2ADR HL2 IO board — the Pico daughter board that mounts on the Hermes-Lite 2's filter-board header and drives amplifier, antenna, transverter, fan and Icom AH-4 tuner lines.
The board has no knowledge of the HPSDR protocol. The PC pushes the transmit frequency, the RF-input routing and the receive frequency codes into its I2C register file, and the Pico firmware makes its switching decisions from those registers. Zeus currently has no I2C path at all — I grepped
ControlFrame,Protocol1ClientandPacketParserfor the HL2 extended opcodes (0x7A/0xFA, and0x3Dfor replies) and there are no hits — so this adds the whole tunnel.How it works
Wire layer (
Zeus.Protocol1)Hl2IoBoard.cs— the register map (mirroring the firmware'si2c_registers.hname-for-name so the two can be diffed by eye), the tunnelled-transaction encoding, and a scheduler that detects the board via the hard-wired PCA9536D at0x41and then cycles its register file. It is a pure state machine — no I/O — so it is testable without a radio, and I/O policy stays inProtocol1Clientwith the rest of the wire scheduling.ControlFrame— a raw I2C control block may displace one rotation slot in the odd USB frame. It does not add a packet, so the EP2 cadence the TX FIFO depends on is untouched; the displaced register comes round again next turn.PacketParser.TryExtractHl2I2cReply— decodes replies, which the HL2 echoes on C&C address0x3D.Protocol1Clientticks the scheduler on the EP2 loop and feeds replies back, gated onBoardKindand the operator's switch.Hosting
PreferredRadioStore/RadioServicepersist the switch alongside Band Volts and re-push it into each fresh client on connect./api/radio/hl2-optionsgainsioBoard, plus a read-onlyioBoardPresentso the UI can distinguish "switched on" from "switched on and answering".BoardCapabilities.HasHl2IoBoardgates the frontend control (HL2 only).Compatibility
Off by default. With it off the EP2 stream is byte-identical to before. With it on but no board fitted, the cost is one read every 2 seconds and no writes ever happen, because writes only begin after detection.
One API note worth a look:
IoBoardon theHl2OptionsSetRequestis nullable, making the PUT a partial update. A client that sends only{bandVolts}— which is what ships today — would otherwise switch the IO board off every time someone toggled Band Volts. This is the divergence the existing comment on that DTO anticipated ("kept distinct so the GET-vs-PUT request shapes can diverge in the future (e.g. PUT becoming a partial update with nullable fields)").Two details that matter on real hardware
The TX frequency is latched per turnaround and written LSB-last. The Pico latches bytes 4..1 into a shadow register and commits the full 40-bit value only when byte 0 arrives, so a retune mid-burst would otherwise leave a torn frequency on the board.
REG_RF_INPUTSis sent as 0 and never inferred. Modes 1 and 2 switch the receiver off the HL2's antenna and onto the board's J9 input, and mode 2 additionally drivesGPIO03_INTTR— the HL2's own T/R relay — on receive. They are correct only when a separate receive antenna is wired into J9, which is what deskHPSDR'sif (alex_antenna != 0)gate expresses. Deriving the mode from PureSignal state looks tempting and is wrong: on a station using the board's J10 PureSignal input without a separate receive antenna it switches the receiver to an empty jack and clicks the T/R relay on receive. I know because I did it and had to be told by the operator that his receive had dropped.N2ADR's own guidance is that the register "is 0, 1 or 2, and is a user setting", so exposing it as an explicit operator control is the right follow-up — I have deliberately left that out of this PR rather than guess at the UI.
Testing
Built clean, and in daily use on a Hermes-Lite 2 with a real IO board fitted:
fcode2band()curve, plus the latch-coherence property above.The scheduler was written to be unit-testable but this repo has no test project, so those assertions live outside the tree. Happy to add them in whatever form suits you.
Operator note
There is no UI control for this — the console is not part of this repository — so the setting is reached through the engine's own API:
One-time: the setting persists and is re-pushed on every reconnect.
ioBoardPresentthen reports whether the board actually answered —truedetected,falseenabled but silent (suspect seating or power),nullno radio connected yet.BoardCapabilities.HasHl2IoBoardis included precisely so a capability-gated control can replace that curl whenever you want one.Since this was opened, the board has also run alongside an HL2+ AK4951 companion board with no interference — they sit on different I2C buses (IO board
0x1Don I2C-2, codec0x12on I2C-1), matching N2ADR's documented position that the two coexist.Notes
I appreciate this repository is published as the complete corresponding source rather than as a development target, so this may be easier for you to port than to merge — please treat it as a patch offered in whatever form is most useful. The frontend checkbox is the remaining piece and is yours to add;
HasHl2IoBoardis there to gate it.Authorship disclosure: this patch was written by Claude (Anthropic's AI) working with the station owner, who provided the Hermes-Lite 2 and IO board hardware, ran the on-air testing, and caught the
REG_RF_INPUTSbug described above. Please review it with that in mind — the reasoning and provenance comments are the AI's, and the hardware verification is the operator's.🤖 Generated with Claude Code