Repository navigation
NA500: match the mock to real hardware; fix NA500 streaming, timing and physio - #20
Merged
Merged
Conversation
cboulay
force-pushed
the
cboulay/na500-conformance
branch
from
November 1, 2026 20:08
3ce0744 to
191e29a
Compare
Measured against a real NA500 (fw 0.4.9) with a new wire-level conformance recorder, scripts/ampserver_conformance.py. Its recording of the device is mock/conformance/na500_fw0.4.9.json, and the observed protocol is written up in docs/NA500_AmpServer.md. Mock: --amp-type NA500 now runs a dedicated session-based server (NA500Server) that matches the device on every compared behavior: one client per port per session, session ends with the command connection, one command per TCP segment, unknown commands re-run the previous one, missing/double replies, unbalanced replies, notification timing, 8-sample blocks, counter restart and leftover packets after cmd_Start, the ~8000 samples/s ceiling at 16 kHz, and sample loss when a client stops reading. --net-code None emulates a unit with no net. NA400 and other models are unchanged. App (all NA500-gated; the NA400 path is untouched): - Discard the leftover packets the NA500 sends after cmd_Start; they carried stale samples into LSL and a false "Packet(s) Dropped". - Skip the Physio16 status wait: the NA500 never sends the notification, and leaving its data port unread for 5 s lost ~4 s of samples. - Set the amp clock with cmd_SetAmpTime at power-on, as Net Station does. - Stop offering 16000 Hz: the NA500 delivers only ~8000 samples/s there. Also bumps liblsl to v1.18.0.b6 and documents finding the NA500 by mDNS.
On the NA500 the EEG reads negative full scale until cmd_SetBufferedReference 1, every cmd_Stop switches it off again, and switch commands sent within ~20 ms of the cmd_SetPower 1 reply are silently lost. The app sent neither, so its NA500 EEG was pinned at -375000 uV. It now waits 200 ms after power-on and enables the buffered reference before each cmd_Start (NA500 only; skipped in impedance mode). The mock and the conformance recorder model and check all three behaviors; the recorder also waits briefly after connecting so the NA500 has accepted the notification port before the first command.
…loss Measured with scripts/audio_latency_test against DAC-stamped tone markers, the NA500's digitization-to-client latency is ~1.0 ms + 1.1 sample periods (5.4 ms at 250 Hz, 2.1 ms at 1 kHz, 1.3 ms at 4 kHz), well under the NA400's ~5 ms, so the 4.5 ms system delay back-dated NA500 samples by 1-3 ms. systemdelayms is now optional. Unset, it defaults to 0.8 ms + one sample period on the NA500 (a little under the measured latency at every rate; verified +0.33 ms at 1 kHz) and to 4.5 ms on every other model, so NA400 behavior is unchanged. The shipped config no longer pins it. The NA500 also delivers at most ~7995 packets/s: 8 kHz silently loses ~4 samples/s (not only 16 kHz), so its timeline drifts. Documented, and the mock's delivery ceiling now matches.
The NA500 delivers at most ~7995 samples/s and silently drops the rest, so at 8000 Hz it loses ~4 samples/s and the sample timeline drifts by milliseconds over a run (measured with the audio latency test). 4000 Hz is now the NA500's highest rate; the NA400's rates are unchanged.
The NA500 sends no Physio16 status notification, so the app streamed no physio at all. Its 16 physio inputs are always in the data stream (pib1_Data[0-7], pib2_Data[0-7]), so the app now always streams them as PIB1-16 on the NA500. Physio gains persist on the amplifier across sessions (a gain set by another client stays), so before each cmd_Start the app pins all 16 to 12, the power-on default, and scales them like the EEG. Measured with the audio latency test: PIB1 timing matches EEG within 0.1 ms at 250-4000 Hz, so no physio realignment is needed (filter delays are 0 on the NA500). The NA500 mock zeroes the unused physio slots, as the device does. All changes are NA500-only; the NA400 physio path is unchanged.
The NA500's DB9 inputs read 0 until cmd_SetDinEnable, and the app never sent it, so its _DIN stream reported all 16 (active-low) inputs as active. The app now enables them, and puts the BNC input in event mode, before each start. Each rising edge on BNC in arrives as a 1 ms event in bit 2 of the packet's first reserved byte (verified with a BNC out -> BNC in loopback at 1 and 4 kHz); the app reports it in the _DIN stream as bit 16 (0x10000). On the NA400 that bit is never set, so its DIN events are unchanged. The NA500 mock emulates the BNC input from its test-jig pulse, and resets the BNC mode at the end of a session as the device does.
The NA400 impedance sequence did not work on the NA500. Changes, all NA500-only unless noted: - Drive amplitude 1023 (its DAC is 10-bit), which gives ~5000 uV p-p on every channel; use Net Station's fixed 5000 uV ideal signal, since the app's self-calibration caught a switching transient (5300-5900 uV). - Switch the oscillator on after setting amplitude and frequency: the NA500 latches them at switch-on, so an app run that followed a reset drove nothing. - Measure the reference in the NA500's COM/REF measurement mode (cmd_SetCOMAndRefForMeasurement, ~1.2 s settles, then re-enable the DINs), as Net Station does. - Restore the acquisition state explicitly when impedance mode ends: cmd_DefaultAcquisitionState does nothing on the NA500. - Apply the NA500 front-end/trigger/physio setup in impedance mode too. Verified on a GES 500 with a test jig that shorts all inputs: 256/256 channels ~0 kOhm, reference ~0.5 kOhm, and normal acquisition afterwards. Mock: the NA500 mock models the 5000 uV drive, the oscillator latch and the COM/REF mode. A long-standing mock bug ignored per-channel drive/10K switches while "all" was set, so per-channel impedance never registered (this also affects the NA400 mock, which now measures per channel).
cboulay
force-pushed
the
cboulay/na500-conformance
branch
from
November 2, 2026 07:01
6ea5fb0 to
a37067f
Compare
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.
Stacked on #18. Everything here was measured against a real Net Amps 500 / GES 500 (firmware 0.4.9). All app changes are gated on the NA500; the NA400 code path is unchanged.
Conformance recorder and NA500 mock
scripts/ampserver_conformance.pyrecords how an AmpServer behaves on the wire, and diffs two recordings. The recording of the real NA500 ismock/conformance/na500_fw0.4.9.json.MockAmpServer --amp-type NA500now runs a session-based server (NA500Server). It matches the device on all 152 compared behaviors, including:cmd_Start;docs/NA500_AmpServer.mddocuments the protocol as observed.App fixes (NA500)
cmd_Stopturns it off, and switch commands sent within ~20 ms of power-on are lost. The app now waits 200 ms after power-on and enables the buffered reference before each start.cmd_Start. They had carried stale samples into LSL and caused a false "Packet(s) Dropped".cmd_SetAmpTimeat power-on, as Net Station does.systemdelaymsis now optional, with a per-model default.Latency (audio tone test, relative to DAC-stamped tone markers)
There is no physio-vs-EEG skew on the NA500, and no FPGA filter delay. So decimated and native modes stream identically.
Also
masteralready has via Update liblsl to v1.18.0.b6 #19.NA500-256ch-<serial>.local).Triggers and impedance (NA500)
cmd_SetDinEnable; without it they read as all 16 active._DINstream as bit 16. Verified with a BNC out → BNC in loopback at 1 and 4 kHz.Not covered