// The items of an exported robotics project's `src/link.rs` (flowrs-appshell's `robotics`): // the generator writes the module's header, these, and the project's `EDGES` table after them. // Its tests run in every export (`cargo test`), over the export's own table: // tests/robotics_interop.rs runs them on the build host. use flowrs_device::HardwareError; use flowrs_device::link::LinkError; use std::collections::BTreeMap; use std::time::Duration; /// A value's type on an edge: the link channel's own. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Ty { Bool, F32, U16, U32, Bytes, } /// One edge between the domains. #[derive(Clone, Copy, Debug)] pub struct Edge { /// Its link channel. pub id: u8, /// Its name: the dora output and input, the Copper message's module. pub name: &'static str, pub ty: Ty, /// Whether the MCU sends it. pub from_mcu: bool, /// Its interface's FIH01 hash (units, axes, clock, bounds and defaults included). pub fih01: &'static str, /// The unit its interface declares. pub unit: Option<&'static str>, /// The edge, in words. pub words: &'static str, } /// The calls a generated pass makes on its link. pub trait Link { fn recv( &mut self, channel: u8, timeout: Duration, ) -> Result>; fn recv_bytes( &mut self, channel: u8, timeout: Duration, ) -> Result, LinkError>; fn recv_fragments( &mut self, first: u8, count: u8, timeout: Duration, ) -> Result, LinkError> { let _ = count; self.recv_bytes(first, timeout) } fn send( &mut self, channel: u8, value: &T, ) -> Result<(), LinkError>; fn send_bytes(&mut self, channel: u8, bytes: &[u8]) -> Result<(), LinkError>; fn send_fragments( &mut self, first: u8, count: u8, bytes: &[u8], ) -> Result<(), LinkError> { let _ = count; self.send_bytes(first, bytes) } } /// What one pass takes and gives: the values the runtime put in before it ran, and the ones /// it sent. #[derive(Debug, Default)] pub struct Mailbox { pub inbox: BTreeMap>, pub outbox: BTreeMap>, } impl Link for Mailbox { fn recv( &mut self, channel: u8, _timeout: Duration, ) -> Result> { let bytes = self.inbox.remove(&channel).ok_or(LinkError::Timeout)?; postcard::from_bytes(&bytes).map_err(|_| LinkError::Decode) } fn recv_bytes( &mut self, channel: u8, _timeout: Duration, ) -> Result, LinkError> { self.inbox.remove(&channel).ok_or(LinkError::Timeout) } fn send( &mut self, channel: u8, value: &T, ) -> Result<(), LinkError> { let bytes = postcard::to_allocvec(value).map_err(|_| LinkError::Encode)?; self.outbox.insert(channel, bytes); Ok(()) } fn send_bytes(&mut self, channel: u8, bytes: &[u8]) -> Result<(), LinkError> { self.outbox.insert(channel, bytes.to_vec()); Ok(()) } } /// The edge on channel `id`. pub fn edge(id: u8) -> Option<&'static Edge> { EDGES.iter().find(|e| e.id == id) } /// A sender's digest is this side's, or the value is refused, naming the edge and both. pub fn check_digest(e: &Edge, got: Option<&str>) -> Result<(), LinkError> { match got { Some(d) if d == e.fih01 => Ok(()), other => Err(LinkError::Serial(HardwareError::CommunicationError(format!( "edge {}: the sender's interface digest is {}, this side's {}: refused", e.name, other.unwrap_or("none"), e.fih01 )))), } } #[cfg(test)] mod tests { use super::*; /// A value whose interface digest is not its edge's own is refused, naming the edge and /// both digests; the edge's own is taken. A pass's values go through as the link's postcard /// bytes, typed both ways. #[test] fn the_link_refuses_another_interface_and_carries_typed_values() { let e = &EDGES[0]; assert!(check_digest(e, Some(e.fih01)).is_ok()); let other = "FIH01_0000000000000000000000000000000000000000000000000000000000000000"; let why = format!("{:?}", check_digest(e, Some(other)).unwrap_err()); assert!(why.contains(e.name) && why.contains(other) && why.contains(e.fih01), "{why}"); assert!(check_digest(e, None).is_err()); assert!(edge(e.id).is_some() && edge(255).is_none()); let mut m = Mailbox::default(); m.inbox.insert(e.id, postcard::to_allocvec(&7u16).unwrap()); let v: u16 = m.recv(e.id, Duration::ZERO).unwrap(); assert_eq!(v, 7); assert!(m.recv::(e.id, Duration::ZERO).is_err(), "a value is taken once"); m.send(e.id, &true).unwrap(); assert!(postcard::from_bytes::(&m.outbox[&e.id]).unwrap()); m.send_bytes(e.id, &[1, 2, 3]).unwrap(); assert_eq!(m.outbox[&e.id], vec![1, 2, 3]); m.inbox.insert(e.id, vec![9, 8]); assert_eq!(m.recv_fragments(e.id, 2, Duration::ZERO).unwrap(), vec![9, 8]); } }