diff --git a/Cargo.lock b/Cargo.lock index 25f829f..9c32bf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" [[package]] name = "flem" -version = "0.6.1" +version = "0.6.2" dependencies = [ "heapless", ] diff --git a/Cargo.toml b/Cargo.toml index bc414bd..1fc2418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flem" -version = "0.6.1" +version = "0.6.2" edition = "2021" description = "Flexible, Light-weight, Embedded Messaging Protocol" repository = "https://github.com/BridgeSource/flem-rs.git" @@ -11,6 +11,10 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +std = [] + [lib] name = "flem" crate-type = ["lib"] @@ -22,8 +26,16 @@ name = "flem" path = "examples/example.rs" [[example]] -name = "traits" -path = "examples/traits.rs" +name = "encode_decode" +path = "examples/encode_decode.rs" + +[[example]] +name = "software_host_simple" +path = "examples/software_host_simple.rs" + +[[example]] +name = "software_host_complex" +path = "examples/software_host_complex.rs" [dev-dependencies] heapless = "0.7" diff --git a/README.md b/README.md index 73debfb..b2e750a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Flem Build and Tests](https://github.com/amcelroy/flem-rust/actions/workflows/rust.yml/badge.svg) -# FLEM Rust 0.6.0 +# FLEM Rust 0.6.2 FLEM stands for Flexible, Light-weight, Embedded Messaging and is a Little Endian messaging protocol intended for use in communicating with embedded @@ -8,6 +8,12 @@ systems targets over numerous types of buses. ## Changelog +### Changelog 0.6.2 +- Added feature = ["std"] +- Added `Channel` trait. This trait requires features = ["std"]. It servers as a set of traits that can be used +to implement different hardware or to emulate a device. It uses the `std` library for threading and `mpsc` channels. +- Added examples to show how to emulate a device use the `Channel` trait. + ### Changelog 0.6.1 - Added fmt::Debug trait to Packet that prints the header, checksum, request, response, length, and status. diff --git a/examples/traits.rs b/examples/encode_decode.rs similarity index 100% rename from examples/traits.rs rename to examples/encode_decode.rs diff --git a/examples/software_host_complex.rs b/examples/software_host_complex.rs new file mode 100644 index 0000000..1ea2d4e --- /dev/null +++ b/examples/software_host_complex.rs @@ -0,0 +1,259 @@ +use flem::{traits::Channel, DataId, Packet}; +use std::{ + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread, + time::Duration, +}; + +const PACKET_SIZE: usize = 512; +const PACKET_DEVICE_SIZE: usize = 128; + +#[derive(Clone)] +struct FlemSoftwareHost { + listening: Arc>, + flem_packet_handler: Option) -> Packet>, +} + +impl FlemSoftwareHost { + pub fn new() -> Self { + FlemSoftwareHost { + listening: Arc::new(Mutex::new(false)), + flem_packet_handler: None, + } + } +} + +impl Channel for FlemSoftwareHost { + type Error = (); + + fn list_devices(&self) -> Vec { + let mut devices = Vec::::new(); + devices.push(String::from("Software Host")); + devices + } + + fn connect(&mut self, device: &String) -> Result<(), Self::Error> { + Ok(()) + } + + fn disconnect(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn listen( + &mut self, + rx_sleep_time_ms: u64, + tx_sleep_time_ms: u64, + ) -> (Sender>, Receiver>) { + // This example is similar to how flem-serial-rs works, except we need to add in a simulated byte-by-byte hardware transmission. + // If there is no need to simulate Rx and Tx hardware, a single thread would work that accepts a packet from `packet_to_transmit`, + // parses it, and sends a response on `validated_packet`. + + // Tx packets are marshalled into a single queue, and dispatched over hardware. + let (tx_packet_from_program, packet_to_transmit) = + mpsc::channel::>(); + + // Rx data is coming off of hardware, usually a byte at a time, and needs to be constructed into a packet and validated before passing back into the program + let (validated_packet, rx_packet_to_program) = mpsc::channel::>(); + + // This is a simple channel to simulated byte-by-byte transmission over hardware + let (simulated_hardware_host_tx, simulated_hardware_device_rx) = mpsc::channel::(); + let (simulated_hardware_device_tx, simulated_hardware_host_rx) = mpsc::channel::(); + + *self.listening.lock().unwrap() = true; + + let listening_clone_rx = self.listening.clone(); + let listening_clone_tx = self.listening.clone(); + let listening_clone_device = self.listening.clone(); + + // Tx Thread - Transmit packets to the "device" + let tx_handle = thread::spawn(move || { + while *listening_clone_tx.lock().unwrap() { + // Check if there is a packet to transmit, use recv_timeout to prevent a blocking thread + if let Ok(tx_packet) = + packet_to_transmit.recv_timeout(Duration::from_millis(tx_sleep_time_ms)) + { + // Send the packet one byte at a time + for byte in tx_packet.bytes() { + simulated_hardware_host_tx.send(*byte).unwrap(); + } + println!( + "Raw packet from host to device in bytes: {:?}", + tx_packet.bytes() + ); + println!("Host transmitted packet successfully"); + } + } + }); + + let device_flem_handler = self.flem_packet_handler.clone(); + + let simulated_device_thread = thread::spawn(move || { + let mut packet = flem::Packet::::new(); + + while *listening_clone_device.lock().unwrap() { + // List for data on hardware + if let Ok(byte) = + simulated_hardware_device_rx.recv_timeout(Duration::from_millis(10)) + { + match packet.construct(byte) { + Ok(_) => { + // Packet received + println!( + "Packet received on device successfully with checksum {}", + packet.get_checksum() + ); + + if device_flem_handler.is_none() { + println!("Packet handler not set, working as a loop-back"); + for byte in packet.bytes() { + simulated_hardware_device_tx.send(*byte).unwrap(); + } + } else { + println!("Packet handler set, calling handler"); + let handler = device_flem_handler.as_ref().unwrap(); + let response = handler(&packet); + for byte in response.bytes() { + simulated_hardware_device_tx.send(*byte).unwrap(); + } + println!( + "Raw packet from device to host in bytes: {:?}", + response.bytes() + ); + } + + println!("Packet sent from device successfully"); + + packet.reset_lazy(); + } + Err(error) => { + match error { + flem::Status::PacketBuilding => { + // Packet not yet received + } + _ => { + println!("Error parsing packet"); + packet.reset_lazy(); + } + } + } + } + } + } + }); + + // Rx Thread - Receive packets from the "device" + let rx_handle = thread::spawn(move || { + let mut packet = flem::Packet::::new(); + + while *listening_clone_rx.lock().unwrap() { + // List for data on hardware + if let Ok(byte) = + simulated_hardware_host_rx.recv_timeout(Duration::from_millis(rx_sleep_time_ms)) + { + match packet.construct(byte) { + Ok(_) => { + // Packet received + println!( + "Packet received successfully with checksum {}", + packet.get_checksum() + ); + validated_packet.send(packet); + + println!("Packet sent to program"); + + packet.reset_lazy(); + } + Err(error) => { + match error { + flem::Status::PacketBuilding => { + // Packet not yet received + } + _ => { + println!("Error parsing packet"); + packet.reset_lazy(); + } + } + } + } + } + } + }); + + (tx_packet_from_program, rx_packet_to_program) + } + + fn unlisten(&mut self) -> Result<(), Self::Error> { + *self.listening.lock().unwrap() = false; + Ok(()) + } +} + +fn main() { + let mut host = FlemSoftwareHost::::new(); + + // Our "device" FLEM handler + fn device_flem_handler(packet: &Packet) -> Packet { + let mut response = Packet::::new(); + + match packet.get_request() { + flem::request::ID => { + let id = DataId::new("Emulated Target", 0, 0, 1, PACKET_DEVICE_SIZE); + + // Respond with ID + response.set_request(flem::request::ID); + response.set_response(flem::response::SUCCESS); + response.pack_id(&id, true); + } + _ => { + response.set_request(flem::request::ID); + response.set_response(flem::response::UNKNOWN_REQUEST); + response.pack(); + } + } + + response + } + + // Configure the packet handler + host.flem_packet_handler = Some(device_flem_handler); + + let (tx, rx) = host.listen(10, 10); + + let mut packet = flem::Packet::::new(); + + packet.set_request(flem::request::ID); + packet.pack(); + + tx.send(packet).unwrap(); + + loop { + if let Ok(packet) = rx.recv_timeout(Duration::from_millis(25)) { + println!("Received packet: {:?}", packet); + + // Do stuff with the packet + match packet.get_request() { + flem::request::ID => { + let id = DataId::from(&packet.get_data()).unwrap(); + println!( + "DataId Message: {}, max packet size: {}, Major: {}, Minor: {}, Patch: {}", + String::from_iter(id.get_name().iter()), + id.get_max_packet_size(), + id.get_major(), + id.get_minor(), + id.get_patch() + ); + } + _ => { + // Unknown request + } + } + + host.unlisten().unwrap(); + break; + } + } +} diff --git a/examples/software_host_simple.rs b/examples/software_host_simple.rs new file mode 100644 index 0000000..d80a217 --- /dev/null +++ b/examples/software_host_simple.rs @@ -0,0 +1,155 @@ +use flem::{ + Packet, + DataId, + traits::Channel +}; +use std::{ + time::Duration, + sync::{ + Arc, + Mutex, + mpsc::{self, Sender, Receiver} + }, + thread, +}; + +const PACKET_SIZE: usize = 512; + +#[derive(Clone)] +struct FlemSoftwareHost{ + listening: Arc>, + flem_packet_handler: Option) -> Packet> +} + +impl FlemSoftwareHost { + pub fn new() -> Self { + FlemSoftwareHost { + listening: Arc::new(Mutex::new(false)), + flem_packet_handler: None, + } + } +} + +impl Channel for FlemSoftwareHost { + type Error = (); + + fn list_devices(&self) -> Vec { + let mut devices = Vec::::new(); + devices.push(String::from("Software Host")); + devices + } + + fn connect(&mut self, device: &String) -> Result<(), Self::Error> { + Ok(()) + } + + fn disconnect(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn listen(&mut self, rx_sleep_time_ms: u64, tx_sleep_time_ms: u64,) -> (Sender>, Receiver>) { + // Tx packets are marshalled into a single queue, and dispatched over hardware. + let (tx_packet_from_program, packet_to_transmit) = mpsc::channel::>(); + + // Rx data is coming off of hardware, usually a byte at a time, and needs to be constructed into a packet and validated before passing back into the program + let (validated_packet, rx_packet_to_program) = mpsc::channel::>(); + + *self.listening.lock().unwrap() = true; + + let listening_clone = self.listening.clone(); + + let device_flem_handler = self.flem_packet_handler.clone(); + + // Tx Thread - Transmit packets to the "device" + let device_handle = thread::spawn(move || { + while *listening_clone.lock().unwrap() { + // Check if there is a packet to transmit, use recv_timeout to prevent a blocking thread + if let Ok(tx_packet) = packet_to_transmit.recv_timeout(Duration::from_millis(tx_sleep_time_ms)) { + if device_flem_handler.is_none() { + println!("Packet handler not set, working as a loop-back"); + validated_packet.send(tx_packet); + }else{ + println!("Packet handler set, calling handler"); + let handler = device_flem_handler.as_ref().unwrap(); + let response = handler(&tx_packet); + validated_packet.send(response); + } + } + } + }); + + (tx_packet_from_program, rx_packet_to_program) + } + + fn unlisten(&mut self) -> Result<(), Self::Error> { + *self.listening.lock().unwrap() = false; + Ok(()) + } +} + +fn main() { + let mut host = FlemSoftwareHost::::new(); + + // Our "device" FLEM handler + fn device_flem_handler(packet: &Packet) -> Packet { + let mut response = Packet::::new(); + + match packet.get_request() { + flem::request::ID => { + let id = DataId::new("Emulated Target", 0, 0, 1, PACKET_SIZE); + + // Respond with ID + response.set_request(flem::request::ID); + response.set_response(flem::response::SUCCESS); + response.pack_id(&id, true); + }, + _ => { + response.set_request(packet.get_request()); + response.set_response(flem::response::UNKNOWN_REQUEST); + response.pack(); + } + } + + response + } + + // Configure the packet handler + host.flem_packet_handler = Some(device_flem_handler); + + let (tx, rx) = host.listen(10, 10); + + let mut packet = flem::Packet::::new(); + + packet.set_request(flem::request::ID); + packet.pack(); + + tx.send(packet).unwrap(); + + loop { + if let Ok(packet) = rx.recv_timeout(Duration::from_millis(25)) + { + println!("Received packet: {:?}", packet); + + // Do stuff with the packet + match packet.get_request() { + flem::request::ID => { + let id = DataId::from(&packet.get_data()).unwrap(); + println!( + "DataId Message: {}, max packet size: {}, Major: {}, Minor: {}, Patch: {}", + String::from_iter(id.get_name().iter()), + id.get_max_packet_size(), + id.get_major(), + id.get_minor(), + id.get_patch() + ); + }, + _ => { + // Unknown request + } + } + + host.unlisten().unwrap(); + break; + } + } +} \ No newline at end of file diff --git a/src/traits.rs b/src/traits.rs index 5a0c1c2..6bbac82 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,3 +1,15 @@ +#[cfg(feature = "std")] +extern crate std; + +#[cfg(feature = "std")] +extern crate alloc; + +#[cfg(feature = "std")] +use std::sync::mpsc::{Sender, Receiver}; + +#[cfg(feature = "std")] +use alloc::{vec::Vec, string::String}; + use crate::Packet; #[derive(Debug, Clone, Copy)] @@ -10,3 +22,14 @@ pub trait DataInterface: Sized { fn encode(&self, packet: &mut Packet) -> Result<(), DataInterfaceErrors>; fn decode(&mut self, packet: &Packet) -> Result<&Self, DataInterfaceErrors>; } + +#[cfg(feature = "std")] +pub trait Channel: Sized { + type Error; + + fn list_devices(&self) -> Vec; + fn connect(&mut self, device: &String) -> Result<(), Self::Error>; + fn disconnect(&mut self) -> Result<(), Self::Error>; + fn listen(&mut self, rx_sleep_time_ms: u64, tx_sleep_time_ms: u64,) -> (Sender>, Receiver>); + fn unlisten(&mut self) -> Result<(), Self::Error>; +}