From 95fea92235cfbef55b9ad059eabd2f84803ade34 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Fri, 19 Jun 2026 12:27:57 +0200 Subject: [PATCH] demo handler --- Cargo.lock | 174 ++++++++++++++++++++++++++++ Cargo.toml | 2 + src/bin/demo_handler.rs | 31 ++++- src/device.rs | 10 +- src/device_handlers/demo_handler.rs | 34 ++++-- src/echo_interface.rs | 36 ------ src/interfaces/echo_interface.rs | 64 ++++++++++ src/interfaces/mod.rs | 1 + src/lib.rs | 27 ++++- src/runtime.rs | 133 ++++++++++++++++----- 10 files changed, 429 insertions(+), 83 deletions(-) delete mode 100644 src/echo_interface.rs create mode 100644 src/interfaces/echo_interface.rs create mode 100644 src/interfaces/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b631bdb..69b813e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,180 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "devicehandling_traits" version = "0.1.0" +dependencies = [ + "log", + "simplelog", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 22fca53..5250d9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +log = "0.4.30" +simplelog = "0.12" \ No newline at end of file diff --git a/src/bin/demo_handler.rs b/src/bin/demo_handler.rs index 58fd99d..ea49497 100644 --- a/src/bin/demo_handler.rs +++ b/src/bin/demo_handler.rs @@ -1,6 +1,33 @@ -use devicehandling_traits::io; +use devicehandling_traits::device_handlers::demo_handler::{self, DemoHandler, FirstTelecommandData, SecondTelecommandData, Telecommands as DemoTelecommands}; +use devicehandling_traits::{init_logging, runtime}; +use devicehandling_traits::interfaces::echo_interface::EchoInterface; +use devicehandling_traits::runtime::{DeviceHandlerRuntime, Mode}; + +type RuntimeTelecommands = runtime::Telecommands; fn main() { - println!("Hello, world!"); + init_logging(log::LevelFilter::Trace); + log::info!("Running Demo Handler\n"); + + let demo_handler = DemoHandler::new(); + let mut echo_interface = EchoInterface::new(); + + let mut runtime = DeviceHandlerRuntime::new(demo_handler); + + let mode_on_tc = RuntimeTelecommands::Mode(Mode::On); + + runtime.run_once(&mode_on_tc, &mut echo_interface); + + let demo_tc_1 = DemoTelecommands::FirstTelecommand(FirstTelecommandData{a_number: 13}); + + runtime.run_once(&RuntimeTelecommands::Device(demo_tc_1), &mut echo_interface); + + let demo_tc_2 = DemoTelecommands::SecondTelecommand(SecondTelecommandData{a_float: 1.2}); + + runtime.run_once(&RuntimeTelecommands::Device(demo_tc_2), &mut echo_interface); + + let mode_off_tc = RuntimeTelecommands::Mode(Mode::Off); + + runtime.run_once(&mode_off_tc, &mut echo_interface); } \ No newline at end of file diff --git a/src/device.rs b/src/device.rs index b5e6f99..b00bfaa 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,7 +1,9 @@ use crate::io::ReadWrite; use crate::io; +use core::fmt::Debug; +#[derive(Debug)] pub enum Error{ Io(io::Error), ParsingError, @@ -18,11 +20,11 @@ impl From for Error{ pub type Result = core::result::Result; -pub trait DeviceHandler { +pub trait DeviceHandler : Debug { /// Type definition for the Telecommands that the handler will receive - type Telecommands; + type Telecommands: Debug; // Type definition for the Telemetry that the handler will create - type Telemetry; + type Telemetry: Debug; /// Configure a device which has just been switched on fn init(&mut self, interface: &mut impl ReadWrite) -> Result<()>; @@ -44,7 +46,7 @@ pub trait DeviceHandler { /// Handle telecommand fn handle_telecommand( &mut self, - telecommand: Self::Telecommands, + telecommand: &Self::Telecommands, interface: &mut impl ReadWrite, ) -> Result>; diff --git a/src/device_handlers/demo_handler.rs b/src/device_handlers/demo_handler.rs index f771a9e..78c6663 100644 --- a/src/device_handlers/demo_handler.rs +++ b/src/device_handlers/demo_handler.rs @@ -1,32 +1,38 @@ use crate::device::{DeviceHandler, Error::*}; +#[derive(Debug)] pub struct FirstTelecommandData { - a_number: u8, + pub a_number: u8, } +#[derive(Debug)] pub struct SecondTelecommandData { - a_float: f32, + pub a_float: f32, } +#[derive(Debug)] pub enum Telecommands { FirstTelecommand(FirstTelecommandData), SecondTelecommand(SecondTelecommandData), } +#[derive(Debug)] pub struct TelemetryData { a_value: u32, } +#[derive(Debug)] pub enum Telemetry { TheOneAndOnlyTelemetry(TelemetryData), } +#[derive(Debug)] pub struct DemoHandler { io_buffer: [u8; 10], } impl DemoHandler { - const fn new() -> Self { + pub const fn new() -> Self { Self { io_buffer: [0; 10] } } @@ -52,7 +58,8 @@ impl DemoHandler { data: &FirstTelecommandData, interface: &mut impl crate::io::ReadWrite, ) -> crate::device::Result<()> { - Ok(()) + let bytes = [data.a_number,0]; + self.send_generic_device_command(&bytes, interface) } /// This function converts pub structured data into a byte slice which is then passed to the function building the device's protocol fn send_second_device_command( @@ -60,7 +67,7 @@ impl DemoHandler { data: &SecondTelecommandData, interface: &mut impl crate::io::ReadWrite, ) -> crate::device::Result<()> { - Ok(()) + self.send_generic_device_command(&[], interface) } /// This fn is stateless and as such not implemented as a member fn @@ -103,6 +110,7 @@ impl DeviceHandler for DemoHandler { type Telemetry = Telemetry; fn init(&mut self, interface: &mut impl crate::io::ReadWrite) -> crate::device::Result<()> { + log::trace!("Init..."); // this is a special write to the device, do it manually: self.io_buffer[0] = 1; self.io_buffer[1] = 2; @@ -125,7 +133,7 @@ impl DeviceHandler for DemoHandler { // Use the already implemented fn to handle tc // Again, `?` to simply exit on error - let tm = self.handle_telecommand(tc, interface)?; + let tm = self.handle_telecommand(&tc, interface)?; // Check the reply let Some(Telemetry::TheOneAndOnlyTelemetry(telemetry_data)) = tm else { @@ -141,13 +149,14 @@ impl DeviceHandler for DemoHandler { } fn to_on(&mut self, interface: &mut impl crate::io::ReadWrite) -> crate::device::Result<()> { + log::trace!("To on..."); // use a predefined telecommand let tc = Telecommands::SecondTelecommand(SecondTelecommandData { a_float: 21.0 }); // Use the already implemented fn to handle tc // Again, `?` to simply exit on error - // This tc is expected to generate not reply and no tm - let None = self.handle_telecommand(tc, interface)? else { + // This tc is expected to generate no reply and no tm + let None = self.handle_telecommand(&tc, interface)? else { return Err(UnrequestedReply); }; @@ -157,14 +166,14 @@ impl DeviceHandler for DemoHandler { fn handle_telecommand( &mut self, - telecommand: Self::Telecommands, + telecommand: &Self::Telecommands, interface: &mut impl crate::io::ReadWrite, ) -> crate::device::Result> { // there might be better ways to pub structure this fn, depending on the device and its commands... // Send the device command corresponding to the telecommand, check if the reply matches match telecommand { - Telecommands::FirstTelecommand(ref data) => { + Telecommands::FirstTelecommand(data) => { self.send_first_device_command(data, interface)?; let reply = interface.read(&mut self.io_buffer)?; let tm = Self::parse_device_reply(reply)?; @@ -175,10 +184,10 @@ impl DeviceHandler for DemoHandler { // }; Ok(Some(tm)) } - Telecommands::SecondTelecommand(ref data) => { + Telecommands::SecondTelecommand(data) => { self.send_second_device_command(data, interface)?; let reply = interface.read(&mut self.io_buffer)?; - if reply.len() != 0 { + if reply.len() != 2 { return Err(UnrequestedReply); } else { Ok(None) @@ -198,6 +207,7 @@ impl DeviceHandler for DemoHandler { &mut self, _interface: &mut impl crate::io::ReadWrite, ) -> crate::device::Result<()> { + log::trace!("Shutdown..."); // Nothing to do Ok(()) } diff --git a/src/echo_interface.rs b/src/echo_interface.rs deleted file mode 100644 index 33c8a94..0000000 --- a/src/echo_interface.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::io::{Read, Write}; - -pub struct EchoInterface{ - last_received: Vec -} - -impl Read for EchoInterface{ - fn read<'a>(&self, buffer: &'a mut [u8]) -> crate::io::Result<&'a [u8]> { - let buffer_len = buffer.len(); - if self.last_received.len() >= buffer_len { - buffer.copy_from_slice(&self.last_received[..buffer.len()]); - Ok(buffer) - } else { - let result = &mut buffer[..self.last_received.len()]; - result.copy_from_slice(&self.last_received); - Ok(result) - } - } -} - -impl Write for EchoInterface{ - fn put(&mut self, data: &[u8]) -> crate::io::Result<()> { - self.last_received.extend_from_slice(data); - Ok(()) - } - - fn putc(&mut self, data: u8) -> crate::io::Result<()> { - self.last_received.push(data); - Ok(()) - } - - fn flush(&mut self) -> crate::io::Result<()> { - // there is no actual output... - Ok(()) - } -} \ No newline at end of file diff --git a/src/interfaces/echo_interface.rs b/src/interfaces/echo_interface.rs new file mode 100644 index 0000000..626a9ba --- /dev/null +++ b/src/interfaces/echo_interface.rs @@ -0,0 +1,64 @@ +//! Simple Interface, which echos previously written data when read +//! +//! Does not really emulate output queues, so reading after `put`ing will return data + +use crate::io::{Read, Write}; + +pub struct EchoInterface { + last_received: Vec, + need_to_clear: bool, +} + +impl EchoInterface { + pub const fn new() -> Self { + Self { + last_received: Vec::new(), + need_to_clear: false, + } + } +} + +impl Read for EchoInterface { + fn read<'a>(&self, buffer: &'a mut [u8]) -> crate::io::Result<&'a [u8]> { + let buffer_len = buffer.len(); + if self.last_received.len() >= buffer_len { + buffer.copy_from_slice(&self.last_received[..buffer.len()]); + log::trace!("Reading {buffer:02x?}"); + Ok(buffer) + } else { + let result = &mut buffer[..self.last_received.len()]; + result.copy_from_slice(&self.last_received); + log::trace!("Reading {result:02x?}"); + Ok(result) + } + } +} + +impl Write for EchoInterface { + fn put(&mut self, data: &[u8]) -> crate::io::Result<()> { + if self.need_to_clear { + self.need_to_clear = false; + self.last_received.clear(); + } + log::trace!("Writing {data:02x?}"); + self.last_received.extend_from_slice(data); + Ok(()) + } + + fn putc(&mut self, data: u8) -> crate::io::Result<()> { + if self.need_to_clear { + self.need_to_clear = false; + self.last_received.clear(); + } + log::trace!("Writing {data:02x?}"); + self.last_received.push(data); + Ok(()) + } + + fn flush(&mut self) -> crate::io::Result<()> { + log::trace!("Flushing"); + // the next write access will be a new buffer, remember to clear + self.need_to_clear = true; + Ok(()) + } +} diff --git a/src/interfaces/mod.rs b/src/interfaces/mod.rs new file mode 100644 index 0000000..5974ae4 --- /dev/null +++ b/src/interfaces/mod.rs @@ -0,0 +1 @@ +pub mod echo_interface; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f512484..23ba288 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,28 @@ +use log::LevelFilter; +use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; + pub mod device; pub mod device_handlers; -pub mod echo_interface; +pub mod interfaces; pub mod io; -pub mod runtime; \ No newline at end of file +pub mod runtime; + +pub fn init_logging(level: log::LevelFilter) { + // - PRINTOUT ---------------------------------------------- + // Disable printing of thread id + let config = ConfigBuilder::new() + .set_thread_level(LevelFilter::Off) + .build(); + // Init the Terminal logger: + // * Print messages at Debug level and above + // * use config built above to disable thread id printout + // * Mixed Mode: Errors go to stderr, rest goes to stdout + // * Print colors if able + TermLogger::init( + level, + config, + TerminalMode::Mixed, + ColorChoice::Auto, + ) + .expect("Logger init failed"); +} diff --git a/src/runtime.rs b/src/runtime.rs index 3d98d4d..8830815 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,52 +1,131 @@ -use crate::device::DeviceHandler; +use core::fmt::Debug; +use crate::{device::DeviceHandler, io::ReadWrite}; -#[derive(PartialEq)] -pub enum Mode{ +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum Mode { On, - Off + Off, } -pub enum Telecommands{ +#[derive(Debug)] +pub enum Telecommands { Mode(Mode), Device(T::Telecommands), } -struct DeviceHandlerRuntime{ +pub struct DeviceHandlerRuntime { handler: T, mode: Mode, } -impl DeviceHandlerRuntime { - fn run_once(&mut self, telecommand: Telecommands) -> Option{ - +impl DeviceHandlerRuntime { + pub const fn new(handler: T) -> Self { + Self { + handler, + mode: Mode::Off, + } } - fn handle_mode_tc(&mut self, mode: Mode) { - // Off to Off is a no-op - if mode == Mode::Off && self.mode == Mode::Off { - return; - } - match mode{ - Mode::Off => {match self.mode { - Mode::Off => (), - Mode::On => { - self.handler.init(interface); - self.handler.to_on(interface); - self.mode = mode; + pub fn run_once( + &mut self, + telecommand: &Telecommands, + interface: &mut impl ReadWrite, + ) -> Option { + let tm = match telecommand { + Telecommands::Mode(commanded_mode) => { + self.handle_mode_tc(*commanded_mode, interface); + None + } + Telecommands::Device(device_tc) => self.handle_device_tc(device_tc, interface), + }; + log::info!("Executed {telecommand:?}, got {tm:?}"); + None + } + + fn handle_mode_tc(&mut self, commanded_mode: Mode, interface: &mut impl ReadWrite) { + match self.mode { + Mode::Off => { + match commanded_mode { + Mode::Off => { + // no-op, announce anyway: + self.set_mode(commanded_mode); + return; + } + Mode::On => { + log::trace!("Going to on from off"); + // we are in off mode, so we first need to initialize the device + let result = self.handler.init(interface); + + if result.is_err() { + log::error!("Device failed to init: {:?}", result); + return; + }; + + // .. then we can switch to on mode + let result = self.handler.to_on(interface); + + if result.is_ok() { + self.set_mode(commanded_mode); + } else { + log::error!("Device failed to transition to on mode: {:?}", result); + } + } } - }} - Mode::On => (), + } + Mode::On => { + match commanded_mode { + Mode::Off => { + log::trace!("Going to off from on"); + // goint to off, call shutdown + let result = self.handler.shutdown(interface); + if result.is_err() { + log::error!( + "Device failed to shutdown, powering off anyway: {:?}", + result + ); + } + // in this simple implementation, device always goes to off + self.set_mode(commanded_mode); + return; + } + Mode::On => { + log::trace!("Going to on from on"); + // we are already in on mode, but transitions are performed whenever commanded: + let result = self.handler.to_on(interface); + + if result.is_ok() { + self.set_mode(commanded_mode); + } else { + log::error!("Device failed to transition to on mode: {:?}", result); + } + } + } + } } - } - fn handle_device_tc(&mut self, tc: T::Telecommands) { - + fn handle_device_tc( + &mut self, + tc: &T::Telecommands, + interface: &mut impl ReadWrite, + ) -> Option { + if self.mode == Mode::Off { + log::error!("received device command in mode Off, ignoring..."); + return None; + } + let result = self.handler.handle_telecommand(tc, interface); + if let Ok(tm) = result { + log::trace!("Executed telecommand {tc:?}"); + tm + } else { + log::error!("Failed to execute telecommand {tc:?}"); + None + } } fn set_mode(&mut self, mode: Mode) { log::info!("Mode changed from {:?} to {:?}", self.mode, mode); self.mode = mode; } -} \ No newline at end of file +}