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}