1pub mod interface;
2pub mod messages;
3
4use crate::board::{self, BoardIo};
5use crate::comm::messages::{Messages, Store, enums::*, messages::*};
6use crate::command::CommandManager;
7use crate::estimator::AttitudeEstimate;
8use crate::events::{
9 AuxCommandReceived, BoardCommandRequested, CalibrationRequested, CommEventQueues, CommResponse,
10 CommandEventQueues, CompanionEventQueues, CompanionHeartbeatReceived, ConfigInfoRequested,
11 ExternalAttitudeReceived, OffboardControlRequested, ParamDefaultsRequested, ParamEventQueues,
12 ParamListRequested, ParamReadRequested, ParamSetRequested, RcTrimCalibrationRequested,
13 ResetOriginRequested, VersionRequested,
14};
15use crate::math::FlightFloat;
16use crate::packets::{RC_PACKET_CHANNELS, RangeType};
17use crate::params::{ParamId, ParamValue, Params};
18use crate::sensors::ProcessedSensors;
19use crate::state_machine::StateManager;
20use core::marker::PhantomData;
21
22#[cfg(feature = "runtime-diagnostics")]
23use core::sync::atomic::{AtomicU32, Ordering};
24
25#[cfg(feature = "runtime-diagnostics")]
26macro_rules! telemetry_sent_counter {
27 ($name:ident) => {
28 #[unsafe(no_mangle)]
29 pub static $name: AtomicU32 = AtomicU32::new(0);
30 };
31}
32
33#[cfg(feature = "runtime-diagnostics")]
34telemetry_sent_counter!(VELOXITY_DIAG_TELEM_IMU_SENT);
35#[cfg(feature = "runtime-diagnostics")]
36telemetry_sent_counter!(VELOXITY_DIAG_TELEM_MAG_SENT);
37#[cfg(feature = "runtime-diagnostics")]
38telemetry_sent_counter!(VELOXITY_DIAG_TELEM_BARO_SENT);
39#[cfg(feature = "runtime-diagnostics")]
40telemetry_sent_counter!(VELOXITY_DIAG_TELEM_PITOT_SENT);
41#[cfg(feature = "runtime-diagnostics")]
42telemetry_sent_counter!(VELOXITY_DIAG_TELEM_RANGE_SENT);
43#[cfg(feature = "runtime-diagnostics")]
44telemetry_sent_counter!(VELOXITY_DIAG_TELEM_GNSS_SENT);
45#[cfg(feature = "runtime-diagnostics")]
46telemetry_sent_counter!(VELOXITY_DIAG_TELEM_BATTERY_SENT);
47#[cfg(feature = "runtime-diagnostics")]
48telemetry_sent_counter!(VELOXITY_DIAG_TELEM_RC_SENT);
49#[cfg(feature = "runtime-diagnostics")]
50telemetry_sent_counter!(VELOXITY_DIAG_TIMESYNC_RESPONSE_SENT);
51
52const MAV_TYPE_FIXED_WING: u8 = 1;
53const MAV_TYPE_QUADROTOR: u8 = 2;
54const OUTPUT_RAW_IMU_DIVISOR: u64 = 8;
55const TELEMETRY_RATE_DISABLED: u16 = u16::MAX;
56pub const MAX_TELEMETRY_RATE_HZ: i32 = 2_000;
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum NamedTelemetryStream {
60 Heartbeat,
61 Status,
62 Imu,
63 Rc,
64 Attitude,
65 OutputRaw,
66 Gnss,
67 DiffPressure,
68 Baro,
69 Mag,
70 Range,
71 Battery,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum RealtimeTelemetryPriorityGate {
76 DueDeadline,
78 FreshSample,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct RealtimeTelemetryPriority {
84 pub stream: NamedTelemetryStream,
85 pub gate: RealtimeTelemetryPriorityGate,
86}
87
88pub struct TelemetryCtx<'a, B, S, A, R>
89where
90 B: BoardIo,
91 R: FlightFloat,
92{
93 pub board: &'a mut B,
94 pub now_us: u64,
95 pub state: &'a StateManager,
96 pub command: &'a CommandManager,
97 pub params: &'a Params,
98 pub estimator_state: &'a S,
99 pub sensors: &'a ProcessedSensors<R>,
100 pub actuator_commands: &'a A,
101 pub sensor_error_count: u16,
102 pub loop_time_us: u16,
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct TelemetryRates {
107 pub heartbeat_hz: u16,
108 pub status_hz: u16,
109 pub imu_hz: u16,
110 pub attitude_hz: u16,
111 pub output_raw_hz: u16,
112 pub diff_pressure_hz: u16,
113 pub baro_hz: u16,
114 pub mag_hz: u16,
115 pub range_hz: u16,
116 pub battery_hz: u16,
117 pub gnss_hz: u16,
118 pub rc_hz: u16,
119 pub output_raw_imu_divisor: u64,
120}
121
122impl TelemetryRates {
123 pub fn from_params(params: &Params) -> Self {
124 Self {
125 heartbeat_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_HEARTBEAT_HZ),
126 status_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_STATUS_HZ),
127 imu_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_IMU_HZ),
128 attitude_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_ATTITUDE_HZ),
129 output_raw_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_OUTPUT_RAW_HZ),
130 diff_pressure_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_DIFF_PRESSURE_HZ),
131 baro_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_BARO_HZ),
132 mag_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_MAG_HZ),
133 range_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_RANGE_HZ),
134 battery_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_BATTERY_HZ),
135 gnss_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_GNSS_HZ),
136 rc_hz: telemetry_rate_param(params, ParamId::PARAM_TELEM_RC_HZ),
137 output_raw_imu_divisor: 0,
138 }
139 }
140
141 pub const fn upstream() -> Self {
142 Self {
143 heartbeat_hz: 1,
144 status_hz: 10,
145 imu_hz: 0,
146 attitude_hz: 0,
147 output_raw_hz: 0,
148 diff_pressure_hz: 0,
149 baro_hz: 0,
150 mag_hz: 0,
151 range_hz: 0,
152 battery_hz: 0,
153 gnss_hz: 0,
154 rc_hz: 0,
155 output_raw_imu_divisor: OUTPUT_RAW_IMU_DIVISOR,
156 }
157 }
158
159 pub const fn bounded_high_rate_transport() -> Self {
160 Self {
161 heartbeat_hz: 1,
162 status_hz: 10,
163 imu_hz: 400,
164 attitude_hz: 50,
165 output_raw_hz: 50,
166 diff_pressure_hz: 50,
167 baro_hz: 25,
168 mag_hz: 25,
169 range_hz: 50,
170 battery_hz: 25,
171 gnss_hz: 10,
172 rc_hz: 100,
173 output_raw_imu_divisor: 0,
174 }
175 }
176}
177
178fn telemetry_rate_param(params: &Params, id: ParamId) -> u16 {
179 match params.get_by_id(id) {
180 ParamValue::Int(-1) => TELEMETRY_RATE_DISABLED,
181 ParamValue::Int(value) if value < -1 => TELEMETRY_RATE_DISABLED,
182 ParamValue::Int(value) => value.min(MAX_TELEMETRY_RATE_HZ) as u16,
183 _ => TELEMETRY_RATE_DISABLED,
184 }
185}
186
187pub fn telemetry_stream_for_param(id: ParamId) -> Option<NamedTelemetryStream> {
188 match id {
189 ParamId::PARAM_TELEM_HEARTBEAT_HZ => Some(NamedTelemetryStream::Heartbeat),
190 ParamId::PARAM_TELEM_STATUS_HZ => Some(NamedTelemetryStream::Status),
191 ParamId::PARAM_TELEM_IMU_HZ => Some(NamedTelemetryStream::Imu),
192 ParamId::PARAM_TELEM_ATTITUDE_HZ => Some(NamedTelemetryStream::Attitude),
193 ParamId::PARAM_TELEM_OUTPUT_RAW_HZ => Some(NamedTelemetryStream::OutputRaw),
194 ParamId::PARAM_TELEM_DIFF_PRESSURE_HZ => Some(NamedTelemetryStream::DiffPressure),
195 ParamId::PARAM_TELEM_BARO_HZ => Some(NamedTelemetryStream::Baro),
196 ParamId::PARAM_TELEM_MAG_HZ => Some(NamedTelemetryStream::Mag),
197 ParamId::PARAM_TELEM_RANGE_HZ => Some(NamedTelemetryStream::Range),
198 ParamId::PARAM_TELEM_BATTERY_HZ => Some(NamedTelemetryStream::Battery),
199 ParamId::PARAM_TELEM_GNSS_HZ => Some(NamedTelemetryStream::Gnss),
200 ParamId::PARAM_TELEM_RC_HZ => Some(NamedTelemetryStream::Rc),
201 _ => None,
202 }
203}
204
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
206struct TelemetryRateState {
207 imu_us: u64,
208 attitude_us: u64,
209 output_raw_us: u64,
210 diff_pressure_us: u64,
211 baro_us: u64,
212 mag_us: u64,
213 range_us: u64,
214 battery_us: u64,
215 gnss_us: u64,
216 rc_us: u64,
217}
218
219#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
220struct TelemetryFreshnessState {
221 imu: Option<u64>,
222 attitude: Option<u64>,
223 output_raw: Option<u64>,
224 diff_pressure: Option<u64>,
225 baro: Option<u64>,
226 mag: Option<u64>,
227 range: Option<u64>,
228 battery: Option<u64>,
229 gnss: Option<u64>,
230 rc: Option<u64>,
231}
232
233impl TelemetryFreshnessState {
234 fn last_sent(&self, stream: NamedTelemetryStream) -> Option<u64> {
235 match stream {
236 NamedTelemetryStream::Imu => self.imu,
237 NamedTelemetryStream::Attitude => self.attitude,
238 NamedTelemetryStream::OutputRaw => self.output_raw,
239 NamedTelemetryStream::DiffPressure => self.diff_pressure,
240 NamedTelemetryStream::Baro => self.baro,
241 NamedTelemetryStream::Mag => self.mag,
242 NamedTelemetryStream::Range => self.range,
243 NamedTelemetryStream::Battery => self.battery,
244 NamedTelemetryStream::Gnss => self.gnss,
245 NamedTelemetryStream::Rc => self.rc,
246 NamedTelemetryStream::Heartbeat | NamedTelemetryStream::Status => None,
247 }
248 }
249
250 fn mark_sent(&mut self, stream: NamedTelemetryStream, timestamp: u64) {
251 let slot = match stream {
252 NamedTelemetryStream::Imu => &mut self.imu,
253 NamedTelemetryStream::Attitude => &mut self.attitude,
254 NamedTelemetryStream::OutputRaw => &mut self.output_raw,
255 NamedTelemetryStream::DiffPressure => &mut self.diff_pressure,
256 NamedTelemetryStream::Baro => &mut self.baro,
257 NamedTelemetryStream::Mag => &mut self.mag,
258 NamedTelemetryStream::Range => &mut self.range,
259 NamedTelemetryStream::Battery => &mut self.battery,
260 NamedTelemetryStream::Gnss => &mut self.gnss,
261 NamedTelemetryStream::Rc => &mut self.rc,
262 NamedTelemetryStream::Heartbeat | NamedTelemetryStream::Status => return,
263 };
264 *slot = Some(timestamp);
265 }
266}
267
268fn telemetry_stream_sample_timestamp<R: FlightFloat>(
269 stream: NamedTelemetryStream,
270 sensors: &ProcessedSensors<R>,
271) -> Option<u64> {
272 match stream {
273 NamedTelemetryStream::Imu
274 | NamedTelemetryStream::Attitude
275 | NamedTelemetryStream::OutputRaw => sensors.imu.map(|packet| packet.header.timestamp),
276 NamedTelemetryStream::Rc => sensors.rc.map(|packet| packet.header.timestamp),
277 NamedTelemetryStream::Gnss => sensors.gnss.map(|packet| packet.header.timestamp),
278 NamedTelemetryStream::DiffPressure => sensors.pitot.map(|packet| packet.header.timestamp),
279 NamedTelemetryStream::Baro => sensors.baro.map(|packet| packet.header.timestamp),
280 NamedTelemetryStream::Mag => sensors.mag.map(|packet| packet.header.timestamp),
281 NamedTelemetryStream::Range => sensors.range.map(|packet| packet.header.timestamp),
282 NamedTelemetryStream::Battery => sensors.battery.map(|packet| packet.header.timestamp),
283 NamedTelemetryStream::Heartbeat | NamedTelemetryStream::Status => None,
284 }
285}
286
287fn stream_due(now_us: u64, last_us: &mut u64, rate_hz: u16) -> bool {
288 if rate_hz == TELEMETRY_RATE_DISABLED {
289 return false;
290 }
291 if rate_hz == 0 {
292 if *last_us == 0 {
293 *last_us = now_us;
294 }
295 return true;
296 }
297
298 let interval_us = 1_000_000_u64 / rate_hz as u64;
299 if *last_us == 0 {
300 *last_us = now_us;
301 true
302 } else if now_us.saturating_sub(*last_us) >= interval_us {
303 let elapsed_intervals = now_us.saturating_sub(*last_us) / interval_us;
304 *last_us = last_us.saturating_add(elapsed_intervals.saturating_mul(interval_us));
305 true
306 } else {
307 false
308 }
309}
310
311fn stream_due_deadline_us(now_us: u64, last_us: u64, rate_hz: u16) -> Option<u64> {
312 if rate_hz == TELEMETRY_RATE_DISABLED {
313 return None;
314 }
315 if rate_hz == 0 {
316 return Some(if last_us == 0 { 0 } else { now_us });
317 }
318 if last_us == 0 {
319 return Some(0);
320 }
321
322 let interval_us = 1_000_000_u64 / rate_hz as u64;
323 let deadline_us = last_us.saturating_add(interval_us);
324 let elapsed_us = now_us.saturating_sub(last_us);
325 if elapsed_us >= interval_us {
326 Some(deadline_us)
327 } else {
328 None
329 }
330}
331
332fn fixed_rate_due(now_us: u64, last_us: u64, rate_hz: u16) -> bool {
333 if rate_hz == TELEMETRY_RATE_DISABLED {
334 return false;
335 }
336 if rate_hz == 0 {
337 return true;
338 }
339
340 let interval_us = 1_000_000_u64 / rate_hz as u64;
341 now_us.saturating_sub(last_us) >= interval_us
342}
343
344fn fixed_rate_due_deadline_us(now_us: u64, last_us: u64, rate_hz: u16) -> Option<u64> {
345 if rate_hz == TELEMETRY_RATE_DISABLED {
346 return None;
347 }
348 if rate_hz == 0 {
349 return Some(now_us);
350 }
351
352 let interval_us = 1_000_000_u64 / rate_hz as u64;
353 let deadline_us = last_us.saturating_add(interval_us);
354 let elapsed_us = now_us.saturating_sub(last_us);
355 if elapsed_us >= interval_us {
356 Some(deadline_us)
357 } else {
358 None
359 }
360}
361
362pub const fn str_to_fixed_bytes(input: &str) -> [u8; 16] {
363 let mut buffer = [0u8; 16];
364 let input_bytes = input.as_bytes();
365
366 let len_to_copy = if input_bytes.len() > 16 {
368 16
369 } else {
370 input_bytes.len()
371 };
372
373 let mut i = 0;
375 while i < len_to_copy {
376 buffer[i] = input_bytes[i];
377 i += 1;
378 }
379
380 buffer
385}
386
387fn param_int(params: &Params, id: ParamId) -> i32 {
388 match params.get_by_id(id) {
389 ParamValue::Int(value) => value,
390 _ => 0,
391 }
392}
393
394pub struct CommManager<B, T>
395where
396 B: board::BoardIo,
397 T: interface::CommInterface<B>,
398{
399 last_heartbeat_us: u64,
400 last_status_send_us: u64,
401 output_raw_imu_count: u64,
402 telemetry_rates: TelemetryRates,
403 telemetry_rate_state: TelemetryRateState,
404 telemetry_freshness: TelemetryFreshnessState,
405
406 pub sysid: u8,
407 comm_link: T,
408 pub msgs: Messages,
409 _board_marker: PhantomData<B>,
410}
411
412impl<B, T> CommManager<B, T>
413where
414 B: board::BoardIo,
415 T: interface::CommInterface<B>,
416{
417 pub fn new(comm_link: T, now_us: u64) -> Self {
418 CommManager {
419 last_heartbeat_us: now_us,
420 last_status_send_us: now_us,
421 output_raw_imu_count: 0,
422 telemetry_rates: TelemetryRates::upstream(),
423 telemetry_rate_state: TelemetryRateState::default(),
424 telemetry_freshness: TelemetryFreshnessState::default(),
425
426 sysid: 1,
427 comm_link,
428 msgs: Messages::default(),
429 _board_marker: PhantomData,
430 }
431 }
432
433 #[cfg(test)]
434 pub(crate) fn comm_link(&self) -> &T {
435 &self.comm_link
436 }
437
438 pub fn process_incoming_messages(&mut self, board: &mut B) {
439 self.comm_link
440 .handle_incoming_messages(board, &mut self.msgs);
441 }
442
443 pub fn has_pending_messages(&self) -> bool {
444 self.msgs.has_pending()
445 }
446
447 pub fn set_telemetry_rates(&mut self, telemetry_rates: TelemetryRates) {
448 self.telemetry_rates = telemetry_rates;
450 self.telemetry_rate_state = TelemetryRateState::default();
451 self.output_raw_imu_count = 0;
452 }
453
454 pub fn configure_telemetry_from_params(&mut self, params: &Params) {
455 self.set_telemetry_rates(TelemetryRates::from_params(params));
456 }
457
458 pub fn update_telemetry_param(&mut self, params: &Params, id: ParamId, now_us: u64) -> bool {
462 let Some(stream) = telemetry_stream_for_param(id) else {
463 return false;
464 };
465 let rate_hz = telemetry_rate_param(params, id);
466 match stream {
467 NamedTelemetryStream::Heartbeat => {
468 self.telemetry_rates.heartbeat_hz = rate_hz;
469 self.last_heartbeat_us = now_us;
470 }
471 NamedTelemetryStream::Status => {
472 self.telemetry_rates.status_hz = rate_hz;
473 self.last_status_send_us = now_us;
474 }
475 NamedTelemetryStream::Imu => {
476 self.telemetry_rates.imu_hz = rate_hz;
477 self.telemetry_rate_state.imu_us = now_us;
478 }
479 NamedTelemetryStream::Rc => {
480 self.telemetry_rates.rc_hz = rate_hz;
481 self.telemetry_rate_state.rc_us = now_us;
482 }
483 NamedTelemetryStream::Attitude => {
484 self.telemetry_rates.attitude_hz = rate_hz;
485 self.telemetry_rate_state.attitude_us = now_us;
486 }
487 NamedTelemetryStream::OutputRaw => {
488 self.telemetry_rates.output_raw_hz = rate_hz;
489 self.telemetry_rates.output_raw_imu_divisor = 0;
490 self.telemetry_rate_state.output_raw_us = now_us;
491 self.output_raw_imu_count = 0;
492 }
493 NamedTelemetryStream::Gnss => {
494 self.telemetry_rates.gnss_hz = rate_hz;
495 self.telemetry_rate_state.gnss_us = now_us;
496 }
497 NamedTelemetryStream::DiffPressure => {
498 self.telemetry_rates.diff_pressure_hz = rate_hz;
499 self.telemetry_rate_state.diff_pressure_us = now_us;
500 }
501 NamedTelemetryStream::Baro => {
502 self.telemetry_rates.baro_hz = rate_hz;
503 self.telemetry_rate_state.baro_us = now_us;
504 }
505 NamedTelemetryStream::Mag => {
506 self.telemetry_rates.mag_hz = rate_hz;
507 self.telemetry_rate_state.mag_us = now_us;
508 }
509 NamedTelemetryStream::Range => {
510 self.telemetry_rates.range_hz = rate_hz;
511 self.telemetry_rate_state.range_us = now_us;
512 }
513 NamedTelemetryStream::Battery => {
514 self.telemetry_rates.battery_hz = rate_hz;
515 self.telemetry_rate_state.battery_us = now_us;
516 }
517 }
518 true
519 }
520
521 #[cfg(test)]
522 pub(crate) fn telemetry_rates(&self) -> TelemetryRates {
523 self.telemetry_rates
524 }
525
526 pub fn named_telemetry_due<R>(
527 &self,
528 now_us: u64,
529 processed_sensors: &ProcessedSensors<R>,
530 ) -> bool
531 where
532 R: FlightFloat,
533 {
534 self.select_due_named_telemetry_stream(now_us, processed_sensors)
535 .is_some()
536 }
537
538 #[cfg(feature = "runtime-diagnostics")]
539 pub(crate) fn telemetry_sample_was_sent(
540 &self,
541 stream: NamedTelemetryStream,
542 timestamp: u64,
543 ) -> bool {
544 self.telemetry_freshness.last_sent(stream) == Some(timestamp)
545 }
546
547 fn select_due_named_telemetry_stream<R>(
548 &self,
549 now_us: u64,
550 processed_sensors: &ProcessedSensors<R>,
551 ) -> Option<NamedTelemetryStream>
552 where
553 R: FlightFloat,
554 {
555 let mut selected = None;
556 let mut selected_deadline = u64::MAX;
557
558 let consider = |selected: &mut Option<NamedTelemetryStream>,
559 selected_deadline: &mut u64,
560 stream: NamedTelemetryStream| {
561 let Some(deadline) =
562 self.named_telemetry_stream_deadline(stream, now_us, processed_sensors)
563 else {
564 return;
565 };
566 if selected.is_none() || deadline < *selected_deadline {
567 *selected = Some(stream);
568 *selected_deadline = deadline;
569 }
570 };
571
572 consider(
573 &mut selected,
574 &mut selected_deadline,
575 NamedTelemetryStream::Heartbeat,
576 );
577 consider(
578 &mut selected,
579 &mut selected_deadline,
580 NamedTelemetryStream::Status,
581 );
582
583 if processed_sensors.imu.is_some() {
584 consider(
585 &mut selected,
586 &mut selected_deadline,
587 NamedTelemetryStream::Imu,
588 );
589 consider(
590 &mut selected,
591 &mut selected_deadline,
592 NamedTelemetryStream::Attitude,
593 );
594 consider(
595 &mut selected,
596 &mut selected_deadline,
597 NamedTelemetryStream::OutputRaw,
598 );
599 }
600
601 if processed_sensors.rc.is_some() {
602 consider(
603 &mut selected,
604 &mut selected_deadline,
605 NamedTelemetryStream::Rc,
606 );
607 }
608 if processed_sensors.gnss.is_some() {
609 consider(
610 &mut selected,
611 &mut selected_deadline,
612 NamedTelemetryStream::Gnss,
613 );
614 }
615 if processed_sensors.pitot.is_some() {
616 consider(
617 &mut selected,
618 &mut selected_deadline,
619 NamedTelemetryStream::DiffPressure,
620 );
621 }
622 if processed_sensors.baro.is_some() {
623 consider(
624 &mut selected,
625 &mut selected_deadline,
626 NamedTelemetryStream::Baro,
627 );
628 }
629 if processed_sensors.mag.is_some() {
630 consider(
631 &mut selected,
632 &mut selected_deadline,
633 NamedTelemetryStream::Mag,
634 );
635 }
636 if processed_sensors.range.is_some() {
637 consider(
638 &mut selected,
639 &mut selected_deadline,
640 NamedTelemetryStream::Range,
641 );
642 }
643 if processed_sensors.battery.is_some() {
644 consider(
645 &mut selected,
646 &mut selected_deadline,
647 NamedTelemetryStream::Battery,
648 );
649 }
650
651 selected
652 }
653
654 fn named_telemetry_stream_deadline<R>(
655 &self,
656 stream: NamedTelemetryStream,
657 now_us: u64,
658 processed_sensors: &ProcessedSensors<R>,
659 ) -> Option<u64>
660 where
661 R: FlightFloat,
662 {
663 if !matches!(
664 stream,
665 NamedTelemetryStream::Heartbeat | NamedTelemetryStream::Status
666 ) {
667 let timestamp = telemetry_stream_sample_timestamp(stream, processed_sensors)?;
668 if self.telemetry_freshness.last_sent(stream) == Some(timestamp) {
669 return None;
670 }
671 }
672
673 match stream {
674 NamedTelemetryStream::Heartbeat => fixed_rate_due_deadline_us(
675 now_us,
676 self.last_heartbeat_us,
677 self.telemetry_rates.heartbeat_hz,
678 ),
679 NamedTelemetryStream::Status => fixed_rate_due_deadline_us(
680 now_us,
681 self.last_status_send_us,
682 self.telemetry_rates.status_hz,
683 ),
684 NamedTelemetryStream::Imu => stream_due_deadline_us(
685 now_us,
686 self.telemetry_rate_state.imu_us,
687 self.telemetry_rates.imu_hz,
688 ),
689 NamedTelemetryStream::Rc => processed_sensors.rc.as_ref().and_then(|_| {
690 stream_due_deadline_us(
691 now_us,
692 self.telemetry_rate_state.rc_us,
693 self.telemetry_rates.rc_hz,
694 )
695 }),
696 NamedTelemetryStream::Attitude => processed_sensors.imu.as_ref().and_then(|_| {
697 stream_due_deadline_us(
698 now_us,
699 self.telemetry_rate_state.attitude_us,
700 self.telemetry_rates.attitude_hz,
701 )
702 }),
703 NamedTelemetryStream::OutputRaw => processed_sensors.imu.as_ref().and_then(|_| {
704 if self.telemetry_rates.output_raw_hz == 0 {
705 (self.telemetry_rates.output_raw_imu_divisor != 0
706 && self.output_raw_imu_count % self.telemetry_rates.output_raw_imu_divisor
707 == 0)
708 .then_some(now_us)
709 } else {
710 stream_due_deadline_us(
711 now_us,
712 self.telemetry_rate_state.output_raw_us,
713 self.telemetry_rates.output_raw_hz,
714 )
715 }
716 }),
717 NamedTelemetryStream::Gnss => processed_sensors.gnss.as_ref().and_then(|_| {
718 stream_due_deadline_us(
719 now_us,
720 self.telemetry_rate_state.gnss_us,
721 self.telemetry_rates.gnss_hz,
722 )
723 }),
724 NamedTelemetryStream::DiffPressure => processed_sensors.pitot.as_ref().and_then(|_| {
725 stream_due_deadline_us(
726 now_us,
727 self.telemetry_rate_state.diff_pressure_us,
728 self.telemetry_rates.diff_pressure_hz,
729 )
730 }),
731 NamedTelemetryStream::Baro => processed_sensors.baro.as_ref().and_then(|_| {
732 stream_due_deadline_us(
733 now_us,
734 self.telemetry_rate_state.baro_us,
735 self.telemetry_rates.baro_hz,
736 )
737 }),
738 NamedTelemetryStream::Mag => processed_sensors.mag.as_ref().and_then(|_| {
739 stream_due_deadline_us(
740 now_us,
741 self.telemetry_rate_state.mag_us,
742 self.telemetry_rates.mag_hz,
743 )
744 }),
745 NamedTelemetryStream::Range => processed_sensors.range.as_ref().and_then(|_| {
746 stream_due_deadline_us(
747 now_us,
748 self.telemetry_rate_state.range_us,
749 self.telemetry_rates.range_hz,
750 )
751 }),
752 NamedTelemetryStream::Battery => processed_sensors.battery.as_ref().and_then(|_| {
753 stream_due_deadline_us(
754 now_us,
755 self.telemetry_rate_state.battery_us,
756 self.telemetry_rates.battery_hz,
757 )
758 }),
759 }
760 }
761
762 fn targets_this_system(&self, target_system: u8) -> bool {
763 target_system == self.sysid
764 }
765
766 pub fn send_named_telemetry_streams<S, A, R>(&mut self, ctx: TelemetryCtx<'_, B, S, A, R>)
767 where
768 S: AttitudeEstimate,
769 A: AsRef<[R]>,
770 R: FlightFloat,
771 {
772 let TelemetryCtx {
773 board,
774 now_us,
775 state: state_manager,
776 command: command_manager,
777 params,
778 estimator_state,
779 sensors: processed_sensors,
780 actuator_commands,
781 sensor_error_count,
782 loop_time_us,
783 } = ctx;
784
785 if fixed_rate_due(
786 now_us,
787 self.last_heartbeat_us,
788 self.telemetry_rates.heartbeat_hz,
789 ) {
790 self.send_rosflight_heartbeat(
791 board,
792 HeartbeatMsg {
793 autopilot: 0,
794 base_mode: 0,
795 custom_mode: 0,
796 mavlink_version: 0,
797 system_status: 0,
798 type_: if param_int(params, ParamId::PARAM_FIXED_WING) != 0 {
799 MAV_TYPE_FIXED_WING
800 } else {
801 MAV_TYPE_QUADROTOR
802 },
803 },
804 );
805 self.last_heartbeat_us = now_us;
806 }
807
808 if fixed_rate_due(
809 now_us,
810 self.last_status_send_us,
811 self.telemetry_rates.status_hz,
812 ) {
813 self.send_rosflight_status(
814 board,
815 RosflightStatusMsg {
816 armed: state_manager.is_armed() as u8,
817 failsafe: state_manager.is_in_failsafe() as u8,
818 rc_override: command_manager.get_rc_override(),
819 offboard: command_manager.is_offboard_active() as u8,
820 error_code: state_manager.get_errors(),
821 control_mode: command_manager.get_control_mode().into(),
822 num_errors: sensor_error_count as i16,
823 loop_time_us: loop_time_us as i16,
824 },
825 );
826 self.last_status_send_us = now_us;
827 }
828
829 if let Some(imu_packet) = processed_sensors.imu {
830 let imu_timestamp = imu_packet.header.timestamp;
831 if self
832 .telemetry_freshness
833 .last_sent(NamedTelemetryStream::Imu)
834 != Some(imu_timestamp)
835 && stream_due(
836 now_us,
837 &mut self.telemetry_rate_state.imu_us,
838 self.telemetry_rates.imu_hz,
839 )
840 {
841 self.send_rosflight_small_imu(
842 board,
843 SmallImuMsg {
844 temperature: imu_packet.temperature,
845 time_boot_us: imu_packet.header.timestamp,
846 xacc: imu_packet.accel[0].to_f32_lossy(),
847 yacc: imu_packet.accel[1].to_f32_lossy(),
848 zacc: imu_packet.accel[2].to_f32_lossy(),
849 xgyro: imu_packet.gyro[0].to_f32_lossy(),
850 ygyro: imu_packet.gyro[1].to_f32_lossy(),
851 zgyro: imu_packet.gyro[2].to_f32_lossy(),
852 },
853 );
854 self.telemetry_freshness
855 .mark_sent(NamedTelemetryStream::Imu, imu_timestamp);
856 }
857
858 let q = estimator_state.q();
859 let qd = estimator_state.q_dot();
860 let rollspeed = 2.0 * (q[0] * qd[1] - q[1] * qd[0] - q[2] * qd[3] + q[3] * qd[2]);
861 let pitchspeed = 2.0 * (q[0] * qd[2] - q[1] * qd[3] - q[2] * qd[0] + q[3] * qd[1]);
862 let yawspeed = 2.0 * (q[0] * qd[3] - q[1] * qd[2] - q[2] * qd[1] + q[3] * qd[0]);
863
864 if self
865 .telemetry_freshness
866 .last_sent(NamedTelemetryStream::Attitude)
867 != Some(imu_timestamp)
868 && stream_due(
869 now_us,
870 &mut self.telemetry_rate_state.attitude_us,
871 self.telemetry_rates.attitude_hz,
872 )
873 {
874 self.send_rosflight_attitude_quaternion(
875 board,
876 AttitudeQuaternionMsg {
877 time_boot_ms: (imu_packet.header.timestamp / 1000) as u32,
878 q1: q[0],
879 q2: q[1],
880 q3: q[2],
881 q4: q[3],
882 rollspeed,
883 pitchspeed,
884 yawspeed,
885 },
886 );
887 self.telemetry_freshness
888 .mark_sent(NamedTelemetryStream::Attitude, imu_timestamp);
889 }
890
891 if self
892 .telemetry_freshness
893 .last_sent(NamedTelemetryStream::OutputRaw)
894 != Some(imu_timestamp)
895 {
896 if self.telemetry_rates.output_raw_hz == 0 {
897 let output_raw_due = self.telemetry_rates.output_raw_imu_divisor != 0
898 && self.output_raw_imu_count % self.telemetry_rates.output_raw_imu_divisor
899 == 0;
900 if output_raw_due {
901 self.send_output_raw(board, actuator_commands);
902 }
903 self.telemetry_freshness
906 .mark_sent(NamedTelemetryStream::OutputRaw, imu_timestamp);
907 self.output_raw_imu_count = self.output_raw_imu_count.wrapping_add(1);
908 } else {
909 let output_raw_due = stream_due(
910 now_us,
911 &mut self.telemetry_rate_state.output_raw_us,
912 self.telemetry_rates.output_raw_hz,
913 );
914 if output_raw_due {
915 self.send_output_raw(board, actuator_commands);
916 self.telemetry_freshness
917 .mark_sent(NamedTelemetryStream::OutputRaw, imu_timestamp);
918 }
919 }
920 }
921 }
922
923 if let Some(packet) = processed_sensors.pitot {
924 if self
925 .telemetry_freshness
926 .last_sent(NamedTelemetryStream::DiffPressure)
927 != Some(packet.header.timestamp)
928 && stream_due(
929 now_us,
930 &mut self.telemetry_rate_state.diff_pressure_us,
931 self.telemetry_rates.diff_pressure_hz,
932 )
933 {
934 self.send_rosflight_diff_pressure(
935 board,
936 DiffPressureMsg {
937 velocity: packet.indicated_airspeed,
938 diff_pressure: packet.differential_pressure,
939 temperature: packet.temperature,
940 },
941 );
942 self.telemetry_freshness
943 .mark_sent(NamedTelemetryStream::DiffPressure, packet.header.timestamp);
944 }
945 }
946
947 if let Some(packet) = processed_sensors.baro {
948 if self
949 .telemetry_freshness
950 .last_sent(NamedTelemetryStream::Baro)
951 != Some(packet.header.timestamp)
952 && stream_due(
953 now_us,
954 &mut self.telemetry_rate_state.baro_us,
955 self.telemetry_rates.baro_hz,
956 )
957 {
958 self.send_rosflight_small_baro(
959 board,
960 SmallBaroMsg {
961 altitude: packet.altitude,
962 pressure: packet.pressure,
963 temperature: packet.temperature,
964 },
965 );
966 self.telemetry_freshness
967 .mark_sent(NamedTelemetryStream::Baro, packet.header.timestamp);
968 }
969 }
970
971 if let Some(packet) = processed_sensors.mag {
972 if self
973 .telemetry_freshness
974 .last_sent(NamedTelemetryStream::Mag)
975 != Some(packet.header.timestamp)
976 && stream_due(
977 now_us,
978 &mut self.telemetry_rate_state.mag_us,
979 self.telemetry_rates.mag_hz,
980 )
981 {
982 self.send_rosflight_small_mag(
983 board,
984 SmallMagMsg {
985 xmag: packet.flux[0],
986 ymag: packet.flux[1],
987 zmag: packet.flux[2],
988 },
989 );
990 self.telemetry_freshness
991 .mark_sent(NamedTelemetryStream::Mag, packet.header.timestamp);
992 }
993 }
994
995 if let Some(packet) = processed_sensors.range {
996 if self
997 .telemetry_freshness
998 .last_sent(NamedTelemetryStream::Range)
999 != Some(packet.header.timestamp)
1000 && stream_due(
1001 now_us,
1002 &mut self.telemetry_rate_state.range_us,
1003 self.telemetry_rates.range_hz,
1004 )
1005 {
1006 self.send_rosflight_small_range(
1007 board,
1008 SmallRangeMsg {
1009 type_: match packet.range_type {
1010 RangeType::Sonar => RosflightRangeType::RosflightRangeSonar,
1011 RangeType::Lidar => RosflightRangeType::RosflightRangeLidar,
1012 },
1013 range: packet.range,
1014 max_range: packet.max_range,
1015 min_range: packet.min_range,
1016 },
1017 );
1018 self.telemetry_freshness
1019 .mark_sent(NamedTelemetryStream::Range, packet.header.timestamp);
1020 }
1021 }
1022
1023 if let Some(packet) = processed_sensors.battery {
1024 if self
1025 .telemetry_freshness
1026 .last_sent(NamedTelemetryStream::Battery)
1027 != Some(packet.header.timestamp)
1028 && stream_due(
1029 now_us,
1030 &mut self.telemetry_rate_state.battery_us,
1031 self.telemetry_rates.battery_hz,
1032 )
1033 {
1034 self.send_rosflight_battery_status(
1035 board,
1036 BatteryStatusMsg {
1037 battery_voltage: packet.voltage,
1038 battery_current: packet.current,
1039 },
1040 );
1041 self.telemetry_freshness
1042 .mark_sent(NamedTelemetryStream::Battery, packet.header.timestamp);
1043 }
1044 }
1045
1046 if let Some(packet) = processed_sensors.gnss {
1047 if self
1048 .telemetry_freshness
1049 .last_sent(NamedTelemetryStream::Gnss)
1050 != Some(packet.header.timestamp)
1051 && stream_due(
1052 now_us,
1053 &mut self.telemetry_rate_state.gnss_us,
1054 self.telemetry_rates.gnss_hz,
1055 )
1056 {
1057 self.send_rosflight_gnss(
1058 board,
1059 RosflightGnssMsg {
1060 rosflight_timestamp: packet.header.timestamp,
1061 seconds: packet.unix_seconds,
1062 nanos: packet.unix_nanos,
1063 fix_type: packet.fix_type,
1064 num_sat: packet.num_sats,
1065 lat: packet.lat,
1066 lon: packet.lon,
1067 height_msl: packet.height_msl,
1068 vel_n: packet.vel_n,
1069 vel_e: packet.vel_e,
1070 vel_d: packet.vel_d,
1071 s_acc: packet.s_acc,
1072 h_acc: packet.h_acc,
1073 v_acc: packet.v_acc,
1074 },
1075 );
1076 self.telemetry_freshness
1077 .mark_sent(NamedTelemetryStream::Gnss, packet.header.timestamp);
1078 }
1079 }
1080
1081 if let Some(packet) = processed_sensors.rc {
1082 if self.telemetry_freshness.last_sent(NamedTelemetryStream::Rc)
1083 != Some(packet.header.timestamp)
1084 && stream_due(
1085 now_us,
1086 &mut self.telemetry_rate_state.rc_us,
1087 self.telemetry_rates.rc_hz,
1088 )
1089 {
1090 let mut channels = [0u16; RC_PACKET_CHANNELS];
1091 let count = (packet.n_chan as usize).min(8).min(RC_PACKET_CHANNELS);
1092 for (dst, src) in channels.iter_mut().zip(packet.chan.iter()).take(count) {
1093 *dst = (*src * 1000.0 + 1000.0) as u16;
1094 }
1095
1096 self.send_rosflight_rc_raw(
1097 board,
1098 RcChannelsMsg {
1099 time_boot_ms: board.clock_millis(),
1100 chancount: count as u8,
1101 channels,
1102 rssi: 0,
1103 },
1104 );
1105 self.telemetry_freshness
1106 .mark_sent(NamedTelemetryStream::Rc, packet.header.timestamp);
1107 }
1108 }
1109 }
1110
1111 pub fn send_one_named_telemetry_stream<S, A, R>(
1112 &mut self,
1113 mut ctx: TelemetryCtx<'_, B, S, A, R>,
1114 ) -> bool
1115 where
1116 S: AttitudeEstimate,
1117 A: AsRef<[R]>,
1118 R: FlightFloat,
1119 {
1120 let Some(stream) = self.select_due_named_telemetry_stream(ctx.now_us, ctx.sensors) else {
1121 return false;
1122 };
1123
1124 self.send_selected_named_telemetry_stream(stream, &mut ctx)
1125 }
1126
1127 pub fn send_named_telemetry_stream_if_due<S, A, R>(
1128 &mut self,
1129 stream: NamedTelemetryStream,
1130 mut ctx: TelemetryCtx<'_, B, S, A, R>,
1131 ) -> bool
1132 where
1133 S: AttitudeEstimate,
1134 A: AsRef<[R]>,
1135 R: FlightFloat,
1136 {
1137 if self
1138 .named_telemetry_stream_deadline(stream, ctx.now_us, ctx.sensors)
1139 .is_none()
1140 {
1141 return false;
1142 }
1143
1144 self.send_selected_named_telemetry_stream(stream, &mut ctx)
1145 }
1146
1147 pub fn send_named_telemetry_stream_with_gate<S, A, R>(
1148 &mut self,
1149 priority: RealtimeTelemetryPriority,
1150 mut ctx: TelemetryCtx<'_, B, S, A, R>,
1151 ) -> bool
1152 where
1153 S: AttitudeEstimate,
1154 A: AsRef<[R]>,
1155 R: FlightFloat,
1156 {
1157 match priority.gate {
1158 RealtimeTelemetryPriorityGate::DueDeadline => {
1159 self.send_named_telemetry_stream_if_due(priority.stream, ctx)
1160 }
1161 RealtimeTelemetryPriorityGate::FreshSample => {
1162 if priority.stream != NamedTelemetryStream::Imu {
1163 return self.send_named_telemetry_stream_if_due(priority.stream, ctx);
1164 }
1165 self.send_selected_named_telemetry_stream_with_gate(
1166 priority.stream,
1167 priority.gate,
1168 &mut ctx,
1169 )
1170 }
1171 }
1172 }
1173
1174 fn send_selected_named_telemetry_stream<S, A, R>(
1175 &mut self,
1176 stream: NamedTelemetryStream,
1177 ctx: &mut TelemetryCtx<'_, B, S, A, R>,
1178 ) -> bool
1179 where
1180 S: AttitudeEstimate,
1181 A: AsRef<[R]>,
1182 R: FlightFloat,
1183 {
1184 self.send_selected_named_telemetry_stream_with_gate(
1185 stream,
1186 RealtimeTelemetryPriorityGate::DueDeadline,
1187 ctx,
1188 )
1189 }
1190
1191 fn send_selected_named_telemetry_stream_with_gate<S, A, R>(
1192 &mut self,
1193 stream: NamedTelemetryStream,
1194 gate: RealtimeTelemetryPriorityGate,
1195 ctx: &mut TelemetryCtx<'_, B, S, A, R>,
1196 ) -> bool
1197 where
1198 S: AttitudeEstimate,
1199 A: AsRef<[R]>,
1200 R: FlightFloat,
1201 {
1202 let sent = match stream {
1203 NamedTelemetryStream::Heartbeat => {
1204 self.send_rosflight_heartbeat(
1205 ctx.board,
1206 HeartbeatMsg {
1207 autopilot: 0,
1208 base_mode: 0,
1209 custom_mode: 0,
1210 mavlink_version: 0,
1211 system_status: 0,
1212 type_: if param_int(ctx.params, ParamId::PARAM_FIXED_WING) != 0 {
1213 MAV_TYPE_FIXED_WING
1214 } else {
1215 MAV_TYPE_QUADROTOR
1216 },
1217 },
1218 );
1219 self.last_heartbeat_us = ctx.now_us;
1220 true
1221 }
1222 NamedTelemetryStream::Status => {
1223 self.send_rosflight_status(
1224 ctx.board,
1225 RosflightStatusMsg {
1226 armed: ctx.state.is_armed() as u8,
1227 failsafe: ctx.state.is_in_failsafe() as u8,
1228 rc_override: ctx.command.get_rc_override(),
1229 offboard: ctx.command.is_offboard_active() as u8,
1230 error_code: ctx.state.get_errors(),
1231 control_mode: ctx.command.get_control_mode().into(),
1232 num_errors: ctx.sensor_error_count as i16,
1233 loop_time_us: ctx.loop_time_us as i16,
1234 },
1235 );
1236 self.last_status_send_us = ctx.now_us;
1237 true
1238 }
1239 NamedTelemetryStream::Imu => match gate {
1240 RealtimeTelemetryPriorityGate::DueDeadline => {
1241 self.send_imu_if_due(ctx.board, ctx.now_us, ctx.sensors)
1242 }
1243 RealtimeTelemetryPriorityGate::FreshSample => {
1244 self.send_imu_if_fresh(ctx.board, ctx.now_us, ctx.sensors)
1245 }
1246 },
1247 NamedTelemetryStream::Rc => self.send_rc_if_due(ctx.board, ctx.now_us, ctx.sensors),
1248 NamedTelemetryStream::Attitude => match ctx.sensors.imu {
1249 Some(imu_packet) => {
1250 if !stream_due(
1251 ctx.now_us,
1252 &mut self.telemetry_rate_state.attitude_us,
1253 self.telemetry_rates.attitude_hz,
1254 ) {
1255 false
1256 } else {
1257 let q = ctx.estimator_state.q();
1258 let qd = ctx.estimator_state.q_dot();
1259 let rollspeed =
1260 2.0 * (q[0] * qd[1] - q[1] * qd[0] - q[2] * qd[3] + q[3] * qd[2]);
1261 let pitchspeed =
1262 2.0 * (q[0] * qd[2] - q[1] * qd[3] - q[2] * qd[0] + q[3] * qd[1]);
1263 let yawspeed =
1264 2.0 * (q[0] * qd[3] - q[1] * qd[2] - q[2] * qd[1] + q[3] * qd[0]);
1265 self.send_rosflight_attitude_quaternion(
1266 ctx.board,
1267 AttitudeQuaternionMsg {
1268 time_boot_ms: (imu_packet.header.timestamp / 1000) as u32,
1269 q1: q[0],
1270 q2: q[1],
1271 q3: q[2],
1272 q4: q[3],
1273 rollspeed,
1274 pitchspeed,
1275 yawspeed,
1276 },
1277 );
1278 true
1279 }
1280 }
1281 None => false,
1282 },
1283 NamedTelemetryStream::OutputRaw => {
1284 if ctx.sensors.imu.is_none() {
1285 false
1286 } else {
1287 let output_raw_due = if self.telemetry_rates.output_raw_hz == 0 {
1288 self.telemetry_rates.output_raw_imu_divisor != 0
1289 && self.output_raw_imu_count
1290 % self.telemetry_rates.output_raw_imu_divisor
1291 == 0
1292 } else {
1293 stream_due(
1294 ctx.now_us,
1295 &mut self.telemetry_rate_state.output_raw_us,
1296 self.telemetry_rates.output_raw_hz,
1297 )
1298 };
1299 self.output_raw_imu_count = self.output_raw_imu_count.wrapping_add(1);
1300 if !output_raw_due {
1301 false
1302 } else {
1303 self.send_output_raw(ctx.board, ctx.actuator_commands);
1304 true
1305 }
1306 }
1307 }
1308 NamedTelemetryStream::Gnss => match ctx.sensors.gnss {
1309 Some(packet) => {
1310 if !stream_due(
1311 ctx.now_us,
1312 &mut self.telemetry_rate_state.gnss_us,
1313 self.telemetry_rates.gnss_hz,
1314 ) {
1315 false
1316 } else {
1317 self.send_rosflight_gnss(
1318 ctx.board,
1319 RosflightGnssMsg {
1320 rosflight_timestamp: packet.header.timestamp,
1321 seconds: packet.unix_seconds,
1322 nanos: packet.unix_nanos,
1323 fix_type: packet.fix_type,
1324 num_sat: packet.num_sats,
1325 lat: packet.lat,
1326 lon: packet.lon,
1327 height_msl: packet.height_msl,
1328 vel_n: packet.vel_n,
1329 vel_e: packet.vel_e,
1330 vel_d: packet.vel_d,
1331 s_acc: packet.s_acc,
1332 h_acc: packet.h_acc,
1333 v_acc: packet.v_acc,
1334 },
1335 );
1336 true
1337 }
1338 }
1339 None => false,
1340 },
1341 NamedTelemetryStream::DiffPressure => match ctx.sensors.pitot {
1342 Some(packet) => {
1343 if !stream_due(
1344 ctx.now_us,
1345 &mut self.telemetry_rate_state.diff_pressure_us,
1346 self.telemetry_rates.diff_pressure_hz,
1347 ) {
1348 false
1349 } else {
1350 self.send_rosflight_diff_pressure(
1351 ctx.board,
1352 DiffPressureMsg {
1353 velocity: packet.indicated_airspeed,
1354 diff_pressure: packet.differential_pressure,
1355 temperature: packet.temperature,
1356 },
1357 );
1358 true
1359 }
1360 }
1361 None => false,
1362 },
1363 NamedTelemetryStream::Baro => match ctx.sensors.baro {
1364 Some(packet) => {
1365 if !stream_due(
1366 ctx.now_us,
1367 &mut self.telemetry_rate_state.baro_us,
1368 self.telemetry_rates.baro_hz,
1369 ) {
1370 false
1371 } else {
1372 self.send_rosflight_small_baro(
1373 ctx.board,
1374 SmallBaroMsg {
1375 altitude: packet.altitude,
1376 pressure: packet.pressure,
1377 temperature: packet.temperature,
1378 },
1379 );
1380 true
1381 }
1382 }
1383 None => false,
1384 },
1385 NamedTelemetryStream::Mag => match ctx.sensors.mag {
1386 Some(packet) => {
1387 if !stream_due(
1388 ctx.now_us,
1389 &mut self.telemetry_rate_state.mag_us,
1390 self.telemetry_rates.mag_hz,
1391 ) {
1392 false
1393 } else {
1394 self.send_rosflight_small_mag(
1395 ctx.board,
1396 SmallMagMsg {
1397 xmag: packet.flux[0],
1398 ymag: packet.flux[1],
1399 zmag: packet.flux[2],
1400 },
1401 );
1402 true
1403 }
1404 }
1405 None => false,
1406 },
1407 NamedTelemetryStream::Range => match ctx.sensors.range {
1408 Some(packet) => {
1409 if !stream_due(
1410 ctx.now_us,
1411 &mut self.telemetry_rate_state.range_us,
1412 self.telemetry_rates.range_hz,
1413 ) {
1414 false
1415 } else {
1416 self.send_rosflight_small_range(
1417 ctx.board,
1418 SmallRangeMsg {
1419 type_: match packet.range_type {
1420 RangeType::Sonar => RosflightRangeType::RosflightRangeSonar,
1421 RangeType::Lidar => RosflightRangeType::RosflightRangeLidar,
1422 },
1423 range: packet.range,
1424 max_range: packet.max_range,
1425 min_range: packet.min_range,
1426 },
1427 );
1428 true
1429 }
1430 }
1431 None => false,
1432 },
1433 NamedTelemetryStream::Battery => match ctx.sensors.battery {
1434 Some(packet) => {
1435 if !stream_due(
1436 ctx.now_us,
1437 &mut self.telemetry_rate_state.battery_us,
1438 self.telemetry_rates.battery_hz,
1439 ) {
1440 false
1441 } else {
1442 self.send_rosflight_battery_status(
1443 ctx.board,
1444 BatteryStatusMsg {
1445 battery_voltage: packet.voltage,
1446 battery_current: packet.current,
1447 },
1448 );
1449 true
1450 }
1451 }
1452 None => false,
1453 },
1454 };
1455 if sent {
1456 #[cfg(feature = "runtime-diagnostics")]
1457 match stream {
1458 NamedTelemetryStream::Imu => {
1459 VELOXITY_DIAG_TELEM_IMU_SENT.fetch_add(1, Ordering::Relaxed)
1460 }
1461 NamedTelemetryStream::Mag => {
1462 VELOXITY_DIAG_TELEM_MAG_SENT.fetch_add(1, Ordering::Relaxed)
1463 }
1464 NamedTelemetryStream::Baro => {
1465 VELOXITY_DIAG_TELEM_BARO_SENT.fetch_add(1, Ordering::Relaxed)
1466 }
1467 NamedTelemetryStream::DiffPressure => {
1468 VELOXITY_DIAG_TELEM_PITOT_SENT.fetch_add(1, Ordering::Relaxed)
1469 }
1470 NamedTelemetryStream::Range => {
1471 VELOXITY_DIAG_TELEM_RANGE_SENT.fetch_add(1, Ordering::Relaxed)
1472 }
1473 NamedTelemetryStream::Gnss => {
1474 VELOXITY_DIAG_TELEM_GNSS_SENT.fetch_add(1, Ordering::Relaxed)
1475 }
1476 NamedTelemetryStream::Battery => {
1477 VELOXITY_DIAG_TELEM_BATTERY_SENT.fetch_add(1, Ordering::Relaxed)
1478 }
1479 NamedTelemetryStream::Rc => {
1480 VELOXITY_DIAG_TELEM_RC_SENT.fetch_add(1, Ordering::Relaxed)
1481 }
1482 NamedTelemetryStream::Heartbeat
1483 | NamedTelemetryStream::Status
1484 | NamedTelemetryStream::Attitude
1485 | NamedTelemetryStream::OutputRaw => 0,
1486 };
1487 if let Some(timestamp) = telemetry_stream_sample_timestamp(stream, ctx.sensors) {
1488 self.telemetry_freshness.mark_sent(stream, timestamp);
1489 }
1490 }
1491 sent
1492 }
1493
1494 fn send_imu_if_due<R>(
1495 &mut self,
1496 board: &mut B,
1497 now_us: u64,
1498 processed_sensors: &ProcessedSensors<R>,
1499 ) -> bool
1500 where
1501 R: FlightFloat,
1502 {
1503 let Some(imu_packet) = processed_sensors.imu else {
1504 return false;
1505 };
1506 if self
1507 .telemetry_freshness
1508 .last_sent(NamedTelemetryStream::Imu)
1509 == Some(imu_packet.header.timestamp)
1510 {
1511 return false;
1512 }
1513 if !stream_due(
1514 now_us,
1515 &mut self.telemetry_rate_state.imu_us,
1516 self.telemetry_rates.imu_hz,
1517 ) {
1518 return false;
1519 }
1520 self.send_rosflight_small_imu(
1521 board,
1522 SmallImuMsg {
1523 temperature: imu_packet.temperature,
1524 time_boot_us: imu_packet.header.timestamp,
1525 xacc: imu_packet.accel[0].to_f32_lossy(),
1526 yacc: imu_packet.accel[1].to_f32_lossy(),
1527 zacc: imu_packet.accel[2].to_f32_lossy(),
1528 xgyro: imu_packet.gyro[0].to_f32_lossy(),
1529 ygyro: imu_packet.gyro[1].to_f32_lossy(),
1530 zgyro: imu_packet.gyro[2].to_f32_lossy(),
1531 },
1532 );
1533 true
1534 }
1535
1536 fn send_imu_if_fresh<R>(
1537 &mut self,
1538 board: &mut B,
1539 now_us: u64,
1540 processed_sensors: &ProcessedSensors<R>,
1541 ) -> bool
1542 where
1543 R: FlightFloat,
1544 {
1545 let Some(imu_packet) = processed_sensors.imu else {
1546 return false;
1547 };
1548 if self
1549 .telemetry_freshness
1550 .last_sent(NamedTelemetryStream::Imu)
1551 == Some(imu_packet.header.timestamp)
1552 {
1553 return false;
1554 }
1555 self.send_rosflight_small_imu(
1556 board,
1557 SmallImuMsg {
1558 temperature: imu_packet.temperature,
1559 time_boot_us: imu_packet.header.timestamp,
1560 xacc: imu_packet.accel[0].to_f32_lossy(),
1561 yacc: imu_packet.accel[1].to_f32_lossy(),
1562 zacc: imu_packet.accel[2].to_f32_lossy(),
1563 xgyro: imu_packet.gyro[0].to_f32_lossy(),
1564 ygyro: imu_packet.gyro[1].to_f32_lossy(),
1565 zgyro: imu_packet.gyro[2].to_f32_lossy(),
1566 },
1567 );
1568 self.telemetry_rate_state.imu_us = now_us;
1569 true
1570 }
1571
1572 fn send_rc_if_due<R>(
1573 &mut self,
1574 board: &mut B,
1575 now_us: u64,
1576 processed_sensors: &ProcessedSensors<R>,
1577 ) -> bool
1578 where
1579 R: FlightFloat,
1580 {
1581 let Some(packet) = processed_sensors.rc else {
1582 return false;
1583 };
1584 if !stream_due(
1585 now_us,
1586 &mut self.telemetry_rate_state.rc_us,
1587 self.telemetry_rates.rc_hz,
1588 ) {
1589 return false;
1590 }
1591 let mut channels = [0u16; RC_PACKET_CHANNELS];
1592 let count = (packet.n_chan as usize).min(8).min(RC_PACKET_CHANNELS);
1593 for (dst, src) in channels.iter_mut().zip(packet.chan.iter()).take(count) {
1594 *dst = (*src * 1000.0 + 1000.0) as u16;
1595 }
1596 self.send_rosflight_rc_raw(
1597 board,
1598 RcChannelsMsg {
1599 time_boot_ms: board.clock_millis(),
1600 chancount: count as u8,
1601 channels,
1602 rssi: 0,
1603 },
1604 );
1605 true
1606 }
1607
1608 fn send_output_raw<A, R>(&mut self, board: &mut B, actuator_commands: &A)
1609 where
1610 A: AsRef<[R]>,
1611 R: FlightFloat,
1612 {
1613 let mut values = [0.0f32; 14];
1614 for (dst, src) in values.iter_mut().zip(actuator_commands.as_ref().iter()) {
1615 *dst = src.to_f32_lossy();
1616 }
1617 self.send_rosflight_output_raw(
1618 board,
1619 RosflightOutputRawMsg {
1620 stamp: board.clock_millis() as u64,
1621 values,
1622 },
1623 );
1624 }
1625
1626 pub fn act_on_messages(
1627 &mut self,
1628 param_events: &mut ParamEventQueues,
1629 comm_events: &mut CommEventQueues,
1630 command_events: &mut CommandEventQueues,
1631 companion_events: &mut CompanionEventQueues,
1632 board: &mut B,
1633 ) {
1634 if let Some(msg) = self.msgs.heartbeat.take() {
1635 companion_events
1636 .heartbeats
1637 .push_or_log(CompanionHeartbeatReceived { msg }, "companion heartbeat");
1638 }
1639
1640 while let Some(msg) = Store::<ParamRequestReadMsg>::take(&mut self.msgs) {
1641 if self.targets_this_system(msg.target_system) {
1642 param_events.read_requests.push_or_log(
1643 ParamReadRequested {
1644 identifier: msg.param_identifier,
1645 },
1646 "param read request",
1647 );
1648 }
1649 }
1650
1651 if let Some(msg) = self.msgs.param_request_list.take() {
1652 if self.targets_this_system(msg.target_system) {
1653 param_events
1654 .list_requests
1655 .push_or_log(ParamListRequested, "param list request");
1656 }
1657 }
1658
1659 let msg_opt: Option<TimesyncMsg> = self.msgs.timesync.take();
1661 if let Some(mut msg) = msg_opt {
1662 if msg.tc1 == 0 {
1663 msg.tc1 = (board.clock_micros() * 1000) as i64;
1664 self.send_timesync(board, msg);
1665 #[cfg(feature = "runtime-diagnostics")]
1666 VELOXITY_DIAG_TIMESYNC_RESPONSE_SENT.fetch_add(1, Ordering::Relaxed);
1667 }
1668 }
1669
1670 if let Some(msg) = self.msgs.offboard_control.take() {
1671 let now_us = board.clock_micros();
1672 command_events
1673 .offboard_control_requests
1674 .push_or_log(OffboardControlRequested { now_us, msg }, "offboard control");
1675 }
1676
1677 if let Some(msg) = self.msgs.aux_cmd.take() {
1678 companion_events
1679 .aux_commands
1680 .push_or_log(AuxCommandReceived { msg }, "aux command");
1681 }
1682
1683 if let Some(msg) = self.msgs.external_attitude.take() {
1684 companion_events
1685 .external_attitudes
1686 .push_or_log(ExternalAttitudeReceived { msg }, "external attitude");
1687 }
1688
1689 while !param_events.set_requests.is_full() {
1690 let Some(msg) = Store::<ParamSetMsg>::take(&mut self.msgs) else {
1691 break;
1692 };
1693
1694 if self.targets_this_system(msg.target_system) {
1695 let pushed = param_events.set_requests.push_or_log(
1696 ParamSetRequested {
1697 value: msg.param_value,
1698 param_id_bytes: msg.param_id,
1699 },
1700 "param set request",
1701 );
1702 debug_assert!(pushed);
1703 }
1704 }
1705
1706 let cmd_msg_opt = self.msgs.cmd.take();
1709 if let Some(msg) = cmd_msg_opt {
1710 let success = RosflightCmdResponse::RosflightCmdFailed;
1712 let mut send_ack_now = true;
1713
1714 match msg.command {
1715 RosflightCmd::RcCalibration => {
1716 if command_events.rc_trim_calibration_requests.push_or_log(
1717 RcTrimCalibrationRequested {
1718 command: msg.command,
1719 },
1720 "rc trim calibration",
1721 ) {
1722 send_ack_now = false;
1723 }
1724 }
1725 RosflightCmd::AccelCalibration => {
1726 if command_events.calibration_requests.push_or_log(
1727 CalibrationRequested {
1728 command: msg.command,
1729 },
1730 "accel calibration",
1731 ) {
1732 send_ack_now = false;
1733 }
1734 }
1735 RosflightCmd::GyroCalibration => {
1736 if command_events.calibration_requests.push_or_log(
1737 CalibrationRequested {
1738 command: msg.command,
1739 },
1740 "gyro calibration",
1741 ) {
1742 send_ack_now = false;
1743 }
1744 }
1745 RosflightCmd::BaroCalibration => {
1746 if command_events.calibration_requests.push_or_log(
1747 CalibrationRequested {
1748 command: msg.command,
1749 },
1750 "baro calibration",
1751 ) {
1752 send_ack_now = false;
1753 }
1754 }
1755 RosflightCmd::AirspeedCalibration => {
1756 if command_events.calibration_requests.push_or_log(
1757 CalibrationRequested {
1758 command: msg.command,
1759 },
1760 "airspeed calibration",
1761 ) {
1762 send_ack_now = false;
1763 }
1764 }
1765 RosflightCmd::ReadParams => {
1766 if command_events.board_command_requests.push_or_log(
1767 BoardCommandRequested {
1768 command: msg.command,
1769 },
1770 "read params command",
1771 ) {
1772 send_ack_now = false;
1773 }
1774 }
1775 RosflightCmd::WriteParams => {
1776 if command_events.board_command_requests.push_or_log(
1777 BoardCommandRequested {
1778 command: msg.command,
1779 },
1780 "write params command",
1781 ) {
1782 send_ack_now = false;
1783 }
1784 }
1785 RosflightCmd::SetParamDefaults => {
1786 if command_events.param_defaults_requests.push_or_log(
1787 ParamDefaultsRequested {
1788 command: msg.command,
1789 },
1790 "param defaults command",
1791 ) {
1792 send_ack_now = false;
1793 }
1794 }
1795 RosflightCmd::Reboot => {
1796 if command_events.board_command_requests.push_or_log(
1797 BoardCommandRequested {
1798 command: msg.command,
1799 },
1800 "reboot command",
1801 ) {
1802 send_ack_now = false;
1803 }
1804 }
1805 RosflightCmd::RebootToBootloader => {
1806 if command_events.board_command_requests.push_or_log(
1807 BoardCommandRequested {
1808 command: msg.command,
1809 },
1810 "bootloader command",
1811 ) {
1812 send_ack_now = false;
1813 }
1814 }
1815 RosflightCmd::SendVersion => {
1816 if command_events.version_requests.push_or_log(
1817 VersionRequested {
1818 command: msg.command,
1819 },
1820 "version command",
1821 ) {
1822 send_ack_now = false;
1823 }
1824 }
1825 RosflightCmd::ResetOrigin => {
1826 if command_events.reset_origin_requests.push_or_log(
1827 ResetOriginRequested {
1828 command: msg.command,
1829 },
1830 "reset origin command",
1831 ) {
1832 send_ack_now = false;
1833 }
1834 }
1835 RosflightCmd::SendAllConfigInfos => {
1836 if command_events.config_info_requests.push_or_log(
1837 ConfigInfoRequested {
1838 command: msg.command,
1839 },
1840 "config info command",
1841 ) {
1842 send_ack_now = false;
1843 }
1844 }
1845 } if send_ack_now {
1848 let ack_msg = RosflightCmdAckMsg {
1849 command: msg.command,
1850 success,
1851 };
1852 comm_events
1853 .responses
1854 .push_or_log(CommResponse::CmdAck(ack_msg), "command ack response");
1855 }
1856 } }
1858
1859 pub fn send_comm_responses(&mut self, board: &mut B, comm_events: &mut CommEventQueues) {
1860 self.send_comm_responses_limited(board, comm_events, usize::MAX);
1861 }
1862
1863 pub fn send_comm_responses_limited(
1864 &mut self,
1865 board: &mut B,
1866 comm_events: &mut CommEventQueues,
1867 max_responses: usize,
1868 ) -> usize {
1869 let mut sent = 0;
1870 while sent < max_responses
1871 && let Some(response) = comm_events.responses.pop()
1872 {
1873 match response {
1874 CommResponse::ParamValue(msg) => {
1875 if msg.param_index == ParamId::PARAM_SYSTEM_ID as u16 {
1876 if let ParamValue::Int(new_sysid) = msg.param_value {
1877 self.sysid = new_sysid as u8;
1878 }
1879 }
1880 self.comm_link.send_named_value(board, self.sysid, msg);
1881 }
1882 CommResponse::CmdAck(msg) => {
1883 self.comm_link.send_cmd_ack(board, self.sysid, msg);
1884 }
1885 CommResponse::Version(msg) => {
1886 self.comm_link.send_version(board, self.sysid, msg);
1887 }
1888 CommResponse::Statustext(msg) => {
1889 self.comm_link.send_statustext(board, self.sysid, msg);
1890 }
1891 CommResponse::HardError(msg) => {
1892 self.comm_link.send_hard_error(board, self.sysid, msg);
1893 }
1894 }
1895 sent += 1;
1896 }
1897 sent
1898 }
1899
1900 pub fn send_timesync(&mut self, board: &mut B, msg: TimesyncMsg) {
1901 self.comm_link.send_timesync(board, self.sysid, msg);
1902 }
1903
1904 pub fn send_rosflight_heartbeat(&mut self, board: &mut B, msg: HeartbeatMsg) {
1905 self.comm_link.send_heartbeat(board, self.sysid, msg);
1906 }
1907
1908 pub fn send_rosflight_status(&mut self, board: &mut B, msg: RosflightStatusMsg) {
1909 self.comm_link.send_status(board, self.sysid, msg);
1910 }
1911
1912 pub fn send_rosflight_attitude_quaternion(
1913 &mut self,
1914 board: &mut B,
1915 msg: AttitudeQuaternionMsg,
1916 ) {
1917 self.comm_link.send_attitude(board, self.sysid, msg);
1918 }
1919
1920 pub fn send_rosflight_small_imu(&mut self, board: &mut B, msg: SmallImuMsg) {
1921 self.comm_link.send_imu(board, self.sysid, msg);
1922 }
1923
1924 pub fn send_rosflight_small_baro(&mut self, board: &mut B, msg: SmallBaroMsg) {
1925 self.comm_link.send_baro(board, self.sysid, msg);
1926 }
1927
1928 pub fn send_rosflight_diff_pressure(&mut self, board: &mut B, msg: DiffPressureMsg) {
1929 self.comm_link.send_diff_pressure(board, self.sysid, msg);
1930 }
1931
1932 pub fn send_rosflight_small_mag(&mut self, board: &mut B, msg: SmallMagMsg) {
1933 self.comm_link.send_mag(board, self.sysid, msg);
1934 }
1935
1936 pub fn send_rosflight_small_range(&mut self, board: &mut B, msg: SmallRangeMsg) {
1937 self.comm_link.send_range(board, self.sysid, msg);
1938 }
1939
1940 pub fn send_rosflight_battery_status(&mut self, board: &mut B, msg: BatteryStatusMsg) {
1941 self.comm_link.send_battery_status(board, self.sysid, msg);
1942 }
1943
1944 pub fn send_rosflight_gnss(&mut self, board: &mut B, msg: RosflightGnssMsg) {
1945 self.comm_link.send_gnss(board, self.sysid, msg);
1946 }
1947
1948 pub fn send_rosflight_rc_raw(&mut self, board: &mut B, msg: RcChannelsMsg) {
1949 self.comm_link.send_rc_raw(board, self.sysid, msg);
1950 }
1951
1952 pub fn send_rosflight_output_raw(&mut self, board: &mut B, msg: RosflightOutputRawMsg) {
1953 self.comm_link.send_output_raw(board, self.sysid, msg);
1954 }
1955}
1956
1957#[cfg(test)]
1958mod tests {
1959 use super::*;
1960 use crate::{
1961 board::BoardIo,
1962 comm::messages::{
1963 enums::{
1964 OffboardControlIgnore, OffboardControlMode, ParamIdentifier, RosflightAuxCmdType,
1965 RosflightCmd, RosflightCmdResponse,
1966 },
1967 messages::{
1968 ExternalAttitudeMsg, HeartbeatMsg, OffboardControlMsg, ParamRequestListMsg,
1969 ParamRequestReadMsg, ParamSetMsg, RosflightAuxCmdMsg, RosflightCmdMsg, TimesyncMsg,
1970 },
1971 },
1972 command::CommandManager,
1973 command::service::{self as command_service, CommandRequestCtx},
1974 controller::quad::QuadController,
1975 events::{
1976 CommEventQueues, CommResponse, CommandEventQueues, CompanionEventQueues,
1977 ParamEventQueues,
1978 },
1979 params::service::{self as param_service, ParamListState, ParamServiceCtx},
1980 params::{ParamId, ParamValue, Params},
1981 sensors::ProcessedSensors,
1982 sensors::processors::CalibrationFlags,
1983 state_machine::{Event, StateManager},
1984 test_support::{RecordingCommLink, TestBoard},
1985 };
1986
1987 fn initialized_state() -> StateManager {
1988 let params = Params::new();
1989 let mut state = StateManager::new();
1990 state.update(Event::INITIALIZED, ¶ms);
1991 state
1992 }
1993
1994 #[test]
1995 fn live_telemetry_update_changes_only_one_stream_and_restarts_its_period() {
1996 let mut params = Params::new();
1997 let mut manager =
1998 CommManager::<TestBoard, RecordingCommLink>::new(RecordingCommLink::new(), 10);
1999 manager.configure_telemetry_from_params(¶ms);
2000 let original = manager.telemetry_rates();
2001
2002 params.set_by_id(ParamId::PARAM_TELEM_BARO_HZ, ParamValue::Int(20));
2003 assert!(manager.update_telemetry_param(¶ms, ParamId::PARAM_TELEM_BARO_HZ, 1_000_000,));
2004
2005 let updated = manager.telemetry_rates();
2006 assert_eq!(updated.baro_hz, 20);
2007 assert_eq!(updated.imu_hz, original.imu_hz);
2008 assert_eq!(updated.rc_hz, original.rc_hz);
2009 assert_eq!(manager.telemetry_rate_state.baro_us, 1_000_000);
2010 assert!(stream_due_deadline_us(1_049_999, 1_000_000, updated.baro_hz).is_none());
2011 assert!(stream_due_deadline_us(1_050_000, 1_000_000, updated.baro_hz).is_some());
2012 }
2013
2014 #[test]
2015 fn telemetry_rate_minus_one_disables_and_zero_is_always_eligible() {
2016 let mut params = Params::new();
2017 params.set_by_id(ParamId::PARAM_TELEM_BARO_HZ, ParamValue::Int(-1));
2018 let disabled = TelemetryRates::from_params(¶ms).baro_hz;
2019 assert_eq!(disabled, TELEMETRY_RATE_DISABLED);
2020 assert!(!stream_due(1_000, &mut 0, disabled));
2021 assert_eq!(stream_due_deadline_us(1_000, 0, disabled), None);
2022
2023 params.set_by_id(ParamId::PARAM_TELEM_BARO_HZ, ParamValue::Int(0));
2024 let whenever = TelemetryRates::from_params(¶ms).baro_hz;
2025 assert!(stream_due(1_000, &mut 0, whenever));
2026 assert!(stream_due(1_001, &mut 1_000, whenever));
2027 }
2028
2029 fn apply_test_command_requests(
2030 command_events: &mut CommandEventQueues,
2031 comm_events: &mut CommEventQueues,
2032 board: &mut TestBoard,
2033 params: &mut Params,
2034 flags: &mut CalibrationFlags,
2035 ) {
2036 let state = initialized_state();
2037 let mut command = CommandManager::new();
2038 let mut controller = QuadController::<f64>::default();
2039 let mut param_events = ParamEventQueues::default();
2040 command_service::apply_command_requests(&mut CommandRequestCtx {
2041 requests: command_events,
2042 param_events: &mut param_events,
2043 comm_events,
2044 state: &state,
2045 command: &mut command,
2046 controller: &mut controller,
2047 board,
2048 flags,
2049 params,
2050 });
2051 }
2052
2053 fn apply_test_param_service(
2054 params: &mut Params,
2055 param_list_state: &mut ParamListState,
2056 param_events: &mut ParamEventQueues,
2057 comm_events: &mut CommEventQueues,
2058 ) {
2059 param_service::service_param_events(&mut ParamServiceCtx {
2060 params,
2061 state: param_list_state,
2062 events: param_events,
2063 comm_events,
2064 });
2065 }
2066
2067 fn telemetry_ctx<'a, S, A, R>(
2068 board: &'a mut TestBoard,
2069 now_us: u64,
2070 state: &'a StateManager,
2071 command: &'a CommandManager,
2072 params: &'a Params,
2073 estimator_state: &'a S,
2074 sensors: &'a ProcessedSensors<R>,
2075 actuator_commands: &'a A,
2076 ) -> TelemetryCtx<'a, TestBoard, S, A, R>
2077 where
2078 R: FlightFloat,
2079 {
2080 TelemetryCtx {
2081 board,
2082 now_us,
2083 state,
2084 command,
2085 params,
2086 estimator_state,
2087 sensors,
2088 actuator_commands,
2089 sensor_error_count: 0,
2090 loop_time_us: 0,
2091 }
2092 }
2093
2094 fn companion_events() -> CompanionEventQueues {
2095 CompanionEventQueues::default()
2096 }
2097
2098 #[test]
2099 fn stream_due_preserves_deadline_cadence_after_late_send() {
2100 let mut last_us = 0;
2101
2102 assert!(stream_due(1_000, &mut last_us, 400));
2103 assert_eq!(last_us, 1_000);
2104
2105 assert!(!stream_due(3_400, &mut last_us, 400));
2106 assert_eq!(last_us, 1_000);
2107
2108 assert!(stream_due(3_700, &mut last_us, 400));
2109 assert_eq!(last_us, 3_500);
2110
2111 assert!(!stream_due(5_900, &mut last_us, 400));
2112 assert_eq!(last_us, 3_500);
2113
2114 assert!(stream_due(6_100, &mut last_us, 400));
2115 assert_eq!(last_us, 6_000);
2116
2117 assert!(stream_due(13_900, &mut last_us, 400));
2118 assert_eq!(last_us, 13_500);
2119 }
2120
2121 #[test]
2122 fn param_set_emits_request_without_mutating_or_acknowledging() {
2123 let mut board = TestBoard::default();
2124 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2125 let params = Params::new();
2126 let mut param_events = ParamEventQueues::default();
2127 let mut comm_events = CommEventQueues::default();
2128 let mut command_events = CommandEventQueues::default();
2129
2130 manager.msgs.store(ParamSetMsg {
2131 target_system: 1,
2132 target_component: 1,
2133 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2134 param_value: ParamValue::Int(42),
2135 });
2136
2137 manager.act_on_messages(
2138 &mut param_events,
2139 &mut comm_events,
2140 &mut command_events,
2141 &mut companion_events(),
2142 &mut board,
2143 );
2144
2145 assert_eq!(
2146 params.get_by_id(ParamId::PARAM_SYSTEM_ID),
2147 ParamValue::Int(1)
2148 );
2149 assert_eq!(manager.comm_link.sent_param_value_count, 0);
2150
2151 let request = param_events.set_requests.pop().unwrap();
2152 assert_eq!(request.value, ParamValue::Int(42));
2153 assert_eq!(request.param_id_bytes, *b"SYS_ID\0\0\0\0\0\0\0\0\0\0");
2154 }
2155
2156 #[test]
2157 fn param_set_ingress_waits_when_ecs_queue_is_full() {
2158 let mut board = TestBoard::default();
2159 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2160 let mut param_events = ParamEventQueues::default();
2161 let mut comm_events = CommEventQueues::default();
2162 let mut command_events = CommandEventQueues::default();
2163
2164 for value in 10..15 {
2165 manager.msgs.store(ParamSetMsg {
2166 target_system: 1,
2167 target_component: 1,
2168 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2169 param_value: ParamValue::Int(value),
2170 });
2171 }
2172
2173 manager.act_on_messages(
2174 &mut param_events,
2175 &mut comm_events,
2176 &mut command_events,
2177 &mut companion_events(),
2178 &mut board,
2179 );
2180
2181 assert!(!param_events.set_requests.is_full());
2182 assert_eq!(param_events.set_requests.len(), 5);
2183 assert_eq!(manager.msgs.param_set.len(), 0);
2184
2185 let _ = param_events.set_requests.pop();
2186 manager.act_on_messages(
2187 &mut param_events,
2188 &mut comm_events,
2189 &mut command_events,
2190 &mut companion_events(),
2191 &mut board,
2192 );
2193
2194 assert!(!param_events.set_requests.is_full());
2195 assert_eq!(manager.msgs.param_set.len(), 0);
2196 let values: heapless::Vec<_, 8> = param_events
2197 .set_requests
2198 .iter()
2199 .map(|req| req.value)
2200 .collect();
2201 assert_eq!(
2202 values.as_slice(),
2203 &[
2204 ParamValue::Int(11),
2205 ParamValue::Int(12),
2206 ParamValue::Int(13),
2207 ParamValue::Int(14),
2208 ]
2209 );
2210 }
2211
2212 #[test]
2213 fn param_set_ingress_accepts_full_companion_param_burst() {
2214 let mut board = TestBoard::default();
2215 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2216 let mut param_events = ParamEventQueues::default();
2217 let mut comm_events = CommEventQueues::default();
2218 let mut command_events = CommandEventQueues::default();
2219
2220 for value in 0..360 {
2221 manager.msgs.store(ParamSetMsg {
2222 target_system: 1,
2223 target_component: 1,
2224 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2225 param_value: ParamValue::Int(value),
2226 });
2227 }
2228
2229 assert_eq!(manager.msgs.param_set.len(), 360);
2230
2231 manager.act_on_messages(
2232 &mut param_events,
2233 &mut comm_events,
2234 &mut command_events,
2235 &mut companion_events(),
2236 &mut board,
2237 );
2238
2239 assert_eq!(manager.msgs.param_set.len(), 0);
2240 assert_eq!(param_events.set_requests.len(), 360);
2241 assert_eq!(
2242 param_events.set_requests.iter().next().unwrap().value,
2243 ParamValue::Int(0)
2244 );
2245 assert_eq!(
2246 param_events.set_requests.iter().last().unwrap().value,
2247 ParamValue::Int(359)
2248 );
2249 }
2250
2251 #[test]
2252 fn param_messages_for_other_system_are_ignored() {
2253 let mut board = TestBoard::default();
2254 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2255 let mut param_events = ParamEventQueues::default();
2256 let mut comm_events = CommEventQueues::default();
2257 let mut command_events = CommandEventQueues::default();
2258
2259 manager.msgs.store(ParamSetMsg {
2260 target_system: 42,
2261 target_component: 1,
2262 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2263 param_value: ParamValue::Int(42),
2264 });
2265 manager.msgs.param_request_list = Some(ParamRequestListMsg {
2266 target_system: 42,
2267 target_component: 1,
2268 });
2269 manager.msgs.store(ParamRequestReadMsg {
2270 target_system: 42,
2271 target_component: 1,
2272 param_identifier: ParamIdentifier::ID(*b"SYS_ID\0\0\0\0\0\0\0\0\0\0"),
2273 });
2274
2275 manager.act_on_messages(
2276 &mut param_events,
2277 &mut comm_events,
2278 &mut command_events,
2279 &mut companion_events(),
2280 &mut board,
2281 );
2282
2283 assert!(param_events.set_requests.is_empty());
2284 assert!(param_events.list_requests.is_empty());
2285 assert!(param_events.read_requests.is_empty());
2286 }
2287
2288 #[test]
2289 fn param_request_list_emits_request_without_streaming_from_comms() {
2290 let mut board = TestBoard::default();
2291 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2292 let mut params = Params::new();
2293 let mut param_list_state = ParamListState::default();
2294 let mut param_events = ParamEventQueues::default();
2295 let mut comm_events = CommEventQueues::default();
2296 let mut command_events = CommandEventQueues::default();
2297
2298 manager.msgs.param_request_list = Some(ParamRequestListMsg {
2299 target_system: 1,
2300 target_component: 1,
2301 });
2302
2303 manager.act_on_messages(
2304 &mut param_events,
2305 &mut comm_events,
2306 &mut command_events,
2307 &mut companion_events(),
2308 &mut board,
2309 );
2310
2311 assert_eq!(manager.comm_link.sent_param_value_count, 0);
2312 assert_eq!(param_events.list_requests.len(), 1);
2313
2314 apply_test_param_service(
2315 &mut params,
2316 &mut param_list_state,
2317 &mut param_events,
2318 &mut comm_events,
2319 );
2320
2321 manager.send_comm_responses(&mut board, &mut comm_events);
2322
2323 assert_eq!(manager.comm_link.sent_param_value_count, 1);
2324 let sent = manager.comm_link.sent_param_values[0].unwrap();
2325 assert_eq!(sent.param_index, ParamId::PARAM_BAUD_RATE as u16);
2326 assert_eq!(sent.param_value, ParamValue::Int(921600));
2327 }
2328
2329 #[test]
2330 fn param_request_read_emits_request_without_reading_from_comms() {
2331 let mut board = TestBoard::default();
2332 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2333 let mut params = Params::new();
2334 params.set_by_id(ParamId::PARAM_SYSTEM_ID, ParamValue::Int(42));
2335 let mut param_events = ParamEventQueues::default();
2336 let mut comm_events = CommEventQueues::default();
2337 let mut command_events = CommandEventQueues::default();
2338
2339 manager.msgs.store(ParamRequestReadMsg {
2340 target_system: 1,
2341 target_component: 1,
2342 param_identifier: ParamIdentifier::ID(*b"SYS_ID\0\0\0\0\0\0\0\0\0\0"),
2343 });
2344
2345 manager.act_on_messages(
2346 &mut param_events,
2347 &mut comm_events,
2348 &mut command_events,
2349 &mut companion_events(),
2350 &mut board,
2351 );
2352
2353 assert_eq!(manager.comm_link.sent_param_value_count, 0);
2354 assert_eq!(param_events.read_requests.len(), 1);
2355
2356 let mut param_list_state = ParamListState::default();
2357 apply_test_param_service(
2358 &mut params,
2359 &mut param_list_state,
2360 &mut param_events,
2361 &mut comm_events,
2362 );
2363
2364 manager.send_comm_responses(&mut board, &mut comm_events);
2365
2366 assert_eq!(manager.comm_link.sent_param_value_count, 1);
2367 let sent = manager.comm_link.sent_param_values[0].unwrap();
2368 assert_eq!(sent.param_index, ParamId::PARAM_SYSTEM_ID as u16);
2369 assert_eq!(sent.param_value, ParamValue::Int(42));
2370 }
2371
2372 #[test]
2373 fn param_request_read_burst_preserves_all_missing_index_requests() {
2374 let mut board = TestBoard::default();
2375 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2376 let mut param_events = ParamEventQueues::default();
2377 let mut comm_events = CommEventQueues::default();
2378 let mut command_events = CommandEventQueues::default();
2379
2380 for index in [330, 331, 332, 333, 334, 335, 336] {
2381 manager.msgs.store(ParamRequestReadMsg {
2382 target_system: 1,
2383 target_component: 1,
2384 param_identifier: ParamIdentifier::INDEX(index),
2385 });
2386 }
2387
2388 manager.act_on_messages(
2389 &mut param_events,
2390 &mut comm_events,
2391 &mut command_events,
2392 &mut companion_events(),
2393 &mut board,
2394 );
2395
2396 let received: heapless::Vec<_, 8> = param_events
2397 .read_requests
2398 .iter()
2399 .map(|req| req.identifier)
2400 .collect();
2401
2402 assert_eq!(received.len(), 7);
2403 assert_eq!(received[0], ParamIdentifier::INDEX(330));
2404 assert_eq!(received[6], ParamIdentifier::INDEX(336));
2405 }
2406
2407 #[test]
2408 fn send_comm_responses_sends_param_value_and_updates_sysid() {
2409 let mut board = TestBoard::default();
2410 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2411 let mut comm_events = CommEventQueues::default();
2412
2413 let _ = comm_events
2414 .responses
2415 .push(CommResponse::ParamValue(ParamValueMsg {
2416 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2417 param_value: ParamValue::Int(42),
2418 param_count: 1,
2419 param_index: ParamId::PARAM_SYSTEM_ID as u16,
2420 }));
2421
2422 manager.send_comm_responses(&mut board, &mut comm_events);
2423
2424 assert_eq!(manager.sysid, 42);
2425 assert_eq!(manager.comm_link.sent_param_value_count, 1);
2426
2427 let sent = manager.comm_link.sent_param_values[0].unwrap();
2428 assert_eq!(sent.param_id, *b"SYS_ID\0\0\0\0\0\0\0\0\0\0");
2429 assert_eq!(sent.param_value, ParamValue::Int(42));
2430 assert!(comm_events.responses.is_empty());
2431 }
2432
2433 #[test]
2434 fn send_comm_responses_sends_command_ack_and_version() {
2435 let mut board = TestBoard::default();
2436 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2437 let mut comm_events = CommEventQueues::default();
2438
2439 let _ = comm_events
2440 .responses
2441 .push(CommResponse::Version(RosflightVersionMsg {
2442 version: [7; 50],
2443 }));
2444 let _ = comm_events
2445 .responses
2446 .push(CommResponse::CmdAck(RosflightCmdAckMsg {
2447 command: RosflightCmd::SendVersion,
2448 success: RosflightCmdResponse::RosflightCmdSuccess,
2449 }));
2450 let _ = comm_events
2451 .responses
2452 .push(CommResponse::Statustext(StatustextMsg {
2453 severity: Severity::Info,
2454 text: [9; 50],
2455 }));
2456
2457 manager.send_comm_responses(&mut board, &mut comm_events);
2458
2459 assert_eq!(manager.comm_link().version_count, 1);
2460 assert_eq!(manager.comm_link().last_version.unwrap().version, [7; 50]);
2461 assert_eq!(manager.comm_link().cmd_ack_count, 1);
2462 assert_eq!(manager.comm_link().statustext_count, 1);
2463 assert_eq!(manager.comm_link().last_statustext.unwrap().text, [9; 50]);
2464 assert!(matches!(
2465 manager.comm_link().last_cmd_ack.unwrap().command,
2466 RosflightCmd::SendVersion
2467 ));
2468 assert!(comm_events.responses.is_empty());
2469 }
2470
2471 #[test]
2472 fn send_version_command_enqueues_version_and_ack_responses() {
2473 let mut board = TestBoard::default();
2474 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2475 let mut param_events = ParamEventQueues::default();
2476 let mut comm_events = CommEventQueues::default();
2477 let mut command_events = CommandEventQueues::default();
2478
2479 manager.msgs.cmd = Some(RosflightCmdMsg {
2480 command: RosflightCmd::SendVersion,
2481 });
2482
2483 manager.act_on_messages(
2484 &mut param_events,
2485 &mut comm_events,
2486 &mut command_events,
2487 &mut companion_events(),
2488 &mut board,
2489 );
2490
2491 assert_eq!(manager.comm_link().version_count, 0);
2492 assert_eq!(manager.comm_link().cmd_ack_count, 0);
2493 assert!(comm_events.responses.is_empty());
2494 assert_eq!(command_events.version_requests.len(), 1);
2495
2496 let mut params = Params::new();
2497 let mut cal_flags = CalibrationFlags::empty();
2498 apply_test_command_requests(
2499 &mut command_events,
2500 &mut comm_events,
2501 &mut board,
2502 &mut params,
2503 &mut cal_flags,
2504 );
2505 manager.send_comm_responses(&mut board, &mut comm_events);
2506
2507 assert_eq!(manager.comm_link().version_count, 1);
2508 assert_eq!(manager.comm_link().cmd_ack_count, 1);
2509 assert!(matches!(
2510 manager.comm_link().last_cmd_ack.unwrap().success,
2511 RosflightCmdResponse::RosflightCmdSuccess
2512 ));
2513 }
2514
2515 #[test]
2516 fn param_set_pipeline_defers_ack_until_after_apply_stage() {
2517 let mut board = TestBoard::default();
2518 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2519 let mut params = Params::new();
2520 let mut param_events = ParamEventQueues::default();
2521 let mut comm_events = CommEventQueues::default();
2522 let mut command_events = CommandEventQueues::default();
2523
2524 manager.msgs.store(ParamSetMsg {
2525 target_system: 1,
2526 target_component: 1,
2527 param_id: *b"SYS_ID\0\0\0\0\0\0\0\0\0\0",
2528 param_value: ParamValue::Int(42),
2529 });
2530
2531 manager.act_on_messages(
2532 &mut param_events,
2533 &mut comm_events,
2534 &mut command_events,
2535 &mut companion_events(),
2536 &mut board,
2537 );
2538
2539 assert_eq!(
2540 params.get_by_id(ParamId::PARAM_SYSTEM_ID),
2541 ParamValue::Int(1)
2542 );
2543 assert_eq!(manager.comm_link.sent_param_value_count, 0);
2544
2545 let mut param_list_state = ParamListState::default();
2546 apply_test_param_service(
2547 &mut params,
2548 &mut param_list_state,
2549 &mut param_events,
2550 &mut comm_events,
2551 );
2552
2553 assert_eq!(
2554 params.get_by_id(ParamId::PARAM_SYSTEM_ID),
2555 ParamValue::Int(42)
2556 );
2557 assert_eq!(manager.comm_link.sent_param_value_count, 0);
2558
2559 let change = param_events.changes.iter().next().unwrap();
2560 assert_eq!(change.id, ParamId::PARAM_SYSTEM_ID);
2561 assert_eq!(change.old, ParamValue::Int(1));
2562 assert_eq!(change.new, ParamValue::Int(42));
2563
2564 manager.send_comm_responses(&mut board, &mut comm_events);
2565
2566 assert_eq!(manager.sysid, 42);
2567 assert_eq!(manager.comm_link.sent_param_value_count, 1);
2568 assert!(comm_events.responses.is_empty());
2569 }
2570
2571 #[test]
2572 fn named_telemetry_sends_sensor_state_and_output_messages() {
2573 let mut board = TestBoard {
2574 current_time_us: 1_100_000,
2575 tx_write_count: 0,
2576 ..Default::default()
2577 };
2578 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
2579 let state_manager = StateManager::new();
2580 let command_manager = CommandManager::new();
2581 let params = Params::new();
2582 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
2583 let actuator_commands = [0.1, 0.2, 0.3, 0.4];
2584 let mut processed_sensors = ProcessedSensors::<f64>::default();
2585 processed_sensors.imu = Some(crate::packets::ImuPacket {
2586 header: crate::packets::RosflightPacketHeader {
2587 timestamp: 9_000,
2588 status: 0,
2589 },
2590 accel: [1.0, 2.0, 3.0],
2591 gyro: [4.0, 5.0, 6.0],
2592 temperature: 25.0,
2593 seq: 1,
2594 });
2595 processed_sensors.pitot = Some(crate::packets::PitotPacket {
2596 differential_pressure: 12.5,
2597 indicated_airspeed: 8.25,
2598 temperature: 24.0,
2599 ..Default::default()
2600 });
2601 processed_sensors.baro = Some(crate::packets::BaroPacket {
2602 altitude: 123.0,
2603 pressure: 95_000.0,
2604 temperature: 22.0,
2605 ..Default::default()
2606 });
2607 processed_sensors.range = Some(crate::packets::RangePacket {
2608 range: 3.5,
2609 min_range: 0.25,
2610 max_range: 8.0,
2611 range_type: crate::packets::RangeType::Lidar,
2612 ..Default::default()
2613 });
2614 processed_sensors.gnss = Some(crate::packets::GNSSPacket {
2615 header: crate::packets::RosflightPacketHeader {
2616 timestamp: 77_000,
2617 status: 0,
2618 },
2619 unix_seconds: 1_700_000_001,
2620 unix_nanos: 123_456_789,
2621 height_msl: 1_402.25,
2622 num_sats: 9,
2623 ..Default::default()
2624 });
2625
2626 let now_us = board.clock_micros();
2627
2628 manager.send_named_telemetry_streams(telemetry_ctx(
2629 &mut board,
2630 now_us,
2631 &state_manager,
2632 &command_manager,
2633 ¶ms,
2634 &estimator_state,
2635 &processed_sensors,
2636 &actuator_commands,
2637 ));
2638
2639 assert_eq!(manager.comm_link().heartbeat_count, 1);
2640 assert_eq!(manager.comm_link().status_count, 1);
2641 assert_eq!(manager.comm_link().imu_count, 1);
2642 assert_eq!(manager.comm_link().attitude_count, 1);
2643 assert_eq!(manager.comm_link().diff_pressure_count, 1);
2644 assert_eq!(manager.comm_link().baro_count, 1);
2645 assert_eq!(manager.comm_link().range_count, 1);
2646 assert_eq!(manager.comm_link().gnss_count, 1);
2647 assert_eq!(manager.comm_link().output_raw_count, 1);
2648 assert_eq!(manager.comm_link().last_imu.unwrap().temperature, 25.0);
2649 assert_eq!(
2650 manager.comm_link().last_diff_pressure.unwrap().velocity,
2651 8.25
2652 );
2653 assert_eq!(manager.comm_link().last_baro.unwrap().altitude, 123.0);
2654 let range = manager.comm_link().last_range.unwrap();
2655 assert!(matches!(
2656 range.type_,
2657 RosflightRangeType::RosflightRangeLidar
2658 ));
2659 assert_eq!(range.min_range, 0.25);
2660 assert_eq!(range.max_range, 8.0);
2661 let gnss = manager.comm_link().last_gnss.unwrap();
2662 assert_eq!(gnss.seconds, 1_700_000_001);
2663 assert_eq!(gnss.nanos, 123_456_789);
2664 assert_eq!(gnss.height_msl, 1_402.25);
2665
2666 let output = manager.comm_link().last_output_raw.unwrap();
2667 assert_eq!(output.stamp, 1100);
2668 assert_eq!(output.values[0], 0.1);
2669 assert_eq!(output.values[1], 0.2);
2670 assert_eq!(output.values[2], 0.3);
2671 assert_eq!(output.values[3], 0.4);
2672 }
2673
2674 #[test]
2675 fn realtime_named_telemetry_sends_deadline_ordered_streams() {
2676 let mut board = TestBoard {
2677 current_time_us: 1_100_000,
2678 tx_write_count: 0,
2679 ..Default::default()
2680 };
2681 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
2682 manager.set_telemetry_rates(TelemetryRates::bounded_high_rate_transport());
2683 let state_manager = StateManager::new();
2684 let command_manager = CommandManager::new();
2685 let params = Params::new();
2686 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
2687 let actuator_commands = [0.1, 0.2, 0.3, 0.4];
2688 let mut processed_sensors = ProcessedSensors::<f64>::default();
2689 processed_sensors.imu = Some(crate::packets::ImuPacket {
2690 header: crate::packets::RosflightPacketHeader {
2691 timestamp: 9_000,
2692 status: 0,
2693 },
2694 accel: [1.0, 2.0, 3.0],
2695 gyro: [4.0, 5.0, 6.0],
2696 temperature: 25.0,
2697 seq: 1,
2698 });
2699 processed_sensors.baro = Some(crate::packets::BaroPacket {
2700 altitude: 123.0,
2701 pressure: 95_000.0,
2702 temperature: 22.0,
2703 ..Default::default()
2704 });
2705
2706 let now_us = board.clock_micros();
2707 assert!(manager.send_one_named_telemetry_stream(telemetry_ctx(
2708 &mut board,
2709 now_us,
2710 &state_manager,
2711 &command_manager,
2712 ¶ms,
2713 &estimator_state,
2714 &processed_sensors,
2715 &actuator_commands,
2716 )));
2717 assert_eq!(manager.comm_link().imu_count, 1);
2718 assert_eq!(manager.comm_link().attitude_count, 0);
2719 assert_eq!(manager.comm_link().output_raw_count, 0);
2720 assert_eq!(manager.comm_link().baro_count, 0);
2721
2722 assert!(manager.send_one_named_telemetry_stream(telemetry_ctx(
2723 &mut board,
2724 now_us,
2725 &state_manager,
2726 &command_manager,
2727 ¶ms,
2728 &estimator_state,
2729 &processed_sensors,
2730 &actuator_commands,
2731 )));
2732 assert_eq!(manager.comm_link().imu_count, 1);
2733 assert_eq!(manager.comm_link().attitude_count, 1);
2734 assert_eq!(manager.comm_link().output_raw_count, 0);
2735 assert_eq!(manager.comm_link().baro_count, 0);
2736
2737 assert!(manager.send_one_named_telemetry_stream(telemetry_ctx(
2738 &mut board,
2739 now_us,
2740 &state_manager,
2741 &command_manager,
2742 ¶ms,
2743 &estimator_state,
2744 &processed_sensors,
2745 &actuator_commands,
2746 )));
2747 assert_eq!(manager.comm_link().output_raw_count, 1);
2748 assert_eq!(manager.comm_link().baro_count, 0);
2749
2750 assert!(manager.send_one_named_telemetry_stream(telemetry_ctx(
2751 &mut board,
2752 now_us,
2753 &state_manager,
2754 &command_manager,
2755 ¶ms,
2756 &estimator_state,
2757 &processed_sensors,
2758 &actuator_commands,
2759 )));
2760 assert_eq!(manager.comm_link().output_raw_count, 1);
2761 assert_eq!(manager.comm_link().baro_count, 1);
2762 }
2763
2764 #[test]
2765 fn realtime_priority_telemetry_uses_stream_due_and_freshness_gates() {
2766 let mut board = TestBoard {
2767 current_time_us: 1_100_000,
2768 tx_write_count: 0,
2769 ..Default::default()
2770 };
2771 let now_us = board.clock_micros();
2772 let mut manager = CommManager::new(RecordingCommLink::new(), now_us);
2773 manager.set_telemetry_rates(TelemetryRates::bounded_high_rate_transport());
2774 manager.last_heartbeat_us = now_us;
2775 manager.last_status_send_us = now_us;
2776 manager.telemetry_rate_state.rc_us = 1_000_000;
2777 manager.telemetry_rate_state.imu_us = 1_097_500;
2778
2779 let state_manager = StateManager::new();
2780 let command_manager = CommandManager::new();
2781 let params = Params::new();
2782 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
2783 let actuator_commands = [0.1, 0.2, 0.3, 0.4];
2784 let mut processed_sensors = ProcessedSensors::<f64>::default();
2785 processed_sensors.imu = Some(crate::packets::ImuPacket {
2786 header: crate::packets::RosflightPacketHeader {
2787 timestamp: 9_000,
2788 status: 0,
2789 },
2790 accel: [1.0, 2.0, 3.0],
2791 gyro: [4.0, 5.0, 6.0],
2792 temperature: 25.0,
2793 seq: 1,
2794 });
2795 processed_sensors.rc = Some(crate::packets::RcPacket {
2796 header: crate::packets::RosflightPacketHeader {
2797 timestamp: 8_000,
2798 status: 0,
2799 },
2800 n_chan: 8,
2801 chan: [0.5; RC_PACKET_CHANNELS],
2802 lol: false,
2803 });
2804
2805 assert!(manager.send_named_telemetry_stream_if_due(
2806 NamedTelemetryStream::Imu,
2807 telemetry_ctx(
2808 &mut board,
2809 now_us,
2810 &state_manager,
2811 &command_manager,
2812 ¶ms,
2813 &estimator_state,
2814 &processed_sensors,
2815 &actuator_commands,
2816 ),
2817 ));
2818 assert_eq!(manager.comm_link().imu_count, 1);
2819 assert_eq!(manager.comm_link().rc_channels_count, 0);
2820
2821 assert!(!manager.send_named_telemetry_stream_if_due(
2822 NamedTelemetryStream::Imu,
2823 telemetry_ctx(
2824 &mut board,
2825 now_us,
2826 &state_manager,
2827 &command_manager,
2828 ¶ms,
2829 &estimator_state,
2830 &processed_sensors,
2831 &actuator_commands,
2832 ),
2833 ));
2834 assert_eq!(manager.comm_link().imu_count, 1);
2835
2836 assert!(manager.send_one_named_telemetry_stream(telemetry_ctx(
2837 &mut board,
2838 now_us,
2839 &state_manager,
2840 &command_manager,
2841 ¶ms,
2842 &estimator_state,
2843 &processed_sensors,
2844 &actuator_commands,
2845 )));
2846 assert_eq!(manager.comm_link().imu_count, 1);
2847 }
2848
2849 #[test]
2850 fn realtime_priority_telemetry_fresh_sample_gate_bypasses_imu_due_phase() {
2851 let mut board = TestBoard {
2852 current_time_us: 1_100_000,
2853 tx_write_count: 0,
2854 ..Default::default()
2855 };
2856 let now_us = board.clock_micros();
2857 let mut manager = CommManager::new(RecordingCommLink::new(), now_us);
2858 manager.set_telemetry_rates(TelemetryRates::bounded_high_rate_transport());
2859 manager.last_heartbeat_us = now_us;
2860 manager.last_status_send_us = now_us;
2861 manager.telemetry_rate_state.imu_us = now_us - 1_000;
2862
2863 let state_manager = StateManager::new();
2864 let command_manager = CommandManager::new();
2865 let params = Params::new();
2866 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
2867 let actuator_commands = [0.1, 0.2, 0.3, 0.4];
2868 let mut processed_sensors = ProcessedSensors::<f64>::default();
2869 processed_sensors.imu = Some(crate::packets::ImuPacket {
2870 header: crate::packets::RosflightPacketHeader {
2871 timestamp: 9_000,
2872 status: 0,
2873 },
2874 accel: [1.0, 2.0, 3.0],
2875 gyro: [4.0, 5.0, 6.0],
2876 temperature: 25.0,
2877 seq: 1,
2878 });
2879
2880 assert!(!manager.send_named_telemetry_stream_if_due(
2881 NamedTelemetryStream::Imu,
2882 telemetry_ctx(
2883 &mut board,
2884 now_us,
2885 &state_manager,
2886 &command_manager,
2887 ¶ms,
2888 &estimator_state,
2889 &processed_sensors,
2890 &actuator_commands,
2891 ),
2892 ));
2893 assert_eq!(manager.comm_link().imu_count, 0);
2894
2895 assert!(manager.send_named_telemetry_stream_with_gate(
2896 RealtimeTelemetryPriority {
2897 stream: NamedTelemetryStream::Imu,
2898 gate: RealtimeTelemetryPriorityGate::FreshSample,
2899 },
2900 telemetry_ctx(
2901 &mut board,
2902 now_us,
2903 &state_manager,
2904 &command_manager,
2905 ¶ms,
2906 &estimator_state,
2907 &processed_sensors,
2908 &actuator_commands,
2909 ),
2910 ));
2911 assert_eq!(manager.comm_link().imu_count, 1);
2912
2913 assert!(!manager.send_named_telemetry_stream_with_gate(
2914 RealtimeTelemetryPriority {
2915 stream: NamedTelemetryStream::Imu,
2916 gate: RealtimeTelemetryPriorityGate::FreshSample,
2917 },
2918 telemetry_ctx(
2919 &mut board,
2920 now_us,
2921 &state_manager,
2922 &command_manager,
2923 ¶ms,
2924 &estimator_state,
2925 &processed_sensors,
2926 &actuator_commands,
2927 ),
2928 ));
2929 assert_eq!(manager.comm_link().imu_count, 1);
2930 }
2931
2932 #[test]
2933 fn bulk_telemetry_sends_each_sensor_timestamp_once_and_keeps_periodic_streams_running() {
2934 let mut board = TestBoard {
2935 current_time_us: 1_100_000,
2936 ..Default::default()
2937 };
2938 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
2939 let state_manager = StateManager::new();
2940 let command_manager = CommandManager::new();
2941 let params = Params::new();
2942 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
2943 let actuator_commands = [0.0; 4];
2944 let mut sensors = ProcessedSensors::<f64>::default();
2945 sensors.imu = Some(crate::packets::ImuPacket::default());
2946 sensors.pitot = Some(crate::packets::PitotPacket::default());
2947 sensors.baro = Some(crate::packets::BaroPacket::default());
2948 sensors.mag = Some(crate::packets::MagPacket::default());
2949 sensors.range = Some(crate::packets::RangePacket::default());
2950 sensors.battery = Some(crate::packets::BatteryPacket::default());
2951 sensors.gnss = Some(crate::packets::GNSSPacket::default());
2952 sensors.rc = Some(crate::packets::RcPacket::default());
2953
2954 let send = |manager: &mut CommManager<TestBoard, RecordingCommLink>,
2955 board: &mut TestBoard| {
2956 let now_us = board.clock_micros();
2957 manager.send_named_telemetry_streams(telemetry_ctx(
2958 board,
2959 now_us,
2960 &state_manager,
2961 &command_manager,
2962 ¶ms,
2963 &estimator_state,
2964 &sensors,
2965 &actuator_commands,
2966 ));
2967 };
2968
2969 send(&mut manager, &mut board);
2971 send(&mut manager, &mut board);
2972
2973 assert_eq!(manager.comm_link().imu_count, 1);
2974 assert_eq!(manager.comm_link().attitude_count, 1);
2975 assert_eq!(manager.comm_link().output_raw_count, 1);
2976 assert_eq!(manager.comm_link().diff_pressure_count, 1);
2977 assert_eq!(manager.comm_link().baro_count, 1);
2978 assert_eq!(manager.comm_link().mag_count, 1);
2979 assert_eq!(manager.comm_link().range_count, 1);
2980 assert_eq!(manager.comm_link().battery_count, 1);
2981 assert_eq!(manager.comm_link().gnss_count, 1);
2982 assert_eq!(manager.comm_link().rc_channels_count, 1);
2983 assert_eq!(manager.comm_link().heartbeat_count, 1);
2984 assert_eq!(manager.comm_link().status_count, 1);
2985
2986 board.current_time_us += 1_000_000;
2988 send(&mut manager, &mut board);
2989 assert_eq!(manager.comm_link().heartbeat_count, 2);
2990 assert_eq!(manager.comm_link().status_count, 2);
2991 assert_eq!(manager.comm_link().baro_count, 1);
2992 assert_eq!(manager.comm_link().mag_count, 1);
2993 }
2994
2995 #[test]
2996 fn budgeted_telemetry_filters_stale_streams_without_starving_fresh_ones() {
2997 let mut board = TestBoard {
2998 current_time_us: 1_000,
2999 ..Default::default()
3000 };
3001 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3002 let mut rates = TelemetryRates::upstream();
3003 rates.heartbeat_hz = TELEMETRY_RATE_DISABLED;
3004 rates.status_hz = TELEMETRY_RATE_DISABLED;
3005 manager.set_telemetry_rates(rates);
3006 let state_manager = StateManager::new();
3007 let command_manager = CommandManager::new();
3008 let params = Params::new();
3009 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3010 let actuator_commands = [0.0; 4];
3011 let mut sensors = ProcessedSensors::<f64>::default();
3012 sensors.baro = Some(crate::packets::BaroPacket {
3013 header: crate::packets::RosflightPacketHeader {
3014 timestamp: 10,
3015 status: 0,
3016 },
3017 ..Default::default()
3018 });
3019 sensors.mag = Some(crate::packets::MagPacket {
3020 header: crate::packets::RosflightPacketHeader {
3021 timestamp: 20,
3022 status: 0,
3023 },
3024 ..Default::default()
3025 });
3026
3027 let send_one = |manager: &mut CommManager<TestBoard, RecordingCommLink>,
3028 board: &mut TestBoard,
3029 sensors: &ProcessedSensors<f64>| {
3030 let now_us = board.clock_micros();
3031 manager.send_one_named_telemetry_stream(telemetry_ctx(
3032 board,
3033 now_us,
3034 &state_manager,
3035 &command_manager,
3036 ¶ms,
3037 &estimator_state,
3038 sensors,
3039 &actuator_commands,
3040 ))
3041 };
3042
3043 assert!(send_one(&mut manager, &mut board, &sensors));
3044 assert!(send_one(&mut manager, &mut board, &sensors));
3045 assert!(!send_one(&mut manager, &mut board, &sensors));
3046 assert_eq!(manager.comm_link().baro_count, 1);
3047 assert_eq!(manager.comm_link().mag_count, 1);
3048
3049 sensors.mag.as_mut().unwrap().header.timestamp = 21;
3050 assert!(send_one(&mut manager, &mut board, &sensors));
3051 assert!(!send_one(&mut manager, &mut board, &sensors));
3052 assert_eq!(manager.comm_link().baro_count, 1);
3053 assert_eq!(manager.comm_link().mag_count, 2);
3054 }
3055
3056 #[test]
3057 fn rate_limited_sensor_retains_latest_unsent_sample_until_due() {
3058 let mut board = TestBoard {
3059 current_time_us: 1_000,
3060 ..Default::default()
3061 };
3062 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3063 let mut rates = TelemetryRates::upstream();
3064 rates.heartbeat_hz = TELEMETRY_RATE_DISABLED;
3065 rates.status_hz = TELEMETRY_RATE_DISABLED;
3066 rates.baro_hz = 10;
3067 manager.set_telemetry_rates(rates);
3068 let state_manager = StateManager::new();
3069 let command_manager = CommandManager::new();
3070 let params = Params::new();
3071 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3072 let actuator_commands = [0.0; 4];
3073 let mut sensors = ProcessedSensors::<f64>::default();
3074 sensors.baro = Some(crate::packets::BaroPacket {
3075 header: crate::packets::RosflightPacketHeader {
3076 timestamp: 1,
3077 status: 0,
3078 },
3079 altitude: 1.0,
3080 ..Default::default()
3081 });
3082
3083 let send_baro = |manager: &mut CommManager<TestBoard, RecordingCommLink>,
3084 board: &mut TestBoard,
3085 sensors: &ProcessedSensors<f64>| {
3086 let now_us = board.clock_micros();
3087 manager.send_named_telemetry_stream_if_due(
3088 NamedTelemetryStream::Baro,
3089 telemetry_ctx(
3090 board,
3091 now_us,
3092 &state_manager,
3093 &command_manager,
3094 ¶ms,
3095 &estimator_state,
3096 sensors,
3097 &actuator_commands,
3098 ),
3099 )
3100 };
3101
3102 assert!(send_baro(&mut manager, &mut board, &sensors));
3103 sensors.baro.as_mut().unwrap().header.timestamp = 2;
3104 sensors.baro.as_mut().unwrap().altitude = 2.0;
3105 board.current_time_us = 50_000;
3106 assert!(!send_baro(&mut manager, &mut board, &sensors));
3107 sensors.baro.as_mut().unwrap().header.timestamp = 3;
3108 sensors.baro.as_mut().unwrap().altitude = 3.0;
3109 board.current_time_us = 101_000;
3110 assert!(send_baro(&mut manager, &mut board, &sensors));
3111 assert_eq!(manager.comm_link().baro_count, 2);
3112 assert_eq!(manager.comm_link().last_baro.unwrap().altitude, 3.0);
3113
3114 manager.set_telemetry_rates(rates);
3115 board.current_time_us = 201_000;
3116 assert!(!send_baro(&mut manager, &mut board, &sensors));
3117 assert_eq!(manager.comm_link().baro_count, 2);
3118 }
3119
3120 #[test]
3121 fn imu_attitude_and_output_track_the_same_source_timestamp_independently() {
3122 let mut board = TestBoard {
3123 current_time_us: 1_000,
3124 ..Default::default()
3125 };
3126 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3127 let mut rates = TelemetryRates::upstream();
3128 rates.heartbeat_hz = TELEMETRY_RATE_DISABLED;
3129 rates.status_hz = TELEMETRY_RATE_DISABLED;
3130 rates.output_raw_hz = 1_000;
3131 rates.output_raw_imu_divisor = 0;
3132 manager.set_telemetry_rates(rates);
3133 let state_manager = StateManager::new();
3134 let command_manager = CommandManager::new();
3135 let params = Params::new();
3136 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3137 let actuator_commands = [0.0; 4];
3138 let mut sensors = ProcessedSensors::<f64>::default();
3139 sensors.imu = Some(crate::packets::ImuPacket::default());
3140
3141 for stream in [
3142 NamedTelemetryStream::Imu,
3143 NamedTelemetryStream::Attitude,
3144 NamedTelemetryStream::OutputRaw,
3145 ] {
3146 let now_us = board.clock_micros();
3147 assert!(manager.send_named_telemetry_stream_if_due(
3148 stream,
3149 telemetry_ctx(
3150 &mut board,
3151 now_us,
3152 &state_manager,
3153 &command_manager,
3154 ¶ms,
3155 &estimator_state,
3156 &sensors,
3157 &actuator_commands,
3158 ),
3159 ));
3160 }
3161 assert_eq!(manager.comm_link().imu_count, 1);
3162 assert_eq!(manager.comm_link().attitude_count, 1);
3163 assert_eq!(manager.comm_link().output_raw_count, 1);
3164
3165 for stream in [
3166 NamedTelemetryStream::Imu,
3167 NamedTelemetryStream::Attitude,
3168 NamedTelemetryStream::OutputRaw,
3169 ] {
3170 let now_us = board.clock_micros();
3171 assert!(!manager.send_named_telemetry_stream_if_due(
3172 stream,
3173 telemetry_ctx(
3174 &mut board,
3175 now_us,
3176 &state_manager,
3177 &command_manager,
3178 ¶ms,
3179 &estimator_state,
3180 &sensors,
3181 &actuator_commands,
3182 ),
3183 ));
3184 }
3185 }
3186
3187 #[test]
3188 fn named_telemetry_matches_upstream_output_raw_every_eighth_imu_sample() {
3189 let mut board = TestBoard {
3190 current_time_us: 1_100_000,
3191 tx_write_count: 0,
3192 ..Default::default()
3193 };
3194 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3195 let state_manager = StateManager::new();
3196 let command_manager = CommandManager::new();
3197 let params = Params::new();
3198 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3199 let mut processed_sensors = ProcessedSensors::<f64>::default();
3200 processed_sensors.imu = Some(crate::packets::ImuPacket::default());
3201 let actuator_commands = [0.0; 4];
3202
3203 for i in 0..8 {
3204 processed_sensors.imu.as_mut().unwrap().header.timestamp = i;
3205 board.current_time_us = 1_100_000 + i;
3206 let now_us = board.clock_micros();
3207 manager.send_named_telemetry_streams(telemetry_ctx(
3208 &mut board,
3209 now_us,
3210 &state_manager,
3211 &command_manager,
3212 ¶ms,
3213 &estimator_state,
3214 &processed_sensors,
3215 &actuator_commands,
3216 ));
3217 }
3218
3219 assert_eq!(manager.comm_link().imu_count, 8);
3220 assert_eq!(manager.comm_link().attitude_count, 8);
3221 assert_eq!(manager.comm_link().output_raw_count, 1);
3222
3223 board.current_time_us += 1;
3224 processed_sensors.imu.as_mut().unwrap().header.timestamp = 8;
3225 let now_us = board.clock_micros();
3226 manager.send_named_telemetry_streams(telemetry_ctx(
3227 &mut board,
3228 now_us,
3229 &state_manager,
3230 &command_manager,
3231 ¶ms,
3232 &estimator_state,
3233 &processed_sensors,
3234 &actuator_commands,
3235 ));
3236
3237 assert_eq!(manager.comm_link().output_raw_count, 2);
3238 }
3239
3240 #[test]
3241 fn explicit_telemetry_rates_bound_high_rate_streams() {
3242 let mut board = TestBoard {
3243 current_time_us: 1_000,
3244 tx_write_count: 0,
3245 ..Default::default()
3246 };
3247 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3248 manager.set_telemetry_rates(TelemetryRates::bounded_high_rate_transport());
3249 let state_manager = StateManager::new();
3250 let command_manager = CommandManager::new();
3251 let params = Params::new();
3252 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3253 let mut processed_sensors = ProcessedSensors::<f64>::default();
3254 processed_sensors.imu = Some(crate::packets::ImuPacket::default());
3255 processed_sensors.baro = Some(crate::packets::BaroPacket::default());
3256 let actuator_commands = [0.0; 4];
3257
3258 let send_at = |manager: &mut CommManager<TestBoard, RecordingCommLink>,
3259 board: &mut TestBoard,
3260 processed_sensors: &mut ProcessedSensors<f64>,
3261 now_us: u64| {
3262 board.current_time_us = now_us;
3263 processed_sensors.imu.as_mut().unwrap().header.timestamp = now_us;
3264 processed_sensors.baro.as_mut().unwrap().header.timestamp = now_us;
3265 manager.send_named_telemetry_streams(telemetry_ctx(
3266 board,
3267 now_us,
3268 &state_manager,
3269 &command_manager,
3270 ¶ms,
3271 &estimator_state,
3272 &processed_sensors,
3273 &actuator_commands,
3274 ));
3275 };
3276
3277 send_at(&mut manager, &mut board, &mut processed_sensors, 1_000);
3278 assert_eq!(manager.comm_link().imu_count, 1);
3279 assert_eq!(manager.comm_link().attitude_count, 1);
3280 assert_eq!(manager.comm_link().baro_count, 1);
3281 assert_eq!(manager.comm_link().output_raw_count, 1);
3282
3283 send_at(&mut manager, &mut board, &mut processed_sensors, 2_000);
3284 assert_eq!(manager.comm_link().imu_count, 1);
3285 assert_eq!(manager.comm_link().attitude_count, 1);
3286 assert_eq!(manager.comm_link().baro_count, 1);
3287 assert_eq!(manager.comm_link().output_raw_count, 1);
3288
3289 send_at(&mut manager, &mut board, &mut processed_sensors, 3_500);
3290 assert_eq!(manager.comm_link().imu_count, 2);
3291 assert_eq!(manager.comm_link().attitude_count, 1);
3292 assert_eq!(manager.comm_link().baro_count, 1);
3293 assert_eq!(manager.comm_link().output_raw_count, 1);
3294
3295 send_at(&mut manager, &mut board, &mut processed_sensors, 11_000);
3296 assert_eq!(manager.comm_link().imu_count, 3);
3297 assert_eq!(manager.comm_link().attitude_count, 1);
3298 assert_eq!(manager.comm_link().baro_count, 1);
3299 assert_eq!(manager.comm_link().output_raw_count, 1);
3300
3301 send_at(&mut manager, &mut board, &mut processed_sensors, 21_000);
3302 assert_eq!(manager.comm_link().imu_count, 4);
3303 assert_eq!(manager.comm_link().attitude_count, 2);
3304 assert_eq!(manager.comm_link().baro_count, 1);
3305 assert_eq!(manager.comm_link().output_raw_count, 2);
3306
3307 send_at(&mut manager, &mut board, &mut processed_sensors, 41_000);
3308 assert_eq!(manager.comm_link().imu_count, 5);
3309 assert_eq!(manager.comm_link().attitude_count, 3);
3310 assert_eq!(manager.comm_link().baro_count, 2);
3311 assert_eq!(manager.comm_link().output_raw_count, 3);
3312 }
3313
3314 #[test]
3315 fn named_rc_telemetry_matches_upstream_raw_channel_packing() {
3316 let mut board = TestBoard {
3317 current_time_us: 1_234_000,
3318 tx_write_count: 0,
3319 ..Default::default()
3320 };
3321 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3322 let state_manager = StateManager::new();
3323 let command_manager = CommandManager::new();
3324 let params = Params::new();
3325 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3326 let mut processed_sensors = ProcessedSensors::<f64>::default();
3327 let mut rc_packet = crate::packets::RcPacket::default();
3328 rc_packet.n_chan = RC_PACKET_CHANNELS as u32;
3329 let test_channels = [
3330 -1.0, -0.5, 0.0, 0.5, 1.0, 0.25, -0.25, 0.75, 0.33, 0.44, 0.55, 0.66, 0.77, 0.88, 0.99,
3331 -0.99, 1.0, -1.0,
3332 ];
3333 rc_packet.chan[..test_channels.len()].copy_from_slice(&test_channels);
3334 processed_sensors.rc = Some(rc_packet);
3335 let now_us = board.clock_micros();
3336 let actuator_commands = [0.0; 4];
3337
3338 manager.send_named_telemetry_streams(telemetry_ctx(
3339 &mut board,
3340 now_us,
3341 &state_manager,
3342 &command_manager,
3343 ¶ms,
3344 &estimator_state,
3345 &processed_sensors,
3346 &actuator_commands,
3347 ));
3348
3349 let msg = manager.comm_link().last_rc_channels.unwrap();
3350 assert_eq!(manager.comm_link().rc_channels_count, 1);
3351 assert_eq!(msg.time_boot_ms, 1234);
3352 assert_eq!(msg.chancount, 8);
3353 assert_eq!(msg.rssi, 0);
3354 assert_eq!(
3355 &msg.channels[..8],
3356 &[0, 500, 1000, 1500, 2000, 1250, 750, 1750]
3357 );
3358 assert!(msg.channels[8..].iter().all(|channel| *channel == 0));
3359 }
3360
3361 #[test]
3362 fn named_status_telemetry_reports_command_manager_override_state() {
3363 let mut board = TestBoard {
3364 current_time_us: 1_100_000,
3365 tx_write_count: 0,
3366 ..Default::default()
3367 };
3368 let mut manager = CommManager::new(RecordingCommLink::new(), 0);
3369 let state_manager = StateManager::new();
3370 let mut command_manager = CommandManager::new();
3371 let params = Params::new();
3372 let estimator_state = crate::estimator::quad::AttitudeState::<f64>::default();
3373 let processed_sensors = ProcessedSensors::<f64>::default();
3374 let actuator_commands = [0.0, 0.0, 0.0, 0.0];
3375
3376 command_manager.set_new_offboard_command(
3377 board.clock_micros(),
3378 &OffboardControlMsg {
3379 mode: OffboardControlMode::ModeRollratePitchrateYawrateThrottle,
3380 ignore: OffboardControlIgnore::empty(),
3381 qx: 0.0,
3382 qy: 0.0,
3383 qz: 0.0,
3384 fx: 0.0,
3385 fy: 0.0,
3386 fz: 0.0,
3387 passthrough: [0.0; 4],
3388 },
3389 ¶ms,
3390 );
3391
3392 manager.send_named_telemetry_streams(telemetry_ctx(
3393 &mut board,
3394 1_100_000,
3395 &state_manager,
3396 &command_manager,
3397 ¶ms,
3398 &estimator_state,
3399 &processed_sensors,
3400 &actuator_commands,
3401 ));
3402
3403 let status = manager.comm_link().last_status.unwrap();
3404 assert_eq!(status.offboard, 1);
3405 assert_eq!(status.rc_override, 0);
3406 }
3407
3408 #[test]
3409 fn calibration_command_ack_is_sent_when_calibration_starts() {
3410 let mut board = TestBoard::default();
3411 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3412 let mut param_events = ParamEventQueues::default();
3413 let mut comm_events = CommEventQueues::default();
3414 let mut command_events = CommandEventQueues::default();
3415 let mut cal_flags = CalibrationFlags::empty();
3416 let mut params = Params::new();
3417
3418 manager.msgs.cmd = Some(RosflightCmdMsg {
3419 command: RosflightCmd::GyroCalibration,
3420 });
3421
3422 manager.act_on_messages(
3423 &mut param_events,
3424 &mut comm_events,
3425 &mut command_events,
3426 &mut companion_events(),
3427 &mut board,
3428 );
3429
3430 assert!(cal_flags.is_empty());
3431 apply_test_command_requests(
3432 &mut command_events,
3433 &mut comm_events,
3434 &mut board,
3435 &mut params,
3436 &mut cal_flags,
3437 );
3438
3439 assert!(cal_flags.contains(CalibrationFlags::GYRO));
3440 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3441 manager.send_comm_responses(&mut board, &mut comm_events);
3442 assert_eq!(manager.comm_link().cmd_ack_count, 1);
3443
3444 let ack = manager.comm_link().last_cmd_ack.unwrap();
3445 assert!(matches!(ack.command, RosflightCmd::GyroCalibration));
3446 assert!(matches!(
3447 ack.success,
3448 RosflightCmdResponse::RosflightCmdSuccess
3449 ));
3450 }
3451
3452 #[test]
3453 fn offboard_control_message_emits_command_event() {
3454 let mut board = TestBoard {
3455 current_time_us: 55_000,
3456 tx_write_count: 0,
3457 ..Default::default()
3458 };
3459 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3460 let mut param_events = ParamEventQueues::default();
3461 let mut comm_events = CommEventQueues::default();
3462 let mut command_events = CommandEventQueues::default();
3463
3464 manager.msgs.offboard_control = Some(OffboardControlMsg {
3465 mode: OffboardControlMode::ModeRollPitchYawrateThrottle,
3466 ignore: OffboardControlIgnore::IGNORE_FY,
3467 qx: 0.1,
3468 qy: 0.2,
3469 qz: 0.3,
3470 fx: 0.4,
3471 fy: 0.5,
3472 fz: 0.6,
3473 passthrough: [0.0; 4],
3474 });
3475
3476 manager.act_on_messages(
3477 &mut param_events,
3478 &mut comm_events,
3479 &mut command_events,
3480 &mut companion_events(),
3481 &mut board,
3482 );
3483
3484 let request = command_events.offboard_control_requests.pop().unwrap();
3485 assert_eq!(request.now_us, 55_000);
3486 assert_eq!(
3487 request.msg.mode,
3488 OffboardControlMode::ModeRollPitchYawrateThrottle
3489 );
3490 assert!(
3491 request
3492 .msg
3493 .ignore
3494 .contains(OffboardControlIgnore::IGNORE_FY)
3495 );
3496 assert_eq!(request.msg.qx, 0.1);
3497 }
3498
3499 #[test]
3500 fn companion_inputs_emit_companion_events() {
3501 let mut board = TestBoard::default();
3502 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3503 let mut param_events = ParamEventQueues::default();
3504 let mut comm_events = CommEventQueues::default();
3505 let mut command_events = CommandEventQueues::default();
3506 let mut companion_events = CompanionEventQueues::default();
3507
3508 manager.msgs.heartbeat = Some(HeartbeatMsg {
3509 type_: 1,
3510 autopilot: 2,
3511 base_mode: 3,
3512 custom_mode: 4,
3513 system_status: 5,
3514 mavlink_version: 6,
3515 });
3516 let mut aux = RosflightAuxCmdMsg {
3517 type_array: [RosflightAuxCmdType::Disabled; 14],
3518 aux_cmd_array: [0.0; 14],
3519 };
3520 aux.type_array[1] = RosflightAuxCmdType::Motor;
3521 aux.aux_cmd_array[1] = 0.4;
3522 manager.msgs.aux_cmd = Some(aux);
3523 manager.msgs.external_attitude = Some(ExternalAttitudeMsg {
3524 qw: 1.0,
3525 qx: 0.1,
3526 qy: 0.2,
3527 qz: 0.3,
3528 });
3529
3530 manager.act_on_messages(
3531 &mut param_events,
3532 &mut comm_events,
3533 &mut command_events,
3534 &mut companion_events,
3535 &mut board,
3536 );
3537
3538 assert_eq!(companion_events.heartbeats.len(), 1);
3539 assert_eq!(companion_events.aux_commands.len(), 1);
3540 assert_eq!(companion_events.external_attitudes.len(), 1);
3541 assert_eq!(
3542 companion_events.heartbeats.pop().unwrap().msg.system_status,
3543 5
3544 );
3545 let aux_event = companion_events.aux_commands.pop().unwrap();
3546 assert!(matches!(
3547 aux_event.msg.type_array[1],
3548 RosflightAuxCmdType::Motor
3549 ));
3550 assert_eq!(aux_event.msg.aux_cmd_array[1], 0.4);
3551 assert_eq!(
3552 companion_events.external_attitudes.pop().unwrap().msg.qz,
3553 0.3
3554 );
3555 }
3556
3557 #[test]
3558 fn timesync_responds_only_to_requests_and_uses_local_time() {
3559 let mut board = TestBoard {
3560 current_time_us: 123,
3561 ..Default::default()
3562 };
3563 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3564 let mut param_events = ParamEventQueues::default();
3565 let mut comm_events = CommEventQueues::default();
3566 let mut command_events = CommandEventQueues::default();
3567
3568 manager.msgs.timesync = Some(TimesyncMsg { tc1: 99, ts1: 55 });
3569 manager.act_on_messages(
3570 &mut param_events,
3571 &mut comm_events,
3572 &mut command_events,
3573 &mut companion_events(),
3574 &mut board,
3575 );
3576 assert_eq!(manager.comm_link().timesync_count, 0);
3577
3578 manager.msgs.timesync = Some(TimesyncMsg { tc1: 0, ts1: 55 });
3579 manager.act_on_messages(
3580 &mut param_events,
3581 &mut comm_events,
3582 &mut command_events,
3583 &mut companion_events(),
3584 &mut board,
3585 );
3586
3587 assert_eq!(manager.comm_link().timesync_count, 1);
3588 let response = manager.comm_link().last_timesync.unwrap();
3589 assert_eq!(response.tc1, 123_000);
3590 assert_eq!(response.ts1, 55);
3591 }
3592
3593 #[test]
3594 fn set_param_defaults_emits_request_and_defers_ack() {
3595 let mut board = TestBoard::default();
3596 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3597 let mut params = Params::new();
3598 params.set_by_id(ParamId::PARAM_SYSTEM_ID, ParamValue::Int(42));
3599 let mut param_events = ParamEventQueues::default();
3600 let mut comm_events = CommEventQueues::default();
3601 let mut command_events = CommandEventQueues::default();
3602
3603 manager.msgs.cmd = Some(RosflightCmdMsg {
3604 command: RosflightCmd::SetParamDefaults,
3605 });
3606
3607 manager.act_on_messages(
3608 &mut param_events,
3609 &mut comm_events,
3610 &mut command_events,
3611 &mut companion_events(),
3612 &mut board,
3613 );
3614
3615 assert_eq!(
3616 params.get_by_id(ParamId::PARAM_SYSTEM_ID),
3617 ParamValue::Int(42)
3618 );
3619 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3620
3621 let mut cal_flags = CalibrationFlags::empty();
3622 apply_test_command_requests(
3623 &mut command_events,
3624 &mut comm_events,
3625 &mut board,
3626 &mut params,
3627 &mut cal_flags,
3628 );
3629
3630 assert_eq!(
3631 params.get_by_id(ParamId::PARAM_SYSTEM_ID),
3632 ParamValue::Int(1)
3633 );
3634 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3635 manager.send_comm_responses(&mut board, &mut comm_events);
3636 assert_eq!(manager.comm_link().cmd_ack_count, 1);
3637
3638 let ack = manager.comm_link().last_cmd_ack.unwrap();
3639 assert!(matches!(ack.command, RosflightCmd::SetParamDefaults));
3640 assert!(matches!(
3641 ack.success,
3642 RosflightCmdResponse::RosflightCmdSuccess
3643 ));
3644 }
3645
3646 #[test]
3647 fn board_command_emits_request_and_defers_ack() {
3648 let mut board = TestBoard::default();
3649 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3650 let mut param_events = ParamEventQueues::default();
3651 let mut comm_events = CommEventQueues::default();
3652 let mut command_events = CommandEventQueues::default();
3653
3654 manager.msgs.cmd = Some(RosflightCmdMsg {
3655 command: RosflightCmd::WriteParams,
3656 });
3657
3658 manager.act_on_messages(
3659 &mut param_events,
3660 &mut comm_events,
3661 &mut command_events,
3662 &mut companion_events(),
3663 &mut board,
3664 );
3665
3666 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3667 assert!(comm_events.responses.is_empty());
3668
3669 let request = command_events.board_command_requests.pop().unwrap();
3670 assert!(matches!(request.command, RosflightCmd::WriteParams));
3671 }
3672
3673 #[test]
3674 fn rc_trim_calibration_emits_request_and_defers_ack() {
3675 let mut board = TestBoard::default();
3676 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3677 let mut param_events = ParamEventQueues::default();
3678 let mut comm_events = CommEventQueues::default();
3679 let mut command_events = CommandEventQueues::default();
3680
3681 manager.msgs.cmd = Some(RosflightCmdMsg {
3682 command: RosflightCmd::RcCalibration,
3683 });
3684
3685 manager.act_on_messages(
3686 &mut param_events,
3687 &mut comm_events,
3688 &mut command_events,
3689 &mut companion_events(),
3690 &mut board,
3691 );
3692
3693 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3694 assert!(comm_events.responses.is_empty());
3695
3696 let request = command_events.rc_trim_calibration_requests.pop().unwrap();
3697 assert!(matches!(request.command, RosflightCmd::RcCalibration));
3698 }
3699
3700 #[test]
3701 fn reset_origin_emits_request_and_defers_ack() {
3702 let mut board = TestBoard::default();
3703 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3704 let mut param_events = ParamEventQueues::default();
3705 let mut comm_events = CommEventQueues::default();
3706 let mut command_events = CommandEventQueues::default();
3707
3708 manager.msgs.cmd = Some(RosflightCmdMsg {
3709 command: RosflightCmd::ResetOrigin,
3710 });
3711
3712 manager.act_on_messages(
3713 &mut param_events,
3714 &mut comm_events,
3715 &mut command_events,
3716 &mut companion_events(),
3717 &mut board,
3718 );
3719
3720 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3721 assert!(comm_events.responses.is_empty());
3722
3723 let request = command_events.reset_origin_requests.pop().unwrap();
3724 assert!(matches!(request.command, RosflightCmd::ResetOrigin));
3725 }
3726
3727 #[test]
3728 fn send_all_config_infos_emits_request_and_defers_ack() {
3729 let mut board = TestBoard::default();
3730 let mut manager = CommManager::new(RecordingCommLink::new(), board.clock_micros());
3731 let mut param_events = ParamEventQueues::default();
3732 let mut comm_events = CommEventQueues::default();
3733 let mut command_events = CommandEventQueues::default();
3734
3735 manager.msgs.cmd = Some(RosflightCmdMsg {
3736 command: RosflightCmd::SendAllConfigInfos,
3737 });
3738
3739 manager.act_on_messages(
3740 &mut param_events,
3741 &mut comm_events,
3742 &mut command_events,
3743 &mut companion_events(),
3744 &mut board,
3745 );
3746
3747 assert_eq!(manager.comm_link().cmd_ack_count, 0);
3748 assert!(comm_events.responses.is_empty());
3749
3750 let request = command_events.config_info_requests.pop().unwrap();
3751 assert!(matches!(request.command, RosflightCmd::SendAllConfigInfos));
3752 }
3753}