Skip to main content

pamoja_mavlink/protocol/
mission.rs

1//! The mission (plan) transfer protocol.
2//!
3//! A plan is transferred one item at a time, driven entirely by the receiver: the receiver
4//! announces or is told the item count, then requests item 0, item 1, and so on, and the
5//! sender answers each request with the matching [`MissionItemInt`]. The transfer ends with a
6//! [`MissionAck`]. Items must arrive in order; an out-of-order item is dropped and the expected
7//! sequence number is re-requested.
8//!
9//! The two roles are symmetric, so this module models them as two machines rather than by
10//! direction: a [`MissionSender`] holds the items and answers requests, and a
11//! [`MissionReceiver`] asks for items and collects them. A ground station uploading a plan is a
12//! sender talking to a vehicle receiver; a ground station downloading a plan is a receiver
13//! talking to a vehicle sender. The same two machines cover both, and neither allocates: the
14//! sender borrows the item slice, and the receiver hands each item back to the caller instead
15//! of storing them.
16
17use crate::dialect::{
18    mav_mission_result, MissionAck, MissionCount, MissionItemInt, MissionRequestInt,
19    MissionRequestList,
20};
21
22/// Holds a plan and answers a receiver's requests for its items.
23///
24/// The sender borrows the items and stamps the target ids, the mission type, and the requested
25/// sequence number onto each one as it is handed out, so the caller supplies only the item
26/// content (command, frame, position, parameters).
27pub struct MissionSender<'a> {
28    items: &'a [MissionItemInt],
29    target_system: u8,
30    target_component: u8,
31    mission_type: u8,
32}
33
34impl<'a> MissionSender<'a> {
35    /// Creates a sender for a plan bound for a target vehicle.
36    ///
37    /// # Arguments
38    ///
39    /// * `items` - the mission items, in sequence order.
40    /// * `target_system` - the receiving system's id.
41    /// * `target_component` - the receiving component's id.
42    /// * `mission_type` - the [`MAV_MISSION_TYPE`](crate::dialect::mav_mission_type) of the plan.
43    ///
44    /// # Returns
45    ///
46    /// The sender.
47    pub fn new(
48        items: &'a [MissionItemInt],
49        target_system: u8,
50        target_component: u8,
51        mission_type: u8,
52    ) -> Self {
53        MissionSender {
54            items,
55            target_system,
56            target_component,
57            mission_type,
58        }
59    }
60
61    /// Returns the number of items in the plan.
62    ///
63    /// # Returns
64    ///
65    /// The item count.
66    pub fn len(&self) -> u16 {
67        self.items.len() as u16
68    }
69
70    /// Reports whether the plan has no items.
71    ///
72    /// # Returns
73    ///
74    /// `true` if the plan is empty.
75    pub fn is_empty(&self) -> bool {
76        self.items.is_empty()
77    }
78
79    /// Builds the [`MissionCount`] that opens the transfer.
80    ///
81    /// # Returns
82    ///
83    /// The count message to send first.
84    pub fn count(&self) -> MissionCount {
85        MissionCount {
86            count: self.len(),
87            target_system: self.target_system,
88            target_component: self.target_component,
89            mission_type: self.mission_type,
90            opaque_id: 0,
91        }
92    }
93
94    /// Builds the item to answer a request for `seq`, stamped with the sequence number, the
95    /// target ids, and the mission type.
96    ///
97    /// # Arguments
98    ///
99    /// * `seq` - the requested sequence number.
100    ///
101    /// # Returns
102    ///
103    /// The item to send, or [`None`] if `seq` is past the end of the plan.
104    pub fn item(&self, seq: u16) -> Option<MissionItemInt> {
105        self.items.get(seq as usize).map(|item| MissionItemInt {
106            seq,
107            target_system: self.target_system,
108            target_component: self.target_component,
109            mission_type: self.mission_type,
110            ..*item
111        })
112    }
113}
114
115/// The next thing a [`MissionReceiver`] should send.
116#[derive(Clone, Copy, Debug, PartialEq)]
117pub enum ReceiverAction {
118    /// Ask for this sequence number.
119    Request(MissionRequestInt),
120    /// The transfer is complete; send this acknowledgment.
121    Ack(MissionAck),
122}
123
124/// Requests a plan's items in order and collects them, ending with an acknowledgment.
125///
126/// The receiver tracks the announced count and the next expected sequence number. It never
127/// stores items; each accepted item is handed back to the caller from
128/// [`on_item`](MissionReceiver::on_item).
129pub struct MissionReceiver {
130    target_system: u8,
131    target_component: u8,
132    mission_type: u8,
133    count: u16,
134    next: u16,
135    complete: bool,
136}
137
138impl MissionReceiver {
139    /// Creates a receiver for a plan from a target vehicle.
140    ///
141    /// # Arguments
142    ///
143    /// * `target_system` - the sending system's id.
144    /// * `target_component` - the sending component's id.
145    /// * `mission_type` - the [`MAV_MISSION_TYPE`](crate::dialect::mav_mission_type) to transfer.
146    ///
147    /// # Returns
148    ///
149    /// The receiver, before any count is known.
150    pub fn new(target_system: u8, target_component: u8, mission_type: u8) -> Self {
151        MissionReceiver {
152            target_system,
153            target_component,
154            mission_type,
155            count: 0,
156            next: 0,
157            complete: false,
158        }
159    }
160
161    /// Builds the [`MissionRequestList`] that starts a download.
162    ///
163    /// Used when the receiver initiates the transfer (a ground station downloading a plan); a
164    /// receiver that is answering an unsolicited [`MissionCount`] does not send it.
165    ///
166    /// # Returns
167    ///
168    /// The request-list message.
169    pub fn request_list(&self) -> MissionRequestList {
170        MissionRequestList {
171            target_system: self.target_system,
172            target_component: self.target_component,
173            mission_type: self.mission_type,
174        }
175    }
176
177    /// Handles the announced item count and returns the first action.
178    ///
179    /// # Arguments
180    ///
181    /// * `count` - the number of items the sender will provide.
182    ///
183    /// # Returns
184    ///
185    /// A [`ReceiverAction::Request`] for item 0, or a [`ReceiverAction::Ack`] straight away if
186    /// the plan is empty.
187    pub fn on_count(&mut self, count: u16) -> ReceiverAction {
188        self.count = count;
189        self.next = 0;
190        if count == 0 {
191            self.complete = true;
192            return ReceiverAction::Ack(self.ack());
193        }
194        ReceiverAction::Request(self.request(0))
195    }
196
197    /// Handles an incoming item and returns the accepted item plus the next action.
198    ///
199    /// An in-order item is accepted and returned, and the receiver advances to request the next
200    /// one or to acknowledge the transfer if it was the last. An out-of-order item is not
201    /// accepted, and the expected sequence number is re-requested.
202    ///
203    /// # Arguments
204    ///
205    /// * `item` - the decoded item.
206    ///
207    /// # Returns
208    ///
209    /// A pair of the accepted item (`Some` only when in order) and the next
210    /// [`ReceiverAction`].
211    pub fn on_item(&mut self, item: &MissionItemInt) -> (Option<MissionItemInt>, ReceiverAction) {
212        if self.complete || item.seq != self.next {
213            // Out of order (or after completion): re-request what is still expected.
214            return (None, ReceiverAction::Request(self.request(self.next)));
215        }
216        self.next += 1;
217        if self.next >= self.count {
218            self.complete = true;
219            (Some(*item), ReceiverAction::Ack(self.ack()))
220        } else {
221            (
222                Some(*item),
223                ReceiverAction::Request(self.request(self.next)),
224            )
225        }
226    }
227
228    /// Reports whether the transfer has finished.
229    ///
230    /// # Returns
231    ///
232    /// `true` once every item has been received and the acknowledgment produced.
233    pub fn is_complete(&self) -> bool {
234        self.complete
235    }
236
237    /// Returns the next sequence number the receiver expects.
238    ///
239    /// # Returns
240    ///
241    /// The expected sequence number.
242    pub fn expected(&self) -> u16 {
243        self.next
244    }
245
246    fn request(&self, seq: u16) -> MissionRequestInt {
247        MissionRequestInt {
248            seq,
249            target_system: self.target_system,
250            target_component: self.target_component,
251            mission_type: self.mission_type,
252        }
253    }
254
255    fn ack(&self) -> MissionAck {
256        MissionAck {
257            target_system: self.target_system,
258            target_component: self.target_component,
259            type_: mav_mission_result::ACCEPTED,
260            mission_type: self.mission_type,
261            opaque_id: 0,
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use crate::dialect::{mav_cmd, mav_frame, mav_mission_type};
270
271    fn waypoint(seq: u16, lat: i32, lon: i32, alt: f32) -> MissionItemInt {
272        MissionItemInt {
273            param1: 0.0,
274            param2: 0.0,
275            param3: 0.0,
276            param4: 0.0,
277            x: lat,
278            y: lon,
279            z: alt,
280            seq,
281            command: mav_cmd::NAV_WAYPOINT,
282            target_system: 0,
283            target_component: 0,
284            frame: mav_frame::GLOBAL_RELATIVE_ALT_INT,
285            current: (seq == 0) as u8,
286            autocontinue: 1,
287            mission_type: mav_mission_type::MISSION,
288        }
289    }
290
291    #[test]
292    fn the_sender_stamps_the_target_and_sequence_onto_each_item() {
293        let items = [waypoint(0, 10, 20, 30.0), waypoint(1, 11, 21, 31.0)];
294        let sender = MissionSender::new(&items, 7, 8, mav_mission_type::MISSION);
295        assert_eq!(sender.count().count, 2);
296        assert_eq!(sender.count().target_system, 7);
297
298        let item = sender.item(1).unwrap();
299        assert_eq!(item.seq, 1);
300        assert_eq!(item.target_system, 7);
301        assert_eq!(item.target_component, 8);
302        assert_eq!(item.x, 11);
303        assert!(sender.item(2).is_none());
304    }
305
306    #[test]
307    fn a_sender_and_receiver_complete_an_in_order_transfer() {
308        // This drives the documented upload/download exchange end to end: COUNT, then a
309        // REQUEST_INT and ITEM_INT for each sequence number, then an ACK.
310        let items = [
311            waypoint(0, 10, 20, 30.0),
312            waypoint(1, 11, 21, 31.0),
313            waypoint(2, 12, 22, 32.0),
314        ];
315        let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
316        let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
317
318        let mut action = receiver.on_count(sender.count().count);
319        let mut collected = [MissionItemInt::default_zeroed(); 3];
320        let mut collected_len = 0usize;
321        loop {
322            match action {
323                ReceiverAction::Request(request) => {
324                    let item = sender.item(request.seq).expect("in range");
325                    let (accepted, next) = receiver.on_item(&item);
326                    if let Some(item) = accepted {
327                        collected[collected_len] = item;
328                        collected_len += 1;
329                    }
330                    action = next;
331                }
332                ReceiverAction::Ack(ack) => {
333                    assert_eq!(ack.type_, mav_mission_result::ACCEPTED);
334                    break;
335                }
336            }
337        }
338        assert!(receiver.is_complete());
339        assert_eq!(collected_len, 3);
340        assert_eq!(collected[0].x, 10);
341        assert_eq!(collected[2].x, 12);
342    }
343
344    #[test]
345    fn an_out_of_order_item_is_dropped_and_re_requested() {
346        let items = [waypoint(0, 10, 20, 30.0), waypoint(1, 11, 21, 31.0)];
347        let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
348        let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
349
350        let first = receiver.on_count(sender.count().count);
351        assert_eq!(first, ReceiverAction::Request(sender_request(0)));
352
353        // The sender (wrongly) answers with item 1 instead of item 0.
354        let (accepted, action) = receiver.on_item(&sender.item(1).unwrap());
355        assert!(accepted.is_none());
356        // The receiver still wants item 0.
357        assert_eq!(action, ReceiverAction::Request(sender_request(0)));
358        assert_eq!(receiver.expected(), 0);
359    }
360
361    #[test]
362    fn an_empty_plan_acknowledges_immediately() {
363        let items: [MissionItemInt; 0] = [];
364        let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
365        let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
366        match receiver.on_count(sender.count().count) {
367            ReceiverAction::Ack(ack) => assert_eq!(ack.type_, mav_mission_result::ACCEPTED),
368            ReceiverAction::Request(_) => panic!("an empty plan needs no items"),
369        }
370        assert!(receiver.is_complete());
371    }
372
373    fn sender_request(seq: u16) -> MissionRequestInt {
374        MissionRequestInt {
375            seq,
376            target_system: 1,
377            target_component: 1,
378            mission_type: mav_mission_type::MISSION,
379        }
380    }
381
382    impl MissionItemInt {
383        fn default_zeroed() -> Self {
384            MissionItemInt {
385                param1: 0.0,
386                param2: 0.0,
387                param3: 0.0,
388                param4: 0.0,
389                x: 0,
390                y: 0,
391                z: 0.0,
392                seq: 0,
393                command: 0,
394                target_system: 0,
395                target_component: 0,
396                frame: 0,
397                current: 0,
398                autocontinue: 0,
399                mission_type: 0,
400            }
401        }
402    }
403}