STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
BoardDiscovery.cpp
Go to the documentation of this file.
2
3#include <arpa/inet.h>
4#include <ifaddrs.h>
5#include <net/if.h>
6#include <netinet/in.h>
7#include <sys/socket.h>
8
9#include <algorithm>
10#include <cstring>
11#include <ctime>
12#include <fstream>
13#include <iomanip>
14#include <iostream>
15#include <map>
16#include <mutex>
17#include <optional>
18#include <sstream>
19
20#include "DiabloPacketUtils.h"
21#include "DiabloPackets.h"
22#include "fsw/BoardTypeWire.hpp"
23#include "util/IpFromMac.hpp"
24
25namespace fsw {
26namespace config {
27
35
36bool BoardDiscovery::initialize(const std::string& network_interface, const std::string& base_ip,
37 uint8_t ip_range_start, uint8_t ip_range_end) {
38 network_interface_ = network_interface;
39 base_ip_ = base_ip;
40 ip_range_start_ = ip_range_start;
41 ip_range_end_ = ip_range_end;
42
43 std::cout << "[BoardDiscovery] Initialized on interface: " << network_interface_ << std::endl;
44 std::cout << "[BoardDiscovery] IP range: " << base_ip_ << "." << (int)ip_range_start_ << "-"
45 << (int)ip_range_end_ << std::endl;
46
47 return true;
48}
49
50void BoardDiscovery::set_static_ip_for_board(uint8_t board_id, const std::string& ip) {
52}
53
55 current_mode_ = mode;
56 discovery_active_ = true;
57
58 std::cout << "[BoardDiscovery] Starting discovery (mode="
59 << (mode == DiscoveryMode::PASSIVE ? "PASSIVE"
60 : mode == DiscoveryMode::ACTIVE ? "ACTIVE"
61 : "HYBRID")
62 << ")" << std::endl;
63
64 if (mode == DiscoveryMode::ACTIVE || mode == DiscoveryMode::HYBRID) {
66 }
67
68 if (mode == DiscoveryMode::PASSIVE || mode == DiscoveryMode::HYBRID) {
70 }
71}
72
74 discovery_active_ = false;
75 std::cout << "[BoardDiscovery] Discovery stopped" << std::endl;
76}
77
78void BoardDiscovery::process_board_announcement(const uint8_t* data, size_t size,
79 const std::string& source_ip) {
80 daq::PacketHeader hdr;
81 daq::BoardHeartbeatPacket hb;
82 if (!daq::parse_board_heartbeat_packet(data, size, hdr, hb)) {
83 return;
84 }
85
86 BoardSignature signature;
87 signature.board_id = static_cast<uint32_t>(hb.board_id);
89 signature.hardware_version = 0;
90 signature.firmware_version = hdr.version;
91 signature.serial_number = hb.board_id;
92
94 board.signature = signature;
95 board.current_ip = source_ip;
96 board.last_seen = std::chrono::steady_clock::now();
97 board.is_configured = false;
98 board.port = 5005; // Default DiabloAvionics port
99
100 // MAC address will be extracted from network layer or calculated from IP
101 // For now, use IP-based hash
102 std::hash<std::string> hasher;
103 uint32_t ip_hash = static_cast<uint32_t>(hasher(source_ip));
104 std::ostringstream mac;
105 mac << std::hex << std::setw(2) << std::setfill('0') << ((ip_hash >> 16) & 0xFF) << ":"
106 << std::hex << std::setw(2) << std::setfill('0') << ((ip_hash >> 8) & 0xFF) << ":"
107 << std::hex << std::setw(2) << std::setfill('0') << (ip_hash & 0xFF) << ":" << std::hex
108 << std::setw(2) << std::setfill('0') << ((ip_hash >> 24) & 0xFF) << ":" << std::hex
109 << std::setw(2) << std::setfill('0') << ((signature.board_id >> 8) & 0xFF) << ":"
110 << std::hex << std::setw(2) << std::setfill('0') << (signature.board_id & 0xFF);
111 board.mac_address = mac.str();
112
114
115 // Assign IP: prefer config static IP (192.168.2.21 for board_id 21) over hash-derived
116 uint8_t board_id_octet = hb.board_id;
117 if (signature_to_ip_.find(signature) == signature_to_ip_.end()) {
118 std::string assigned_ip;
119 auto it = static_ip_overrides_.find(board_id_octet);
120 if (it != static_ip_overrides_.end()) {
121 assigned_ip = it->second;
122 } else {
123 assigned_ip = util::calculate_ip_from_mac(board.mac_address, base_ip_, ip_range_start_,
125 }
126 if (is_ip_available(assigned_ip)) {
127 signature_to_ip_[signature] = assigned_ip;
128 ip_to_signature_[assigned_ip] = signature;
129 board.current_ip = assigned_ip;
130 std::cout << "[BoardDiscovery] Assigned IP " << assigned_ip << " to "
131 << signature.to_string() << " (MAC: " << board.mac_address << ")"
132 << std::endl;
133 }
134 }
135
136 stats_.boards_discovered++;
137 stats_.last_discovery = std::chrono::steady_clock::now();
138
139 // Notify callbacks
140 for (const auto& callback : discovery_callbacks_) {
141 callback(board);
142 }
143}
144
145void BoardDiscovery::process_sensor_data(const uint8_t* data, size_t size,
146 const std::string& source_ip) {
147 daq::PacketHeader hdr;
148 std::vector<daq::SensorDataChunkCollection> chunks;
149 if (!daq::parse_sensor_data_packet(data, size, hdr, chunks) || chunks.empty()) {
150 return;
151 }
152
153 auto it = ip_to_signature_.find(source_ip);
154 uint8_t board_type_wire = daq_wire::kPressureTransducer;
155
156 if (it != ip_to_signature_.end()) {
157 board_type_wire = discovered_boards_[it->second].signature.board_type;
158 if (board_type_wire == daq_wire::kUnknown) {
159 board_type_wire = daq_wire::kPressureTransducer;
160 }
161 }
162
163 std::map<uint8_t, bool> sensor_ids;
164 for (const auto& dp : chunks[0].datapoints) {
165 sensor_ids[dp.sensor_id] = (dp.data != 0);
166 }
167 if (sensor_ids.empty()) {
168 return;
169 }
170
171 std::vector<SensorInfo> sensors;
172 for (const auto& [sensor_id, is_active] : sensor_ids) {
174 info.sensor_type = board_type_wire;
175 info.channel_id = sensor_id;
176 info.sensor_count = 1;
177 info.is_active = is_active;
178 info.quality = is_active ? 255 : 0;
179 sensors.push_back(info);
180 }
181
182 if (it != ip_to_signature_.end()) {
183 auto& board = discovered_boards_[it->second];
184 board.sensors = sensors;
185 board.active_sensors = sensors.size();
186 board.last_seen = std::chrono::steady_clock::now();
187 stats_.sensors_detected += sensors.size();
188 } else {
189 BoardSignature sig;
190 std::hash<std::string> hasher;
191 sig.board_id = static_cast<uint32_t>(hasher(source_ip));
192 sig.board_type = board_type_wire;
193 sig.hardware_version = 0;
194 sig.firmware_version = 0;
195 sig.serial_number = 0;
196
198 board.signature = sig;
199 board.current_ip = source_ip;
200 board.sensors = sensors;
201 board.last_seen = std::chrono::steady_clock::now();
202 board.is_configured = false;
203 board.port = 5005;
204 board.max_sensors = sensors.size();
205 board.active_sensors = sensors.size();
206
208 stats_.boards_discovered++;
209 }
210}
211
212std::vector<SensorInfo> BoardDiscovery::detect_sensors_from_packet(const uint8_t* data,
213 size_t size) const {
214 std::vector<SensorInfo> sensors;
215 daq::PacketHeader hdr;
216 std::vector<daq::SensorDataChunkCollection> chunks;
217 if (!daq::parse_sensor_data_packet(data, size, hdr, chunks) || chunks.empty()) {
218 return sensors;
219 }
220 std::map<uint8_t, bool> sensor_ids;
221 for (const auto& dp : chunks[0].datapoints) {
222 sensor_ids[dp.sensor_id] = (dp.data != 0);
223 }
224 for (const auto& [sensor_id, is_active] : sensor_ids) {
226 info.sensor_type = 0;
227 info.channel_id = sensor_id;
228 info.sensor_count = 1;
229 info.is_active = is_active;
230 info.quality = is_active ? 255 : 0;
231 sensors.push_back(info);
232 }
233 return sensors;
234}
235
237 const std::vector<SensorInfo>& sensors) const {
238 if (sensors.empty()) {
239 return BoardType::UNKNOWN;
240 }
241
242 // Count sensor types
243 std::map<uint8_t, size_t> type_counts;
244 for (const auto& sensor : sensors) {
245 type_counts[sensor.sensor_type]++;
246 }
247
248 if (type_counts.size() == 1) {
249 // Single sensor type board (matching DiabloAvionics BoardType enum)
250 switch (type_counts.begin()->first) {
251 case 1:
252 return BoardType::PT_BOARD; // PRESSURE_TRANSDUCER = 1
253 case 2:
254 return BoardType::LC_BOARD; // LOAD_CELL = 2
255 case 3:
256 return BoardType::RTD_BOARD; // RTD = 3
257 case 4:
258 return BoardType::TC_BOARD; // THERMOCOUPLE = 4
259 case 5:
260 return BoardType::MIXED_BOARD; // ACTUATOR = 5 (repurposed)
261 default:
262 return BoardType::UNKNOWN;
263 }
264 } else {
265 // Mixed sensor types
266 return BoardType::MIXED_BOARD;
267 }
268}
269
270std::optional<std::string> BoardDiscovery::assign_ip(const BoardSignature& signature) {
271 // Calculate IP based on signature (deterministic assignment)
272 std::string ip = calculate_ip_from_signature(signature);
273
274 if (is_ip_available(ip)) {
275 signature_to_ip_[signature] = ip;
276 ip_to_signature_[ip] = signature;
277
278 // Update board
279 auto it = discovered_boards_.find(signature);
280 if (it != discovered_boards_.end()) {
281 it->second.current_ip = ip;
282 }
283
284 stats_.ip_assignments++;
285 return ip;
286 }
287
288 return std::nullopt;
289}
290
292 // Use MAC address if available, otherwise fall back to board_id hash
293 // This will be called after MAC is set in process_board_announcement
294 // For now, use board_id hash (MAC-based assignment happens in process_board_announcement)
295 uint32_t hash = signature.board_id;
296 uint8_t ip_octet = ip_range_start_ + (hash % (ip_range_end_ - ip_range_start_ + 1));
297
298 // Parse base IP
299 size_t last_dot = base_ip_.rfind('.');
300 std::string base = base_ip_.substr(0, last_dot);
301
302 return base + "." + std::to_string(ip_octet);
303}
304
305bool BoardDiscovery::is_ip_available(const std::string& ip) const {
306 // Check if IP is already assigned
307 return ip_to_signature_.find(ip) == ip_to_signature_.end();
308}
309
311 std::lock_guard<std::mutex> lock(boards_mutex_);
312
313 auto it = discovered_boards_.find(board.signature);
314 if (it == discovered_boards_.end()) {
315 discovered_boards_[board.signature] = board;
316 } else {
317 // Update existing board
318 it->second.last_seen = board.last_seen;
319 if (!board.sensors.empty()) {
320 it->second.sensors = board.sensors;
321 it->second.active_sensors = board.sensors.size();
322 }
323 if (!board.current_ip.empty()) {
324 it->second.current_ip = board.current_ip;
325 }
326 }
327}
328
329std::vector<DiscoveredBoard> BoardDiscovery::get_discovered_boards() {
330 std::lock_guard<std::mutex> lock(boards_mutex_);
331
332 std::vector<DiscoveredBoard> boards;
333 for (const auto& [sig, board] : discovered_boards_) {
334 boards.push_back(board);
335 }
336 return boards;
337}
338
339std::optional<DiscoveredBoard> BoardDiscovery::get_board_by_ip(const std::string& ip) {
340 std::lock_guard<std::mutex> lock(boards_mutex_);
341 auto it = ip_to_signature_.find(ip);
342 if (it == ip_to_signature_.end())
343 return std::nullopt;
344 auto board_it = discovered_boards_.find(it->second);
345 if (board_it == discovered_boards_.end())
346 return std::nullopt;
347 return board_it->second;
348}
349
350std::map<std::string, std::map<std::string, std::string>> BoardDiscovery::generate_config() {
351 std::lock_guard<std::mutex> lock(boards_mutex_);
352
353 std::map<std::string, std::map<std::string, std::string>> config;
354
355 // Generate board configurations
356 size_t board_idx = 0;
357 for (const auto& [sig, board] : discovered_boards_) {
358 std::string board_key = "board_" + std::to_string(board_idx++);
359 config[board_key] = generate_board_config(board);
360
361 // Generate sensor configs for this board
362 auto sensor_config = generate_sensor_config(board.sensors);
363 if (!sensor_config.empty()) {
364 config[board_key + ".sensors"] = sensor_config;
365 }
366 }
367
368 return config;
369}
370
371std::map<std::string, std::string> BoardDiscovery::generate_board_config(
372 const DiscoveredBoard& board) const {
373 std::map<std::string, std::string> config;
374
375 config["ip"] = board.current_ip;
376 config["port"] = std::to_string(board.port);
377 config["mac_address"] = board.mac_address;
378 config["board_id"] = "0x" + std::to_string(board.signature.board_id);
379 config["board_type"] = std::to_string(board.signature.board_type);
380 config["max_sensors"] = std::to_string(board.max_sensors);
381 config["active_sensors"] = std::to_string(board.active_sensors);
382
383 return config;
384}
385
386std::map<std::string, std::string> BoardDiscovery::generate_sensor_config(
387 const std::vector<SensorInfo>& sensors) const {
388 std::map<std::string, std::string> config;
389
390 // Group sensors by type
391 std::map<uint8_t, std::vector<SensorInfo>> sensors_by_type;
392 for (const auto& sensor : sensors) {
393 sensors_by_type[sensor.sensor_type].push_back(sensor);
394 }
395
396 for (const auto& [type, sensor_list] : sensors_by_type) {
397 std::string type_name;
398 switch (type) {
399 case 0x01:
400 type_name = "pt";
401 break;
402 case 0x02:
403 type_name = "tc";
404 break;
405 case 0x03:
406 type_name = "rtd";
407 break;
408 case 0x04:
409 type_name = "lc";
410 break;
411 default:
412 type_name = "unknown_" + std::to_string(type);
413 break;
414 }
415
416 config[type_name + "_enabled"] = "true";
417 config[type_name + "_count"] = std::to_string(sensor_list.size());
418
419 // Generate channel list
420 std::string channels;
421 for (size_t i = 0; i < sensor_list.size(); ++i) {
422 if (i > 0)
423 channels += ",";
424 channels += std::to_string(sensor_list[i].channel_id);
425 }
426 config[type_name + "_channels"] = channels;
427 }
428
429 return config;
430}
431
433 // TODO: Implement active network scanning
434 std::cout << "[BoardDiscovery] Scanning network..." << std::endl;
435}
436
438 // TODO: Implement passive listening for board announcements
439 std::cout << "[BoardDiscovery] Listening for board announcements..." << std::endl;
440}
441
443 std::function<void(const DiscoveredBoard&)> callback) {
444 discovery_callbacks_.push_back(callback);
445}
446
447} // namespace config
448} // namespace fsw
static const uint32_t channels[BOARD_ADC_COUNT]
Definition board_adc.c:7
static board_sleep_info_t info
Definition board_sleep.c:14
void start_discovery(DiscoveryMode mode=DiscoveryMode::HYBRID)
Start discovery process.
std::atomic< bool > discovery_active_
std::vector< SensorInfo > detect_sensors_from_packet(const uint8_t *data, size_t size) const
std::map< std::string, BoardSignature > ip_to_signature_
std::map< BoardSignature, std::string > signature_to_ip_
BoardType detect_board_type(const std::vector< SensorInfo > &sensors) const
void add_or_update_board(const DiscoveredBoard &board)
void set_static_ip_for_board(uint8_t board_id, const std::string &ip)
Set static IP for board_id (from config).
std::optional< std::string > assign_ip(const BoardSignature &signature)
Assign IP address to board based on signature.
std::map< BoardSignature, DiscoveredBoard > discovered_boards_
void process_board_announcement(const uint8_t *data, size_t size, const std::string &source_ip)
Process incoming board announcement packet.
std::map< std::string, std::map< std::string, std::string > > generate_config()
Generate configuration from discovered boards.
void register_discovery_callback(std::function< void(const DiscoveredBoard &)> callback)
Register callback for board discovery events.
std::string calculate_ip_from_signature(const BoardSignature &signature) const
void stop_discovery()
Stop discovery process.
std::map< std::string, std::string > generate_board_config(const DiscoveredBoard &board) const
void process_sensor_data(const uint8_t *data, size_t size, const std::string &source_ip)
Process sensor data packet to detect sensors.
std::optional< DiscoveredBoard > get_board_by_ip(const std::string &ip)
Get board by IP address.
bool initialize(const std::string &network_interface, const std::string &base_ip, uint8_t ip_range_start=100, uint8_t ip_range_end=200)
Initialize discovery system.
std::map< uint8_t, std::string > static_ip_overrides_
std::map< std::string, std::string > generate_sensor_config(const std::vector< SensorInfo > &sensors) const
std::vector< std::function< void(const DiscoveredBoard &)> > discovery_callbacks_
bool is_ip_available(const std::string &ip) const
std::vector< DiscoveredBoard > get_discovered_boards()
Get all discovered boards.
std::vector< daq::SensorDataChunkCollection > chunks
Definition main.cpp:55
byte mac[6]
Definition main.cpp:31
static uint8_t board_id
Definition main.cpp:30
static osMutexId_t lock
Definition mode.c:11
bool parse_board_heartbeat_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, BoardHeartbeatPacket &data_out)
Parses a Board Heartbeat packet from buffer.
bool parse_sensor_data_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< SensorDataChunkCollection > &chunks_out)
Parses a Sensor Data packet from buffer into chunk collections.
constexpr uint8_t kPressureTransducer
constexpr uint8_t kUnknown
Legacy DAQv2 wire numeric board_type (Elodin / discovery); heartbeat body no longer carries type.
std::string calculate_ip_from_mac(const std::string &mac_address, const std::string &base_ip, uint8_t ip_range_start, uint8_t ip_range_end)
Deterministic last-octet from MAC string "aa:bb:cc:dd:ee:ff" into base_ip range.
Definition IpFromMac.cpp:9
Motorized ball valve position: raw ADC counts -> % open.
Board signature/identifier structure.
std::string to_string() const
Discovered board information.
Detected sensor information from board.
std::string board(const std::string &name, const std::string &id, bool enabled=true)