|
STAR
Cal STAR — firmware, DAQ server, and engine design
|
How configuration is stored, edited, and applied on the DAQ server.
The rule: an edit is a draft. Drafts apply when a session starts. There are two deliberate exceptions, both listed below. If you are reading this because something you changed did not take effect, the answer is almost certainly "start a session".
| What it is | Who writes it | Tracked in git | |
|---|---|---|---|
| config/profiles/<name>/config.toml | The draft. What the config editor reads and writes. | The editor, via POST /api/config | Yes |
| config/config.toml | The deployed artifact. What the C++ services actually read. Generated. | deployActiveProfile() only | No (gitignored) |
| config/.active_profile | One line naming the active profile. Machine-specific. | Profile switch | No (gitignored) |
The three state-machine CSVs (state_machine_actuators.csv, state_machine_actuator_delays.csv, state_transitions.csv) live beside the profile's config.toml and deploy with it. They are profile-owned; the copies in config/ are generated the same way.
Editing config/config.toml by hand is pointless — the next deploy overwrites it. Edit the profile.
deployActiveProfile() (backend/src/routes/config-profiles.ts) copies the active profile's config.toml and all its CSVs into config/. It is all-or-nothing: every target is snapshotted first and rolled back together if the new config does not parse, because a half-applied deploy — new roles, old state table — is worse than no deploy.
It runs in two situations:
During a session every write path degrades to a draft:
The editor shows a freeze banner and a count of un-applied changes while this is in effect.
The session-gated pipeline units — sensor-elodin, sensor-daq, sensor-calibration, sensor-controller, sensor-actuator — are stopped and started by the session. Each reads config.toml once at startup and holds it for the life of the run. There is no reload verb and no file watching; the sequencer's RELOAD_CONFIG was removed precisely because a hot-reload path in the safety-critical services is machinery that can only surprise you.
service-controller.ts also snapshots the config each run was started with next to its Elodin database (<dbDir>.toml), so a recorded run can always be read back with the config it was produced under.
The backend is different: it is always-on and never restarts, so it must notice a deploy. It does that with a cache invalidated in deployActiveProfile() (readDeployedConfig() in routes/config.ts), plus a CONFIG_UPDATED broadcast that tells browsers to refetch. Same rule, different mechanism, because the constraint is different.
config_broadcast_service is always-on, not session-gated, and re-reads config.toml on every broadcast cycle (~1 Hz). Board-level settings therefore reach hardware without a session:
This is intentional. It is also worth understanding rather than assuming: the session freeze is the only thing that currently stops someone changing an abort trip mid-run, and that is a property of the freeze, not a decision the broadcaster makes.
One correction to an earlier version of this page, which said the boards act on [abort_pts] live. They do not, today. abort_pt_locations is only read in the actuator board's NoConnectionAbort and PTAbort states (Actuator_Hotfire/src/main.cpp:541-571), and the only entry to that chain is heartbeat-loss detection at :838, gated on ENABLE_ALL_STATE_TRANSITIONS — which is false in hotfire_config.h:33-34 with no override anywhere in firmware/. The thresholds reach the boards; nothing consumes them. See docs/IMPROVEMENTS.md.
The related endpoint POST /api/board-log-mode rides this exception — it surgically edits one field so verbosity can be raised during the misbehaviour it is meant to diagnose. It writes both config.toml (so the board gets it now) and the active profile (so the next deploy does not silently revert it).
Calibration is not in config.toml at all. The live store is scripts/calibration/calibrations/cubic_calibration.json, written only by calibration_service, with named snapshots under profiles/ and a .active pointer.
Capture, zero, clear and whole-profile load all apply immediately, mid-session, by design — the capture-and-verify loop is the run. Loading a calibration profile publishes command 7 over Elodin and the service re-reads its store live.
The config.toml keys calibration does read (calibration_model_<board>, calibration_full_scale_<board>, calibration_sense_resistor_<board>, [adc], [calibration.*]) are ordinary boot-time config and follow the normal rule.
The DAQ shares the apps box with the Docker stack and the site LAN, so "the interface the kernel picks" is no longer the same thing as "the board LAN". Every board-facing socket therefore binds its local address to the NIC holding the board subnet, resolved once at startup by fsw::net::resolveDaqBindAddress() (lib/include/net/DaqInterface.hpp). Each service logs the result in one line — board traffic pinned to eth2 192.168.2.20 (auto).
Resolution order:
Note the mechanism is a source-address bind, not SO_BINDTODEVICE — the services run as unprivileged systemd --user units and SO_BINDTODEVICE needs CAP_NET_RAW.
Broadcast destinations are subnet-directed ([server_heartbeat].broadcast_ip, shipped as 192.168.2.255) rather than the limited 255.255.255.255, for the same reason: a limited broadcast is the one destination that does not resolve to a single route. The sequencer's ABORT broadcast reads that key too, so a rig that moves the broadcast address moves the abort with it.
[discovery].network_interface is not this knob — BoardDiscovery prints it and never applies it to a socket.
These are runtime commands, not configuration, and are unaffected by any of the above: state transitions, manual actuator overrides, debug mode, extend fire, countdown target, session start/stop/extend, and control unlock. They go over the WebSocket as SEND_COMMAND and are gated by operator arming, not by the config freeze.
Simulated sessions do not deploy the profile. The sim pipeline reads config/sim_config.toml, regenerated at session start from the frozen config_base.toml with 192.168.2. rewritten to 127.0.0. So profile edits are invisible to sim runs.
This is deliberate rather than an oversight: the point of a sim run is that it behaves identically on every box, which a per-box active profile would destroy. The cost is that "config applies at session start" is not true on the sim path, so the session page says so when Simulated is selected. If you are testing config behaviour, test it on a live-mode session or you will conclude the model is broken.
Session control is off unless SESSION_SERVICE_MODE=systemd, which is set only in deploy/systemd/sensor-backend.service. On the tmux dev stack and on laptops the mode is off, sessionActive is permanently false, and every save deploys immediately — the freeze is inert there. Do not verify freeze behaviour on the dev stack and conclude it works.