STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
SensorAssignment.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <cstring>
6#include <fstream>
7#include <iomanip>
8#include <iostream>
9#include <sstream>
10
11#include "DiabloPacketUtils.h"
12
13namespace fsw {
14namespace config {
15
24
25bool SensorAssignmentManager::load_sensor_definitions(const std::string& /* config_path */) {
26 // Initialize flight pressure sensors
27 pressure_sensor_specs_["PT_HP"] = {"PT_HP",
28 "High Pressure PT",
29 "High pressure PT capable of going up to 5k PSI reading",
30 5000,
32 "Rocket",
33 "High pressure"};
34
35 pressure_sensor_specs_["PT_LP"] = {"PT_LP",
36 "COPV PT Post Regulator",
37 "COPV PT post regulator (1000psi)",
38 1000,
40 "Rocket",
41 "COPV post regulator"};
42
43 pressure_sensor_specs_["PT_FUP"] = {
44 "PT_FUP", "Upstream Fuel PT", "Upstream Fuel PT (1000 psi)", 1000, SystemState::FLIGHT,
45 "Rocket", "Fuel upstream"};
46
47 pressure_sensor_specs_["PT_FDP"] = {
48 "PT_FDP", "Downstream Fuel PT", "Downstream Fuel PT (1000 psi)", 1000, SystemState::FLIGHT,
49 "Rocket", "Fuel downstream"};
50
51 pressure_sensor_specs_["PT_OUP"] = {"PT_OUP",
52 "Upstream Oxidizer PT",
53 "Upstream Oxidizer PT (1000 PSI)",
54 1000,
56 "Rocket",
57 "Oxidizer upstream"};
58
59 pressure_sensor_specs_["PT_ODP"] = {"PT_ODP",
60 "Downstream Oxidizer PT",
61 "Downstream Oxidizer PT (1000 PSI)",
62 1000,
64 "Rocket",
65 "Oxidizer downstream"};
66
67 // Initialize GSE pressure sensors
68 pressure_sensor_specs_["PT_OF"] = {"PT_OF",
69 "LOX Fill PT",
70 "LOX Fill PT that goes up to 1000 psi",
71 1000,
73 "GSE - LOX Fill",
74 "LOX fill pressure"};
75
76 pressure_sensor_specs_["PT_FF"] = {"PT_FF",
77 "Fuel Fill PT",
78 "Fuel fill PT that goes up to 1000 psi",
79 1000,
81 "GSE - Fuel Fill",
82 "Fuel fill pressure"};
83
84 pressure_sensor_specs_["PT_HPF"] = {"PT_HPF",
85 "High Pressure Fill PT",
86 "High pressure fill with PT rated to 5000 psi",
87 5000,
89 "GSE - Pressurant Fill",
90 "High pressure fill"};
91
92 pressure_sensor_specs_["PT_MPF"] = {"PT_MPF",
93 "Medium Pressure Fill PT",
94 "Medium press fill with PT rated to 2000 PSI",
95 2000,
97 "GSE - Pressurant Fill",
98 "Medium pressure fill"};
99
100 pressure_sensor_specs_["PT_LPF"] = {"PT_LPF",
101 "Low Pressure Fill PT",
102 "Low pressure line with PT rated to 1000 psi",
103 1000,
105 "GSE - Pressurant Fill",
106 "Low pressure fill"};
107
108 std::cout << "[SensorAssignment] Loaded " << pressure_sensor_specs_.size()
109 << " pressure sensor definitions" << std::endl;
110
111 return true;
112}
113
114void SensorAssignmentManager::set_static_board_ip(uint8_t board_id, const std::string& ip) {
115 if (!ip.empty()) {
117 // If the board was already configured, update its IP
118 auto it = board_configs_.find(board_id);
119 if (it != board_configs_.end() && it->second.board_ip != ip) {
120 std::cout << "[SensorAssignment] Forcing static IP for board " << (int)board_id
121 << " from " << it->second.board_ip << " to " << ip << std::endl;
122 it->second.board_ip = ip;
123 for (auto& sensor : it->second.sensors) {
124 sensor.board_ip = ip;
125 }
126 for (auto& [sensor_id, assignment] : sensor_assignments_) {
127 if (assignment.board_id == board_id) {
128 assignment.board_ip = ip;
129 }
130 }
131 }
132 }
133}
134
136 const std::string& mac_address,
137 SystemState system_state,
138 const std::string& source_ip) {
139 // Check if we have a static IP defined for this board
140 std::string assigned_ip;
141 auto static_it = static_board_ips_.find(board_id);
142 if (static_it != static_board_ips_.end() && !static_it->second.empty()) {
143 assigned_ip = static_it->second;
144 }
145
146 // Check if board already has IP assigned
147 auto it = board_configs_.find(board_id);
148 if (it != board_configs_.end() && !it->second.board_ip.empty()) {
149 if (!assigned_ip.empty() && it->second.board_ip != assigned_ip) {
150 std::cout << "[SensorAssignment] Updating IP for board " << (int)board_id
151 << " to static config IP " << assigned_ip << std::endl;
152 it->second.board_ip = assigned_ip;
153 // Update IP in assigned sensors
154 for (auto& sensor : it->second.sensors) {
155 sensor.board_ip = assigned_ip;
156 }
157 // Update sensor_assignments_ map
158 for (auto& [sensor_id, assignment] : sensor_assignments_) {
159 if (assignment.board_id == board_id) {
160 assignment.board_ip = assigned_ip;
161 }
162 }
163 } else if (assigned_ip.empty() && !source_ip.empty() && it->second.board_ip != source_ip) {
164 std::cout << "[SensorAssignment] Updating IP for unconfigured board " << (int)board_id
165 << " from " << it->second.board_ip << " to heartbeat source " << source_ip
166 << std::endl;
167 it->second.board_ip = source_ip;
168 // Update IP in assigned sensors
169 for (auto& sensor : it->second.sensors) {
170 sensor.board_ip = source_ip;
171 }
172 // Update sensor_assignments_ map
173 for (auto& [sensor_id, assignment] : sensor_assignments_) {
174 if (assignment.board_id == board_id) {
175 assignment.board_ip = source_ip;
176 }
177 }
178 }
179 return it->second.board_ip;
180 }
181
182 // If no static IP was defined, use source IP, else calculate from MAC
183 if (assigned_ip.empty()) {
184 assigned_ip =
185 !source_ip.empty() ? source_ip : calculate_ip_from_mac(mac_address, system_state);
186 std::cout << "[SensorAssignment] Board " << (int)board_id
187 << " not found in config.toml! Using pseudo-random auto-discovery IP."
188 << std::endl;
189 }
190
191 // Create or update board configuration
193 config.board_id = board_id;
194 config.board_ip = assigned_ip;
195 config.board_port = 5005; // Default DiabloAvionics port
196 config.mac_address = mac_address;
197 config.system_state = system_state;
198 config.is_configured = false;
199
200 std::cout << "[SensorAssignment] Assigned IP " << assigned_ip << " to board " << (int)board_id
201 << " (MAC: " << mac_address
202 << ", State: " << (system_state == SystemState::GSE ? "GSE" : "FLIGHT") << ")"
203 << std::endl;
204
205 return assigned_ip;
206}
207
209 const std::vector<std::string>& sensor_ids,
210 uint8_t start_channel) {
211 // Verify board exists
212 auto board_it = board_configs_.find(board_id);
213 if (board_it == board_configs_.end()) {
214 std::cerr << "[SensorAssignment] Error: Board " << (int)board_id << " not found"
215 << std::endl;
216 return false;
217 }
218
219 BoardConfiguration& board_config = board_it->second;
220
221 // Assign sensors
222 uint8_t channel = start_channel;
223 for (const auto& sensor_id : sensor_ids) {
224 // Verify sensor exists
225 auto sensor_it = pressure_sensor_specs_.find(sensor_id);
226 if (sensor_it == pressure_sensor_specs_.end()) {
227 std::cerr << "[SensorAssignment] Warning: Sensor " << sensor_id
228 << " not found, skipping" << std::endl;
229 continue;
230 }
231
232 const auto& spec = sensor_it->second;
233
234 // Create assignment
235 SensorAssignment assignment;
236 assignment.sensor_id = sensor_id;
237 assignment.board_id = board_id;
238 assignment.channel_id = channel++;
239 assignment.sensor_type = SensorType::PT; // All current sensors are PT
240 assignment.system_state = spec.system_state;
241 assignment.is_active = true;
242 assignment.board_ip = board_config.board_ip;
243 assignment.board_port = board_config.board_port;
244
245 // Store assignment
246 sensor_assignments_[sensor_id] = assignment;
247 board_config.sensors.push_back(assignment);
248
249 // Set primary sensor type if not set
250 if (board_config.sensors.size() == 1) {
251 board_config.primary_sensor_type = assignment.sensor_type;
252 }
253
254 std::cout << "[SensorAssignment] Assigned sensor " << sensor_id << " to board "
255 << (int)board_id << " channel " << (int)assignment.channel_id << std::endl;
256 }
257
258 return true;
259}
260
262 auto it = board_configs_.find(board_id);
263 if (it == board_configs_.end()) {
264 return {};
265 }
266 const auto& config = it->second;
267
268 // Generate SENSOR_CONFIG packet using canonical DAQv2-Comms serializer so that
269 // the on-wire format exactly matches daq::parse_sensor_config_packet.
270 // Body: num_sensors | sensor_ids | reference_voltage | necessary_for_abort
271 // | [controller_ip if necessary_for_abort] | enable_serial_printing
272
273 std::vector<uint8_t> sensor_ids;
274 sensor_ids.reserve(config.sensors.size());
275 for (const auto& sensor : config.sensors) {
276 // Use board channel_id as sensor_id (matches DAQv2-Comms expectations)
277 sensor_ids.push_back(sensor.channel_id);
278 }
279
280 uint8_t reference_voltage = 0; // 0 = internal 2.5V
281 bool necessary_for_abort = false; // FSW boards are not abort-critical by default
282 uint32_t controller_ip = 0; // Unused when necessary_for_abort == false
283 uint8_t enable_serial_printing = 0; // Do not enable Serial printing from FSW configs
284
285 constexpr size_t MAX_PACKET_SIZE = 512;
286 uint8_t buffer[MAX_PACKET_SIZE];
287
288 uint32_t ts_ms = static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
289 std::chrono::steady_clock::now().time_since_epoch())
290 .count() &
291 0xFFFFFFFFu);
292 size_t written = daq::create_sensor_config_packet(
293 sensor_ids, reference_voltage, necessary_for_abort, controller_ip, enable_serial_printing,
294 ts_ms, buffer, sizeof(buffer));
295
296 if (written == 0 || written > MAX_PACKET_SIZE) {
297 std::cerr << "[SensorAssignment] Failed to serialize SENSOR_CONFIG for board "
298 << static_cast<int>(board_id) << " using DAQv2-Comms serializer" << std::endl;
299 return {};
300 }
301
302 return std::vector<uint8_t>(buffer, buffer + written);
303}
304
306 const std::string& sensor_id) const {
307 auto it = sensor_assignments_.find(sensor_id);
308 if (it == sensor_assignments_.end()) {
309 return std::nullopt;
310 }
311 return it->second;
312}
313
314std::vector<SensorAssignment> SensorAssignmentManager::get_board_sensors(uint8_t board_id) const {
315 auto it = board_configs_.find(board_id);
316 if (it == board_configs_.end()) {
317 return {};
318 }
319 return it->second.sensors;
320}
321
323 std::vector<SensorAssignment> sensors;
324 for (const auto& [sensor_id, assignment] : sensor_assignments_) {
325 if (assignment.system_state == state) {
326 sensors.push_back(assignment);
327 }
328 }
329 return sensors;
330}
331
332std::optional<BoardConfiguration> SensorAssignmentManager::get_board_config(
333 uint8_t board_id) const {
334 auto it = board_configs_.find(board_id);
335 if (it == board_configs_.end()) {
336 return std::nullopt;
337 }
338 return it->second;
339}
340
342 const uint8_t* /* data */,
343 size_t /* size */) {
344 // TODO: Parse board response packet
345 auto it = board_configs_.find(board_id);
346 if (it != board_configs_.end()) {
347 it->second.is_configured = true;
348
349 // Notify callbacks
350 for (const auto& callback : config_callbacks_) {
351 callback(board_id, it->second);
352 }
353
354 return true;
355 }
356 return false;
357}
358
359std::string SensorAssignmentManager::calculate_ip_from_mac(const std::string& mac_address,
360 SystemState state) const {
361 // Parse MAC address
362 std::istringstream mac_stream(mac_address);
363 std::string byte_str;
364 uint32_t mac_hash = 0;
365 int byte_count = 0;
366
367 while (std::getline(mac_stream, byte_str, ':') && byte_count < 6) {
368 uint8_t byte_val = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
369 mac_hash = (mac_hash << 8) | byte_val;
370 byte_count++;
371 }
372
373 // Choose IP range based on system state
374 const std::string& base_ip = (state == SystemState::GSE) ? gse_base_ip_ : flight_base_ip_;
375 uint8_t range_start =
378
379 // Calculate IP octet
380 uint8_t ip_octet = range_start + (mac_hash % (range_end - range_start + 1));
381
382 // Parse base IP
383 size_t last_dot = base_ip.rfind('.');
384 std::string base = base_ip.substr(0, last_dot);
385
386 return base + "." + std::to_string(ip_octet);
387}
388
390 if (type_str == "PT")
391 return SensorType::PT;
392 if (type_str == "TC")
393 return SensorType::TC;
394 if (type_str == "RTD")
395 return SensorType::RTD;
396 if (type_str == "LC")
397 return SensorType::LC;
398 if (type_str == "ACTUATOR")
400 return SensorType::PT; // Default
401}
402
404 switch (type) {
405 case SensorType::PT:
406 return "PT";
407 case SensorType::TC:
408 return "TC";
409 case SensorType::RTD:
410 return "RTD";
411 case SensorType::LC:
412 return "LC";
414 return "ACTUATOR";
415 default:
416 return "PT";
417 }
418}
419
421 std::function<void(uint8_t, const BoardConfiguration&)> callback) {
422 config_callbacks_.push_back(callback);
423}
424
425} // namespace config
426} // namespace fsw
#define MAX_PACKET_SIZE
Definition main.h:6
std::string sensor_type_to_string(SensorType type) const
std::string assign_board_ip(uint8_t board_id, const std::string &mac_address, SystemState system_state, const std::string &source_ip="")
Assign IP address to board (called by FSW) If a static IP was set for this board_id,...
bool update_board_config_from_packet(uint8_t board_id, const uint8_t *data, size_t size)
Update board configuration from received packet.
std::vector< uint8_t > generate_board_config_packet(uint8_t board_id) const
Get board configuration for sending to board.
bool load_sensor_definitions(const std::string &config_path)
Load sensor definitions from config.
SensorType sensor_type_from_string(const std::string &type_str) const
void register_config_update_callback(std::function< void(uint8_t, const BoardConfiguration &)> callback)
Register callback for board configuration updates.
void set_static_board_ip(uint8_t board_id, const std::string &ip)
Set a static IP for a board from loaded configuration.
std::optional< BoardConfiguration > get_board_config(uint8_t board_id) const
Get board configuration.
std::vector< std::function< void(uint8_t, const BoardConfiguration &)> > config_callbacks_
std::map< uint8_t, std::string > static_board_ips_
std::map< std::string, PressureSensorSpec > pressure_sensor_specs_
std::map< std::string, SensorAssignment > sensor_assignments_
std::string calculate_ip_from_mac(const std::string &mac_address, SystemState state) const
std::map< uint8_t, BoardConfiguration > board_configs_
std::vector< SensorAssignment > get_board_sensors(uint8_t board_id) const
Get all sensors for a board.
bool assign_sensors_to_board(uint8_t board_id, const std::vector< std::string > &sensor_ids, uint8_t start_channel=0)
Assign sensors to board (called by FSW).
std::optional< SensorAssignment > get_sensor_assignment(const std::string &sensor_id) const
Get sensor assignment for a sensor ID.
std::vector< SensorAssignment > get_system_sensors(SystemState state) const
Get all sensors for a system state.
static uint8_t board_id
Definition main.cpp:30
static ActuatorControllerState state
Definition main.cpp:57
size_t create_sensor_config_packet(const std::vector< uint8_t > &sensor_ids, uint8_t reference_voltage, bool necessary_for_abort, uint32_t controller_ip, uint8_t enable_serial_printing, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Sensor Config packet in the provided buffer.
SystemState
System state (GSE or Flight).
SensorType
Sensor types.
Motorized ball valve position: raw ADC counts -> % open.
Board configuration (assigned by FSW).
std::vector< SensorAssignment > sensors
Sensor assignment to board.