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