162 lines
5.4 KiB
Rust
162 lines
5.4 KiB
Rust
// 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<T: serde::de::DeserializeOwned>(
|
|
&mut self,
|
|
channel: u8,
|
|
timeout: Duration,
|
|
) -> Result<T, LinkError<HardwareError>>;
|
|
fn recv_bytes(
|
|
&mut self,
|
|
channel: u8,
|
|
timeout: Duration,
|
|
) -> Result<Vec<u8>, LinkError<HardwareError>>;
|
|
fn recv_fragments(
|
|
&mut self,
|
|
first: u8,
|
|
count: u8,
|
|
timeout: Duration,
|
|
) -> Result<Vec<u8>, LinkError<HardwareError>> {
|
|
let _ = count;
|
|
self.recv_bytes(first, timeout)
|
|
}
|
|
fn send<T: serde::Serialize + ?Sized>(
|
|
&mut self,
|
|
channel: u8,
|
|
value: &T,
|
|
) -> Result<(), LinkError<HardwareError>>;
|
|
fn send_bytes(&mut self, channel: u8, bytes: &[u8]) -> Result<(), LinkError<HardwareError>>;
|
|
fn send_fragments(
|
|
&mut self,
|
|
first: u8,
|
|
count: u8,
|
|
bytes: &[u8],
|
|
) -> Result<(), LinkError<HardwareError>> {
|
|
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<u8, Vec<u8>>,
|
|
pub outbox: BTreeMap<u8, Vec<u8>>,
|
|
}
|
|
|
|
impl Link for Mailbox {
|
|
fn recv<T: serde::de::DeserializeOwned>(
|
|
&mut self,
|
|
channel: u8,
|
|
_timeout: Duration,
|
|
) -> Result<T, LinkError<HardwareError>> {
|
|
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<Vec<u8>, LinkError<HardwareError>> {
|
|
self.inbox.remove(&channel).ok_or(LinkError::Timeout)
|
|
}
|
|
fn send<T: serde::Serialize + ?Sized>(
|
|
&mut self,
|
|
channel: u8,
|
|
value: &T,
|
|
) -> Result<(), LinkError<HardwareError>> {
|
|
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<HardwareError>> {
|
|
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<HardwareError>> {
|
|
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::<u16>(e.id, Duration::ZERO).is_err(), "a value is taken once");
|
|
m.send(e.id, &true).unwrap();
|
|
assert!(postcard::from_bytes::<bool>(&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]);
|
|
}
|
|
}
|