28namespace fs = std::filesystem;
35static void check(
bool ok,
const std::string& what) {
36 std::cout << (
ok ?
" ok " :
" FAIL ") << what << std::endl;
48 "open_valve(VENT_VALVE)\n"
50 "close_valve(VENT_VALVE)\n"
51 "transition_to(PRESS_STANDBY)\n";
65 const std::string dyn = f.
on_fire_state ?
"Fire" :
"Dyn State";
68 std::ofstream t(
g_dir /
"state_transitions.csv");
69 t <<
",Idle,Armed,Press Standby,Fire,Dyn State\n";
70 t <<
"Idle,1,1,1,0,1\n";
71 t <<
"Armed,1,1,1,0,1\n";
72 t <<
"Press Standby,1,1,1,1,1\n";
73 t <<
"Fire,0,1,0,1,0\n";
76 t <<
"Dyn State,1,0,1,0,1\n";
79 std::ofstream
a(
g_dir /
"state_machine_actuators.csv");
81 a <<
",Idle,Armed,Press Standby,Fire\n";
82 a <<
"Vent Valve,CLOSE,CLOSE,CLOSE,CLOSE\n";
83 a <<
"Main Valve,CLOSE,CLOSE,CLOSE,OPEN\n";
89 a <<
",Idle,Armed,Press Standby,Fire,Dyn State\n";
90 a <<
"Vent Valve,CLOSE,CLOSE,CLOSE,CLOSE,CLOSE\n";
91 a <<
"Main Valve,CLOSE,CLOSE,CLOSE,OPEN,OPEN\n";
95 std::ofstream d(
g_dir /
"state_machine_actuator_delays.csv");
97 d <<
",Idle,Armed,Press Standby,Fire\n";
98 d <<
"Vent Valve,0,0,0,0\n";
99 d <<
"Main Valve,0,0,0,0\n";
101 d <<
",Idle,Armed,Press Standby,Fire,Dyn State\n";
102 d <<
"Vent Valve,0,0,0,0,0\n";
103 d <<
"Main Valve,0,0,0,0,0\n";
107 fs::create_directories(
g_dir /
"scripts");
108 fs::remove(
g_dir /
"scripts" /
"good.script");
112 if (f.
script_file.find(
'/') == std::string::npos &&
120 std::ofstream c(cfg_path);
121 c <<
"[network]\nactuator_cmd_port = " <<
kActPort <<
"\n\n"
122 <<
"[database]\nport = 2\n\n"
125 <<
"[controller_service]\nhost = \"192.0.2.1\"\nport = 9999\n\n"
126 <<
"[boards.act_board]\ntype = \"ACTUATOR\"\nip = \"127.0.0.1\"\nboard_id = 12\n"
127 "enabled = true\nnum_actuators = 10\n\n"
128 <<
"[boards.pt_board]\ntype = \"PT\"\nip = \"127.0.0.1\"\nboard_id = 21\nenabled = true\n\n"
129 <<
"[sensor_roles_pt_board]\n\"Tank Pressure\" = 1\n\"Bottle Pressure\" = 2\n\n"
130 <<
"[actuator_roles]\n\"Vent Valve\" = [\"NC\", 1, 12]\n"
131 "\"Main Valve\" = [\"NC\", 2, 12]\n\n"
132 <<
"[state_machine]\n"
133 <<
"transitions_csv = \"" << (
g_dir /
"state_transitions.csv").
string() <<
"\"\n"
134 <<
"actuator_csv = \"" << (
g_dir /
"state_machine_actuators.csv").
string() <<
"\"\n"
135 <<
"actuator_delay_csv = \"" << (
g_dir /
"state_machine_actuator_delays.csv").
string()
137 <<
"[fire]\nstate = \"Fire\"\nexpiry_target = \"Armed\"\nduration_ms = 800\n\n"
138 <<
"[[states]]\nid = 1\nname = \"Idle\"\nis_boot = true\n\n"
139 <<
"[[states]]\nid = 2\nname = \"Armed\"\n\n"
140 <<
"[[states]]\nid = 3\nname = \"Press Standby\"\n\n";
142 auto scriptKeys = [&](std::ofstream&
o) {
146 o <<
"script_timeout_ms = " << f.
timeout_ms <<
"\n";
153 c <<
"[[states]]\nid = 12\nname = \"Fire\"\n";
158 c <<
"[[states]]\nid = 13\nname = \"Dyn State\"\n";
162 c <<
"is_flow = true\n";
164 c <<
"is_abort = true\n";
170 c <<
"[flow]\nreturn_target = \"Idle\"\nduration_ms = 500\n\n";
189 if (!svc.
init(cfg_path)) {
190 a.reason =
"init failed";
204 std::this_thread::sleep_for(std::chrono::milliseconds(250));
220 const bool ok = !
a.accepted &&
a.landed == State::IDLE && !
a.moved;
223 std::cout <<
" accepted=" <<
a.accepted
225 <<
" reason=\"" <<
a.reason <<
"\"" << std::endl;
230 std::cout <<
"=== Dynamic-state refusals ===" << std::endl;
232 g_dir = fs::temp_directory_path() /
"daq_script_states_test";
233 fs::remove_all(
g_dir);
234 fs::create_directories(
g_dir);
244 check(
a.accepted &&
a.landed == dyn,
"a valid dynamic state is enterable");
246 "entering it applies its actuator column (the baseline the script layers on)");
248 std::cout <<
" reason=\"" <<
a.reason <<
"\"" << std::endl;
255 refuses(f,
"no script_timeout_ms — refused, nothing moved");
260 refuses(f,
"script_timeout_ms above the ceiling — refused, nothing moved");
267 refuses(f,
"no script_return_target — refused, nothing moved");
272 refuses(f,
"no script_timeout_target — refused, nothing moved");
277 refuses(f,
"script_return_target names no known state — refused, nothing moved");
282 refuses(f,
"script_timeout_target is the state itself — refused, nothing moved");
288 refuses(f,
"script_return_target is not an allowed transition — refused, nothing moved");
295 refuses(f,
"script file missing (did not deploy) — refused, nothing moved");
300 refuses(f,
"script_file with path separators — refused, nothing moved");
305 refuses(f,
"script_file without the .script suffix — refused, nothing moved");
310 refuses(f,
"empty script — refused, nothing moved");
316 f.
script_body =
"open_valve(VENT_VALVE)\ndelay(0.5\n";
317 refuses(f,
"script does not parse — refused, nothing moved");
321 f.
script_body =
"open_valve(VNET_VALVE)\ndelay(0.5)\n";
322 refuses(f,
"script names an unknown valve — refused, nothing moved");
326 f.
script_body =
"x = pressure(NO_SUCH_SENSOR)\ndelay(x)\n";
327 refuses(f,
"script names an unknown sensor — refused, nothing moved");
334 "if pressure(TANK_PRESSURE) > 100:\n"
335 " transition_to(ARMED)\n"
337 refuses(f,
"a nested transition_to the matrix forbids — refused, nothing moved");
341 f.
script_body =
"while pressure(TANK_PRESSURE) < 100:\n open_valve(VENT_VALVE)\n";
342 refuses(f,
"a loop that never delays — refused, nothing moved");
349 refuses(f,
"is_flow and a script on one state — refused, nothing moved");
354 refuses(f,
"an abort state with a script — refused, nothing moved");
361 check(!
a.accepted && !
a.moved,
"the fire state with a script — refused, nothing moved");
368 refuses(f,
"no actuator column for the dynamic state — refused, nothing moved");
374 f.
script_body =
"open_valve(VNET_VALVE)\ndelay(0.5)\n";
380 check(report.find(
"REFUSED") != std::string::npos,
"SCRIPTS reports the state as REFUSED");
381 check(report.find(
"VNET_VALVE") != std::string::npos,
382 "the reason names the offending slug, so the panel can show it on hover");
383 check(report.find(
"line 1") != std::string::npos,
"the reason carries the line number");
384 check(report.find(
"END") != std::string::npos,
"the report is terminated");
390 const std::string report = svc.scriptStatusReport();
391 check(report.find(
":OK:") != std::string::npos,
"SCRIPTS reports a good script as OK");
394 fs::remove_all(
g_dir);
395 std::cout << (
g_failures == 0 ?
"\nAll dynamic-state refusal checks passed.\n"
396 :
"\nFAILURES: " + std::to_string(
g_failures) +
"\n");
static State fromName(const std::string &name)
Translate a CSV name (e.g.
static std::string name(State s)
Human-readable name for a State (e.g.
Collects UDP actuator packets, folding the 3x retransmit of one batch into a single burst.
std::vector< Burst > bursts() const
Top-level sequencer service.
bool init(const std::string &config_path="config/config.toml")
Load config, CSVs, connect to Elodin.
State currentState() const
bool transitionTo(const std::string &state_name)
Request a state transition by name (e.g.
std::string scriptStatusReport() const
What this process made of its dynamic states at startup, as newline-terminated text.
Parses state_transitions.csv and validates state transitions.
*brief create JSON pointer *sa reference_tokens const string_t & a
long long firstCommand(const std::vector< Burst > &bursts, uint8_t ch, uint8_t hw, long long after_ms=-1)
When ch was first commanded to hw at or after after_ms.
constexpr uint32_t kMaxScriptTimeoutMs
Hard ceiling on script_timeout_ms, independent of per-state config.
State
System state IDs — must match the TypeScript SystemState enum in web-gui/shared/types....
Bring a service up on cfg, try to enter Dyn State, and report what happened.
Everything a case may vary.
bool on_fire_state
Put the script on the Fire state instead of Dyn State.
std::string return_target
std::string timeout_target
bool drop_actuator_column
Omit the dynamic state's column from state_machine_actuators.csv.
std::string string() const
static constexpr uint16_t kActPort
static std::string writeConfig(const Cfg &f)
static void refuses(const Cfg &f, const std::string &what)
A refusal case: the state must not be enterable AND no board may hear anything.
static void check(bool ok, const std::string &what)
static Attempt attemptEntry(const std::string &cfg_path, const char *target="Dyn State")