STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
DatabaseConfig.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <iostream>
5#include <string>
6
7#include "db.hpp" // utl/db.hpp — VTable builder, Msg, postcard encoding
8
9using namespace vtable;
10using namespace vtable::builder;
11
12namespace fsw {
13namespace elodin {
14
15// ── Helper: encode a db.hpp message and send via ElodinClient ──────────────
16template <typename T>
17static bool send_msg(ElodinClient& client, T msg) {
18 auto buf = Msg(msg).encode_vec();
19 if (buf.empty()) {
20 std::cerr << "[DatabaseConfig] Failed to encode message" << std::endl;
21 return false;
22 }
23 return client.send_msg({0, 0}, buf); // packet_id unused — already in buf
24}
25
27 const std::vector<BoardChannels>& boards) {
28 bool ok = true;
29 for (const auto& board : boards) {
30 if (board.board_id == 0 ||
31 std::find(board.channels.begin(), board.channels.end(), 1) == board.channels.end())
32 continue;
33 const std::string entity = "ENV" + std::to_string(board.board_id);
34 const std::string prefix = entity + ".";
35 auto vt = builder::vtable({
36 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
37 raw_field(8, 4, schema(PrimType::F32(), {}, component(prefix + "temperature_c"))),
38 raw_field(12, 4, schema(PrimType::U32(), {}, component(prefix + "pressure_pa"))),
39 raw_field(16, 4, schema(PrimType::F32(), {}, component(prefix + "humidity_rh"))),
40 raw_field(20, 4, schema(PrimType::U32(), {}, component(prefix + "sample_ts_ms"))),
41 });
42 if (!send_msg(client, VTableMsg{.id = {0x25, board.board_id}, .vtable = vt})) {
43 ok = false;
44 continue;
45 }
46 for (const char* field :
47 {"timestamp_ns", "temperature_c", "pressure_pa", "humidity_rh", "sample_ts_ms"})
48 ok = send_msg(client, set_component_name(prefix + field)) && ok;
49 ok = send_msg(client, set_entity_name((0x25u << 8) | board.board_id, entity)) && ok;
50 }
51 return ok;
52}
53
54// ── Helper: register one raw-sensor VTable for a single channel ────────────
55// All raw sensor messages share the same 21-byte layout:
56// u64 timestamp_ns | u8 channel_id | u8[3] pad | u32 raw_adc | u32 sample_ts | u8 status
58 ElodinClient& client,
59 uint8_t type_hi, // high byte of packet_id (0x20=PT, 0x21=TC, 0x22=RTD, 0x23=LC, 0x30=ACT)
60 uint8_t channel_id, // low byte of packet_id (1-based channel)
61 uint64_t entity_id, // unique entity id for set_entity_name
62 const std::string& entity_name, // human-readable name
63 const std::string& adc_field_name // "raw_adc_counts" or "raw_resistance_counts"
64) {
65 std::string prefix = entity_name + ".";
66
67 auto vt = builder::vtable({
68 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
69 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "channel_id"))),
70 // 3 bytes padding at offset 9 — skip
71 raw_field(12, 4, schema(PrimType::U32(), {}, component(prefix + adc_field_name))),
72 raw_field(16, 4, schema(PrimType::U32(), {}, component(prefix + "sample_ts_ms"))),
73 raw_field(20, 1, schema(PrimType::U8(), {}, component(prefix + "status"))),
74 });
75
76 if (!send_msg(client, VTableMsg{.id = {type_hi, channel_id}, .vtable = vt})) {
77 std::cerr << "[DatabaseConfig] ❌ VTable failed: " << entity_name << std::endl;
78 return false;
79 }
80
81 // Name the per-field components
82 send_msg(client, set_component_name(prefix + "timestamp_ns"));
83 send_msg(client, set_component_name(prefix + "channel_id"));
84 send_msg(client, set_component_name(prefix + adc_field_name));
85 send_msg(client, set_component_name(prefix + "sample_ts_ms"));
86 send_msg(client, set_component_name(prefix + "status"));
87
88 // Name the entity
89 send_msg(client, set_entity_name(entity_id, entity_name));
90
91 return true;
92}
93
94// ── Helper: register one calibrated-sensor VTable for a single channel ─────
95// All calibrated messages share the same 21-byte layout:
96// u64 timestamp_ns | u8 channel_id | u8[3] pad | f32 calibrated_value | u32 raw_counts | u8
97// cal_status
99 ElodinClient& client,
100 uint8_t type_hi, // high byte (0x20=PT, 0x21=TC, 0x22=RTD, 0x23=LC)
101 uint8_t pkt_lo, // full low byte for packet ID (already includes board offset + 0x10)
102 uint64_t entity_id, const std::string& entity_name,
103 const std::string& cal_field_name, // "pressure_psi", "temperature_c", "force_lbf"
104 const std::string& raw_field_name // "raw_adc", "raw_resistance"
105) {
106 std::string prefix = entity_name + ".";
107
108 auto vt = builder::vtable({
109 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
110 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "channel_id"))),
111 // 3 bytes padding at offset 9 — skip
112 raw_field(12, 4, schema(PrimType::F32(), {}, component(prefix + cal_field_name))),
113 raw_field(16, 4, schema(PrimType::U32(), {}, component(prefix + raw_field_name))),
114 raw_field(20, 1, schema(PrimType::U8(), {}, component(prefix + "cal_status"))),
115 });
116
117 if (!send_msg(client, VTableMsg{.id = {type_hi, pkt_lo}, .vtable = vt})) {
118 std::cerr << "[DatabaseConfig] ❌ Calibrated VTable failed: " << entity_name << std::endl;
119 return false;
120 }
121
122 send_msg(client, set_component_name(prefix + "timestamp_ns"));
123 send_msg(client, set_component_name(prefix + "channel_id"));
124 send_msg(client, set_component_name(prefix + cal_field_name));
125 send_msg(client, set_component_name(prefix + raw_field_name));
126 send_msg(client, set_component_name(prefix + "cal_status"));
127 send_msg(client, set_entity_name(entity_id, entity_name));
128
129 return true;
130}
131
132// ── Helper: register actuator state VTable (0=closed, 1=open) ───────────────
133// Layout: u64 timestamp_ns | u8 channel_id | u8 actuator_state (10 bytes)
134static bool register_actuator_state_vtable(ElodinClient& client, uint8_t type_hi,
135 uint8_t channel_id, uint64_t entity_id,
136 const std::string& entity_name) {
137 std::string prefix = entity_name + ".";
138
139 auto vt = builder::vtable({
140 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
141 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "channel_id"))),
142 raw_field(9, 1, schema(PrimType::U8(), {}, component(prefix + "actuator_state"))),
143 });
144
145 if (!send_msg(client, VTableMsg{.id = {type_hi, channel_id}, .vtable = vt})) {
146 std::cerr << "[DatabaseConfig] ❌ Actuator state VTable failed: " << entity_name
147 << std::endl;
148 return false;
149 }
150
151 send_msg(client, set_component_name(prefix + "timestamp_ns"));
152 send_msg(client, set_component_name(prefix + "channel_id"));
153 send_msg(client, set_component_name(prefix + "actuator_state"));
154 send_msg(client, set_entity_name(entity_id, entity_name));
155
156 return true;
157}
158
159// ════════════════════════════════════════════════════════════════════════════
160// PUBLIC API — RAW VTables only (for daq_bridge)
161// ════════════════════════════════════════════════════════════════════════════
162
163namespace {
164/** Append (type_hi, (board_number-1)*0x20 + offset + ch) for every active channel. */
165void appendTables(std::vector<std::pair<uint8_t, uint8_t>>& out,
166 const std::vector<BoardChannels>& boards, uint8_t type_hi, uint8_t offset) {
167 for (const auto& board : boards)
168 for (uint8_t ch : board.channels)
169 out.push_back(
170 {type_hi, static_cast<uint8_t>((board.board_number - 1) * 0x20 + offset + ch)});
171}
172} // namespace
173
174std::vector<std::pair<uint8_t, uint8_t>> raw_sensor_tables(
175 const std::vector<BoardChannels>& pt_boards, const std::vector<BoardChannels>& act_boards,
176 const std::vector<BoardChannels>& tc_boards, const std::vector<BoardChannels>& rtd_boards,
177 const std::vector<BoardChannels>& lc_boards, const std::vector<BoardChannels>& enc_boards) {
178 std::vector<std::pair<uint8_t, uint8_t>> t;
179 appendTables(t, pt_boards, 0x20, 0x00);
180 appendTables(t, act_boards, 0x30, 0x00);
181 appendTables(t, tc_boards, 0x21, 0x00);
182 appendTables(t, rtd_boards, 0x22, 0x00);
183 appendTables(t, lc_boards, 0x23, 0x00);
184 appendTables(t, enc_boards, 0x24, 0x00);
185 return t;
186}
187
188std::vector<std::pair<uint8_t, uint8_t>> calibrated_sensor_tables(
189 const std::vector<BoardChannels>& pt_boards, const std::vector<BoardChannels>& tc_boards,
190 const std::vector<BoardChannels>& rtd_boards, const std::vector<BoardChannels>& lc_boards,
191 const std::vector<BoardChannels>& enc_boards, const std::vector<BoardChannels>& act_boards) {
192 std::vector<std::pair<uint8_t, uint8_t>> t;
193 appendTables(t, pt_boards, 0x20, 0x10);
194 appendTables(t, tc_boards, 0x21, 0x10);
195 appendTables(t, rtd_boards, 0x22, 0x10);
196 appendTables(t, lc_boards, 0x23, 0x10);
197 appendTables(t, enc_boards, 0x24, 0x10);
198 appendTables(t, act_boards, 0x31, 0x10);
199 return t;
200}
201
202namespace {
203// One T7 VTable: 21 bytes, u64 @0 | u8 @8 | pad | 4-byte value @12 | 4 bytes @16 | u8 @20.
204bool register_t7_vtable(ElodinClient& client, uint8_t lo, const std::string& entity,
205 const char* value_field, const char* u32_field, PrimType u32_type,
206 const char* status_field) {
207 const std::string prefix = entity + ".";
208 auto vt = builder::vtable({
209 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
210 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "channel_id"))),
211 raw_field(12, 4, schema(PrimType::F32(), {}, component(prefix + value_field))),
212 raw_field(16, 4, schema(u32_type, {}, component(prefix + u32_field))),
213 raw_field(20, 1, schema(PrimType::U8(), {}, component(prefix + status_field))),
214 });
215 if (!send_msg(client, VTableMsg{.id = {0x26, lo}, .vtable = vt})) {
216 std::cerr << "[DatabaseConfig] T7 VTable failed: " << entity << std::endl;
217 return false;
218 }
219 bool ok = true;
220 for (const char* field : {"timestamp_ns", "channel_id", value_field, u32_field, status_field})
221 ok = send_msg(client, set_component_name(prefix + field)) && ok;
222 return send_msg(client, set_entity_name((static_cast<uint64_t>(0x26) << 8) | lo, entity)) && ok;
223}
224} // namespace
225
227 const std::vector<uint8_t>& ains, bool cjc) {
228 bool ok = true;
229 const uint8_t base = static_cast<uint8_t>((slot - 1) * 0x20);
230 const std::string n = std::to_string(slot);
231 for (uint8_t ain : ains)
232 ok = register_t7_vtable(client, static_cast<uint8_t>(base + ain),
233 "T7" + n + ".AIN" + std::to_string(ain), "volts", "sample_ts_ms",
234 PrimType::U32(), "status") &&
235 ok;
236 if (cjc)
237 ok = register_t7_vtable(client, static_cast<uint8_t>(base + 0x0F), "T7" + n + ".CJC",
238 "temperature_c", "sample_ts_ms", PrimType::U32(), "status") &&
239 ok;
240 return ok;
241}
242
244 const std::vector<T7Table>& tables) {
245 bool ok = true;
246 const uint8_t base = static_cast<uint8_t>((slot - 1) * 0x20 + 0x10);
247 const std::string n = std::to_string(slot);
248 for (const auto& t : tables)
249 ok = register_t7_vtable(client, static_cast<uint8_t>(base + t.ain),
250 "T7" + n + "_Cal.AIN" + std::to_string(t.ain), t.cal_field.c_str(),
251 "raw_uv", PrimType::I32(), "cal_status") &&
252 ok;
253 return ok;
254}
255
257 const std::vector<BoardChannels>& pt_boards,
258 const std::vector<BoardChannels>& act_boards,
259 const std::vector<BoardChannels>& tc_boards,
260 const std::vector<BoardChannels>& rtd_boards,
261 const std::vector<BoardChannels>& lc_boards,
262 const std::vector<BoardChannels>& enc_boards) {
263 std::cout << "[DatabaseConfig] Registering RAW VTables (board-namespaced)..." << std::endl;
264 int registered = 0;
265 int pt_count = 0, act_count = 0, tc_count = 0, rtd_count = 0, lc_count = 0, enc_count = 0;
266
267 for (const auto& board : pt_boards) {
268 for (uint8_t ch : board.channels) {
269 std::string entity =
270 "PT" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
271 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
272 uint64_t entity_id = (static_cast<uint64_t>(0x20) << 8) | lo;
273 if (register_raw_sensor_vtable(client, 0x20, lo, entity_id, entity, "raw_adc_counts")) {
274 registered++;
275 pt_count++;
276 }
277 }
278 }
279
280 for (const auto& board : act_boards) {
281 for (uint8_t ch : board.channels) {
282 std::string entity =
283 "ACT" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
284 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
285 uint64_t entity_id = (static_cast<uint64_t>(0x30) << 8) | lo;
286 if (register_raw_sensor_vtable(client, 0x30, lo, entity_id, entity, "raw_adc_counts")) {
287 registered++;
288 act_count++;
289 }
290 }
291 }
292
293 for (const auto& board : tc_boards) {
294 for (uint8_t ch : board.channels) {
295 std::string entity =
296 "TC" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
297 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
298 uint64_t entity_id = (static_cast<uint64_t>(0x21) << 8) | lo;
299 if (register_raw_sensor_vtable(client, 0x21, lo, entity_id, entity, "raw_adc_counts")) {
300 registered++;
301 tc_count++;
302 }
303 }
304 }
305
306 for (const auto& board : rtd_boards) {
307 for (uint8_t ch : board.channels) {
308 std::string entity =
309 "RTD" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
310 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
311 uint64_t entity_id = (static_cast<uint64_t>(0x22) << 8) | lo;
312 if (register_raw_sensor_vtable(client, 0x22, lo, entity_id, entity,
313 "raw_resistance_counts")) {
314 registered++;
315 rtd_count++;
316 }
317 }
318 }
319
320 for (const auto& board : lc_boards) {
321 for (uint8_t ch : board.channels) {
322 std::string entity =
323 "LC" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
324 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
325 uint64_t entity_id = (static_cast<uint64_t>(0x23) << 8) | lo;
326 if (register_raw_sensor_vtable(client, 0x23, lo, entity_id, entity, "raw_adc_counts")) {
327 registered++;
328 lc_count++;
329 }
330 }
331 }
332
333 for (const auto& board : enc_boards) {
334 for (uint8_t ch : board.channels) {
335 std::string entity =
336 "ENC" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
337 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
338 uint64_t entity_id = (static_cast<uint64_t>(0x24) << 8) | lo;
339 if (register_raw_sensor_vtable(client, 0x24, lo, entity_id, entity, "raw_angle")) {
340 registered++;
341 enc_count++;
342 }
343 }
344 }
345
346 std::cout << "[DatabaseConfig] ✅ Registered " << registered << " RAW VTables (" << pt_count
347 << " PT, " << act_count << " ACT, " << tc_count << " TC, " << rtd_count << " RTD, "
348 << lc_count << " LC, " << enc_count << " ENC)" << std::endl;
349 return registered > 0;
350}
351
352// ════════════════════════════════════════════════════════════════════════════
353// PUBLIC API — CALIBRATED VTables only (for calibration_service)
354// ════════════════════════════════════════════════════════════════════════════
355
357 const std::vector<BoardChannels>& pt_boards,
358 const std::vector<BoardChannels>& tc_boards,
359 const std::vector<BoardChannels>& rtd_boards,
360 const std::vector<BoardChannels>& lc_boards,
361 const std::vector<BoardChannels>& enc_boards,
362 const std::vector<BoardChannels>& act_boards) {
363 std::cout << "[DatabaseConfig] Registering CALIBRATED VTables..." << std::endl;
364 int registered = 0;
365
366 for (const auto& board : pt_boards) {
367 for (uint8_t ch : board.channels) {
368 std::string entity =
369 "PT" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
370 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
371 uint64_t entity_id = (static_cast<uint64_t>(0x20) << 8) | lo;
372 if (register_calibrated_vtable(client, 0x20, lo, entity_id, entity, "pressure_psi",
373 "raw_adc"))
374 registered++;
375 }
376 }
377
378 for (const auto& board : tc_boards) {
379 for (uint8_t ch : board.channels) {
380 std::string entity =
381 "TC" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
382 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
383 uint64_t entity_id = (static_cast<uint64_t>(0x21) << 8) | lo;
384 if (register_calibrated_vtable(client, 0x21, lo, entity_id, entity, "temperature_c",
385 "raw_adc"))
386 registered++;
387 }
388 }
389
390 for (const auto& board : rtd_boards) {
391 for (uint8_t ch : board.channels) {
392 std::string entity =
393 "RTD" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
394 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
395 uint64_t entity_id = (static_cast<uint64_t>(0x22) << 8) | lo;
396 if (register_calibrated_vtable(client, 0x22, lo, entity_id, entity, "temperature_c",
397 "raw_resistance"))
398 registered++;
399 }
400 }
401
402 for (const auto& board : lc_boards) {
403 for (uint8_t ch : board.channels) {
404 std::string entity =
405 "LC" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
406 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
407 uint64_t entity_id = (static_cast<uint64_t>(0x23) << 8) | lo;
408 if (register_calibrated_vtable(client, 0x23, lo, entity_id, entity, "force_kg",
409 "raw_adc"))
410 registered++;
411 }
412 }
413
414 for (const auto& board : enc_boards) {
415 for (uint8_t ch : board.channels) {
416 std::string entity =
417 "ENC" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
418 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
419 uint64_t entity_id = (static_cast<uint64_t>(0x24) << 8) | lo;
420 if (register_calibrated_vtable(client, 0x24, lo, entity_id, entity, "position_deg",
421 "raw_adc"))
422 registered++;
423 }
424 }
425
426 // Actuators use 0x31 for calibrated (separate type byte to avoid collision with raw 0x30)
427 for (const auto& board : act_boards) {
428 for (uint8_t ch : board.channels) {
429 std::string entity =
430 "ACT" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
431 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
432 uint64_t entity_id = (static_cast<uint64_t>(0x31) << 8) | lo;
433 if (register_calibrated_vtable(client, 0x31, lo, entity_id, entity, "current_a",
434 "raw_adc"))
435 registered++;
436 }
437 }
438
439 std::cout << "[DatabaseConfig] ✅ Registered " << registered << " CALIBRATED VTables"
440 << std::endl;
441 return registered > 0;
442}
443
444// ════════════════════════════════════════════════════════════════════════════
445// PUBLIC API — BOARD HEARTBEAT VTables
446// ════════════════════════════════════════════════════════════════════════════
447
449 const std::vector<uint8_t>& board_ids) {
450 std::cout << "[DatabaseConfig] Registering BOARD_HEARTBEAT VTables (" << board_ids.size()
451 << " boards)..." << std::endl;
452 int registered = 0;
453
454 // Board heartbeat layout (16 bytes, no padding required):
455 // u64 timestamp_ns (0,8) | u8 board_id (8) | u8 board_type (9)
456 // u8 engine_state (10) | u8 board_state (11) | u32 packet_ts_ms (12,4)
457 for (uint8_t board_id : board_ids) {
458 std::string entity = "BOARD.HB_" + std::to_string(board_id);
459 std::string prefix = entity + ".";
460
461 auto vt = builder::vtable({
462 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
463 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "board_id"))),
464 raw_field(9, 1, schema(PrimType::U8(), {}, component(prefix + "board_type"))),
465 raw_field(10, 1, schema(PrimType::U8(), {}, component(prefix + "engine_state"))),
466 raw_field(11, 1, schema(PrimType::U8(), {}, component(prefix + "board_state"))),
467 raw_field(12, 4, schema(PrimType::U32(), {}, component(prefix + "packet_ts_ms"))),
468 });
469
470 uint64_t entity_id = 0x1000 + board_id;
471 if (!send_msg(client, VTableMsg{.id = {0x10, board_id}, .vtable = vt})) {
472 std::cerr << "[DatabaseConfig] ❌ Heartbeat VTable failed: " << entity << std::endl;
473 continue;
474 }
475
476 send_msg(client, set_component_name(prefix + "timestamp_ns"));
477 send_msg(client, set_component_name(prefix + "board_id"));
478 send_msg(client, set_component_name(prefix + "board_type"));
479 send_msg(client, set_component_name(prefix + "engine_state"));
480 send_msg(client, set_component_name(prefix + "board_state"));
481 send_msg(client, set_component_name(prefix + "packet_ts_ms"));
482 send_msg(client, set_entity_name(entity_id, entity));
483 registered++;
484 }
485
486 std::cout << "[DatabaseConfig] ✅ Registered " << registered << " HEARTBEAT VTables"
487 << std::endl;
488 return registered > 0;
489}
490
491// Per-sensor self-test VTables: [0x60 + sensor_id, board_id].
492// Each sensor result gets its own VTable so Elodin VTableStream delivers every
493// result individually (a shared table only delivers the last-written row).
494static constexpr uint8_t SELF_TEST_HIGH_BASE = 0x60;
495static constexpr uint8_t SELF_TEST_MAX_SENSOR = 15;
496
497static bool register_self_test_vtable(ElodinClient& client, uint8_t board_id, uint8_t sensor_id) {
498 std::string entity = "SELF_TEST.BOARD_" + std::to_string(board_id);
499 std::string prefix = entity + ".s" + std::to_string(sensor_id) + ".";
500
501 auto vt = builder::vtable({
502 raw_field(0, 8, schema(PrimType::U64(), {}, component(prefix + "timestamp_ns"))),
503 raw_field(8, 1, schema(PrimType::U8(), {}, component(prefix + "sensor_id"))),
504 raw_field(9, 1, schema(PrimType::U8(), {}, component(prefix + "result"))),
505 });
506
507 uint8_t high = SELF_TEST_HIGH_BASE + sensor_id;
508 uint64_t entity_id = (static_cast<uint64_t>(high) << 8) | board_id;
509 if (!send_msg(client, VTableMsg{.id = {high, board_id}, .vtable = vt}))
510 return false;
511
512 send_msg(client, set_component_name(prefix + "timestamp_ns"));
513 send_msg(client, set_component_name(prefix + "sensor_id"));
514 send_msg(client, set_component_name(prefix + "result"));
515 send_msg(client, set_entity_name(entity_id, entity));
516 return true;
517}
518
520 const std::vector<uint8_t>& board_ids) {
521 std::cout << "[DatabaseConfig] Registering SELF_TEST VTables (" << board_ids.size()
522 << " boards, sensors 0-" << (int)SELF_TEST_MAX_SENSOR << ")..." << std::endl;
523 int registered = 0;
524 for (uint8_t board_id : board_ids) {
525 for (uint8_t s = 0; s <= SELF_TEST_MAX_SENSOR; ++s) {
526 if (register_self_test_vtable(client, board_id, s))
527 registered++;
528 }
529 }
530 std::cout << "[DatabaseConfig] ✅ Registered " << registered << " SELF_TEST VTables"
531 << std::endl;
532 return registered > 0;
533}
534
535// ── Helper: register Sequencer tables ──────────────
537 // SequencerState: u64@0 + u8@8 + 3-byte hole + u32@12 + u8@16 = 17 bytes (u32 4-aligned like
538 // sensor rows)
539 auto vt1 = builder::vtable({
540 raw_field(0, 8, schema(PrimType::U64(), {}, component("SEQUENCER.state.timestamp_ns"))),
541 raw_field(8, 1, schema(PrimType::U8(), {}, component("SEQUENCER.state.current_state"))),
542 raw_field(12, 4, schema(PrimType::U32(), {}, component("SEQUENCER.state.allowed_bitmask"))),
543 raw_field(16, 1, schema(PrimType::U8(), {}, component("SEQUENCER.state.debug_mode"))),
544 });
545 if (!send_msg(client, VTableMsg{.id = {0x50, 0x00}, .vtable = vt1}))
546 return false;
547 send_msg(client, set_component_name("SEQUENCER.state.timestamp_ns"));
548 send_msg(client, set_component_name("SEQUENCER.state.current_state"));
549 send_msg(client, set_component_name("SEQUENCER.state.allowed_bitmask"));
550 send_msg(client, set_component_name("SEQUENCER.state.debug_mode"));
551 send_msg(client, set_entity_name(0x5000, "SEQUENCER.state"));
552
553 // StateTransition: U64+U8+U8+U8 (11 bytes)
554 auto vt2 = builder::vtable({
555 raw_field(0, 8, schema(PrimType::U64(), {}, component("CONTROLLER.state.timestamp_ns"))),
556 raw_field(8, 1, schema(PrimType::U8(), {}, component("CONTROLLER.state.from_state"))),
557 raw_field(9, 1, schema(PrimType::U8(), {}, component("CONTROLLER.state.to_state"))),
558 raw_field(10, 1, schema(PrimType::U8(), {}, component("CONTROLLER.state.reason"))),
559 });
560 if (!send_msg(client, VTableMsg{.id = {0x43, 0x00}, .vtable = vt2}))
561 return false;
562 send_msg(client, set_component_name("CONTROLLER.state.timestamp_ns"));
563 send_msg(client, set_component_name("CONTROLLER.state.from_state"));
564 send_msg(client, set_component_name("CONTROLLER.state.to_state"));
565 send_msg(client, set_component_name("CONTROLLER.state.reason"));
566 send_msg(client, set_entity_name(0x4300, "CONTROLLER.state"));
567
568 return true;
569}
570
572 // CalibrationCommand: u64 timestamp_ns | u8 type | u8 pad | u16 sensor_id (LE) | f32 reference.
573 // sensor_id is the uid board_id*100+connector (e.g. board 21 conn 5 = 2105) — a full u16, so it
574 // sits at the even offset 10 (2-byte aligned). It used to be declared u8 at offset 9; Elodin
575 // zeroes any byte no field covers, so the high byte was dropped and every capture to a board
576 // with id >= 3 (uid > 255) landed on uid & 0xFF (e.g. 2105 -> 57). The backend writer and the
577 // service parser use this same layout.
578 auto vt = builder::vtable({
579 raw_field(0, 8, schema(PrimType::U64(), {}, component("CALIBRATION.command.timestamp_ns"))),
580 raw_field(8, 1, schema(PrimType::U8(), {}, component("CALIBRATION.command.type"))),
581 raw_field(10, 2, schema(PrimType::U16(), {}, component("CALIBRATION.command.sensor_id"))),
582 raw_field(12, 4,
583 schema(PrimType::F32(), {}, component("CALIBRATION.command.reference_value"))),
584 });
585 if (!send_msg(client, VTableMsg{.id = {0x46, 0x00}, .vtable = vt}))
586 return false;
587 send_msg(client, set_component_name("CALIBRATION.command.timestamp_ns"));
588 send_msg(client, set_component_name("CALIBRATION.command.type"));
589 send_msg(client, set_component_name("CALIBRATION.command.sensor_id"));
590 send_msg(client, set_component_name("CALIBRATION.command.reference_value"));
591 send_msg(client, set_entity_name(0x4600, "CALIBRATION.command"));
592 return true;
593}
594
595// ════════════════════════════════════════════════════════════════════════════
596// PUBLIC API — ball valve boards (0x33 raw, 0x34 calibrated, 0x35 commanded)
597// ════════════════════════════════════════════════════════════════════════════
598
599std::vector<std::pair<uint8_t, uint8_t>> ball_valve_raw_tables(
600 const std::vector<BoardChannels>& bv_boards) {
601 std::vector<std::pair<uint8_t, uint8_t>> t;
602 appendTables(t, bv_boards, kBallValveRawHi, 0x00);
603 return t;
604}
605
606std::vector<std::pair<uint8_t, uint8_t>> ball_valve_calibrated_tables(
607 const std::vector<BoardChannels>& bv_boards) {
608 std::vector<std::pair<uint8_t, uint8_t>> t;
609 appendTables(t, bv_boards, kBallValveCalHi, 0x10);
610 return t;
611}
612
613std::vector<std::pair<uint8_t, uint8_t>> ball_valve_cmd_tables(
614 const std::vector<BoardChannels>& bv_boards) {
615 std::vector<std::pair<uint8_t, uint8_t>> t;
616 appendTables(t, bv_boards, kBallValveCmdHi, 0x00);
617 return t;
618}
619
621 const std::vector<BoardChannels>& bv_boards) {
622 bool ok = true;
623 for (const auto& board : bv_boards)
624 for (uint8_t ch : board.channels) {
625 std::string entity =
626 "BV" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
627 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
628 uint64_t entity_id = (static_cast<uint64_t>(kBallValveRawHi) << 8) | lo;
629 ok = register_raw_sensor_vtable(client, kBallValveRawHi, lo, entity_id, entity,
630 "raw_adc_counts") &&
631 ok;
632 }
633 return ok;
634}
635
637 ElodinClient& client, const std::vector<BoardChannels>& bv_boards) {
638 bool ok = true;
639 for (const auto& board : bv_boards)
640 for (uint8_t ch : board.channels) {
641 std::string entity =
642 "BV" + std::to_string(board.board_number) + "_Cal.CH" + std::to_string(ch);
643 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + 0x10 + ch);
644 uint64_t entity_id = (static_cast<uint64_t>(kBallValveCalHi) << 8) | lo;
645 ok = register_calibrated_vtable(client, kBallValveCalHi, lo, entity_id, entity,
646 "position_pct", "raw_adc") &&
647 ok;
648 }
649 return ok;
650}
651
653 const std::vector<BoardChannels>& bv_boards) {
654 bool ok = true;
655 for (const auto& board : bv_boards)
656 for (uint8_t ch : board.channels) {
657 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
658 std::string entity =
659 "BV_CMD.B" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
660 uint64_t entity_id = (static_cast<uint64_t>(kBallValveCmdHi) << 8) | lo;
661 ok = register_actuator_state_vtable(client, kBallValveCmdHi, lo, entity_id, entity) &&
662 ok;
663 }
664 return ok;
665}
666
668 const std::vector<BoardChannels>& act_boards) {
669 bool ok = true;
670 if (!register_sequencer_vtable(client))
671 ok = false;
673 ok = false;
674
675 // Register actuator commanded state VTables [0x32, (board_number-1)*0x20 + ch]
676 int act_cmd_count = 0;
677 if (!act_boards.empty()) {
678 for (const auto& board : act_boards) {
679 for (uint8_t ch : board.channels) {
680 uint8_t lo = static_cast<uint8_t>((board.board_number - 1) * 0x20 + ch);
681 std::string entity_name =
682 "ACT_CMD.B" + std::to_string(board.board_number) + ".CH" + std::to_string(ch);
683 uint64_t entity_id = (static_cast<uint64_t>(0x32) << 8) | lo;
684 if (register_actuator_state_vtable(client, 0x32, lo, entity_id, entity_name))
685 act_cmd_count++;
686 }
687 }
688 } else {
689 // Fallback: register for 4 boards x 10 channels when no board info available
690 for (int bn = 1; bn <= 4; ++bn) {
691 for (int ch = 1; ch <= 10; ++ch) {
692 uint8_t lo = static_cast<uint8_t>((bn - 1) * 0x20 + ch);
693 std::string entity_name =
694 "ACT_CMD.B" + std::to_string(bn) + ".CH" + std::to_string(ch);
695 uint64_t entity_id = (static_cast<uint64_t>(0x32) << 8) | lo;
696 if (register_actuator_state_vtable(client, 0x32, lo, entity_id, entity_name))
697 act_cmd_count++;
698 }
699 }
700 }
701 if (act_cmd_count > 0)
702 std::cout << "[DatabaseConfig] ✅ Registered " << act_cmd_count
703 << " actuator commanded VTables [0x32]" << std::endl;
704
705 if (ok) {
706 std::cout << "[DatabaseConfig] ✅ Registered Sequencer/Controller VTables" << std::endl;
707 }
708 return ok;
709}
710
711} // namespace elodin
712} // namespace fsw
static bool register_ball_valve_calibrated_tables(ElodinClient &client, const std::vector< BoardChannels > &bv_boards)
Register ball valve CALIBRATED position VTables [0x34] (calibration_service).
static bool register_calibrated_tables(ElodinClient &client, const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards, const std::vector< BoardChannels > &act_boards)
Register CALIBRATED VTables.
static bool register_non_sensor_tables(ElodinClient &client, const std::vector< BoardChannels > &act_boards={})
Register sequencer/controller and ACT_CMD tables.
static bool register_environmental_tables(ElodinClient &client, const std::vector< BoardChannels > &boards)
static bool register_heartbeat_tables(ElodinClient &client, const std::vector< uint8_t > &board_ids)
Register BOARD_HEARTBEAT VTables for specific board IDs.
static bool register_tables(ElodinClient &client, const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &act_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards)
Register RAW sensor / actuator VTables.
static bool register_self_test_tables(ElodinClient &client, const std::vector< uint8_t > &board_ids)
Register SELF_TEST VTables for specific board IDs.
static bool register_ball_valve_cmd_tables(ElodinClient &client, const std::vector< BoardChannels > &bv_boards)
Register ball valve commanded-state VTables [0x35] (sequencer_service).
static bool register_t7_raw_tables(ElodinClient &client, uint8_t slot, const std::vector< uint8_t > &ains, bool cjc)
Register one LabJack T7's RAW VTables (t7_bridge).
static bool register_t7_calibrated_tables(ElodinClient &client, uint8_t slot, const std::vector< T7Table > &tables)
static bool register_ball_valve_tables(ElodinClient &client, const std::vector< BoardChannels > &bv_boards)
Register ball valve RAW position VTables [0x33] (daq_bridge).
Standalone Elodin database client.
static uint8_t board_id
Definition main.cpp:30
static bool ok
Definition flight_e2e.c:435
void appendTables(std::vector< std::pair< uint8_t, uint8_t > > &out, const std::vector< BoardChannels > &boards, uint8_t type_hi, uint8_t offset)
Append (type_hi, (board_number-1)*0x20 + offset + ch) for every active channel.
bool register_t7_vtable(ElodinClient &client, uint8_t lo, const std::string &entity, const char *value_field, const char *u32_field, PrimType u32_type, const char *status_field)
static bool register_actuator_state_vtable(ElodinClient &client, uint8_t type_hi, uint8_t channel_id, uint64_t entity_id, const std::string &entity_name)
static constexpr uint8_t SELF_TEST_HIGH_BASE
static bool send_msg(ElodinClient &client, T msg)
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_calibrated_tables(const std::vector< BoardChannels > &bv_boards)
constexpr uint8_t kBallValveCmdHi
static bool register_self_test_vtable(ElodinClient &client, uint8_t board_id, uint8_t sensor_id)
static bool register_calibration_command_vtable(ElodinClient &client)
constexpr uint8_t kBallValveCalHi
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_cmd_tables(const std::vector< BoardChannels > &bv_boards)
std::vector< std::pair< uint8_t, uint8_t > > raw_sensor_tables(const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &act_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards)
Elodin table ids (hi, lo) for the RAW sensor VTables the given boards publish.
static bool register_sequencer_vtable(ElodinClient &client)
std::vector< std::pair< uint8_t, uint8_t > > calibrated_sensor_tables(const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards, const std::vector< BoardChannels > &act_boards)
Elodin table ids for the CALIBRATED VTables, matching register_calibrated_tables() (same encoding as ...
static bool register_raw_sensor_vtable(ElodinClient &client, uint8_t type_hi, uint8_t channel_id, uint64_t entity_id, const std::string &entity_name, const std::string &adc_field_name)
constexpr uint8_t kBallValveRawHi
Ball valve boards (BALL_VALVE) have their own type_hi bytes, so a valve never shares a key with an AC...
static constexpr uint8_t SELF_TEST_MAX_SENSOR
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_raw_tables(const std::vector< BoardChannels > &bv_boards)
static bool register_calibrated_vtable(ElodinClient &client, uint8_t type_hi, uint8_t pkt_lo, uint64_t entity_id, const std::string &entity_name, const std::string &cal_field_name, const std::string &raw_field_name)
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
std::string board(const std::string &name, const std::string &id, bool enabled=true)
static SlugTables tables()
The tables every case validates against.
static std::string ch(int ain, const std::string &kind, const std::string &extra="", const std::string &board="lj")
Definition test_t7.cpp:98