|
STAR
Cal STAR — firmware, DAQ server, and engine design
|
Motorized ball valve position: raw ADC counts -> % open. More...
Namespaces | |
| namespace | calibration |
| Output smoothing for calibrated PT pressure, per sensor uid (ESP32 and LabJack T7 alike). | |
| namespace | config |
| namespace | control |
| namespace | elodin |
| namespace | daq_wire |
| namespace | fsw |
| namespace | nav |
| namespace | net |
| namespace | routing |
| namespace | script |
| namespace | streams |
| namespace | t7 |
| namespace | time |
| namespace | util |
Motorized ball valve position: raw ADC counts -> % open.
The state-script language: a small Python-looking language an operator writes in the config UI, which the sequencer runs on entry to a dynamic state.
The bridge between config and the script language.
Which table subscriptions the DB refused, so a reader can ask again for exactly those.
The valve reports its position as a 4-20 mA loop (4 mA closed, 20 mA open) across a 100 ohm shunt into the ball valve board's 12-bit ESP32 ADC (firmware/Hotfire_Code/BallValve_Hotfire, analogRead at 11 dB, 3.3 V full scale), so 0.4-2.0 V on the pin. The board sends raw counts and the conversion happens here, like every other board.
Elodin DB refuses a VTableStream for a table nobody has registered yet, replying ErrorResponse (msg id [0xE0, 0x1D]) with the request id of the subscription it refused. Services start together, so a reader routinely subscribes a few hundred ms before the publisher registers: on 2026-11-05 calibration_service subscribed to the ball valve's raw tables before daq_bridge registered them, and published no ball valve position for the whole session, while every other table, registered a moment earlier, streamed normally.
The blunt alternatives are both wrong. Waiting for "no packets at all" never fires when only some tables were refused. Re-asking for every table that has not delivered yet (PressureFeed) is fine for a reader that keeps only the latest value, but a table the DB ACCEPTED and that is merely silent (a board that is off) would gain a second stream per retry, and calibration would then process every sample several times. So: tag each subscription with its own request id, and retry only what was refused.
Pure logic, no socket: the caller sends with ElodinClient::subscribe_table(hi, lo, id).
Kept out of StateScript.hpp on purpose: the language itself is pure — strings in, diagnostics out, no config, no I/O, no clock — which is what lets it be tested exhaustively and linked into a hermetic checker binary. This header is where it learns what a real rig declares.
── What this is, and what it deliberately is not ─────────────────────────────────────────────
A script never becomes a C++ function. It is parsed once, at sequencer startup, into the flat arena below, and an interpreter walks that arena. There is no code generation, no dlopen, no eval, and no compiler anywhere near the stand.
That is not merely the easier option, it is the correct one. Because the program is data being walked, the runner can check a stop flag, a wall deadline and an iteration budget between any two statements — which is what lets an abort preempt a running script at a known boundary and what makes a runaway loop bounded. Compiled code offers no such seam.
── Names are slugs, resolved positionally ───────────────────────────────────────────────────
open_valve(GSE_HIGH_PRESS_CONTROL), not open_valve("GSE High Press Control"). The slug is the canonical config name uppercased with whitespace collapsed to underscores.
The namespace comes from the ARGUMENT SLOT, never from the name itself: the argument of open_valve/close_valve is an actuator role, of pressure() a PT sensor role, of transition_to() a state. An identifier anywhere else is a variable.
That is not a style choice. On the shipped server profile FUEL_PRESS and FUEL_VENT are each BOTH a valve and a state, and on digital-twin FUEL_UPSTREAM is both a valve and a PT sensor. A flat namespace would be ambiguous against config that exists today. Positional resolution also means collisions WITHIN a namespace are an error while collisions ACROSS namespaces are fine and expected — a blanket collision rule would refuse this rig's live config.
── Everything checkable at parse time is checked at parse time ──────────────────────────────
A script that cannot be proven bounded, or that names something config does not define, is refused at load. The state then never becomes enterable, so the failure is an unavailable button rather than a valve opening on a script that was going to fail anyway.