Second Claude Code Review for mag-usb.¶
Focused on the scaling and configuration issues that have been observed in live data.
[Note: This is a follow-up to the first Claude code review, which focused on code quality and maintainability. This review focuses on correctness and operator experience.]
[Note: A second review using the Gemini model was performed to examine standalone code quality and maintainability, without the context of the live data issues. That review is documented separately.]
[Note: This represents feedback within a team consisting of three humans: Mike, Rob, and Dave. Dave is the author of the code under review, and Rob is a third team member working with Mike. Claude, Gemini, and their friends are AI Assistants. Claude is providing objective feedback. Dave is also being assisted by one of more AI tools.]
[Note: Mike, Rob, and Claude are working together to integrate this program (mag-usb) into a larger system (sigmond), that may also incorporate other components (mag-recorder, magrec, a global installation process, etc.) when used with sigmond.]
[Note: mag-usb must also retain the ability to be configured independently.]
[Note: The tone is collaborative and constructive, with a focus on improving the code and the operator experience.]
PR-1: drop the spurious /NOSRegValue factor in POLL-mode scaling¶
src/main.c:606-608:
xyz[0] = (((double)p->XYZ[0] / p->NOSRegValue) / p->x_gain) * 1000;
The PNI formula Dave himself pasted into src/magdata.c:142-148 is µT = raw / (0.3671·CC + 1.5), no NOS term. With NOSRegValue defaulting to 60, every reported value is off by ~60× — that’s exactly what live data on my desk shows (-210..+412 where Earth-field axis components should be 10,000–30,000 nT). NOS only belongs in the scaling for CMM-with-averaging, where the chip has been programmed to sum N samples internally; in single-shot POLL mode the chip already returns per-sample values.
Suggested fix: drop the /NOSRegValue. If Dave wants to keep an averaging path for a future CMM mode, gate it on p->samplingMode == CMM and only when the chip’s NOS register has actually been written.
PR-2: actually program the chip’s CC and NOS registers (or remove the knobs)¶
src/magdata.c:166-180 (setCycleCountRegs)
has every i2c_write commented out.
Same in setNOSReg at magdata.c:106-114:
the whole body is dead. Net effect: cc_x/y/z and NOSRegValue in config are host-side ghosts. The chip stays at power-on defaults (CC=200/axis), and any operator who edits cc_x = 400 in tools/config.toml thinks they’ve doubled their sensitivity but actually they’ve just made the host divide by a bigger number. This is upstream of the magnitude bug in PR-1 — both fixes are needed.
Chosen approach:
Restore the writes using the Pololu I²C path (the rest of the I/O in this binary already does this), so config takes effect. Cleaner long-term.
Make note in documentation and comment in code: “[Path not taken]: remove the host-side CC/NOS knobs entirely, hard-code gain from chip defaults, and document that. Smaller patch, less flexible.”
Either is fine; the current limbo is what’s broken.
PR-3: fix the example tools/config.toml¶
tools/config.toml ships cc_x = cc_y = cc_z = 400
This combined with PR-2 silently mis-scales. After PR-2 lands this becomes a correctness fix or a delete-the-knob change, depending on which path Dave picks.
mag-usb layering PRs (so mag-recorder can fully own operator config)
The pattern I’d like to align with: mag-recorder is the program the operator configures; mag-usb is the sensor driver mag-recorder drives. Operator never sees /etc/mag-usb/anything. All knobs live in /etc/mag-recorder/mag-recorder-config.toml.
PR-4: accept config-file path on the CLI¶
src/main.c currently auto-discovers /etc/mag-usb/config.toml then ./config.toml, with no way to point it elsewhere.
Make it accept -f
(or –config if Dave wants to move to getopt_long) — note -c is already taken by cycle-count. When -f is given, skip the auto-discovery entirely so the binary’s behavior is fully determined by argv.
mag-recorder will then render a private TOML at /etc/mag-recorder/mag-usb-driver.toml and pass it explicitly.
Optional sweetener: also accept -A
PR-5: make -P (show settings) print chip state, not host state¶
Today -P prints the host’s intended values. After PR-2 those should match the chip, but a real -P that reads back the CC/NOS/TMRC registers would catch any future regression where the writes silently stop landing. Small but high-leverage.
What this lets mag-recorder do (no Dave work, just queued on our side)
Render /etc/mag-recorder/mag-usb-driver.toml as an install step — sourced from [mag] keys in mag-recorder-config.toml. Operator never edits the driver TOML.
Patch supervisor.py:_mag_usb_source to construct argv = [binary, “-O”, device, “-f”, “/etc/mag-recorder/mag-usb-driver.toml”]. The currently-dangling mag_usb_config key in mag-recorder-config.toml finally connects to something.
Add an install step that drops install/99-PololuI2C.rules from upstream mag-usb into /etc/udev/rules.d/ and runs udevadm trigger. This is the “predictable device regardless of USB port” piece — /dev/ttyMAG0 becomes the stable symlink mag-recorder’s config already points at by default.
Add magrec to dialout in deploy (already done in the service file’s SupplementaryGroups=dialout; confirm the user-creation step also sets it).
Strengthen mag-recorder validate to fail loudly when the configured [mag].i2c_address doesn’t match what mag-usb -M reports — so an operator with a different carrier-board strapping sees a clear validate error instead of a silent NACK at runtime. Once PR-1+2+4 land in wittend/mag-usb (and we cherry-pick / merge into our sigmond-integration branch):
PR-6: render /etc/mag-recorder/mag-usb-driver.toml from mag-recorder-config.toml¶
Render /etc/mag-recorder/mag-usb-driver.toml as an install step — sourced from [mag] keys in mag-recorder-config.toml. Operator never edits the driver TOML.
Patch supervisor.py:_mag_usb_source to construct argv = [binary, “-O”, device, “-f”, “/etc/mag-recorder/mag-usb-driver.toml”]. The currently-dangling mag_usb_config key in mag-recorder-config.toml finally connects to something.
Add an install step that drops install/99-PololuI2C.rules from upstream mag-usb into /etc/udev/rules.d/ and runs udevadm trigger. This is the “predictable device regardless of USB port” piece — /dev/ttyMAG0 becomes the stable symlink mag-recorder’s config already points at by default.
Add magrec to dialout in deploy (already done in the service file’s SupplementaryGroups=dialout; confirm the user-creation step also sets it).
Strengthen mag-recorder validate to fail loudly when the configured [mag].i2c_address doesn’t match what mag-usb -M reports — so an operator with a different carrier-board strapping sees a clear validate error instead of a silent NACK at runtime.
Drop /etc/mag-usb/ from documentation entirely — README/PROVENANCE shouldn’t mention it once PR-4 lands.
Suggested order for your conversation with Dave¶
PR-1 (60× scaling — operator-visible correctness; this is the headline)
PR-2 (CC/NOS writes — explains why the scaling looks the way it does, and unblocks per-axis tuning)
PR-3 (example TOML — trivial follow-on)
PR-4 (config-file CLI flag — layering; this is the one that lets mag-recorder act like a proper sigmond client)
PR-5 (-P reads chip state — diagnostic hygiene)