Skip to main content

pamoja_mavlink/protocol/
offboard.rs

1//! Offboard control setpoints: the `type_mask` builder and setpoint constructors.
2//!
3//! An offboard setpoint carries position, velocity, and acceleration fields all at once, and a
4//! `type_mask` says which of them the vehicle should act on and which to ignore. Getting the
5//! mask wrong is the classic offboard bug: a velocity setpoint with the position bits left
6//! active makes the vehicle chase a zero position. [`TypeMask`] builds the mask from the named
7//! [`POSITION_TARGET_TYPEMASK`](crate::dialect::position_target_typemask) bits, starting from
8//! "ignore everything" and enabling only the dimensions a setpoint sets, and the constructors
9//! on [`SetPositionTargetLocalNed`] and [`SetPositionTargetGlobalInt`] use it to produce
10//! ready-to-send position and velocity setpoints.
11
12use crate::dialect::position_target_typemask as bits;
13use crate::dialect::{SetPositionTargetGlobalInt, SetPositionTargetLocalNed};
14
15/// Builds a `type_mask` for a `SET_POSITION_TARGET_*` message.
16///
17/// A set bit tells the vehicle to ignore that dimension, so the builder starts from
18/// [`ignore_all`](TypeMask::ignore_all) and each `use_*` method clears the ignore bits for the
19/// dimensions the setpoint provides.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct TypeMask(u16);
22
23impl TypeMask {
24    // Every ignorable dimension, i.e. every bit except FORCE_SET, which is a mode selector.
25    const ALL_IGNORE: u16 = bits::X_IGNORE
26        | bits::Y_IGNORE
27        | bits::Z_IGNORE
28        | bits::VX_IGNORE
29        | bits::VY_IGNORE
30        | bits::VZ_IGNORE
31        | bits::AX_IGNORE
32        | bits::AY_IGNORE
33        | bits::AZ_IGNORE
34        | bits::YAW_IGNORE
35        | bits::YAW_RATE_IGNORE;
36
37    /// Starts from a mask that ignores every dimension.
38    ///
39    /// # Returns
40    ///
41    /// A mask with every ignore bit set.
42    pub fn ignore_all() -> Self {
43        TypeMask(Self::ALL_IGNORE)
44    }
45
46    /// Enables the position fields (`x`, `y`, `z`).
47    ///
48    /// # Returns
49    ///
50    /// The mask, for chaining.
51    pub fn use_position(mut self) -> Self {
52        self.0 &= !(bits::X_IGNORE | bits::Y_IGNORE | bits::Z_IGNORE);
53        self
54    }
55
56    /// Enables the velocity fields (`vx`, `vy`, `vz`).
57    ///
58    /// # Returns
59    ///
60    /// The mask, for chaining.
61    pub fn use_velocity(mut self) -> Self {
62        self.0 &= !(bits::VX_IGNORE | bits::VY_IGNORE | bits::VZ_IGNORE);
63        self
64    }
65
66    /// Enables the acceleration fields (`afx`, `afy`, `afz`).
67    ///
68    /// # Returns
69    ///
70    /// The mask, for chaining.
71    pub fn use_acceleration(mut self) -> Self {
72        self.0 &= !(bits::AX_IGNORE | bits::AY_IGNORE | bits::AZ_IGNORE);
73        self
74    }
75
76    /// Enables the `yaw` field.
77    ///
78    /// # Returns
79    ///
80    /// The mask, for chaining.
81    pub fn use_yaw(mut self) -> Self {
82        self.0 &= !bits::YAW_IGNORE;
83        self
84    }
85
86    /// Enables the `yaw_rate` field.
87    ///
88    /// # Returns
89    ///
90    /// The mask, for chaining.
91    pub fn use_yaw_rate(mut self) -> Self {
92        self.0 &= !bits::YAW_RATE_IGNORE;
93        self
94    }
95
96    /// Sets the force flag, so the acceleration fields are interpreted as a force.
97    ///
98    /// # Returns
99    ///
100    /// The mask, for chaining.
101    pub fn force(mut self) -> Self {
102        self.0 |= bits::FORCE_SET;
103        self
104    }
105
106    /// Returns the assembled mask value.
107    ///
108    /// # Returns
109    ///
110    /// The `type_mask` bits.
111    pub fn bits(self) -> u16 {
112        self.0
113    }
114}
115
116impl SetPositionTargetLocalNed {
117    /// Builds a local-frame position setpoint, ignoring velocity, acceleration, and yaw.
118    ///
119    /// # Arguments
120    ///
121    /// * `time_boot_ms` - the sender's boot timestamp, in milliseconds.
122    /// * `coordinate_frame` - the [`MAV_FRAME`](crate::dialect::mav_frame) of the setpoint.
123    /// * `target_system` - the target system id.
124    /// * `target_component` - the target component id.
125    /// * `x`, `y`, `z` - the position, in meters in the chosen frame.
126    ///
127    /// # Returns
128    ///
129    /// The setpoint, with only the position fields active in its `type_mask`.
130    pub fn position(
131        time_boot_ms: u32,
132        coordinate_frame: u8,
133        target_system: u8,
134        target_component: u8,
135        x: f32,
136        y: f32,
137        z: f32,
138    ) -> Self {
139        SetPositionTargetLocalNed {
140            time_boot_ms,
141            x,
142            y,
143            z,
144            type_mask: TypeMask::ignore_all().use_position().bits(),
145            target_system,
146            target_component,
147            coordinate_frame,
148            ..Self::zeroed()
149        }
150    }
151
152    /// Builds a local-frame velocity setpoint, ignoring position, acceleration, and yaw.
153    ///
154    /// # Arguments
155    ///
156    /// * `time_boot_ms` - the sender's boot timestamp, in milliseconds.
157    /// * `coordinate_frame` - the [`MAV_FRAME`](crate::dialect::mav_frame) of the setpoint.
158    /// * `target_system` - the target system id.
159    /// * `target_component` - the target component id.
160    /// * `vx`, `vy`, `vz` - the velocity, in meters per second in the chosen frame.
161    ///
162    /// # Returns
163    ///
164    /// The setpoint, with only the velocity fields active in its `type_mask`.
165    pub fn velocity(
166        time_boot_ms: u32,
167        coordinate_frame: u8,
168        target_system: u8,
169        target_component: u8,
170        vx: f32,
171        vy: f32,
172        vz: f32,
173    ) -> Self {
174        SetPositionTargetLocalNed {
175            time_boot_ms,
176            vx,
177            vy,
178            vz,
179            type_mask: TypeMask::ignore_all().use_velocity().bits(),
180            target_system,
181            target_component,
182            coordinate_frame,
183            ..Self::zeroed()
184        }
185    }
186}
187
188impl SetPositionTargetGlobalInt {
189    /// Builds a global-frame position setpoint, ignoring velocity, acceleration, and yaw.
190    ///
191    /// # Arguments
192    ///
193    /// * `time_boot_ms` - the sender's boot timestamp, in milliseconds.
194    /// * `coordinate_frame` - the [`MAV_FRAME`](crate::dialect::mav_frame) of the setpoint.
195    /// * `target_system` - the target system id.
196    /// * `target_component` - the target component id.
197    /// * `lat_int`, `lon_int` - latitude and longitude, in degrees times 1e7.
198    /// * `alt` - the altitude, in meters in the chosen frame.
199    ///
200    /// # Returns
201    ///
202    /// The setpoint, with only the position fields active in its `type_mask`.
203    pub fn position(
204        time_boot_ms: u32,
205        coordinate_frame: u8,
206        target_system: u8,
207        target_component: u8,
208        lat_int: i32,
209        lon_int: i32,
210        alt: f32,
211    ) -> Self {
212        SetPositionTargetGlobalInt {
213            time_boot_ms,
214            lat_int,
215            lon_int,
216            alt,
217            type_mask: TypeMask::ignore_all().use_position().bits(),
218            target_system,
219            target_component,
220            coordinate_frame,
221            ..Self::zeroed()
222        }
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::dialect::mav_frame;
230
231    #[test]
232    fn ignore_all_sets_every_dimension_but_the_force_flag() {
233        let mask = TypeMask::ignore_all().bits();
234        // Every ignore bit is set.
235        assert_eq!(mask & bits::X_IGNORE, bits::X_IGNORE);
236        assert_eq!(mask & bits::VZ_IGNORE, bits::VZ_IGNORE);
237        assert_eq!(mask & bits::YAW_RATE_IGNORE, bits::YAW_RATE_IGNORE);
238        // FORCE_SET is a mode selector, not an ignore bit, so it stays clear.
239        assert_eq!(mask & bits::FORCE_SET, 0);
240    }
241
242    #[test]
243    fn a_position_mask_enables_only_the_position_bits() {
244        let mask = TypeMask::ignore_all().use_position().bits();
245        // Position is acted on: its ignore bits are clear.
246        assert_eq!(mask & (bits::X_IGNORE | bits::Y_IGNORE | bits::Z_IGNORE), 0);
247        // Everything else is still ignored.
248        assert_eq!(mask & bits::VX_IGNORE, bits::VX_IGNORE);
249        assert_eq!(mask & bits::AX_IGNORE, bits::AX_IGNORE);
250        assert_eq!(mask & bits::YAW_IGNORE, bits::YAW_IGNORE);
251        // The exact value, so a regression in the bit layout is caught.
252        assert_eq!(mask, TypeMask::ALL_IGNORE & !(1 | 2 | 4));
253    }
254
255    #[test]
256    fn a_velocity_setpoint_ignores_position() {
257        let setpoint =
258            SetPositionTargetLocalNed::velocity(1000, mav_frame::LOCAL_NED, 1, 1, 0.5, 0.0, -0.2);
259        assert_eq!(setpoint.vx, 0.5);
260        assert_eq!(setpoint.vz, -0.2);
261        // Position is ignored, velocity is not.
262        assert_eq!(setpoint.type_mask & bits::X_IGNORE, bits::X_IGNORE);
263        assert_eq!(setpoint.type_mask & bits::VX_IGNORE, 0);
264    }
265
266    #[test]
267    fn a_global_position_setpoint_carries_scaled_coordinates() {
268        let setpoint = SetPositionTargetGlobalInt::position(
269            2000,
270            mav_frame::GLOBAL_RELATIVE_ALT_INT,
271            1,
272            1,
273            473_977_418,
274            85_455_939,
275            10.0,
276        );
277        assert_eq!(setpoint.lat_int, 473_977_418);
278        assert_eq!(setpoint.alt, 10.0);
279        assert_eq!(
280            setpoint.type_mask & (bits::X_IGNORE | bits::Y_IGNORE | bits::Z_IGNORE),
281            0
282        );
283    }
284}