demo handler

This commit is contained in:
mohr committed 2026-06-19 12:27:57 +02:00
1 parent 6cbd3d3271
commit 95fea92235
10 files changed
+429 -83

No files matched your search

Generated
+174
View File
@@ -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",
]
+2
View File
@@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024"
[dependencies]
log = "0.4.30"
simplelog = "0.12"
+29 -2
View File
@@ -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<DemoHandler>;
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);
}
+6 -4
View File
@@ -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<io::Error> for Error{
pub type Result<T> = core::result::Result<T,Error>;
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<Option<Self::Telemetry>>;
+22 -12
View File
@@ -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<Option<Self::Telemetry>> {
// 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(())
}
-36
View File
@@ -1,36 +0,0 @@
use crate::io::{Read, Write};
pub struct EchoInterface{
last_received: Vec<u8>
}
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(())
}
}
+64
View File
@@ -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<u8>,
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(())
}
}
+1
View File
@@ -0,0 +1 @@
pub mod echo_interface;
+25 -2
View File
@@ -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;
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");
}
+106 -27
View File
@@ -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<T: DeviceHandler>{
#[derive(Debug)]
pub enum Telecommands<T: DeviceHandler> {
Mode(Mode),
Device(T::Telecommands),
}
struct DeviceHandlerRuntime<T: DeviceHandler>{
pub struct DeviceHandlerRuntime<T: DeviceHandler> {
handler: T,
mode: Mode,
}
impl<T:DeviceHandler> DeviceHandlerRuntime<T> {
fn run_once(&mut self, telecommand: Telecommands<T>) -> Option<T::Telemetry>{
impl<T: DeviceHandler> DeviceHandlerRuntime<T> {
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<T>,
interface: &mut impl ReadWrite,
) -> Option<T::Telemetry> {
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<T::Telemetry> {
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;
}
}
}