Skip to main content

pamoja_mavlink/dialect/
common.rs

1//! The typed common-dialect messages.
2//!
3//! Each is declared with the `message!` macro from its name, id, official `CRC_EXTRA`, and
4//! base fields in wire order. The set spans what a ground station and an autopilot exchange
5//! to fly a mission: the heartbeat, system status, the command, parameter, and mission
6//! protocols, and the core position and attitude telemetry.
7
8use super::{Message, MessageDescriptor};
9
10/// Every typed message's shape, in message-id order.
11///
12/// This is what [`descriptor`](super::descriptor) and [`descriptor_by_name`](super::descriptor_by_name) search, and what a caller
13/// enumerates to discover which messages this build types.
14pub const DESCRIPTORS: &[&MessageDescriptor<'static>] = &[
15    <Heartbeat as Message>::DESCRIPTOR,
16    <SysStatus as Message>::DESCRIPTOR,
17    <SystemTime as Message>::DESCRIPTOR,
18    <Ping as Message>::DESCRIPTOR,
19    <SetMode as Message>::DESCRIPTOR,
20    <ParamRequestRead as Message>::DESCRIPTOR,
21    <ParamRequestList as Message>::DESCRIPTOR,
22    <ParamValue as Message>::DESCRIPTOR,
23    <ParamSet as Message>::DESCRIPTOR,
24    <GpsRawInt as Message>::DESCRIPTOR,
25    <Attitude as Message>::DESCRIPTOR,
26    <AttitudeQuaternion as Message>::DESCRIPTOR,
27    <LocalPositionNed as Message>::DESCRIPTOR,
28    <GlobalPositionInt as Message>::DESCRIPTOR,
29    <ServoOutputRaw as Message>::DESCRIPTOR,
30    <MissionRequest as Message>::DESCRIPTOR,
31    <MissionCurrent as Message>::DESCRIPTOR,
32    <MissionRequestList as Message>::DESCRIPTOR,
33    <MissionCount as Message>::DESCRIPTOR,
34    <MissionClearAll as Message>::DESCRIPTOR,
35    <MissionAck as Message>::DESCRIPTOR,
36    <MissionRequestInt as Message>::DESCRIPTOR,
37    <RcChannels as Message>::DESCRIPTOR,
38    <ManualControl as Message>::DESCRIPTOR,
39    <MissionItemInt as Message>::DESCRIPTOR,
40    <VfrHud as Message>::DESCRIPTOR,
41    <CommandInt as Message>::DESCRIPTOR,
42    <CommandLong as Message>::DESCRIPTOR,
43    <CommandAck as Message>::DESCRIPTOR,
44    <SetPositionTargetLocalNed as Message>::DESCRIPTOR,
45    <SetPositionTargetGlobalInt as Message>::DESCRIPTOR,
46    <BatteryStatus as Message>::DESCRIPTOR,
47    <AutopilotVersion as Message>::DESCRIPTOR,
48    <HomePosition as Message>::DESCRIPTOR,
49    <ExtendedSysState as Message>::DESCRIPTOR,
50    <Statustext as Message>::DESCRIPTOR,
51];
52
53message! {
54    /// `HEARTBEAT`: the periodic broadcast every MAVLink node sends to announce its type,
55    /// autopilot, and status, and the frame a peer waits for before deciding a link is up.
56    Heartbeat = 0, crc = 50, name = "HEARTBEAT";
57    custom_mode: u32,
58    type_: u8,
59    autopilot: u8,
60    base_mode: u8,
61    system_status: u8,
62    mavlink_version: u8,
63}
64
65message! {
66    /// `SYS_STATUS`: the vehicle's onboard sensor health, load, and battery state.
67    SysStatus = 1, crc = 124, name = "SYS_STATUS";
68    onboard_control_sensors_present: u32,
69    onboard_control_sensors_enabled: u32,
70    onboard_control_sensors_health: u32,
71    load: u16,
72    voltage_battery: u16,
73    current_battery: i16,
74    drop_rate_comm: u16,
75    errors_comm: u16,
76    errors_count1: u16,
77    errors_count2: u16,
78    errors_count3: u16,
79    errors_count4: u16,
80    battery_remaining: i8,
81}
82
83message! {
84    /// `SYSTEM_TIME`: the vehicle's Unix and boot time, used to align clocks across a link.
85    SystemTime = 2, crc = 137, name = "SYSTEM_TIME";
86    time_unix_usec: u64,
87    time_boot_ms: u32,
88}
89
90message! {
91    /// `PING`: a round-trip timing and reachability probe.
92    Ping = 4, crc = 237, name = "PING";
93    time_usec: u64,
94    seq: u32,
95    target_system: u8,
96    target_component: u8,
97}
98
99message! {
100    /// `ATTITUDE`: the vehicle's orientation and angular rates in radians.
101    Attitude = 30, crc = 39, name = "ATTITUDE";
102    time_boot_ms: u32,
103    roll: f32,
104    pitch: f32,
105    yaw: f32,
106    rollspeed: f32,
107    pitchspeed: f32,
108    yawspeed: f32,
109}
110
111message! {
112    /// `GLOBAL_POSITION_INT`: the fused global position, altitude, and velocity.
113    GlobalPositionInt = 33, crc = 104, name = "GLOBAL_POSITION_INT";
114    time_boot_ms: u32,
115    lat: i32,
116    lon: i32,
117    alt: i32,
118    relative_alt: i32,
119    vx: i16,
120    vy: i16,
121    vz: i16,
122    hdg: u16,
123}
124
125message! {
126    /// `COMMAND_LONG`: a command for the vehicle to run, with up to seven float parameters.
127    CommandLong = 76, crc = 152, name = "COMMAND_LONG";
128    param1: f32,
129    param2: f32,
130    param3: f32,
131    param4: f32,
132    param5: f32,
133    param6: f32,
134    param7: f32,
135    command: u16,
136    target_system: u8,
137    target_component: u8,
138    confirmation: u8,
139}
140
141message! {
142    /// `COMMAND_ACK`: the vehicle's acknowledgment of a command, carrying the result.
143    ///
144    /// The extension fields report progress for a long-running command
145    /// ([`mav_result::IN_PROGRESS`](crate::dialect::mav_result::IN_PROGRESS)), a
146    /// command-specific second result value, and which system and component the
147    /// acknowledgment is addressed to.
148    CommandAck = 77, crc = 143, name = "COMMAND_ACK";
149    command: u16,
150    result: u8;
151    ext {
152        progress: u8,
153        result_param2: i32,
154        target_system: u8,
155        target_component: u8,
156    }
157}
158
159message! {
160    /// `COMMAND_INT`: a command in a coordinate frame, with integer-encoded position.
161    CommandInt = 75, crc = 158, name = "COMMAND_INT";
162    param1: f32,
163    param2: f32,
164    param3: f32,
165    param4: f32,
166    x: i32,
167    y: i32,
168    z: f32,
169    command: u16,
170    target_system: u8,
171    target_component: u8,
172    frame: u8,
173    current: u8,
174    autocontinue: u8,
175}
176
177message! {
178    /// `SET_MODE`: requests the vehicle switch base and custom flight modes.
179    SetMode = 11, crc = 89, name = "SET_MODE";
180    custom_mode: u32,
181    target_system: u8,
182    base_mode: u8,
183}
184
185message! {
186    /// `MANUAL_CONTROL`: a pilot's stick and button input, normalized to a fixed range.
187    ManualControl = 69, crc = 243, name = "MANUAL_CONTROL";
188    x: i16,
189    y: i16,
190    z: i16,
191    r: i16,
192    buttons: u16,
193    target: u8,
194}
195
196message! {
197    /// `PARAM_REQUEST_READ`: asks the vehicle for one onboard parameter, by name or index.
198    ParamRequestRead = 20, crc = 214, name = "PARAM_REQUEST_READ";
199    param_index: i16,
200    target_system: u8,
201    target_component: u8,
202    param_id: [char; 16],
203}
204
205message! {
206    /// `PARAM_REQUEST_LIST`: asks the vehicle to stream all of its onboard parameters.
207    ParamRequestList = 21, crc = 159, name = "PARAM_REQUEST_LIST";
208    target_system: u8,
209    target_component: u8,
210}
211
212message! {
213    /// `PARAM_VALUE`: one onboard parameter's value, type, and position in the set.
214    ParamValue = 22, crc = 220, name = "PARAM_VALUE";
215    param_value: f32,
216    param_count: u16,
217    param_index: u16,
218    param_id: [char; 16],
219    param_type: u8,
220}
221
222message! {
223    /// `PARAM_SET`: sets one onboard parameter on the vehicle.
224    ParamSet = 23, crc = 168, name = "PARAM_SET";
225    param_value: f32,
226    target_system: u8,
227    target_component: u8,
228    param_id: [char; 16],
229    param_type: u8,
230}
231
232message! {
233    /// `GPS_RAW_INT`: the raw GPS fix: position, accuracy, speed, and satellite count.
234    GpsRawInt = 24, crc = 24, name = "GPS_RAW_INT";
235    time_usec: u64,
236    lat: i32,
237    lon: i32,
238    alt: i32,
239    eph: u16,
240    epv: u16,
241    vel: u16,
242    cog: u16,
243    fix_type: u8,
244    satellites_visible: u8,
245}
246
247message! {
248    /// `ATTITUDE_QUATERNION`: orientation as a quaternion, with angular rates.
249    AttitudeQuaternion = 31, crc = 246, name = "ATTITUDE_QUATERNION";
250    time_boot_ms: u32,
251    q1: f32,
252    q2: f32,
253    q3: f32,
254    q4: f32,
255    rollspeed: f32,
256    pitchspeed: f32,
257    yawspeed: f32,
258}
259
260message! {
261    /// `LOCAL_POSITION_NED`: the vehicle's local position and velocity in the NED frame.
262    LocalPositionNed = 32, crc = 185, name = "LOCAL_POSITION_NED";
263    time_boot_ms: u32,
264    x: f32,
265    y: f32,
266    z: f32,
267    vx: f32,
268    vy: f32,
269    vz: f32,
270}
271
272message! {
273    /// `SERVO_OUTPUT_RAW`: the raw PWM values driving the first eight servo outputs.
274    ServoOutputRaw = 36, crc = 222, name = "SERVO_OUTPUT_RAW";
275    time_usec: u32,
276    servo1_raw: u16,
277    servo2_raw: u16,
278    servo3_raw: u16,
279    servo4_raw: u16,
280    servo5_raw: u16,
281    servo6_raw: u16,
282    servo7_raw: u16,
283    servo8_raw: u16,
284    port: u8,
285}
286
287message! {
288    /// `RC_CHANNELS`: the raw values of up to eighteen RC input channels.
289    RcChannels = 65, crc = 118, name = "RC_CHANNELS";
290    time_boot_ms: u32,
291    chan1_raw: u16,
292    chan2_raw: u16,
293    chan3_raw: u16,
294    chan4_raw: u16,
295    chan5_raw: u16,
296    chan6_raw: u16,
297    chan7_raw: u16,
298    chan8_raw: u16,
299    chan9_raw: u16,
300    chan10_raw: u16,
301    chan11_raw: u16,
302    chan12_raw: u16,
303    chan13_raw: u16,
304    chan14_raw: u16,
305    chan15_raw: u16,
306    chan16_raw: u16,
307    chan17_raw: u16,
308    chan18_raw: u16,
309    chancount: u8,
310    rssi: u8,
311}
312
313message! {
314    /// `VFR_HUD`: the heads-up flight summary: speeds, heading, throttle, altitude, climb.
315    VfrHud = 74, crc = 20, name = "VFR_HUD";
316    airspeed: f32,
317    groundspeed: f32,
318    alt: f32,
319    climb: f32,
320    heading: i16,
321    throttle: u16,
322}
323
324message! {
325    /// `BATTERY_STATUS`: a battery's charge, current, temperature, and per-cell voltages.
326    BatteryStatus = 147, crc = 154, name = "BATTERY_STATUS";
327    current_consumed: i32,
328    energy_consumed: i32,
329    temperature: i16,
330    voltages: [u16; 10],
331    current_battery: i16,
332    id: u8,
333    battery_function: u8,
334    type_: u8,
335    battery_remaining: i8,
336}
337
338message! {
339    /// `MISSION_REQUEST_LIST`: asks the vehicle to begin downloading a plan by reporting its
340    /// [`MissionCount`].
341    MissionRequestList = 43, crc = 132, name = "MISSION_REQUEST_LIST";
342    target_system: u8,
343    target_component: u8;
344    ext { mission_type: u8 }
345}
346
347message! {
348    /// `MISSION_COUNT`: announces how many items a mission transfer will contain, and which
349    /// plan (mission, geofence, or rally) the transfer carries.
350    MissionCount = 44, crc = 221, name = "MISSION_COUNT";
351    count: u16,
352    target_system: u8,
353    target_component: u8;
354    ext { mission_type: u8, opaque_id: u32 }
355}
356
357message! {
358    /// `MISSION_REQUEST_INT`: requests one mission item by sequence number, with position
359    /// returned integer-encoded.
360    MissionRequestInt = 51, crc = 196, name = "MISSION_REQUEST_INT";
361    seq: u16,
362    target_system: u8,
363    target_component: u8;
364    ext { mission_type: u8 }
365}
366
367message! {
368    /// `MISSION_REQUEST`: requests one mission item by sequence number, the legacy request
369    /// some autopilots still send in place of [`MissionRequestInt`].
370    MissionRequest = 40, crc = 230, name = "MISSION_REQUEST";
371    seq: u16,
372    target_system: u8,
373    target_component: u8;
374    ext { mission_type: u8 }
375}
376
377message! {
378    /// `MISSION_ITEM_INT`: one mission item, with integer-encoded position.
379    MissionItemInt = 73, crc = 38, name = "MISSION_ITEM_INT";
380    param1: f32,
381    param2: f32,
382    param3: f32,
383    param4: f32,
384    x: i32,
385    y: i32,
386    z: f32,
387    seq: u16,
388    command: u16,
389    target_system: u8,
390    target_component: u8,
391    frame: u8,
392    current: u8,
393    autocontinue: u8;
394    ext { mission_type: u8 }
395}
396
397message! {
398    /// `MISSION_CURRENT`: the sequence number of the mission item the vehicle is running.
399    MissionCurrent = 42, crc = 28, name = "MISSION_CURRENT";
400    seq: u16,
401}
402
403message! {
404    /// `MISSION_CLEAR_ALL`: asks the vehicle to erase a stored plan.
405    MissionClearAll = 45, crc = 232, name = "MISSION_CLEAR_ALL";
406    target_system: u8,
407    target_component: u8;
408    ext { mission_type: u8 }
409}
410
411message! {
412    /// `MISSION_ACK`: acknowledges the end of a mission transfer with a result.
413    MissionAck = 47, crc = 153, name = "MISSION_ACK";
414    target_system: u8,
415    target_component: u8,
416    type_: u8;
417    ext { mission_type: u8, opaque_id: u32 }
418}
419
420message! {
421    /// `SET_POSITION_TARGET_LOCAL_NED`: an offboard position, velocity, or thrust setpoint
422    /// in the local NED frame.
423    SetPositionTargetLocalNed = 84, crc = 143, name = "SET_POSITION_TARGET_LOCAL_NED";
424    time_boot_ms: u32,
425    x: f32,
426    y: f32,
427    z: f32,
428    vx: f32,
429    vy: f32,
430    vz: f32,
431    afx: f32,
432    afy: f32,
433    afz: f32,
434    yaw: f32,
435    yaw_rate: f32,
436    type_mask: u16,
437    target_system: u8,
438    target_component: u8,
439    coordinate_frame: u8,
440}
441
442message! {
443    /// `SET_POSITION_TARGET_GLOBAL_INT`: an offboard setpoint in the global frame, with
444    /// integer-encoded latitude and longitude.
445    SetPositionTargetGlobalInt = 86, crc = 5, name = "SET_POSITION_TARGET_GLOBAL_INT";
446    time_boot_ms: u32,
447    lat_int: i32,
448    lon_int: i32,
449    alt: f32,
450    vx: f32,
451    vy: f32,
452    vz: f32,
453    afx: f32,
454    afy: f32,
455    afz: f32,
456    yaw: f32,
457    yaw_rate: f32,
458    type_mask: u16,
459    target_system: u8,
460    target_component: u8,
461    coordinate_frame: u8,
462}
463
464message! {
465    /// `HOME_POSITION`: the vehicle's home location and the approach vector to it.
466    HomePosition = 242, crc = 104, name = "HOME_POSITION";
467    latitude: i32,
468    longitude: i32,
469    altitude: i32,
470    x: f32,
471    y: f32,
472    z: f32,
473    q: [f32; 4],
474    approach_x: f32,
475    approach_y: f32,
476    approach_z: f32,
477}
478
479message! {
480    /// `AUTOPILOT_VERSION`: the autopilot's capability flags and firmware versions.
481    AutopilotVersion = 148, crc = 178, name = "AUTOPILOT_VERSION";
482    capabilities: u64,
483    uid: u64,
484    flight_sw_version: u32,
485    middleware_sw_version: u32,
486    os_sw_version: u32,
487    board_version: u32,
488    vendor_id: u16,
489    product_id: u16,
490    flight_custom_version: [u8; 8],
491    middleware_custom_version: [u8; 8],
492    os_custom_version: [u8; 8],
493}
494
495message! {
496    /// `EXTENDED_SYS_STATE`: the VTOL and landed state of the vehicle.
497    ExtendedSysState = 245, crc = 130, name = "EXTENDED_SYS_STATE";
498    vtol_state: u8,
499    landed_state: u8,
500}
501
502message! {
503    /// `STATUSTEXT`: a human-readable status message with a severity.
504    Statustext = 253, crc = 83, name = "STATUSTEXT";
505    severity: u8,
506    text: [char; 50],
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512    use crate::dialect::{crc_extra, Message};
513    use crate::message_crc_extra;
514
515    // Re-derives a message's CRC_EXTRA from its field definitions and checks it against the
516    // official dialect value the message declares and the registry returns. A wrong field
517    // type, name, or order changes the derived seed, so this catches it.
518    fn verify<M: Message>() {
519        assert_eq!(
520            message_crc_extra(M::NAME, M::BASE_FIELDS),
521            M::CRC_EXTRA,
522            "derived CRC_EXTRA disagrees with the dialect for {}",
523            M::NAME
524        );
525        assert_eq!(
526            crc_extra(M::ID),
527            Some(M::CRC_EXTRA),
528            "registry disagrees for {}",
529            M::NAME
530        );
531    }
532
533    #[test]
534    fn every_message_crc_extra_matches_the_dialect() {
535        verify::<Heartbeat>();
536        verify::<SysStatus>();
537        verify::<SystemTime>();
538        verify::<Ping>();
539        verify::<Attitude>();
540        verify::<GlobalPositionInt>();
541        verify::<CommandLong>();
542        verify::<CommandAck>();
543        verify::<CommandInt>();
544        verify::<SetMode>();
545        verify::<ManualControl>();
546        verify::<ParamRequestRead>();
547        verify::<ParamRequestList>();
548        verify::<ParamValue>();
549        verify::<ParamSet>();
550        verify::<GpsRawInt>();
551        verify::<AttitudeQuaternion>();
552        verify::<LocalPositionNed>();
553        verify::<ServoOutputRaw>();
554        verify::<RcChannels>();
555        verify::<VfrHud>();
556        verify::<BatteryStatus>();
557        verify::<MissionRequestList>();
558        verify::<MissionCount>();
559        verify::<MissionRequestInt>();
560        verify::<MissionRequest>();
561        verify::<MissionItemInt>();
562        verify::<MissionCurrent>();
563        verify::<MissionClearAll>();
564        verify::<MissionAck>();
565        verify::<SetPositionTargetLocalNed>();
566        verify::<SetPositionTargetGlobalInt>();
567        verify::<HomePosition>();
568        verify::<AutopilotVersion>();
569        verify::<ExtendedSysState>();
570        verify::<Statustext>();
571    }
572
573    #[test]
574    fn a_heartbeat_encodes_to_the_documented_frame() {
575        // A published worked example of a v1 HEARTBEAT on the wire (custom_mode 0, a
576        // quadrotor running ArduPilot, base_mode 0x51, status active). Encoding the same
577        // message must reproduce it byte for byte, checksum included, which anchors the
578        // whole v1 frame layout to an external reference rather than a round-trip.
579        let heartbeat = Heartbeat {
580            custom_mode: 0,
581            type_: 2,
582            autopilot: 3,
583            base_mode: 0x51,
584            system_status: 4,
585            mavlink_version: 3,
586        };
587        let mut payload = [0u8; 255];
588        let len = heartbeat.encode(&mut payload);
589        let frame = crate::Frame::encode_v1(
590            crate::Header::new(1, 1, 0x4E),
591            Heartbeat::ID,
592            &payload[..len],
593            Heartbeat::CRC_EXTRA,
594        )
595        .unwrap();
596        let expected = [
597            0xFE, 0x09, 0x4E, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x51, 0x04,
598            0x03, 0x1C, 0x7F,
599        ];
600        assert_eq!(frame.as_bytes(), &expected);
601    }
602
603    #[test]
604    fn a_heartbeat_round_trips_through_its_payload() {
605        let heartbeat = Heartbeat {
606            custom_mode: 0x0A0B0C0D,
607            type_: 2,
608            autopilot: 12,
609            base_mode: 0x81,
610            system_status: 4,
611            mavlink_version: 3,
612        };
613        let mut buf = [0u8; 255];
614        let len = heartbeat.encode(&mut buf);
615        assert_eq!(len, Heartbeat::WIRE_LEN);
616        assert_eq!(Heartbeat::decode(&buf[..len]).unwrap(), heartbeat);
617    }
618
619    #[test]
620    fn a_command_long_round_trips_through_field_reordering() {
621        // COMMAND_LONG mixes 4-, 2-, and 1-byte fields, so this exercises the wire
622        // reordering the declaration encodes (floats first, then the command, then the bytes).
623        let command = CommandLong {
624            param1: 1.0,
625            param2: 0.0,
626            param3: 0.0,
627            param4: 0.0,
628            param5: 12.34,
629            param6: 56.78,
630            param7: 100.0,
631            command: 400, // MAV_CMD_COMPONENT_ARM_DISARM
632            target_system: 1,
633            target_component: 1,
634            confirmation: 0,
635        };
636        let mut buf = [0u8; 255];
637        let len = command.encode(&mut buf);
638        assert_eq!(len, CommandLong::WIRE_LEN);
639        assert_eq!(CommandLong::decode(&buf[..len]).unwrap(), command);
640    }
641
642    #[test]
643    fn an_extension_field_round_trips_after_the_base_fields() {
644        // MISSION_COUNT gained the `mission_type` and `opaque_id` extension fields; a full
645        // encode carries them after the base fields and decodes them back.
646        let count = MissionCount {
647            count: 7,
648            target_system: 1,
649            target_component: 1,
650            mission_type: crate::dialect::mav_mission_type::FENCE,
651            opaque_id: 0xDEAD_BEEF,
652        };
653        let mut buf = [0u8; 255];
654        let len = count.encode(&mut buf);
655        assert_eq!(len, MissionCount::WIRE_LEN);
656        // Base fields (count, targets) occupy four bytes; the extensions follow.
657        assert_eq!(len, 4 + 1 + 4);
658        assert_eq!(MissionCount::decode(&buf[..len]).unwrap(), count);
659    }
660
661    #[test]
662    fn a_base_only_payload_decodes_extensions_as_zero() {
663        // A peer that sends only the base fields (a MAVLink 1 sender, or truncated MAVLink 2)
664        // still decodes; the extensions read as zero, which is what keeps a message that gains
665        // extensions compatible with one that predates them.
666        let base_only = [7u8, 0, 1, 1]; // count = 7, target_system = 1, target_component = 1
667        let decoded = MissionCount::decode(&base_only).unwrap();
668        assert_eq!(decoded.count, 7);
669        assert_eq!(decoded.mission_type, 0);
670        assert_eq!(decoded.opaque_id, 0);
671    }
672
673    #[test]
674    fn command_ack_keeps_its_seed_after_gaining_extensions() {
675        // The extensions are excluded from CRC_EXTRA, so COMMAND_ACK's seed is still the
676        // pre-extension value a peer that only knows the base fields computes.
677        assert_eq!(
678            message_crc_extra("COMMAND_ACK", CommandAck::BASE_FIELDS),
679            CommandAck::CRC_EXTRA
680        );
681        assert_eq!(CommandAck::CRC_EXTRA, 143);
682        // The base fields drive the seed; the extensions are not among them.
683        assert_eq!(CommandAck::BASE_FIELDS.len(), 2);
684    }
685
686    #[test]
687    fn decode_zero_extends_a_truncated_payload() {
688        // A HEARTBEAT whose trailing bytes were truncated to a single zero still decodes,
689        // with the missing fields read as zero.
690        let decoded = Heartbeat::decode(&[0]).unwrap();
691        assert_eq!(decoded, Heartbeat::default_zeroed());
692    }
693
694    impl Heartbeat {
695        fn default_zeroed() -> Self {
696            Heartbeat {
697                custom_mode: 0,
698                type_: 0,
699                autopilot: 0,
700                base_mode: 0,
701                system_status: 0,
702                mavlink_version: 0,
703            }
704        }
705    }
706}