diff --git a/Cargo.lock b/Cargo.lock index b9784bc..24e69b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" [[package]] name = "flem" -version = "0.5.1" +version = "0.6.0" dependencies = [ "heapless", ] diff --git a/Cargo.toml b/Cargo.toml index 12c9a95..48fe513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flem" -version = "0.5.1" +version = "0.6.0" edition = "2021" description = "Flexible, Light-weight, Embedded Messaging Protocol" repository = "https://github.com/BridgeSource/flem-rs.git" @@ -18,7 +18,7 @@ bench = false path = "src/lib.rs" [[example]] -name = "core" +name = "flem" path = "examples/example.rs" [[example]] diff --git a/README.md b/README.md index c359f8f..94269cf 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ ![Flem Build and Tests](https://github.com/amcelroy/flem-rust/actions/workflows/rust.yml/badge.svg) -# FLEM Rust +# FLEM Rust 0.6.0 FLEM stands for Flexible, Light-weight, Embedded Messaging and is a Little Endian messaging protocol intended for use in communicating with embedded -systems targets over numerous types of busses. The host makes requests to the -client (typically the embedded target). The client processes the requests and -responds. The client can asynchronously send an Event packet that the host can -deal with as needed. Together, a host and a client make a partner. +systems targets over numerous types of buses. + +## Changelog +### Changelog 0.6.0 (from 0.5.0) +- Requests are now 2 byte u16 instead of 1 byte u8 +- Responses are now 2 byte u16 instead of 1 byte u8 +- Events are no longer a concept. Instead, each device should check incoming packets +for requests and handle those as normal. This should simplify code and prevent +accidentally mixing requests / responses / events. +- Built in responses have changed: Busy was removed, and SUCCESS was moved from 0x00 to 0x0001. ASYNC is now 0x0000 and represents a packet being sent without asking and is the default option upon a packet reset +or instantiation. +- Updated unit tests and examples ## Concepts At its core, FLEM has packets composed of a header, and a data payload. The -header is 8 bytes and consists of: +header is 10 bytes and consists of: - Header - 2 bytes - Should always be a value of 0x5555 -- Checksum - 2 bytes - CRC-16 (IBM) of the packet (exludes the header and +- Checksum - 2 bytes - CRC-16 (IBM) of the packet (excludes the header and checksum bytes) -- Request - 1 byte - A value from 0 to 255 that indicates what the client +- Request - 2 byte - A value from 0 to 65535 that indicates what the client should do. There are some reserved values, see below. -- Response - 1 byte - A value of 0 to 255 that indicates additional information +- Response - 2 byte - A value of 0 to 65535 that indicates additional information about the client. - Length - 2 bytes - Number of bytes being transmitted in the `data` buffer. Can be 0 to u16::MAX. @@ -48,8 +56,6 @@ add data, set the header bytes, and perform the pack operation: Request byte set by the user. - `pack_error` - Adds data to a packet with Request and Response bytes specified by the user. If no data needs to be transmitted, use an empty data array `&[]`. -- `pack_event` - Adds data to a packet with the Request byte set as an `EVENT` -and the Response byte set to a user defined value. __Note__: If no data is to be transmitted, set data to an empty data array `&[]`. @@ -66,59 +72,69 @@ header or the checksum bytes; ensure they are either zero or skipped if implemented in another language. ## Request -Typically, a host sends a 1-byte request to a client. A request doesn't need to -have any data payload to it, in which case a simple request packet is 8 bytes. +Typically, a host sends a 2-byte request to a client. A request doesn't need to +have any data payload, in which case a simple request packet is 10 bytes. A response packet should always echo the request to ensure the partner device can double check and route the response correctly. __Note__, the client is not required to respond. The client responses help -ensure commands were sent, processed, successful (or not) and may help validate -that the hardware tranmission of data is performing as intended. +ensure commands were sent and processed successful (or not). It will also help validate +that the hardware transmission of data is performing as intended via the checksum. -Requests are mostly left up to the user to implement, though there are some -pre-defined ones. +Requests are mostly left up to the user to implement, though there is one +pre-defined: -- Event (0x00) - A request of 0 indicates the partner is sending out data without -a request being sent first. If this is the case, the Response Byte will contain -the u8 command of the event, which should be pre-determined between host and -client. - Id (0x01) - Each device using FLEM should implement a `DataId` struct that indicates a version, serial number, name, or some other information (30 bytes, char) and a u16 indicating the partners max packet size. This requires that -the client / host use packet sizes of at least 32 bytes. Smaller Ids can be +the client / host use packet sizes of at least 30 bytes. Smaller Ids can be used, or not responded to, but it is up to the user to implement. -Our company has a seperate project that has all of the responses, requests, and -events for each project in a different Rust sub-module. This way, our -communication protocols are all in one spot and revision controlled for use in -future projects. +Our company has a separate project that has all of the responses and requests +for each project in a different Rust sub-module. Typically, each project has +something like: +``` +pub mod host_requests { + pub const READ_CONFIG: u16 = ...; + pub const WRITE_CONFIG: u16 = ...; +} + +pub mod client_requests { + pub const INTERRUPT: u16 = ...; + pub const DATA_ACQUIRED: u16 = ...; +} +``` ## Response -Responses are 1-byte codes that indicate the status of the client. +Responses are 2-byte codes that indicate the status of the partner device, if needed. #### Response byte if the Request is an Event: There are some reserved non-event responses: -- Success - 0x00 - Nothing went wrong -- Busy - 0x01 - Indicates the the partner is busy -- PacketOverflow - 0xFC - Too much data was sent to the partner and it exceeded -the packet buffer. -- UnknownRequest - 0xFD - The request wasn't recognized by the partner -- ChecksumError - 0xFE - Checksum did not compute correctly -- Error - 0xFF - Unspecified error when processing a request - -#### Response byte if the Request is an Event: -If the partner is sending an event, the Response byte represents a command that -the partner would like to execute or convery to the host. As with Requests, the -Response byte code in the case of an event should be pre-determined between the -host and the client. +- ASYNC - 0x0000 - The packet is being sent without asking +- SUCCESS - 0x0001 - Nothing went wrong processing the request, the request is likewise echoed in the response packet. +- UNKNOWN_REQUEST - 0xFFFD - The request wasn't recognized by the partner +- CHECKSUM_ERROR - 0xFFFF - Checksum did not compute correctly ## Length -Two bytes inidcating the amount of data to expect in the packets data field. +Two bytes indicating the amount of data to expect in the packets data field. This can be 0 to u16::MAX, though typically it would be something smaller. ## Data The packet data payload. Can be 0 to u16::MAX bytes. +## Traits + +FLEM offers a `DataInterface` trait that can be implemented on a struct that +allow the user to encode and decode the struct into a FLEM packet. In general, +check that the struct can fit in the packet when encoding, and that the packet +has enough data to fit into the struct when decoding. + +Once this check is done, the data can be moved into or out of the struct, making +sure that the encoding and decoding order is consistent. See `examples/traits.rs` +for an example. There are convenience functions in `src/buffer.rs` that can be +used when decoding, with unit tests for these functions in `tests/tests.rs`. + + ## Examples See `examples/example.rs` for a host to client request and a client to host diff --git a/examples/example.rs b/examples/example.rs index f628ea8..69d96ec 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -7,11 +7,12 @@ use std::iter::FromIterator; // So a size of 108 would leave 100 bytes for data const FLEM_PACKET_SIZE: usize = 100; -// Implement our own custom Request commands -struct FlemRequestProjectX; +pub mod host_requests { + pub const GET_DATA: u16 = 10; +} -impl FlemRequestProjectX { - const GET_DATA: u8 = 10; +pub mod client_requests { + // If the client is to make requests, this is a good place to define them } fn main() { @@ -32,7 +33,7 @@ fn main() { println!("Packet length: {}", client_rx.length()); host_tx.reset(); - host_tx.set_request(flem::Request::ID); // Change this for different responses from the client + host_tx.set_request(flem::request::ID); // Change this for different responses from the client host_tx.pack(); // Pack runs checksum and after that it is ready to send // Simulates byte-by-byte tranmission @@ -66,13 +67,10 @@ fn main() { /* Process request on the client side */ client_tx.reset_lazy(); match client_rx.get_request() { - Request::EVENT => { - // Clients typically send events, but maybe not in your case! - } - Request::ID => { + request::ID => { client_tx.pack_id(&client_flem_id, true).unwrap(); } - FlemRequestProjectX::GET_DATA => { + host_requests::GET_DATA => { // Custom command implemented for this project (Project X) let project_x_data = [0 as u8; 40]; client_tx @@ -86,7 +84,7 @@ fn main() { client_tx .pack_error( client_rx.get_request(), - flem::Response::UnknownRequest as u8, + flem::response::UNKNOWN_REQUEST, &[], ) .unwrap_or_else(|error| { @@ -98,17 +96,14 @@ fn main() { /* Send response back to host */ for byte in client_tx.bytes() { - // ** Byte is trasmitting over hardware ** + // ** Byte is transmitting over hardware ** - // ** Byte recevied by host, construct the + // ** Byte received by host, construct the match host_rx.construct(*byte) { Ok(_) => { // Determine what to do with the received packet match host_rx.get_request() { - Request::EVENT => { - // Hosts typically consume events, but maybe not in your case! - } - Request::ID => { + request::ID => { let host_size_data_id = flem::DataId::from(&host_rx.get_data()).unwrap(); println!( "DataId Message: {}, max packet size: {}, Major: {}, Minor: {}, Patch: {}", @@ -119,7 +114,7 @@ fn main() { host_size_data_id.get_patch() ); } - FlemRequestProjectX::GET_DATA => { + host_requests::GET_DATA => { // Custom command implemented for this project (Project X) // Do something with the requested data } diff --git a/examples/traits.rs b/examples/traits.rs index b078d45..e4b6846 100644 --- a/examples/traits.rs +++ b/examples/traits.rs @@ -7,11 +7,8 @@ use flem::*; // So a size of 108 would leave 100 bytes for data const FLEM_PACKET_SIZE: usize = 100; -// Implement our own custom Request commands -struct FlemRequestProjectX; - -impl FlemRequestProjectX { - const GET_DIAGNOSTICS: u8 = 10; +pub mod request_projectx { + pub const GET_DIAGNOSTICS: u16 = 10; } /// Task times in milliseconds @@ -70,20 +67,23 @@ fn main() { let mut client_rx = flem::Packet::::new(); let mut client_tx = flem::Packet::::new(); + + // Default values of - task_1: 100, task_2: 10, task_3: 25 let client_diagnostics = Diagnostics::new(); host_tx.reset_lazy(); - host_tx.set_request(FlemRequestProjectX::GET_DIAGNOSTICS); + host_tx.set_request(request_projectx::GET_DIAGNOSTICS); host_tx.pack(); // Transmit request for byte in host_tx.bytes() { match client_rx.construct(*byte) { Ok(_) => { - print!("Packet received on client"); + println!("Packet received on client"); } // More error checking here, if needed Err(status) => { + // We expect Status::PacketBuilding if status != Status::PacketBuilding { assert!(true, "An error shouldn't have occurred in this example"); } @@ -93,44 +93,58 @@ fn main() { client_tx.reset_lazy(); client_tx.set_request(client_rx.get_request()); - client_tx.set_response(Status::Ok as u8); + client_tx.set_response(flem::response::SUCCESS); + // Encodes the struct into the packet client_diagnostics.encode(&mut client_tx).unwrap(); + // Compute and store the checksum client_tx.pack(); for byte in client_tx.bytes() { match host_rx.construct(*byte) { Ok(_) => { - print!("Packet received on host"); + println!("Packet received on host"); } - Err(_) => { - assert!(true, "An error shouldn't have occurred in this example"); + Err(result) => { + // We expect Status::PacketBuilding + if result != Status::PacketBuilding { + assert!(true, "An error shouldn't have occurred in this example"); + } } } } + // Create a new struct to hold the decoded data let mut host_diagnostics = Diagnostics { task_1: 0, task_2: 0, task_3: 0, }; - host_diagnostics.decode(&mut host_rx).unwrap(); + // Decode the packet into the struct + host_diagnostics.decode(&host_rx).unwrap(); - assert_ne!( + assert_eq!( client_diagnostics.task_1, host_diagnostics.task_1, "Task 1 not the same" ); - assert_ne!( + assert_eq!( client_diagnostics.task_2, host_diagnostics.task_2, "Task 2 not the same" ); - assert_ne!( + assert_eq!( client_diagnostics.task_3, host_diagnostics.task_3, "Task 3 not the same" ); - print!( - "Task 1: {}, Task 2: {}, Task 3: {}", + println!( + "Client: Task 1: {}, Task 2: {}, Task 3: {}", + client_diagnostics.task_1, client_diagnostics.task_2, client_diagnostics.task_3 + ); + + println!( + "Host: Task 1: {}, Task 2: {}, Task 3: {}", host_diagnostics.task_1, host_diagnostics.task_2, host_diagnostics.task_3 ); + + println!("Transmission successful!"); } diff --git a/src/lib.rs b/src/lib.rs index 0c0485b..b2fc99a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,6 @@ pub mod buffer; pub mod traits; -pub struct Request; - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Status { Ok, @@ -135,8 +133,8 @@ impl DataId { pub struct Packet { header: u16, checksum: u16, - request: u8, - response: u8, + request: u16, + response: u16, length: u16, data: [u8; T], internal_counter: u32, @@ -144,22 +142,19 @@ pub struct Packet { status: Status, } -pub enum Response { - Success = 0x00, - Busy = 0x01, - PacketOverflow = 0xFC, - UnknownRequest = 0xFD, - ChecksumError = 0xFE, - Error = 0xFF, +pub mod response { + pub const ASYNC: u16 = 0x0000; + pub const SUCCESS: u16 = 0x0001; + pub const UNKNOWN_REQUEST: u16 = 0xFFFE; + pub const CHECKSUM_ERROR: u16 = 0xFFFF; } -/// Pre-defined requests. It is easier to extend u8 values rather than an enum -impl Request { - pub const EVENT: u8 = 0; - pub const ID: u8 = 1; +/// Pre-defined requests +pub mod request { + pub const ID: u16 = 0x0001; } -pub const FLEM_HEADER_SIZE: usize = 8; +pub const FLEM_HEADER_SIZE: usize = 10; pub const FLEM_HEADER: u16 = 0x5555; const CRC16_TAB: [u16; 256] = [ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, @@ -211,13 +206,13 @@ impl Packet { }; } - /// Convenience function to response with data. The response byte is automatically set to Response::Success. - pub fn pack_data(&mut self, request: u8, data: &[u8]) -> Result<(), Status> { + /// Convenience function to response with data. The response byte is automatically set to SUCCESS. + pub fn pack_data(&mut self, request: u16, data: &[u8]) -> Result<(), Status> { self.reset_lazy(); self.request = request; match self.add_data(data) { Ok(_) => { - self.response = Response::Success as u8; + self.response = response::SUCCESS; self.pack(); Ok(()) } @@ -226,7 +221,7 @@ impl Packet { } /// Convenience function to respond quickly if an error occurs (without data). - pub fn pack_error(&mut self, request: u8, error: u8, data: &[u8]) -> Result<(), Status> { + pub fn pack_error(&mut self, request: u16, error: u16, data: &[u8]) -> Result<(), Status> { self.reset_lazy(); self.request = request; self.response = error; @@ -240,19 +235,6 @@ impl Packet { } } - pub fn pack_event(&mut self, request_of_host: u8, data: &[u8]) -> Result<(), Status> { - self.reset_lazy(); - self.request = Request::EVENT; - self.response = request_of_host; - match self.add_data(data) { - Ok(_) => { - self.pack(); - Ok(()) - } - Err(e) => Err(e), - } - } - /// Convenience function to respond with the ID. If communicating with UTF-8 partners, ascii should be true. This /// can only be used if the data packets are 30 bytes or longer (or twice that if ascii = false). /// @@ -261,8 +243,8 @@ impl Packet { /// * `ascii` - Packages the ID as a UTF-8 ID. Used when talking to C/C++ partners. pub fn pack_id(&mut self, id: &DataId, ascii: bool) -> Result<(), Status> { self.reset_lazy(); - self.request = Request::ID as u8; - self.response = Response::Success as u8; + self.request = request::ID; + self.response = response::SUCCESS; if ascii { self.add_data(&[id.get_major(); 1])?; @@ -297,7 +279,7 @@ impl Packet { /// /// const PACKET_SIZE: usize = 64; // 64 byte packet /// - /// const FLEM_EXAMPLE_REQUEST: u8 = 0xF; + /// const FLEM_EXAMPLE_REQUEST: u16 = 0xF; /// /// let mut rx = Packet::::new(); /// @@ -352,7 +334,7 @@ impl Packet { return crc == self.checksum; } - /// Contruct a packet one byte at a time. An internal counter keeps track of where the byte should go. + /// Construct a packet one byte at a time. An internal counter keeps track of where the byte should go. /// The current return value is the Status and should be one of the following: /// - HeaderBytesNotFound - The packet header was not found /// - ChecksumError - The computed checksum does not match the sent checksum @@ -371,7 +353,7 @@ impl Packet { /// /// const PACKET_SIZE: usize = 64; // 64 byte packet /// - /// const FLEM_EXAMPLE_REQUEST: u8 = 0xF; + /// const FLEM_EXAMPLE_REQUEST: u16 = 0xF; /// /// let mut rx = Packet::::new(); /// let mut tx = Packet::::new(); @@ -434,15 +416,21 @@ impl Packet { self.checksum |= (byte as u16) << 8; } 4 => { - self.request = byte; + self.request = byte as u16; } 5 => { - self.response = byte; + self.request |= (byte as u16) << 8; } 6 => { - self.length = byte as u16; + self.response = byte as u16; } 7 => { + self.response |= (byte as u16) << 8; + } + 8 => { + self.length = byte as u16; + } + 9 => { self.length |= (byte as u16) << 8; self.data_length_counter = 0; if self.length == 0 { @@ -495,7 +483,7 @@ impl Packet { /// error occurs or status is Status::GetByteFinished. /// /// It is often easier to use .bytes(), but this function is meant to be used - /// in an async nature, for example an interrupt drivern UART transmit FIFO. + /// in an async nature, for example an interrupt driven UART transmit FIFO. /// /// The return value is a Result composed of the byte requested if everything is going /// well, or a Status as an Error indicating all bytes have been gotten. @@ -506,7 +494,7 @@ impl Packet { /// use flem::{Packet}; /// use heapless; /// const PACKET_SIZE: usize = 64; // 64 byte packet - /// const FLEM_EXAMPLE_REQUEST: u8 = 0xF; + /// const FLEM_EXAMPLE_REQUEST: u16 = 0xF; /// /// let mut rx = Packet::::new(); /// let mut tx = Packet::::new(); @@ -554,7 +542,7 @@ impl Packet { /// /// assert!(packet_received, "Packet should have been transferred"); /// - /// // This test is redundant, since the checkums passed, still nice to see + /// // This test is redundant, since the checksums passed, still nice to see /// /// let rx_bytes = rx.bytes(); /// let tx_bytes = tx.bytes(); @@ -582,12 +570,12 @@ impl Packet { } /// Sets the Flem request field - pub fn set_request(&mut self, request: u8) { + pub fn set_request(&mut self, request: u16) { self.request = request; } /// Gets the Flem request field - pub fn get_request(&self) -> u8 { + pub fn get_request(&self) -> u16 { self.request } @@ -597,12 +585,12 @@ impl Packet { } /// Sets the Flem response field - pub fn set_response(&mut self, response: u8) { + pub fn set_response(&mut self, response: u16) { self.response = response; } /// Gets the Flem response field - pub fn get_response(&self) -> u8 { + pub fn get_response(&self) -> u16 { self.response } diff --git a/tests/tests.rs b/tests/tests.rs index 3dbcff6..16a4107 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -10,7 +10,7 @@ mod tests { #[test] fn sending() { - const REQUEST: u8 = 0xF; + const CUSTOM_REQUEST: u16 = 0xF; let mut rx = flem::Packet::::new(); let mut tx = flem::Packet::::new(); @@ -20,13 +20,13 @@ mod tests { payload[i] = i as u8; } - tx.set_request(REQUEST); - assert_eq!(REQUEST, tx.get_request(), "Requests do not match"); + tx.set_request(CUSTOM_REQUEST); + assert_eq!(CUSTOM_REQUEST, tx.get_request(), "Requests do not match"); assert!( tx.add_data(&payload).is_ok(), "Payload is exactly packet size, SHOULD NOT cause error" ); - assert_eq!(tx.checksum(true), 50848, "Checksum mismatch"); + assert_eq!(tx.checksum(true), tx.checksum(false), "Checksum mismatch"); tx.pack(); let mut byte_counter = 0; @@ -78,10 +78,10 @@ mod tests { fn checksum() { let mut rx = flem::Packet::::new(); - rx.set_request(flem::Request::ID); + rx.set_request(flem::request::ID); let checksum = rx.checksum(true); - assert_eq!(checksum, 64513, "Checksum mismatch"); + assert_eq!(checksum, rx.checksum(false), "Checksum mismatch"); assert_eq!(checksum, rx.get_checksum(), "Checksum mismatch"); } @@ -89,7 +89,7 @@ mod tests { fn size_check() { let mut rx = flem::Packet::::new(); - assert_eq!(rx.length(), 8, "Size should be 14 (i.e. just the header)"); + assert_eq!(rx.length(), 10, "Size should be 10 (i.e. just the header)"); let payload = [10 as u8; FLEM_PACKET_SIZE + 1]; assert!( @@ -99,7 +99,7 @@ mod tests { assert_eq!( rx.length(), flem::FLEM_HEADER_SIZE as usize, - "Size should be 14 (i.e. just the header)" + "Size should be 10 (i.e. just the header)" ); let smaller_payload = [10 as u8; 60]; @@ -136,7 +136,7 @@ mod tests { use flem::Packet; use heapless; const PACKET_SIZE: usize = 64; // 64 byte packet - const FLEM_EXAMPLE_REQUEST: u8 = 0xF; + const FLEM_EXAMPLE_REQUEST: u16 = 0xF; let mut rx = Packet::::new(); let mut tx = Packet::::new(); @@ -182,7 +182,7 @@ mod tests { assert!(packet_received, "Packet should have been transferred"); - // This test is redundant, since the checkums passed, still nice to see + // This test is redundant, since the checksums passed, still nice to see let rx_bytes = rx.bytes(); let tx_bytes = tx.bytes();