diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index ce4b34a..8d3da42 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -137,6 +137,7 @@ dependencies = [ "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "logos 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", "nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=cd893e6)", "tm4c129x 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -157,6 +158,26 @@ name = "libm" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "logos" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "logos-derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "logos-derive" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "managed" version = "0.5.1" @@ -209,6 +230,11 @@ name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_version" version = "0.2.3" @@ -285,6 +311,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vcell" version = "0.1.2" @@ -342,6 +373,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" +"checksum logos 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f494e22d293fa05db60b3fd95fb30e9409feb5672b56ce6f250f99d9fbae6b93" +"checksum logos-derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)" = "13ff1b1068db09ee21d12baf55eccc0900a781a735273e0a606f6f4fbb32a322" "checksum managed 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43e2737ecabe4ae36a68061398bf27d2bfd0763f4c3c837a398478459494c4b7" "checksum nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" @@ -350,6 +383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" @@ -360,6 +394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tm4c129x 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d430ed4ed06dd9fff3d4517a37343e1b53789218f2f608bf1e0432f67abf624" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" "checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index ac7f3d3..3c14a69 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -19,6 +19,7 @@ cortex-m-semihosting = "0.3" byteorder = { version = "1.3", default-features = false } bit_field = "0.10" bare-metal = "0.2" +logos = { version = "~0.9", default-features = false, features = ["export_derive"] } [dependencies.smoltcp] git = "https://github.com/m-labs/smoltcp" diff --git a/firmware/src/command_parser.rs b/firmware/src/command_parser.rs new file mode 100644 index 0000000..36be426 --- /dev/null +++ b/firmware/src/command_parser.rs @@ -0,0 +1,85 @@ +use logos::{Logos, Lexer}; +use super::session::ReportMode; + +#[derive(Logos, Debug, PartialEq)] +enum Token { + #[end] + End, + #[error] + Error, + + #[token = "Quit"] + Quit, + #[token = "show"] + Show, + #[token = "channel"] + Channel, + #[token = "report"] + Report, + #[token = "mode"] + Mode, + #[token = "off"] + Off, + #[token = "once"] + Once, + #[token = "continuous"] + Continuous, + + #[regex = "[0-9]+"] + Number, +} + +#[derive(Debug)] +pub enum Error { + Parser, + UnexpectedEnd, + UnexpectedToken(Token), +} + +#[derive(Debug)] +pub enum CommandShow { + ReportMode, +} + +#[derive(Debug)] +pub enum Command { + Quit, + Show(CommandShow), + Report(ReportMode), +} + + + +impl Command { + pub fn parse(input: &str) -> Result { + let mut lexer = Token::lexer(input); + + macro_rules! choice { + [$($token: tt => $block: stmt,)*] => { + match lexer.token { + $( + Token::$token => { + lexer.advance(); + $block + } + )* + Token::End => Err(Error::UnexpectedEnd), + _ => Err(Error::UnexpectedToken(lexer.token)) + } + } + } + + choice![ + Quit => Ok(Command::Quit), + Report => choice![ + Mode => choice![ + End => Ok(Command::Show(CommandShow::ReportMode)), + Off => Ok(Command::Report(ReportMode::Off)), + Once => Ok(Command::Report(ReportMode::Once)), + Continuous => Ok(Command::Report(ReportMode::Continuous)), + ], + End => Ok(Command::Report(ReportMode::Once)), + ], + ] + } +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 24d2e23..5fd4ffd 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -34,6 +34,10 @@ pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! { mod board; use self::board::{gpio::Gpio, systick::get_time}; mod ethmac; +mod command_parser; +use command_parser::{Command, CommandShow}; +mod session; +use self::session::{Session, SessionOutput}; mod ad7172; pub struct UART0; @@ -129,6 +133,7 @@ fn main() -> ! { writeln!(stdout, "Corrupt ADC id: {:04X}", id).unwrap(), }; } + let mut hardware_addr = EthernetAddress(board::get_mac_address()); if hardware_addr.is_multicast() { println!("programmed MAC address is invalid, using default"); @@ -166,21 +171,20 @@ fn main() -> ! { create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, tcp_handle5); create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, tcp_handle6); create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, tcp_handle7); - let handles = [ - tcp_handle0, - tcp_handle1, - tcp_handle2, - tcp_handle3, - tcp_handle4, - tcp_handle5, - tcp_handle6, - tcp_handle7, + let mut sessions_handles = [ + (Session::new(), tcp_handle0), + (Session::new(), tcp_handle1), + (Session::new(), tcp_handle2), + (Session::new(), tcp_handle3), + (Session::new(), tcp_handle4), + (Session::new(), tcp_handle5), + (Session::new(), tcp_handle6), + (Session::new(), tcp_handle7), ]; let mut read_times = [0, 0]; let mut data = None; // if a socket has sent the latest data - let mut socket_pending = [false; 8]; loop { let _ = adc.data_ready() .and_then(|channel| @@ -190,8 +194,8 @@ fn main() -> ! { read_times[0] = read_times[1]; read_times[1] = now; data = Some((now, Ok((channel, new_data)))); - for p in socket_pending.iter_mut() { - *p = true; + for (session, _) in sessions_handles.iter_mut() { + session.set_report_pending(); } }) ).unwrap_or(Ok(())) @@ -199,17 +203,43 @@ fn main() -> ! { .map_err(|e| { let now = get_time(); data = Some((now, Err(e))); - for p in socket_pending.iter_mut() { - *p = true; + for (session, _) in sessions_handles.iter_mut() { + session.set_report_pending(); } }); - for (&tcp_handle, pending) in handles.iter().zip(socket_pending.iter_mut()) { - let socket = &mut *sockets.get::(tcp_handle); + for (session, tcp_handle) in sessions_handles.iter_mut() { + let socket = &mut *sockets.get::(*tcp_handle); if !socket.is_open() { + if session.is_dirty() { + // Reset a previously uses session/socket + *session = Session::new(); + } socket.listen(23).unwrap() } - if socket.may_send() && *pending { + if socket.may_recv() && socket.may_send() { + let command = socket.recv(|buf| session.feed(buf)); + + match command { + Ok(SessionOutput::Nothing) => {} + Ok(SessionOutput::Command(Command::Quit)) => + socket.close(), + Ok(SessionOutput::Command(Command::Report(mode))) => { + let _ = writeln!(socket, "Report mode: {:?}", mode); + } + Ok(SessionOutput::Command(Command::Show(CommandShow::ReportMode))) => { + let _ = writeln!(socket, "Report mode: {:?}", session.report_mode()); + } + Ok(SessionOutput::Command(command)) => { + let _ = writeln!(socket, "Not implemented: {:?}", command); + } + Ok(SessionOutput::Error(e)) => { + let _ = writeln!(socket, "Command error: {:?}", e); + } + Err(_) => {} + } + } + if socket.may_send() && session.is_report_pending() { match &data { Some((time, Ok((channel, input)))) => { let interval = read_times[1] - read_times[0]; @@ -223,7 +253,7 @@ fn main() -> ! { } None => {} } - *pending = false; + session.mark_report_sent(); } } match iface.poll(&mut sockets, Instant::from_millis((get_time() / 1000) as i64)) { diff --git a/firmware/src/session.rs b/firmware/src/session.rs new file mode 100644 index 0000000..0f18e0b --- /dev/null +++ b/firmware/src/session.rs @@ -0,0 +1,124 @@ +use super::command_parser::{Command, Error as ParserError}; + + +const MAX_LINE_LEN: usize = 64; + +struct LineReader { + buf: [u8; MAX_LINE_LEN], + pos: usize, +} + +impl LineReader { + pub fn new() -> Self { + LineReader { + buf: [0; MAX_LINE_LEN], + pos: 0, + } + } + + pub fn feed(&mut self, c: u8) -> Option<&str> { + if (c == 13 || c == 10) { + // Enter + if self.pos > 0 { + let len = self.pos; + self.pos = 0; + core::str::from_utf8(&self.buf[..len]).ok() + } else { + None + } + } else if self.pos < self.buf.len() { + // Add input + self.buf[self.pos] = c; + self.pos += 1; + None + } else { + // Buffer is full, ignore + None + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ReportMode { + Off, + Once, + Continuous, +} + +pub enum SessionOutput { + Nothing, + Command(Command), + Error(ParserError), +} + +impl From> for SessionOutput { + fn from(input: Result) -> Self { + input.map(SessionOutput::Command) + .unwrap_or_else(SessionOutput::Error) + } +} + +pub struct Session { + reader: LineReader, + report_mode: ReportMode, + report_pending: bool, +} + +impl Session { + pub fn new() -> Self { + Session { + reader: LineReader::new(), + report_mode: ReportMode::Off, + report_pending: false, + } + } + + pub fn is_dirty(&self) -> bool { + self.reader.pos > 0 + } + + pub fn report_mode(&self) -> ReportMode { + self.report_mode + } + + pub fn set_report_pending(&mut self) { + self.report_pending = true; + } + + pub fn is_report_pending(&self) -> bool { + match self.report_mode { + ReportMode::Off => false, + _ => self.report_pending, + } + } + + pub fn mark_report_sent(&mut self) { + self.report_pending = false; + match self.report_mode { + ReportMode::Once => + self.report_mode = ReportMode::Off, + _ => {} + } + } + + pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionOutput) { + let mut buf_bytes = 0; + for (i, b) in buf.iter().enumerate() { + buf_bytes = i + 1; + match self.reader.feed(*b) { + Some(line) => { + let command = Command::parse(line); + match command { + Ok(Command::Report(mode)) => { + self.report_mode = mode; + } + _ => {} + } + return (buf_bytes, command.into()); + } + None => {} + } + } + (buf_bytes, SessionOutput::Nothing) + } +}