STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
SensorFramePipeline.cpp
Go to the documentation of this file.
2
3#include <chrono>
4#include <cmath>
5#include <cstring>
6#include <iostream>
7
8#include "DiabloEnums.h"
9#include "DiabloPacketUtils.h"
10#include "DiabloPackets.h"
11#include "daq-protocol.h"
12
13namespace fsw {
14namespace streams {
15
16SensorFramePipeline::SensorFramePipeline(const std::string& bind_address, uint16_t bind_port)
18 socket_ = std::make_unique<daq_comms::transport::UDPSocket>(bind_address, bind_port);
19
20 if (!socket_->is_valid()) {
21 last_error_ = "Failed to create UDP socket: " + socket_->last_error();
22 }
23}
24
25std::optional<daq_comms::protocol::SensorBatch> SensorFramePipeline::poll() {
26 if (!socket_->is_valid()) {
27 last_error_ = "Socket not valid: " + socket_->last_error();
28 return std::nullopt;
29 }
30
31 uint16_t source_port = 0;
32 ssize_t received = socket_->receive_from(receive_buffer_.data(), receive_buffer_.size(),
33 last_source_ip_, source_port);
34 if (received <= 0) {
35 if (received < 0 && !socket_->last_error().empty()) {
36 static size_t error_count = 0;
37 if (error_count++ < 5) {
38 std::cerr << "[Pipeline] Socket receive error: " << socket_->last_error()
39 << std::endl;
40 }
41 }
42 return std::nullopt;
43 }
44
45 if (received < static_cast<ssize_t>(sizeof(daq::PacketHeader))) {
46 return std::nullopt;
47 }
48
49 daq::PacketHeader peek{};
50 std::memcpy(&peek, receive_buffer_.data(), sizeof(peek));
51
52 if (peek.packet_type == daq::PacketType::ENVIRONMENTAL_DATA) {
53 daq::PacketHeader header{};
54 daq::EnvironmentalDataPacket data{};
55 if (peek.version != DIABLO_COMMS_VERSION ||
56 received != static_cast<ssize_t>(sizeof(header) + sizeof(data)) ||
57 !daq::parse_environmental_data_packet(receive_buffer_.data(), received, header, data) ||
58 !std::isfinite(data.temperature_c) || !std::isfinite(data.humidity_rh) ||
59 data.humidity_rh < 0.0f || data.humidity_rh > 100.0f || data.pressure_pa == 0) {
60 return std::nullopt;
61 }
63 EnvironmentalSample{data.temperature_c, data.pressure_pa, data.humidity_rh,
64 header.timestamp, last_source_ip_};
65 return std::nullopt;
66 }
67
68 if (peek.packet_type == daq::PacketType::SENSOR_DATA) {
69 daq::PacketHeader sensor_header;
70 std::vector<daq::SensorDataChunkCollection> chunks;
71 if (!daq::parse_sensor_data_packet(receive_buffer_.data(), static_cast<size_t>(received),
72 sensor_header, chunks)) {
73 static size_t parse_fail_count = 0;
74 if (++parse_fail_count <= 10 || parse_fail_count % 500 == 0) {
75 std::cerr << "[Pipeline] SENSOR_DATA parse failed #" << parse_fail_count
76 << " (size=" << received << ") from " << last_source_ip_ << std::endl;
77 }
78 return std::nullopt;
79 }
80
82 if (!chunks.empty()) {
83 batch.frame_timestamp_ns = static_cast<uint64_t>(chunks[0].timestamp) * 1000000ULL;
84 } else {
85 auto now = std::chrono::steady_clock::now();
86 batch.frame_timestamp_ns =
87 std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch())
88 .count();
89 }
90 batch.sequence_id = sensor_header.timestamp & 0xFFFF;
91 batch.is_valid = true;
92
93 for (const auto& chunk : chunks) {
94 for (const auto& dp : chunk.datapoints) {
96 sample.channel_id = dp.sensor_id;
97 sample.raw_adc_counts = dp.data;
98 sample.sample_timestamp_ms = chunk.timestamp;
99 sample.status_flags = 0;
100 batch.pt_samples.push_back(sample);
101 }
102 }
103 return batch;
104 }
105
106 if (peek.packet_type == daq::PacketType::SELF_TEST) {
107 daq::PacketHeader st_header;
108 uint8_t adc_good = 0;
109 std::vector<daq::SelfTestResult> st_results;
110 if (!daq::parse_self_test_packet(receive_buffer_.data(), static_cast<size_t>(received),
111 st_header, adc_good, st_results)) {
112 std::cerr << "[Pipeline] SELF_TEST parse failed from " << last_source_ip_ << std::endl;
113 return std::nullopt;
114 }
115
117 auto now = std::chrono::steady_clock::now();
118 batch.frame_timestamp_ns =
119 std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
120 batch.sequence_id = st_header.timestamp & 0xFFFF;
121 batch.is_valid = true;
122
124 parsed.packet_type = static_cast<uint8_t>(st_header.packet_type);
125 parsed.version = st_header.version;
126 parsed.timestamp = st_header.timestamp;
127 parsed.adc_good = adc_good;
128 parsed.num_sensors = static_cast<uint8_t>(st_results.size());
129 parsed.is_valid = true;
130 for (const auto& r : st_results) {
131 parsed.results.push_back({r.sensor_id, r.result});
132 }
133 batch.self_tests.push_back(std::move(parsed));
134 return batch;
135 }
136
137 if (peek.packet_type == daq::PacketType::BOARD_HEARTBEAT) {
139 receive_buffer_.data() + static_cast<size_t>(received));
141 return std::nullopt;
142 }
143
144 if (peek.packet_type == daq::PacketType::LOGS) {
145 last_log_buffer_.assign(receive_buffer_.data(),
146 receive_buffer_.data() + static_cast<size_t>(received));
148 return std::nullopt;
149 }
150
151 return std::nullopt;
152}
153
155 if (!socket_ || !socket_->is_valid())
156 return false;
157 return socket_->set_broadcast(enable);
158}
159
160ssize_t SensorFramePipeline::send_to(const std::string& dest_ip, uint16_t dest_port,
161 const uint8_t* data, size_t size) {
162 if (!socket_ || !socket_->is_valid())
163 return -1;
164 return socket_->send_to(dest_ip, dest_port, data, size);
165}
166
167std::optional<SensorFramePipeline::LastHeartbeat> SensorFramePipeline::get_last_heartbeat() {
168 if (last_heartbeat_buffer_.empty()) {
169 return std::nullopt;
170 }
171 LastHeartbeat out;
172 out.data = std::move(last_heartbeat_buffer_);
173 out.source_ip = std::move(last_heartbeat_source_ip_);
176 return out;
177}
178
179std::optional<SensorFramePipeline::LastLog> SensorFramePipeline::get_last_log() {
180 if (last_log_buffer_.empty()) {
181 return std::nullopt;
182 }
183 LastLog out;
184 out.data = std::move(last_log_buffer_);
185 out.source_ip = std::move(last_log_source_ip_);
186 last_log_buffer_.clear();
187 last_log_source_ip_.clear();
188 return out;
189}
190
192 return socket_ && socket_->is_valid();
193}
194
195std::optional<SensorFramePipeline::EnvironmentalSample>
197 return std::exchange(last_environmental_, std::nullopt);
198}
199
201 return last_error_;
202}
203
204} // namespace streams
205} // namespace fsw
static void enable(board_bringup_t *b, uint16_t device_id)
static uint8_t chunk[64]
Definition board_uart9.c:12
std::vector< uint8_t > last_heartbeat_buffer_
static constexpr size_t RECEIVE_BUFFER_SIZE
std::unique_ptr< daq_comms::transport::UDPSocket > socket_
SensorFramePipeline(const std::string &bind_address, uint16_t bind_port)
std::optional< EnvironmentalSample > get_last_environmental()
std::optional< daq_comms::protocol::SensorBatch > poll()
std::optional< LastLog > get_last_log()
std::optional< EnvironmentalSample > last_environmental_
std::optional< LastHeartbeat > get_last_heartbeat()
ssize_t send_to(const std::string &dest_ip, uint16_t dest_port, const uint8_t *data, size_t size)
#define DIABLO_COMMS_VERSION
Definition daq-protocol.h:4
std::vector< daq::SensorDataChunkCollection > chunks
Definition main.cpp:55
bool parse_self_test_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, uint8_t &adc_good_out, std::vector< SelfTestResult > &results_out)
Parses a Self Test packet from buffer.
bool parse_environmental_data_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, EnvironmentalDataPacket &data_out)
Parses an Environmental Data 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.
Motorized ball valve position: raw ADC counts -> % open.
Parsed DAQv2 SELF_TEST payload (host-side mirror; filled from daq::parse_self_test_packet).
std::vector< RawPTSample > pt_samples
std::vector< ParsedSelfTestPacket > self_tests
static boot_report_t r