STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
SensorRouter.cpp
Go to the documentation of this file.
2
3#include <cmath>
4#include <cstdint>
5#include <iostream>
6#include <sstream>
7
8namespace fsw {
9namespace routing {
10
12 // Default table IDs (can be overridden by config)
13 // These should match what's configured in Elodin
14 // For now, use simple sequential IDs
15}
16
17bool SensorRouter::load_config(const std::string& config_path) {
18 // TODO: Implement TOML parsing
19 // For now, use hardcoded defaults matching config/sensor_routing.toml
20 // This will be implemented when we add TOML parsing library
21
22 // PT channels
23 SensorChannelConfig pt_chamber{.channel_id = 0,
24 .table_name = "pt_chamber_raw",
25 .table_id = {0x20, 0x00}, // Table ID 0x2000
26 .sensor_type = "PT",
27 .location = "chamber"};
28 add_channel(pt_chamber);
29
30 SensorChannelConfig pt_fuel_inlet{.channel_id = 1,
31 .table_name = "pt_fuel_inlet_raw",
32 .table_id = {0x20, 0x01}, // Table ID 0x2001
33 .sensor_type = "PT",
34 .location = "fuel_inlet"};
35 add_channel(pt_fuel_inlet);
36
37 SensorChannelConfig pt_ox_inlet{.channel_id = 2,
38 .table_name = "pt_ox_inlet_raw",
39 .table_id = {0x20, 0x02}, // Table ID 0x2002
40 .sensor_type = "PT",
41 .location = "ox_inlet"};
42 add_channel(pt_ox_inlet);
43
44 // TC channels
45 SensorChannelConfig tc_exhaust{.channel_id = 0,
46 .table_name = "tc_exhaust_raw",
47 .table_id = {0x21, 0x00}, // Table ID 0x2100
48 .sensor_type = "TC",
49 .location = "exhaust"};
50 add_channel(tc_exhaust);
51
52 SensorChannelConfig tc_chamber{.channel_id = 1,
53 .table_name = "tc_chamber_raw",
54 .table_id = {0x21, 0x01}, // Table ID 0x2101
55 .sensor_type = "TC",
56 .location = "chamber"};
57 add_channel(tc_chamber);
58
59 // RTD channels
60 SensorChannelConfig rtd_fuel{.channel_id = 0,
61 .table_name = "rtd_fuel_raw",
62 .table_id = {0x22, 0x00}, // Table ID 0x2200
63 .sensor_type = "RTD",
64 .location = "fuel"};
65 add_channel(rtd_fuel);
66
67 SensorChannelConfig rtd_ox{.channel_id = 1,
68 .table_name = "rtd_ox_raw",
69 .table_id = {0x22, 0x01}, // Table ID 0x2201
70 .sensor_type = "RTD",
71 .location = "ox"};
72 add_channel(rtd_ox);
73
74 // LC channels
75 SensorChannelConfig lc_thrust{.channel_id = 0,
76 .table_name = "lc_thrust_raw",
77 .table_id = {0x23, 0x00}, // Table ID 0x2300
78 .sensor_type = "LC",
79 .location = "thrust"};
80 add_channel(lc_thrust);
81
82 return true;
83}
84
86 std::string key = make_key(config.sensor_type, config.channel_id);
87 channel_map_[key] = config;
88}
89
90std::array<uint8_t, 2> SensorRouter::get_table_id(const std::string& sensor_type,
91 uint8_t channel_id) const {
92 std::string key = make_key(sensor_type, channel_id);
93 auto it = channel_map_.find(key);
94 if (it != channel_map_.end()) {
95 return it->second.table_id;
96 }
97 // Default fallback table ID
98 return {0x00, 0x00};
99}
100
101std::string SensorRouter::get_table_name(const std::string& sensor_type, uint8_t channel_id) const {
102 std::string key = make_key(sensor_type, channel_id);
103 auto it = channel_map_.find(key);
104 if (it != channel_map_.end()) {
105 return it->second.table_name;
106 }
107 return "unknown_" + sensor_type + "_" + std::to_string(channel_id);
108}
109
110std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawPTMessage>>
112 uint64_t receive_timestamp_ns) const {
113 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawPTMessage>> messages;
114
115 for (const auto& sample : batch.pt_samples) {
116 // Per-channel packet_id: {0x20, channel_id} — channel_id is already 1-indexed connector
117 std::array<uint8_t, 2> pkt_id = {0x20, static_cast<uint8_t>(sample.channel_id)};
118
120 msg.setField<0>(receive_timestamp_ns);
121 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
122 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
123 msg.setField<3>(sample.raw_adc_counts);
124 msg.setField<4>(sample.sample_timestamp_ms);
125 msg.setField<5>(sample.status_flags);
126
127 messages.emplace_back(pkt_id, msg);
128 }
129
130 return messages;
131}
132
133std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedPTMessage>>
135 uint64_t receive_timestamp_ns,
136 uint8_t pt_board_slot) const {
137 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedPTMessage>>
138 messages;
139
140 if (!pt_calibration_) {
141 return messages;
142 }
143
144 for (const auto& sample : batch.pt_samples) {
145 // Per-channel calibrated packet_id: {0x20, 0x10 + channel_id} — channel_id is already
146 // 1-indexed connector
147 std::array<uint8_t, 2> pkt_id = {0x20, static_cast<uint8_t>(0x10 + sample.channel_id)};
148
149 int32_t adc_code = static_cast<int32_t>(sample.raw_adc_counts);
150 const uint8_t log_ch = calibration::pt_logical_calibration_channel(
151 pt_board_slot, static_cast<uint8_t>(sample.channel_id));
152 double pressure_psi = pt_calibration_->calculate_pressure(log_ch, adc_code);
153 uint8_t calibration_status = pt_calibration_->is_calibrated(log_ch) ? 1 : 0;
154
156 msg.setField<0>(receive_timestamp_ns);
157 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
158 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
159 msg.setField<3>(static_cast<float>(pressure_psi));
160 msg.setField<4>(sample.raw_adc_counts);
161 msg.setField<5>(calibration_status);
162
163 messages.emplace_back(pkt_id, msg);
164 }
165
166 return messages;
167}
168
169std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawTCMessage>>
171 uint64_t receive_timestamp_ns) const {
172 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawTCMessage>> messages;
173
174 for (const auto& sample : batch.tc_samples) {
175 std::array<uint8_t, 2> pkt_id = {0x21, static_cast<uint8_t>(sample.channel_id)};
176
178 msg.setField<0>(receive_timestamp_ns);
179 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
180 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
181 msg.setField<3>(sample.raw_adc_counts);
182 msg.setField<4>(sample.sample_timestamp_ms);
183 msg.setField<5>(sample.status_flags);
184
185 messages.emplace_back(pkt_id, msg);
186 }
187
188 return messages;
189}
190
191std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawRTDMessage>>
193 uint64_t receive_timestamp_ns) const {
194 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawRTDMessage>> messages;
195
196 for (const auto& sample : batch.rtd_samples) {
197 std::array<uint8_t, 2> pkt_id = {0x22, static_cast<uint8_t>(sample.channel_id)};
198
200 msg.setField<0>(receive_timestamp_ns);
201 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
202 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
203 msg.setField<3>(sample.raw_resistance_counts);
204 msg.setField<4>(sample.sample_timestamp_ms);
205 msg.setField<5>(sample.status_flags);
206
207 messages.emplace_back(pkt_id, msg);
208 }
209
210 return messages;
211}
212
213std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawLCMessage>>
215 uint64_t receive_timestamp_ns) const {
216 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::RawLCMessage>> messages;
217
218 for (const auto& sample : batch.lc_samples) {
219 std::array<uint8_t, 2> pkt_id = {0x23, static_cast<uint8_t>(sample.channel_id)};
220
222 msg.setField<0>(receive_timestamp_ns);
223 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
224 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
225 msg.setField<3>(sample.raw_adc_counts);
226 msg.setField<4>(sample.sample_timestamp_ms);
227 msg.setField<5>(sample.status_flags);
228
229 messages.emplace_back(pkt_id, msg);
230 }
231
232 return messages;
233}
234
235// ── Calibrated TC ─────────────────────────────────────────────────────────
236std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedTCMessage>>
238 uint64_t receive_timestamp_ns) const {
239 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedTCMessage>>
240 messages;
241 if (!tc_calibration_)
242 return messages;
243
244 for (const auto& sample : batch.tc_samples) {
245 // Calibrated TC packet_id: {0x21, 0x10 + channel_id} — channel_id is already 1-indexed
246 // connector
247 std::array<uint8_t, 2> pkt_id = {0x21, static_cast<uint8_t>(0x10 + sample.channel_id)};
248 int32_t raw = static_cast<int32_t>(sample.raw_adc_counts);
249 double temp_c = tc_calibration_->calculate(sample.channel_id, raw);
250 uint8_t status = tc_calibration_->is_calibrated(sample.channel_id) ? 1 : 0;
251
253 msg.setField<0>(receive_timestamp_ns);
254 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
255 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
256 msg.setField<3>(static_cast<float>(temp_c));
257 msg.setField<4>(sample.raw_adc_counts);
258 msg.setField<5>(status);
259 messages.emplace_back(pkt_id, msg);
260 }
261 return messages;
262}
263
264// ── Calibrated RTD ────────────────────────────────────────────────────────
265std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedRTDMessage>>
267 uint64_t receive_timestamp_ns) const {
268 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedRTDMessage>>
269 messages;
270 if (!rtd_calibration_)
271 return messages;
272
273 for (const auto& sample : batch.rtd_samples) {
274 // Calibrated RTD packet_id: {0x22, 0x10 + channel_id} — channel_id is already 1-indexed
275 // connector
276 std::array<uint8_t, 2> pkt_id = {0x22, static_cast<uint8_t>(0x10 + sample.channel_id)};
277 int32_t raw = static_cast<int32_t>(sample.raw_resistance_counts);
278 double temp_c = rtd_calibration_->calculate(sample.channel_id, raw);
279 uint8_t status = rtd_calibration_->is_calibrated(sample.channel_id) ? 1 : 0;
280
282 msg.setField<0>(receive_timestamp_ns);
283 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
284 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
285 msg.setField<3>(static_cast<float>(temp_c));
286 msg.setField<4>(sample.raw_resistance_counts);
287 msg.setField<5>(status);
288 messages.emplace_back(pkt_id, msg);
289 }
290 return messages;
291}
292
293// ── Calibrated LC ─────────────────────────────────────────────────────────
294std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedLCMessage>>
296 uint64_t receive_timestamp_ns) const {
297 std::vector<std::pair<std::array<uint8_t, 2>, comms::messages::sensor::CalibratedLCMessage>>
298 messages;
299 if (!lc_calibration_)
300 return messages;
301
302 for (const auto& sample : batch.lc_samples) {
303 // Calibrated LC packet_id: {0x23, 0x10 + channel_id} — channel_id is already 1-indexed
304 // connector
305 std::array<uint8_t, 2> pkt_id = {0x23, static_cast<uint8_t>(0x10 + sample.channel_id)};
306 int32_t raw = static_cast<int32_t>(sample.raw_adc_counts);
307 double force = lc_calibration_->calculate(sample.channel_id, raw);
308 uint8_t status = lc_calibration_->is_calibrated(sample.channel_id) ? 1 : 0;
309
311 msg.setField<0>(receive_timestamp_ns);
312 msg.setField<1>(static_cast<uint8_t>(sample.channel_id));
313 msg.setField<2>(std::array<uint8_t, 3>{0, 0, 0});
314 msg.setField<3>(static_cast<float>(force));
315 msg.setField<4>(sample.raw_adc_counts);
316 msg.setField<5>(status);
317 messages.emplace_back(pkt_id, msg);
318 }
319 return messages;
320}
321
322std::string SensorRouter::make_key(const std::string& sensor_type, uint8_t channel_id) const {
323 return sensor_type + ":" + std::to_string(channel_id);
324}
325
326} // namespace routing
327} // namespace fsw
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::RawLCMessage > > route_lc_samples(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::RawRTDMessage > > route_rtd_samples(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::RawTCMessage > > route_tc_samples(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
std::array< uint8_t, 2 > get_table_id(const std::string &sensor_type, uint8_t channel_id) const
Get table ID for a sensor channel.
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::RawPTMessage > > route_pt_samples(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
Convert a sensor batch into Elodin messages.
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::CalibratedPTMessage > > route_pt_samples_calibrated(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns, uint8_t pt_board_slot=1) const
Convert PT samples to calibrated messages.
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::CalibratedRTDMessage > > route_rtd_samples_calibrated(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
const calibration::PTCalibrationManager * pt_calibration_
const calibration::SensorCalibrationManager * tc_calibration_
const calibration::SensorCalibrationManager * rtd_calibration_
std::unordered_map< std::string, SensorChannelConfig > channel_map_
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::CalibratedTCMessage > > route_tc_samples_calibrated(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
std::string get_table_name(const std::string &sensor_type, uint8_t channel_id) const
Get table name for a sensor channel (for debugging).
bool load_config(const std::string &config_path)
Load routing configuration from TOML file.
std::string make_key(const std::string &sensor_type, uint8_t channel_id) const
void add_channel(const SensorChannelConfig &config)
Add a sensor channel configuration manually.
std::vector< std::pair< std::array< uint8_t, 2 >, comms::messages::sensor::CalibratedLCMessage > > route_lc_samples_calibrated(const daq_comms::protocol::SensorBatch &batch, uint64_t receive_timestamp_ns) const
const calibration::SensorCalibrationManager * lc_calibration_
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, uint32_t, uint32_t, uint8_t > RawTCMessage
Raw Thermocouple sample message for Elodin CRITICAL: Includes padding bytes to ensure uint32_t fields...
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, float, uint32_t, uint8_t > CalibratedRTDMessage
Calibrated RTD message for Elodin.
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, float, uint32_t, uint8_t > CalibratedLCMessage
Calibrated Load Cell message for Elodin.
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, float, uint32_t, uint8_t > CalibratedTCMessage
Calibrated Thermocouple message for Elodin.
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, uint32_t, uint32_t, uint8_t > RawRTDMessage
Raw RTD sample message for Elodin CRITICAL: Includes padding bytes to ensure uint32_t fields are 4-by...
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, float, uint32_t, uint8_t > CalibratedPTMessage
Calibrated Pressure Transducer message for Elodin.
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, uint32_t, uint32_t, uint8_t > RawLCMessage
Raw Load Cell sample message for Elodin CRITICAL: Includes padding bytes to ensure uint32_t fields ar...
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, uint32_t, uint32_t, uint8_t > RawPTMessage
Raw Pressure Transducer sample message for Elodin.
uint8_t pt_logical_calibration_channel(uint8_t board_slot_number, uint8_t local_connector)
PT board slot (1–10, from board_id % 10 with 0→10) + local connector (1–10) → factory/JSON logical ch...
Motorized ball valve position: raw ADC counts -> % open.
void setField(ValueType &&value)
std::vector< RawTCSample > tc_samples
std::vector< RawPTSample > pt_samples
std::vector< RawLCSample > lc_samples
std::vector< RawRTDSample > rtd_samples
Configuration for sensor channel routing.