Skip to main content

veloxity_core/
comm.rs

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    /// Use the stream's normal telemetry rate and freshness gates.
47    DueDeadline,
48    /// Send a fresh sample immediately, without waiting for the stream's rate gate.
49    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    // Determine how many bytes to copy (at most 16)
269    let len_to_copy = if input_bytes.len() > 16 {
270        16
271    } else {
272        input_bytes.len()
273    };
274
275    // Copy the bytes from the input string
276    let mut i = 0;
277    while i < len_to_copy {
278        buffer[i] = input_bytes[i];
279        i += 1;
280    }
281
282    // If the input was shorter than 16, the spot after the last character
283    // is already a 0 from the initial buffer creation, so it is null-terminated.
284    // If the input was 16 or longer, the buffer is full and not null-terminated.
285
286    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    /// Applies one live telemetry parameter without disturbing the deadlines
361    /// of unrelated streams. The changed stream begins a new period at
362    /// `now_us`, avoiding a catch-up burst.
363    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        // next check for timesync messages
1431        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        // now act on ROSflight Commands
1476
1477        let cmd_msg_opt = self.msgs.cmd.take();
1478        if let Some(msg) = cmd_msg_opt {
1479            // Assume failure unless explicitly set to success
1480            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            } // end match
1615
1616            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        } // end if let Some(msg)
1626    }
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, &params);
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(&params);
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(&params, 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(&params).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(&params).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            &params,
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            &params,
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            &params,
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            &params,
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            &params,
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                &params,
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                &params,
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            &params,
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                &params,
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                &params,
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                &params,
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                &params,
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            &params,
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                &params,
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            &params,
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            &params,
2897        );
2898
2899        manager.send_named_telemetry_streams(telemetry_ctx(
2900            &mut board,
2901            1_100_000,
2902            &state_manager,
2903            &command_manager,
2904            &params,
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}