From 8ae60ccfb8f93b1bc11585503f27fc2a501e6ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Wed, 21 Jun 2023 12:12:46 +0200 Subject: [PATCH] feat: add command handling support --- src/commands.rs | 6 + src/commands/command.rs | 7 + src/commands/command_argument.rs | 62 +++++++++ src/commands/command_handler.rs | 230 +++++++++++++++++++++++++++++++ src/main.rs | 61 ++++++++ 5 files changed, 366 insertions(+) create mode 100644 src/commands.rs create mode 100644 src/commands/command.rs create mode 100644 src/commands/command_argument.rs create mode 100644 src/commands/command_handler.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..6b93a44 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,6 @@ +pub mod hello_world_command; +pub mod command_handler; +pub mod command; +pub mod command_argument; +pub mod set_command; +pub mod reset_command; \ No newline at end of file diff --git a/src/commands/command.rs b/src/commands/command.rs new file mode 100644 index 0000000..c86f38b --- /dev/null +++ b/src/commands/command.rs @@ -0,0 +1,7 @@ +use crate::commands::command_argument::CommandArgument; + +pub struct Command<'d> +{ + pub full: &'d [char], + pub parsed_arguments: &'d [CommandArgument<'d>], +} \ No newline at end of file diff --git a/src/commands/command_argument.rs b/src/commands/command_argument.rs new file mode 100644 index 0000000..9136f36 --- /dev/null +++ b/src/commands/command_argument.rs @@ -0,0 +1,62 @@ +use esp_println::{print, println}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct CommandArgument<'d> +{ + pub data: &'d [char], +} + +impl<'d> CommandArgument<'d> +{ + pub fn new(data: &'d [char]) -> Self + { + Self { + data + } + } + + pub fn chars(&self) -> &'d [char] + { + self.data + } + + pub fn try_to_integer(&self) -> Option + { + let length = self.data.len(); + let mut multiplier = 1u32; + for _ in 0..length - 1 { + multiplier *= 10; + } + + let mut result = 0u32; + + for c in self.chars().iter() { + let num = (*c as u32) - (b'0' as u32); + if num > 9 { + return None; + } + + result += num * multiplier; + multiplier /= 10; + } + + Some(result) + } + + pub fn compare(&self, to: &str) -> bool { + if self.data.len() != to.len() { + return false; + } + + let to = to.as_bytes(); + for (i, c) in self.data.iter().enumerate() { + let compare_against = to[i]; + + if compare_against != (*c) as u8 { + return false; + } + } + + true + } +} \ No newline at end of file diff --git a/src/commands/command_handler.rs b/src/commands/command_handler.rs new file mode 100644 index 0000000..3e42b19 --- /dev/null +++ b/src/commands/command_handler.rs @@ -0,0 +1,230 @@ +use core::slice::IterMut; +use embedded_hal::serial::{Read, Write}; +use esp_println::{print, println}; +use nb::block; +use nb::Error::{Other, WouldBlock}; +use crate::command_handler::CommandHandleError::{CommandNotRead, NotFound}; +use crate::command_handler::CommandReadError::{BufferOverflowed, CommandLoadedAlready, UnexpectedEndOfLine}; +use crate::commands::command::Command; +use crate::commands::command_argument::CommandArgument; +use crate::map::Map; + +pub trait SpecificCommandHandler { + fn handle(&self, command: &mut CommandData) -> Result<(), CommandHandleError>; + fn help(&self) -> &'static str; +} + +pub struct CommandData<'d, 'a> { + pub command: Command<'d>, + pub map: &'d mut Map<'a>, +} + +pub struct CommandHandler<'d, const BUFFER_SIZE: usize, const HANDLERS_COUNT: usize> { + buffer_position: usize, + command_loaded: bool, + buffer: [char; BUFFER_SIZE], + handlers: [(&'d str, &'d dyn SpecificCommandHandler); HANDLERS_COUNT], + handling_special: u8 +} + +#[derive(Debug, Eq, PartialEq)] +pub enum CommandReadError { + UnexpectedEndOfLine, + BufferOverflowed, + CommandLoadedAlready, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum CommandHandleError { + NotFound, + WrongArguments, + CommandNotRead, +} + +impl<'d, const BUFFER_SIZE: usize, const HANDLERS_COUNT: usize> CommandHandler<'d, BUFFER_SIZE, HANDLERS_COUNT> { + pub fn new(handlers: [(&'d str, &'d dyn SpecificCommandHandler); HANDLERS_COUNT], buffer: [char; BUFFER_SIZE]) -> Self + { + Self { + command_loaded: false, + handling_special: 0, + buffer_position: 0, + buffer, + handlers, + } + } + + pub fn reset(&mut self) -> () + { + self.buffer_position = 0; + self.command_loaded = false; + self.handling_special = 0; + } + + fn handle_special(&mut self, serial: &mut Serial, data: u8) -> bool + where Serial: Read + Write + { + if self.handling_special > 0 { // some special characters have char length 2. + self.handling_special -= 1; + return false; + } + + match data { + b'\x08' => { // backspace + if self.buffer_position > 0 { + self.buffer_position -= 1; + block!(serial.write(b'\x08')).ok().unwrap(); + block!(serial.write(b' ')).ok().unwrap(); + block!(serial.write(b'\x08')).ok().unwrap(); + } + false + }, + b'\x1b' => { // other special characters + self.handling_special = 2; + false + }, + _ => true + } + } + + pub fn read_command(&mut self, serial: &mut Serial) -> nb::Result<(), CommandReadError> + where Serial: Read + Write + { + if self.command_loaded { + return Err(Other(CommandLoadedAlready)); + } + + let read = serial.read(); + + if self.buffer_position >= BUFFER_SIZE { + return Err(Other(BufferOverflowed)); + } + + match read { + Ok(data) => { + if self.handle_special(serial, data) { + self.buffer[self.buffer_position] = data as char + } else { // special character handled + return Err(WouldBlock); + } + } + Err(_) => return Err(WouldBlock) + }; + + let read = self.buffer[self.buffer_position]; + self.buffer_position += 1; + + block!(serial.write(read as u8)).ok().unwrap(); + + if read != '\r' { + return Err(WouldBlock); + } + + block!(serial.write(b'\n')).ok().unwrap(); + + if self.buffer_position <= 1 { + self.reset(); + return Err(Other(UnexpectedEndOfLine)); + } + + self.command_loaded = true; + Ok(()) + } + + fn parse_command<'a>(&self, buffer: &'a [char], args: &'a mut [CommandArgument<'a>]) -> Command<'a> + { + let mut last_arg_index = 0; + let mut length = 0; + let mut args_iter = args.iter_mut(); + + for i in 0..buffer.len() { + let argument_length = i - last_arg_index; + + if buffer[i] == ' ' { + if argument_length > 0 { + *args_iter.next().unwrap() = CommandArgument::new(&buffer[last_arg_index..i]); + length += 1; + } + + last_arg_index = i + 1; + } + + if buffer[i] == '\r' || buffer[i] == '\n' { + if argument_length > 0 { + *args_iter.next().unwrap() = CommandArgument::new(&buffer[last_arg_index..i]); + length += 1; + } + + break; + } + } + + Command { + full: buffer, + parsed_arguments: &args[0..length], + } + } + + fn handle_help(&self) -> Result<(), CommandHandleError> + { + println!("Available commands:\r"); + for (cmd, handler) in self.handlers { + println!(" {0} {1}\r", cmd, handler.help()); + } + + Ok(()) + } + + pub fn handle_command(&mut self, map: &mut Map) -> Result<(), CommandHandleError> + { + if !self.command_loaded { + return Err(CommandNotRead); + } + + let buffer = &self.buffer[0..self.buffer_position]; + let mut args = [CommandArgument::new(buffer); BUFFER_SIZE]; + + let command = self.parse_command(buffer, &mut args); + + if command.parsed_arguments.len() == 0 { + self.reset(); + return Err(NotFound); + } + + let first_argument = command.parsed_arguments[0]; + + if first_argument.compare("HELP") { + let help_handled = self.handle_help(); + self.reset(); + return help_handled; + } + + for (handler_command, handler) in self.handlers { + if !first_argument.compare(handler_command) { + continue; + } + + let mut command_data = CommandData { + command, + map, + }; + + let handled = handler.handle(&mut command_data); + self.reset(); + return handled; + } + + self.reset(); + Err(NotFound) + } +} + +// HELP +// DEFAULT +// SET +// ALL +// RESET +// ITER [X] [Y] (X start, Y end) +// SNAKE [X] [Y] (X start, Y end) +// RAINBOW -- show rainbow + +// WIFI diff --git a/src/main.rs b/src/main.rs index 9d80953..1949c9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod strip; mod map; +mod commands; use embedded_hal::serial::{Read, Write}; use esp_backtrace as _; @@ -13,8 +14,14 @@ use hal::uart::TxRxPins; use nb::block; use nb::Error::{WouldBlock, Other}; use smart_leds::{RGB8, SmartLedsWrite}; +use crate::commands::command_handler::{CommandHandler}; +use crate::commands::command_handler; +use crate::commands::hello_world_command::HelloWorldCommand; +use crate::commands::reset_command::ResetCommand; +use crate::commands::set_command::SetCommand; use crate::map::Map; use crate::strip::StripTiming; + const LEDS_COUNT: usize = 72; const COMMAND_BUFFER: usize = 200; @@ -84,7 +91,61 @@ fn main() -> ! { let mut rgb_data: [RGB8; 72] = [RGB8 { r: 0, g: 0, b: 0 }; 72]; let mut map = map::Map::new(&map::INDEX_MAP, &mut rgb_data); + let mut handler = CommandHandler::new( + [ + ], + ['\0'; COMMAND_BUFFER], + ); + + block!(serial.write(b'>')).ok().unwrap(); + block!(serial.write(b' ')).ok().unwrap(); loop { + let handled = match handler.read_command(&mut serial) { + Ok(()) => { + println!("\r"); + let result = handler.handle_command(&mut map); + + match result { + Ok(()) => Ok(true), + Err(err) => Err(err) + } + } + Err(err) => { + match err { + Other(error) => { + match error { + command_handler::CommandReadError::BufferOverflowed => println!("Command is too long.\r"), + command_handler::CommandReadError::UnexpectedEndOfLine => (), + command_handler::CommandReadError::CommandLoadedAlready => println!("FATAL: Previous command not processed correctly.\r") + }; + Ok(true) + } + _ => Ok(false) + } + } + }; + + let new_command = match handled { + Ok(handled) => { + handled + } + Err(err) => { + match err { + command_handler::CommandHandleError::NotFound => println!("Command not found.\r"), + command_handler::CommandHandleError::WrongArguments => println!("Wrong arguments.\r"), + command_handler::CommandHandleError::CommandNotRead => println!("FATAL: Command is not prepared.\r") + } + true + } + }; + + if new_command { + println!("\r"); + block!(serial.write(b'>')).ok().unwrap(); + block!(serial.write(b' ')).ok().unwrap(); + } + strip.write(map.get_map().cloned()).unwrap(); + delay.delay_us(500u32); } } \ No newline at end of file -- 2.48.1