STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
Config.cpp
Go to the documentation of this file.
1#include "config/Config.hpp"
2
3#include <algorithm>
4#include <iostream>
5#include <stdexcept>
6#include <toml++/toml.hpp>
7
8namespace fsw {
9namespace config {
10
11namespace detail {
12// T7Config.cpp — kept out of T7Config.hpp so the header does not drag toml++ into every includer.
13std::vector<T7Board> parse_t7_boards(const toml::table& root, std::vector<std::string>& errors);
14} // namespace detail
15
16namespace {
17
18using CNV = toml::node_view<const toml::node>;
19
20std::string s_or(CNV n, const std::string& d) {
21 return n.value<std::string>().value_or(d);
22}
23int64_t i_or(CNV n, int64_t d) {
24 return n.value<int64_t>().value_or(d);
25}
26double d_or(CNV n, double d) {
27 return n.value<double>().value_or(d);
28}
29bool b_or(CNV n, bool d) {
30 return n.value<bool>().value_or(d);
31}
32
33// Verbatim from the old LoadActiveBoards.cpp: log slot collisions (kind-scoped) and slot>8
34// overflow.
35void check_board_slots(std::map<ActiveBoardKind, std::map<uint8_t, std::string>>& slot_owner,
36 ActiveBoardKind kind, uint8_t slot, const std::string& section,
37 int board_id) {
38 auto& owners = slot_owner[kind];
39 auto prev = owners.find(slot);
40 if (prev != owners.end()) {
41 std::cerr << "[Config] ERROR: board slot " << static_cast<int>(slot) << " claimed by both ["
42 << prev->second << "] and [" << section
43 << "] - their channels will merge into one Elodin entity. Give them board_ids "
44 "that differ mod 10."
45 << std::endl;
46 } else {
47 owners.emplace(slot, section);
48 }
49 if (slot > 8) {
50 std::cerr << "[Config] ERROR: [" << section << "] board_id " << board_id << " maps to slot "
51 << static_cast<int>(slot)
52 << ", but the packet id low byte only encodes slots 1-8. Its channels will "
53 "collide with another board."
54 << std::endl;
55 }
56}
57
58} // namespace
59
60ActiveBoardKind kind_of(const std::string& type_str) {
61 if (type_str == "PT")
63 if (type_str == "TC")
65 if (type_str == "RTD")
67 if (type_str == "LC")
69 if (type_str == "ENCODER")
71 if (type_str == "ENVIRONMENTAL")
73 if (type_str == "ACTUATOR")
75 if (type_str == "BALL_VALVE")
78}
79
80namespace {
81
82BoardConfig parse_board(const std::string& section, const toml::table& bt) {
84 b.section = section;
85 b.type = s_or(bt["type"], "");
86 b.ip = s_or(bt["ip"], "");
87 b.enabled = b_or(bt["enabled"], true);
88 const auto id_node = bt.contains("board_id") ? bt["board_id"] : bt["id"];
89 const auto id = i_or(id_node, -1);
90 if (b.enabled && b.type == "ENVIRONMENTAL" && (!id_node.is_integer() || id < 1 || id > 255)) {
91 throw std::invalid_argument("[Config] [" + section +
92 "] environmental board ID must be an integer from 1 to 255");
93 }
94 b.board_id =
95 static_cast<int>(b.type == "ENVIRONMENTAL" ? id : i_or(bt["board_id"], i_or(bt["id"], -1)));
96 b.num_actuators = static_cast<int>(i_or(bt["num_actuators"], 0));
97 b.send_port = static_cast<uint16_t>(i_or(bt["send_port"], 5005));
98 b.listen_port = static_cast<uint16_t>(i_or(bt["listen_port"], 5005));
99 b.voltage_reference = static_cast<int>(i_or(bt["voltage_reference"], 0));
100 b.necessary_for_abort = b_or(bt["necessary_for_abort"], false);
101 b.designated_survivor = b_or(bt["designated_survivor"], false);
102
103 // enable_serial_printing tolerates a bool or an int; clamp 0..3 (matches config_broadcast).
104 int esp = 0;
105 if (auto iv = bt["enable_serial_printing"].value<int64_t>())
106 esp = static_cast<int>(*iv);
107 else if (auto bv = bt["enable_serial_printing"].value<bool>())
108 esp = *bv ? 1 : 0;
109 b.enable_serial_printing = std::clamp(esp, 0, 3);
110
111 if (auto arr = bt["active_connectors"].as_array())
112 for (const auto& el : *arr)
113 if (auto iv = el.value<int64_t>())
114 b.active_connectors.push_back(static_cast<int>(*iv));
115
116 b.pt_type = s_or(bt["pt_type"], "");
117 b.adc_ref_voltage = d_or(bt["adc_ref_voltage"], 2.5);
118 b.has_hp_pt_keys = bt.contains("hp_pt_connectors") || bt.contains("hp_pt_full_scale_psi");
119 b.hp_pt_full_scale_psi = d_or(bt["hp_pt_full_scale_psi"], 5000.0);
120 b.hp_pt_sense_resistor_ohms = d_or(bt["hp_pt_sense_resistor_ohms"], 120.0);
121 return b;
122}
123
125 std::map<int, std::string> owners;
126 for (const auto& b : cfg.boards) {
127 if (!b.enabled || b.type != "ENVIRONMENTAL")
128 continue;
129 if (b.board_id < 1 || b.board_id > 255) {
130 throw std::invalid_argument(
131 "[Config] [" + b.section +
132 "] environmental board ID must be an integer from 1 to 255");
133 }
134 const auto [owner, inserted] = owners.emplace(b.board_id, b.section);
135 if (!inserted) {
136 throw std::invalid_argument("[Config] environmental board ID " +
137 std::to_string(b.board_id) + " is claimed by both [" +
138 owner->second + "] and [" + b.section + "]");
139 }
140 }
141}
142
143Config from_table(const toml::table& t) {
144 Config c;
145
146 c.network.bind_ip = s_or(t["network"]["bind_ip"], c.network.bind_ip);
147 c.network.sensor_port = static_cast<uint16_t>(i_or(t["network"]["sensor_port"], 5006));
149 static_cast<uint16_t>(i_or(t["network"]["actuator_cmd_port"], 5005));
150
151 c.database.host = s_or(t["database"]["host"], c.database.host);
152 c.database.port = static_cast<uint16_t>(i_or(t["database"]["port"], 2240));
153
155 static_cast<uint32_t>(i_or(t["server_heartbeat"]["interval_ms"], 1000));
157 static_cast<uint16_t>(i_or(t["server_heartbeat"]["broadcast_port"], 5005));
159 s_or(t["server_heartbeat"]["broadcast_ip"], c.server_heartbeat.broadcast_ip);
160
161 c.heartbeat_service.enabled = b_or(t["heartbeat_service"]["enabled"], false);
163 static_cast<uint32_t>(i_or(t["heartbeat_service"]["interval_ms"], 1000));
164 // broadcast_ip/port fall back to [server_heartbeat] when absent (matches the old heartbeat
165 // main).
167 s_or(t["heartbeat_service"]["broadcast_ip"],
168 s_or(t["server_heartbeat"]["broadcast_ip"], c.heartbeat_service.broadcast_ip));
170 static_cast<uint16_t>(i_or(t["heartbeat_service"]["broadcast_port"],
171 i_or(t["server_heartbeat"]["broadcast_port"], 5005)));
173 s_or(t["heartbeat_service"]["elodin_host"], c.heartbeat_service.elodin_host);
175 static_cast<uint16_t>(i_or(t["heartbeat_service"]["elodin_port"], 2240));
176
177 // Default: the bridge sends the heartbeat unless the standalone service is enabled.
179 b_or(t["server_heartbeat"]["send_from_daq_bridge"], !c.heartbeat_service.enabled);
180
181 c.logs.backend_udp_port = static_cast<uint16_t>(i_or(t["logs"]["backend_udp_port"], 8092));
182
183 {
184 const std::string m = s_or(t["time_sync"]["mode"], "board_clock");
188 static_cast<uint32_t>(i_or(t["time_sync"]["window_seconds"], 10));
190 static_cast<uint32_t>(i_or(t["time_sync"]["max_plausible_gap_s"], 60));
192 static_cast<uint32_t>(i_or(t["time_sync"]["max_batch_age_s"], 5));
194 static_cast<uint32_t>(i_or(t["time_sync"]["resync_threshold_ms"], 1000));
196 static_cast<uint32_t>(i_or(t["time_sync"]["log_interval_s"], 10));
197 }
198
200 static_cast<uint32_t>(i_or(t["config_broadcast_service"]["interval_ms"], 1000));
201
202 if (auto boards = t["boards"].as_table())
203 for (auto&& [k, v] : *boards)
204 if (auto bt = v.as_table())
205 c.boards.push_back(parse_board("boards." + std::string(k.str()), *bt));
206
207 if (auto ar = t["actuator_roles"].as_table())
208 for (auto&& [k, v] : *ar)
209 if (auto arr = v.as_array(); arr && arr->size() >= 3) {
211 r.kind = (*arr)[0].value<std::string>().value_or("NC");
212 r.is_no = (r.kind == "NO" || r.kind == "no");
213 r.channel = static_cast<int>((*arr)[1].value<int64_t>().value_or(0));
214 r.board_id = static_cast<int>((*arr)[2].value<int64_t>().value_or(0));
215 // Optional 4th element: which controller_service PWM output this actuator serves
216 // ("pwm_fuel" / "pwm_ox"). Absent means the sequencer owns it.
217 if (arr->size() >= 4)
218 r.controller_role = (*arr)[3].value<std::string>().value_or("");
219 c.actuator_roles[std::string(k.str())] = r;
220 }
221
222 // A role on a BALL_VALVE board has no polarity: 1 is open and 0 is closed on the wire. Force
223 // pass-through so a stray "NO" cannot invert it (config validation rejects "NO" there too).
224 for (auto& [name, r] : c.actuator_roles) {
225 for (const auto& b : c.boards) {
226 if (b.board_id != r.board_id || kind_of(b.type) != ActiveBoardKind::BALL_VALVE)
227 continue;
228 r.ball_valve = true;
229 if (r.is_no)
230 std::cerr << "[Config] WARNING: actuator role '" << name
231 << "' is on ball valve board " << r.board_id
232 << " and is marked NO; ball valves have no NO/NC, treating it as "
233 "pass-through (1 = open)."
234 << std::endl;
235 r.is_no = false;
236 break;
237 }
238 }
239
240 c.fire.state = s_or(t["fire"]["state"], "");
241 c.fire.expiry_target = s_or(t["fire"]["expiry_target"], "");
242 // Durations: [fire] is the home; fall back to the legacy [controller_service].fire_* keys.
243 c.fire.duration_ms = static_cast<uint32_t>(
244 i_or(t["fire"]["duration_ms"], i_or(t["controller_service"]["fire_duration_ms"], 6000)));
245 c.fire.extended_ms = static_cast<uint32_t>(
246 i_or(t["fire"]["extended_ms"], i_or(t["controller_service"]["fire_extended_ms"], 10000)));
247
248 // [flow] — the characterization hold. No `state` key by design: which state this is comes from
249 // the is_flow flag on a [[states]] entry, so a rename cannot orphan it the way [fire].state
250 // can.
251 c.flow.return_target = s_or(t["flow"]["return_target"], "");
252 c.flow.duration_ms = static_cast<uint32_t>(i_or(t["flow"]["duration_ms"], 0));
253 c.flow.max_ms = static_cast<uint32_t>(i_or(t["flow"]["max_ms"], 0));
254 c.fire.gate_actuator = s_or(t["fire"]["gate_actuator"], "");
255 c.flow.gate_actuator = s_or(t["flow"]["gate_actuator"], "");
256
257 c.controller_service.port = static_cast<uint16_t>(i_or(t["controller_service"]["port"], 9999));
258 c.controller_service.host = s_or(t["controller_service"]["host"], c.controller_service.host);
260 static_cast<uint32_t>(i_or(t["controller_service"]["fire_duration_ms"], 6000));
262 static_cast<uint32_t>(i_or(t["controller_service"]["fire_extended_ms"], 10000));
264 b_or(t["controller_service"]["sequencer_owns_valves"], false);
265
266 c.controller.pwm_frequency_hz = d_or(t["controller"]["pwm_frequency_hz"], 10.0);
268 static_cast<uint32_t>(i_or(t["controller"]["pwm_duration_ms"], 10000));
269 c.controller.controller_loop_hz = d_or(t["controller"]["controller_loop_hz"], 10.0);
270 c.controller.P_copv_min_pa = d_or(t["controller"]["P_copv_min_pa"], 0.0);
271 c.controller.fallback_fuel_duty_cycle = d_or(t["controller"]["fallback_fuel_duty_cycle"], 0.0);
272 c.controller.fallback_ox_duty_cycle = d_or(t["controller"]["fallback_ox_duty_cycle"], 0.0);
273 c.controller.lut_path = s_or(t["controller"]["lut_path"], "");
274 c.controller.thrust_curve_path = s_or(t["controller"]["thrust_curve_path"], "");
275
276 c.actuator_service.port = static_cast<uint16_t>(i_or(t["actuator_service"]["port"], 9998));
278 s_or(t["actuator_service"]["bind_address"], c.actuator_service.bind_address);
279
280 c.calibration.tc_adc_ref_voltage = d_or(t["calibration"]["tc"]["adc_ref_voltage"], 2.5);
281 c.calibration.rtd_adc_ref_voltage = d_or(t["calibration"]["rtd"]["adc_ref_voltage"], 2.5);
282 c.calibration.rtd_excitation_ua = d_or(t["calibration"]["rtd"]["excitation_ua"], 1000.0);
283 c.calibration.rtd_r0_ohm = d_or(t["calibration"]["rtd"]["r0_ohm"], 1000.0);
285 d_or(t["calibration"]["lc"]["sensitivity_mv_per_v"], 2.0);
286 c.calibration.lc_pga_gain = d_or(t["calibration"]["lc"]["pga_gain"], 32.0);
287 c.calibration.lc_full_scale_value = d_or(t["calibration"]["lc"]["full_scale_value"], 300.0);
288
289 // [calibration.<type>] coefficient sources (json_dir + first of csv_paths) — previously
290 // ignored.
291 auto first_csv = [&](const char* type) -> std::string {
292 if (auto arr = t["calibration"][type]["csv_paths"].as_array())
293 for (const auto& el : *arr)
294 if (auto sv = el.value<std::string>())
295 return *sv;
296 return std::string();
297 };
298 c.calibration.tc_json_dir = s_or(t["calibration"]["tc"]["json_dir"], "");
299 c.calibration.tc_csv_path = first_csv("tc");
300 c.calibration.rtd_json_dir = s_or(t["calibration"]["rtd"]["json_dir"], "");
301 c.calibration.rtd_csv_path = first_csv("rtd");
302 c.calibration.lc_json_dir = s_or(t["calibration"]["lc"]["json_dir"], "");
303 c.calibration.lc_csv_path = first_csv("lc");
304
306 s_or(t["state_machine"]["transitions_csv"], c.state_machine.transitions_csv);
308 s_or(t["state_machine"]["actuator_csv"], c.state_machine.actuator_csv);
310 s_or(t["state_machine"]["actuator_delay_csv"], c.state_machine.actuator_delay_csv);
311
312 if (auto states = t["states"].as_array())
313 for (const auto& el : *states)
314 if (auto st = el.as_table()) {
315 StateDef s;
316 s.id = static_cast<int>((*st)["id"].value<int64_t>().value_or(-1));
317 s.name = (*st)["name"].value<std::string>().value_or("");
318 s.is_abort = (*st)["is_abort"].value<bool>().value_or(false);
319 s.is_boot = (*st)["is_boot"].value<bool>().value_or(false);
320 s.is_flow = (*st)["is_flow"].value<bool>().value_or(false);
321 s.script_file = (*st)["script_file"].value<std::string>().value_or("");
323 static_cast<uint32_t>((*st)["script_timeout_ms"].value<int64_t>().value_or(0));
325 (*st)["script_return_target"].value<std::string>().value_or("");
327 (*st)["script_timeout_target"].value<std::string>().value_or("");
328 c.states.push_back(s);
329 }
330
331 // All [sensor_roles*] top-level tables -> section -> {role name -> channel}.
332 for (auto&& [k, v] : t) {
333 const std::string key(k.str());
334 if (key.rfind("sensor_roles", 0) == 0)
335 if (auto rt = v.as_table()) {
336 auto& m = c.sensor_roles[key];
337 for (auto&& [rk, rv] : *rt)
338 if (auto iv = rv.value<int64_t>())
339 m[std::string(rk.str())] = static_cast<int>(*iv);
340 }
341 }
342
343 if (auto ap = t["abort_pts"].as_table())
344 for (auto&& [k, v] : *ap) {
345 if (auto dv = v.value<double>())
346 c.abort_pts[std::string(k.str())] = *dv;
347 else if (auto iv = v.value<int64_t>())
348 c.abort_pts[std::string(k.str())] = static_cast<double>(*iv);
349 }
350
351 // All [calibration_model*] top-level tables -> section -> {role name -> model string}.
352 for (auto&& [k, v] : t) {
353 const std::string key(k.str());
354 if (key.rfind("calibration_model", 0) == 0)
355 if (auto rt = v.as_table()) {
356 auto& m = c.calibration_models[key];
357 for (auto&& [rk, rv] : *rt)
358 if (auto sv = rv.value<std::string>())
359 m[std::string(rk.str())] = *sv;
360 }
361 }
362
363 // Per-sensor physics params: [calibration_full_scale*] and [calibration_sense_resistor*] as
364 // role -> double sections (accept int or double).
365 for (auto&& [k, v] : t) {
366 const std::string key(k.str());
367 std::map<std::string, std::map<std::string, double>>* target = nullptr;
368 if (key.rfind("calibration_full_scale", 0) == 0)
370 else if (key.rfind("calibration_sense_resistor", 0) == 0)
372 else if (key.rfind("calibration_sensitivity", 0) == 0)
374 else if (key.rfind("calibration_pga", 0) == 0)
376 if (target)
377 if (auto rt = v.as_table()) {
378 auto& m = (*target)[key];
379 for (auto&& [rk, rv] : *rt) {
380 if (auto dv = rv.value<double>())
381 m[std::string(rk.str())] = *dv;
382 else if (auto iv = rv.value<int64_t>())
383 m[std::string(rk.str())] = static_cast<double>(*iv);
384 }
385 }
386 }
387
390 return c;
391}
392
393} // namespace
394
395const std::map<std::string, int>* Config::sensor_roles_for(const std::string& board_key) const {
396 auto it = sensor_roles.find(board_key);
397 if (it != sensor_roles.end() && !it->second.empty())
398 return &it->second;
399 it = sensor_roles.find("sensor_roles"); // legacy fallback (only if non-empty)
400 if (it != sensor_roles.end() && !it->second.empty())
401 return &it->second;
402 return nullptr;
403}
404
405const std::map<std::string, std::string>* Config::calibration_model_for(
406 const std::string& section_key) const {
407 auto it = calibration_models.find(section_key);
408 if (it != calibration_models.end() && !it->second.empty())
409 return &it->second;
410 return nullptr;
411}
412
413const std::map<std::string, double>* Config::full_scale_for(const std::string& section_key) const {
414 auto it = calibration_full_scale.find(section_key);
415 if (it != calibration_full_scale.end() && !it->second.empty())
416 return &it->second;
417 return nullptr;
418}
419
420const std::map<std::string, double>* Config::sense_resistor_for(
421 const std::string& section_key) const {
422 auto it = calibration_sense_resistor.find(section_key);
423 if (it != calibration_sense_resistor.end() && !it->second.empty())
424 return &it->second;
425 return nullptr;
426}
427
428const std::map<std::string, double>* Config::sensitivity_for(const std::string& section_key) const {
429 auto it = calibration_sensitivity.find(section_key);
430 if (it != calibration_sensitivity.end() && !it->second.empty())
431 return &it->second;
432 return nullptr;
433}
434
435const std::map<std::string, double>* Config::pga_gain_for(const std::string& section_key) const {
436 auto it = calibration_pga_gain.find(section_key);
437 if (it != calibration_pga_gain.end() && !it->second.empty())
438 return &it->second;
439 return nullptr;
440}
441
442Config load(const std::string& path) {
443 try {
444 toml::table tbl = toml::parse_file(path);
445 return from_table(tbl);
446 } catch (const toml::parse_error& e) {
447 std::cerr << "[Config] TOML parse error in " << path << ": " << e.description()
448 << std::endl;
449 return Config{};
450 } catch (const std::invalid_argument&) {
451 throw; // Invalid board identities must not become an empty/default configuration.
452 } catch (const std::exception& e) {
453 std::cerr << "[Config] Failed to read " << path << ": " << e.what() << std::endl;
454 return Config{};
455 }
456}
457
458Config load_from_string(const std::string& text) {
459 try {
460 toml::table tbl = toml::parse(text);
461 return from_table(tbl);
462 } catch (const toml::parse_error& e) {
463 std::cerr << "[Config] TOML parse error (string): " << e.description() << std::endl;
464 return Config{};
465 }
466}
467
468std::map<ActiveBoardKind, std::vector<elodin::BoardChannels>> active_boards(const Config& cfg) {
469 validate_environmental_boards(cfg);
470 std::map<ActiveBoardKind, std::vector<elodin::BoardChannels>> result;
471 std::map<ActiveBoardKind, std::map<uint8_t, std::string>> slot_owner;
472 for (const auto& b : cfg.boards) {
473 if (b.type.empty() || !b.enabled || b.board_id < 0)
474 continue;
475 ActiveBoardKind bt = kind_of(b.type);
476 if (bt == ActiveBoardKind::UNKNOWN)
477 continue;
478 const uint8_t slot = b.slot();
479 // Environmental tables use the full board ID, not a channel-slot block.
481 check_board_slots(slot_owner, bt, slot, b.section, b.board_id);
482
484 bc.board_id = static_cast<uint8_t>(b.board_id);
485 bc.board_number = slot;
486 // active_connectors is the only statement of which channels exist; an empty list
487 // means none. This used to fall back to 1..num_sensors, a config-only field with
488 // no counterpart on the wire or in firmware.
489 for (int ch : b.active_connectors)
490 bc.channels.push_back(static_cast<uint8_t>(ch));
491 result[bt].push_back(std::move(bc));
492 }
493 return result;
494}
495
496std::map<uint8_t, PtBoardConfig> pt_boards(const Config& cfg) {
497 std::map<uint8_t, PtBoardConfig> result;
498 bool warned_legacy = false;
499 for (const auto& b : cfg.boards) {
500 if (b.type != "PT" || !b.enabled || b.board_id < 0)
501 continue;
503 p.board_id = static_cast<uint8_t>(b.board_id);
504 p.board_number = b.slot();
505
506 if (!b.pt_type.empty()) {
507 if (b.pt_type == "4-20 mA absolute") {
509 } else if (b.pt_type == "0-5V ratiometric") {
511 } else if (b.pt_type == "0-5V absolute") {
512 std::cerr << "[Config] ERROR: [" << b.section << "] pt_type \"" << b.pt_type
513 << "\" is not supported by the PT firmware." << std::endl;
514 p.interface = PtInterface::ABSOLUTE_0_5V;
515 } else {
516 std::cerr << "[Config] ERROR: [" << b.section << "] unknown pt_type \"" << b.pt_type
517 << "\"; treating as 0-5V ratiometric." << std::endl;
518 }
519 } else if (b.has_hp_pt_keys) {
521 if (!warned_legacy) {
522 warned_legacy = true;
523 std::cerr << "[Config] DEPRECATED: [" << b.section
524 << "] has no pt_type; inferring \"4-20 mA absolute\" from its hp_pt_* "
525 "keys. Add pt_type to the board."
526 << std::endl;
527 }
528 }
529
530 p.full_scale_psi = b.hp_pt_full_scale_psi;
531 p.sense_resistor_ohms = b.hp_pt_sense_resistor_ohms;
532 p.adc_ref_voltage = b.adc_ref_voltage;
533 result[p.board_number] = p;
534 }
535 return result;
536}
537
538} // namespace config
539} // namespace fsw
static osThreadId_t volatile owner
Definition board_uart9.c:14
static volatile uint32_t errors
Definition board_uart9.c:13
static uint8_t board_id
Definition main.cpp:30
long target
Definition main.cpp:38
int32_t p
Definition flight_e2e.c:78
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
json_pointer result
Definition json.hpp:14064
detail namespace with internal helper functions
Definition json.hpp:249
BoardConfig parse_board(const std::string &section, const toml::table &bt)
Definition Config.cpp:82
toml::node_view< const toml::node > CNV
Definition Config.cpp:18
void check_board_slots(std::map< ActiveBoardKind, std::map< uint8_t, std::string > > &slot_owner, ActiveBoardKind kind, uint8_t slot, const std::string &section, int board_id)
Definition Config.cpp:35
void validate_environmental_boards(const Config &cfg)
Definition Config.cpp:124
Config from_table(const toml::table &t)
Definition Config.cpp:143
std::string s_or(CNV n, const std::string &d)
Definition Config.cpp:20
std::vector< T7Board > parse_t7_boards(const toml::table &root, std::vector< std::string > &errors)
Definition T7Config.cpp:138
std::map< uint8_t, PtBoardConfig > pt_boards(const Config &cfg)
Derive PT board 4-20 mA parameters keyed by Elodin slot (mirrors old load_pt_boards()).
Definition Config.cpp:496
std::map< ActiveBoardKind, std::vector< elodin::BoardChannels > > active_boards(const Config &cfg)
Derive the active-board table (enabled boards -> Elodin BoardChannels), with the same slot-collision ...
Definition Config.cpp:468
ActiveBoardKind
Board kinds from [boards.
ActiveBoardKind kind_of(const std::string &type_str)
[boards.
Definition Config.cpp:60
Config load_from_string(const std::string &text)
Parse config from an in-memory TOML string (SequencerService / ActuatorCommander hold it so).
Definition Config.cpp:458
@ CURRENT_LOOP_4_20MA
4-20 mA absolute: supply-independent loop across a shunt resistor.
@ ABSOLUTE_0_5V
0-5V absolute. Not implemented in firmware; see load_pt_boards().
@ RATIOMETRIC_0_5V
0-5V ratiometric: excitation is the reference, ratio cancels in HW.
Config load(const std::string &path)
Parse config.toml.
Definition Config.cpp:442
Motorized ball valve position: raw ADC counts -> % open.
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
The whole parsed config.toml, restricted to what C++ consumes.
Definition Config.hpp:264
ActuatorServiceConfig actuator_service
Definition Config.hpp:283
ServerHeartbeatConfig server_heartbeat
Definition Config.hpp:267
std::map< std::string, std::map< std::string, double > > calibration_sense_resistor
Definition Config.hpp:298
FireConfig fire
Definition Config.hpp:279
DatabaseConfig database
Definition Config.hpp:266
std::map< std::string, std::map< std::string, double > > calibration_sensitivity
Definition Config.hpp:301
const std::map< std::string, double > * sense_resistor_for(const std::string &section_key) const
[calibration_sense_resistor_<board>] role->Ω map, or nullptr when absent/empty.
Definition Config.cpp:420
ControllerConfig controller
Definition Config.hpp:282
std::vector< BoardConfig > boards
Definition Config.hpp:272
std::vector< T7Board > t7_boards
Definition Config.hpp:276
FlowConfig flow
Definition Config.hpp:280
const std::map< std::string, double > * full_scale_for(const std::string &section_key) const
[calibration_full_scale_<board>] role->PSI map, or nullptr when absent/empty.
Definition Config.cpp:413
std::map< std::string, std::map< std::string, double > > calibration_full_scale
Definition Config.hpp:297
std::vector< std::string > t7_errors
Definition Config.hpp:277
CalibrationConfig calibration
Definition Config.hpp:284
fsw::time::TimeSyncConfig time_sync
Definition Config.hpp:270
const std::map< std::string, std::string > * calibration_model_for(const std::string &section_key) const
[calibration_model_<board>] role->model map, or nullptr when absent/empty.
Definition Config.cpp:405
StateMachinePaths state_machine
Definition Config.hpp:285
HeartbeatServiceConfig heartbeat_service
Definition Config.hpp:268
std::map< std::string, double > abort_pts
Definition Config.hpp:289
const std::map< std::string, int > * sensor_roles_for(const std::string &board_key) const
[sensor_roles_<board>] with a legacy [sensor_roles] fallback.
Definition Config.cpp:395
std::map< std::string, ActuatorRole > actuator_roles
Definition Config.hpp:278
std::vector< StateDef > states
Definition Config.hpp:286
std::map< std::string, std::map< std::string, std::string > > calibration_models
Definition Config.hpp:293
const std::map< std::string, double > * sensitivity_for(const std::string &section_key) const
[calibration_sensitivity_<board>] role->mV/V map, or nullptr when absent/empty.
Definition Config.cpp:428
const std::map< std::string, double > * pga_gain_for(const std::string &section_key) const
[calibration_pga_<board>] role->gain map, or nullptr when absent/empty.
Definition Config.cpp:435
NetworkConfig network
Definition Config.hpp:265
std::map< std::string, std::map< std::string, double > > calibration_pga_gain
Definition Config.hpp:302
ControllerServiceConfig controller_service
Definition Config.hpp:281
uint32_t config_broadcast_interval_ms
Definition Config.hpp:271
LogsConfig logs
Definition Config.hpp:269
std::map< std::string, std::map< std::string, int > > sensor_roles
Definition Config.hpp:288
bool sequencer_owns_valves
Who drives the valves during the burn.
Definition Config.hpp:69
std::string gate_actuator
The valve the burn is timed around, if the fire state staggers its actuators.
Definition Config.hpp:122
std::string expiry_target
Definition Config.hpp:110
std::string gate_actuator
The actuator the hold is timed around — the one whose opening actually starts the flow.
Definition Config.hpp:146
std::string return_target
Definition Config.hpp:134
uint16_t backend_udp_port
Definition Config.hpp:52
Per-board 4-20 mA conversion parameters, keyed by Elodin slot (board_number).
std::string script_timeout_target
Where the state lands when script_timeout_ms expires.
Definition Config.hpp:198
uint32_t script_timeout_ms
Wall-clock ceiling for the whole script, in milliseconds.
Definition Config.hpp:187
std::string script_file
Script filename inside the profile's scripts/ directory, e.g.
Definition Config.hpp:182
std::string script_return_target
Where the state lands when the script runs off its end without calling transition_to.
Definition Config.hpp:192
bool is_flow
Marks the state the characterization hold drives.
Definition Config.hpp:162
Per-board channel info for board-namespaced entity registration.
std::vector< uint8_t > channels
static boot_report_t r
static std::string ch(int ain, const std::string &kind, const std::string &extra="", const std::string &board="lj")
Definition test_t7.cpp:98