use crate::dialect::{
mav_mission_result, MissionAck, MissionCount, MissionItemInt, MissionRequestInt,
MissionRequestList,
};
pub struct MissionSender<'a> {
items: &'a [MissionItemInt],
target_system: u8,
target_component: u8,
mission_type: u8,
}
impl<'a> MissionSender<'a> {
pub fn new(
items: &'a [MissionItemInt],
target_system: u8,
target_component: u8,
mission_type: u8,
) -> Self {
MissionSender {
items,
target_system,
target_component,
mission_type,
}
}
pub fn len(&self) -> u16 {
self.items.len() as u16
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn count(&self) -> MissionCount {
MissionCount {
count: self.len(),
target_system: self.target_system,
target_component: self.target_component,
mission_type: self.mission_type,
opaque_id: 0,
}
}
pub fn item(&self, seq: u16) -> Option<MissionItemInt> {
self.items.get(seq as usize).map(|item| MissionItemInt {
seq,
target_system: self.target_system,
target_component: self.target_component,
mission_type: self.mission_type,
..*item
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ReceiverAction {
Request(MissionRequestInt),
Ack(MissionAck),
}
pub struct MissionReceiver {
target_system: u8,
target_component: u8,
mission_type: u8,
count: u16,
next: u16,
complete: bool,
}
impl MissionReceiver {
pub fn new(target_system: u8, target_component: u8, mission_type: u8) -> Self {
MissionReceiver {
target_system,
target_component,
mission_type,
count: 0,
next: 0,
complete: false,
}
}
pub fn request_list(&self) -> MissionRequestList {
MissionRequestList {
target_system: self.target_system,
target_component: self.target_component,
mission_type: self.mission_type,
}
}
pub fn on_count(&mut self, count: u16) -> ReceiverAction {
self.count = count;
self.next = 0;
if count == 0 {
self.complete = true;
return ReceiverAction::Ack(self.ack());
}
ReceiverAction::Request(self.request(0))
}
pub fn on_item(&mut self, item: &MissionItemInt) -> (Option<MissionItemInt>, ReceiverAction) {
if self.complete || item.seq != self.next {
return (None, ReceiverAction::Request(self.request(self.next)));
}
self.next += 1;
if self.next >= self.count {
self.complete = true;
(Some(*item), ReceiverAction::Ack(self.ack()))
} else {
(
Some(*item),
ReceiverAction::Request(self.request(self.next)),
)
}
}
pub fn is_complete(&self) -> bool {
self.complete
}
pub fn expected(&self) -> u16 {
self.next
}
fn request(&self, seq: u16) -> MissionRequestInt {
MissionRequestInt {
seq,
target_system: self.target_system,
target_component: self.target_component,
mission_type: self.mission_type,
}
}
fn ack(&self) -> MissionAck {
MissionAck {
target_system: self.target_system,
target_component: self.target_component,
type_: mav_mission_result::ACCEPTED,
mission_type: self.mission_type,
opaque_id: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dialect::{mav_cmd, mav_frame, mav_mission_type};
fn waypoint(seq: u16, lat: i32, lon: i32, alt: f32) -> MissionItemInt {
MissionItemInt {
param1: 0.0,
param2: 0.0,
param3: 0.0,
param4: 0.0,
x: lat,
y: lon,
z: alt,
seq,
command: mav_cmd::NAV_WAYPOINT,
target_system: 0,
target_component: 0,
frame: mav_frame::GLOBAL_RELATIVE_ALT_INT,
current: (seq == 0) as u8,
autocontinue: 1,
mission_type: mav_mission_type::MISSION,
}
}
#[test]
fn the_sender_stamps_the_target_and_sequence_onto_each_item() {
let items = [waypoint(0, 10, 20, 30.0), waypoint(1, 11, 21, 31.0)];
let sender = MissionSender::new(&items, 7, 8, mav_mission_type::MISSION);
assert_eq!(sender.count().count, 2);
assert_eq!(sender.count().target_system, 7);
let item = sender.item(1).unwrap();
assert_eq!(item.seq, 1);
assert_eq!(item.target_system, 7);
assert_eq!(item.target_component, 8);
assert_eq!(item.x, 11);
assert!(sender.item(2).is_none());
}
#[test]
fn a_sender_and_receiver_complete_an_in_order_transfer() {
let items = [
waypoint(0, 10, 20, 30.0),
waypoint(1, 11, 21, 31.0),
waypoint(2, 12, 22, 32.0),
];
let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
let mut action = receiver.on_count(sender.count().count);
let mut collected = [MissionItemInt::default_zeroed(); 3];
let mut collected_len = 0usize;
loop {
match action {
ReceiverAction::Request(request) => {
let item = sender.item(request.seq).expect("in range");
let (accepted, next) = receiver.on_item(&item);
if let Some(item) = accepted {
collected[collected_len] = item;
collected_len += 1;
}
action = next;
}
ReceiverAction::Ack(ack) => {
assert_eq!(ack.type_, mav_mission_result::ACCEPTED);
break;
}
}
}
assert!(receiver.is_complete());
assert_eq!(collected_len, 3);
assert_eq!(collected[0].x, 10);
assert_eq!(collected[2].x, 12);
}
#[test]
fn an_out_of_order_item_is_dropped_and_re_requested() {
let items = [waypoint(0, 10, 20, 30.0), waypoint(1, 11, 21, 31.0)];
let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
let first = receiver.on_count(sender.count().count);
assert_eq!(first, ReceiverAction::Request(sender_request(0)));
let (accepted, action) = receiver.on_item(&sender.item(1).unwrap());
assert!(accepted.is_none());
assert_eq!(action, ReceiverAction::Request(sender_request(0)));
assert_eq!(receiver.expected(), 0);
}
#[test]
fn an_empty_plan_acknowledges_immediately() {
let items: [MissionItemInt; 0] = [];
let sender = MissionSender::new(&items, 1, 1, mav_mission_type::MISSION);
let mut receiver = MissionReceiver::new(1, 1, mav_mission_type::MISSION);
match receiver.on_count(sender.count().count) {
ReceiverAction::Ack(ack) => assert_eq!(ack.type_, mav_mission_result::ACCEPTED),
ReceiverAction::Request(_) => panic!("an empty plan needs no items"),
}
assert!(receiver.is_complete());
}
fn sender_request(seq: u16) -> MissionRequestInt {
MissionRequestInt {
seq,
target_system: 1,
target_component: 1,
mission_type: mav_mission_type::MISSION,
}
}
impl MissionItemInt {
fn default_zeroed() -> Self {
MissionItemInt {
param1: 0.0,
param2: 0.0,
param3: 0.0,
param4: 0.0,
x: 0,
y: 0,
z: 0.0,
seq: 0,
command: 0,
target_system: 0,
target_component: 0,
frame: 0,
current: 0,
autocontinue: 0,
mission_type: 0,
}
}
}
}