diff --git a/Cargo.lock b/Cargo.lock index 60d0568..9d4776a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,16 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "857afb5ee9e767c3a73b2ad7212b6deea0c3761a27db1e20ea0ed57ee352cfef" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "bitflags", + "cfg-if", +] + [[package]] name = "log" version = "0.4.8" @@ -163,12 +173,28 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + [[package]] name = "nb" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc" +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "panic-abort" version = "0.3.2" @@ -312,7 +338,9 @@ dependencies = [ "cortex-m-rt", "embedded-hal", "hash2hwaddr", + "lexical-core", "log", + "nom", "panic-abort", "panic-semihosting", "smoltcp", @@ -338,6 +366,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + [[package]] name = "void" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index c0e6b88..f1555b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ smoltcp = { version = "0.6.0", default-features = false, features = ["proto-ipv4 hash2hwaddr = { version = "0.0", optional = true } bit_field = "0.10" byteorder = { version = "1", default-features = false } +nom = { version = "5", default-features = false } +lexical-core = { version = "0.7", default-features = false } [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/command_parser.rs b/src/command_parser.rs new file mode 100644 index 0000000..ea901b7 --- /dev/null +++ b/src/command_parser.rs @@ -0,0 +1,533 @@ +use core::fmt; +use nom::{ + IResult, + branch::alt, + bytes::complete::{is_a, tag, take_while1}, + character::{is_digit, complete::{char, one_of}}, + combinator::{complete, map, opt, value}, + sequence::{preceded, separated_pair}, + multi::{fold_many0, fold_many1}, + error::ErrorKind, +}; +use lexical_core as lexical; + + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + Parser(ErrorKind), + Incomplete, + UnexpectedInput(u8), + ParseNumber(lexical::Error) +} + +impl<'t> From> for Error { + fn from(e: nom::Err<(&'t [u8], ErrorKind)>) -> Self { + match e { + nom::Err::Incomplete(_) => + Error::Incomplete, + nom::Err::Error((_, e)) => + Error::Parser(e), + nom::Err::Failure((_, e)) => + Error::Parser(e), + } + } +} + +impl From for Error { + fn from(e: lexical::Error) -> Self { + Error::ParseNumber(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Error::Incomplete => + "incomplete input".fmt(fmt), + Error::UnexpectedInput(c) => { + "unexpected input: ".fmt(fmt)?; + c.fmt(fmt) + } + Error::Parser(e) => { + "parser: ".fmt(fmt)?; + (e as &dyn core::fmt::Debug).fmt(fmt) + } + Error::ParseNumber(e) => { + "parsing number: ".fmt(fmt)?; + (e as &dyn core::fmt::Debug).fmt(fmt) + } + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ShowCommand { + Input, + Reporting, + Pwm, + Pid, + SteinhartHart, + PostFilter, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PidParameter { + Target, + KP, + KI, + KD, + OutputMin, + OutputMax, + IntegralMin, + IntegralMax, +} + +/// Steinhart-Hart equation parameter +#[derive(Debug, Clone, PartialEq)] +pub enum ShParameter { + A, + B, + C, + ParallelR, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PwmConfig { + pub width: u16, + pub total: u16, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PwmMode { + Manual(PwmConfig), + Pid, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PwmSetup { + ISet(PwmMode), + MaxIPos(PwmConfig), + MaxINeg(PwmConfig), + MaxV(PwmConfig), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Command { + Quit, + Show(ShowCommand), + Reporting(bool), + Pwm { + channel: usize, + setup: PwmSetup, + }, + Pid { + channel: usize, + parameter: PidParameter, + value: f32, + }, + SteinhartHart { + channel: usize, + parameter: ShParameter, + value: f32, + }, + PostFilter { + channel: usize, + rate: f32, + }, +} + +fn end(input: &[u8]) -> IResult<&[u8], ()> { + complete( + fold_many0( + one_of("\r\n\t "), + (), |(), _| () + ) + )(input) +} + +fn whitespace(input: &[u8]) -> IResult<&[u8], ()> { + fold_many1(char(' '), (), |(), _| ())(input) +} + +fn unsigned(input: &[u8]) -> IResult<&[u8], Result> { + take_while1(is_digit)(input) + .map(|(input, digits)| { + let result = lexical::parse(digits) + .map_err(|e| e.into()); + (input, result) + }) +} + +fn float(input: &[u8]) -> IResult<&[u8], Result> { + let (input, sign) = opt(is_a("-"))(input)?; + let negative = sign.is_some(); + let (input, digits) = take_while1(|c| is_digit(c) || c == '.' as u8)(input)?; + let result = lexical::parse(digits) + .map(|result: f32| if negative { -result } else { result }) + .map_err(|e| e.into()); + Ok((input, result)) +} + +fn off_on(input: &[u8]) -> IResult<&[u8], bool> { + alt((value(false, tag("off")), + value(true, tag("on")) + ))(input) +} + +fn channel(input: &[u8]) -> IResult<&[u8], usize> { + map(one_of("01"), |c| (c as usize) - ('0' as usize))(input) +} + +fn report(input: &[u8]) -> IResult<&[u8], Command> { + preceded( + tag("report"), + alt(( + preceded( + whitespace, + preceded( + tag("mode"), + alt(( + preceded( + whitespace, + // `report mode ` - Switch repoting mode + map(off_on, Command::Reporting) + ), + // `report mode` - Show current reporting state + value(Command::Show(ShowCommand::Reporting), end) + )) + )), + // `report` - Report once + value(Command::Show(ShowCommand::Input), end) + )) + )(input) +} + +/// `pwm ... ` - Set pwm duty cycle +fn pwm_config(input: &[u8]) -> IResult<&[u8], Result> { + let (input, width) = unsigned(input)?; + let width = match width { + Ok(width) => width, + Err(e) => return Ok((input, Err(e.into()))), + }; + let (input, _) = whitespace(input)?; + let (input, total) = unsigned(input)?; + let total = match total { + Ok(total) => total, + Err(e) => return Ok((input, Err(e.into()))), + }; + Ok((input, Ok(PwmConfig { width, total }))) +} + +fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result> { + alt(( + map( + preceded( + tag("max_i_pos"), + preceded( + whitespace, + pwm_config + ) + ), + |result| result.map(PwmSetup::MaxIPos) + ), + map( + preceded( + tag("max_i_neg"), + preceded( + whitespace, + pwm_config + ) + ), + |result| result.map(PwmSetup::MaxINeg) + ), + map( + preceded( + tag("max_v"), + preceded( + whitespace, + pwm_config + ) + ), + |result| result.map(PwmSetup::MaxV) + ), + map(pwm_config, |result| result.map(|config| { + PwmSetup::ISet(PwmMode::Manual(config)) + })) + ))(input) +} + +/// `pwm <0-1> pid` - Set PWM to be controlled by PID +fn pwm_pid(input: &[u8]) -> IResult<&[u8], Result> { + value(Ok(PwmSetup::ISet(PwmMode::Pid)), tag("pid"))(input) +} + +fn pwm(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("pwm")(input)?; + alt(( + preceded( + whitespace, + map( + separated_pair( + channel, + whitespace, + alt(( + pwm_pid, + pwm_setup + )) + ), + |(channel, setup)| setup.map(|setup| Command::Pwm { channel, setup }) + ) + ), + value(Ok(Command::Show(ShowCommand::Pwm)), end) + ))(input) +} + +/// `pid <0-1> ` +fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result> { + let (input, channel) = channel(input)?; + let (input, _) = whitespace(input)?; + let (input, parameter) = + alt((value(PidParameter::Target, tag("target")), + value(PidParameter::KP, tag("kp")), + value(PidParameter::KI, tag("ki")), + value(PidParameter::KD, tag("kd")), + value(PidParameter::OutputMin, tag("output_min")), + value(PidParameter::OutputMax, tag("output_max")), + value(PidParameter::IntegralMin, tag("integral_min")), + value(PidParameter::IntegralMax, tag("integral_max")) + ))(input)?; + let (input, _) = whitespace(input)?; + let (input, value) = float(input)?; + let result = value + .map(|value| Command::Pid { channel, parameter, value }); + Ok((input, result)) +} + +/// `pid` | `pid ` +fn pid(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("pid")(input)?; + alt(( + preceded( + whitespace, + pid_parameter + ), + value(Ok(Command::Show(ShowCommand::Pid)), end) + ))(input) +} + +/// `s-h <0-1> ` +fn steinhart_hart_parameter(input: &[u8]) -> IResult<&[u8], Result> { + let (input, channel) = channel(input)?; + let (input, _) = whitespace(input)?; + let (input, parameter) = + alt((value(ShParameter::A, tag("a")), + value(ShParameter::B, tag("b")), + value(ShParameter::C, tag("c")), + value(ShParameter::ParallelR, tag("parallel_r")) + ))(input)?; + let (input, _) = whitespace(input)?; + let (input, value) = float(input)?; + let result = value + .map(|value| Command::SteinhartHart { channel, parameter, value }); + Ok((input, result)) +} + +/// `s-h` | `s-h ` +fn steinhart_hart(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("s-h")(input)?; + alt(( + preceded( + whitespace, + steinhart_hart_parameter + ), + value(Ok(Command::Show(ShowCommand::SteinhartHart)), end) + ))(input) +} + +fn postfilter(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("postfilter")(input)?; + alt(( + preceded( + whitespace, + |input| { + let (input, channel) = channel(input)?; + let (input, _) = whitespace(input)?; + let (input, _) = tag("rate")(input)?; + let (input, _) = whitespace(input)?; + let (input, rate) = float(input)?; + let result = rate + .map(|rate| Command::PostFilter { + channel, rate, + }); + Ok((input, result)) + } + ), + value(Ok(Command::Show(ShowCommand::PostFilter)), end) + ))(input) +} + +fn command(input: &[u8]) -> IResult<&[u8], Result> { + alt((value(Ok(Command::Quit), tag("quit")), + map(report, Ok), + pwm, + pid, + steinhart_hart, + postfilter, + ))(input) +} + +impl Command { + pub fn parse(input: &[u8]) -> Result { + match command(input) { + Ok((b"", result)) => + result, + Ok((input_remain, _)) => + Err(Error::UnexpectedInput(input_remain[0])), + Err(e) => + Err(e.into()), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_quit() { + let command = Command::parse(b"quit"); + assert_eq!(command, Ok(Command::Quit)); + } + + #[test] + fn parse_report() { + let command = Command::parse(b"report"); + assert_eq!(command, Ok(Command::Show(ShowCommand::Input))); + } + + #[test] + fn parse_report_mode() { + let command = Command::parse(b"report mode"); + assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting))); + } + + #[test] + fn parse_report_mode_on() { + let command = Command::parse(b"report mode on"); + assert_eq!(command, Ok(Command::Reporting(true))); + } + + #[test] + fn parse_report_mode_off() { + let command = Command::parse(b"report mode off"); + assert_eq!(command, Ok(Command::Reporting(false))); + } + + #[test] + fn parse_pwm_manual() { + let command = Command::parse(b"pwm 1 16383 65535"); + assert_eq!(command, Ok(Command::Pwm { + channel: 1, + setup: PwmSetup::ISet(PwmMode::Manual(PwmConfig { + width: 16383, + total: 65535, + })), + })); + } + + #[test] + fn parse_pwm_pid() { + let command = Command::parse(b"pwm 0 pid"); + assert_eq!(command, Ok(Command::Pwm { + channel: 0, + setup: PwmSetup::ISet(PwmMode::Pid), + })); + } + + #[test] + fn parse_pwm_max_i_pos() { + let command = Command::parse(b"pwm 0 max_i_pos 7 13"); + assert_eq!(command, Ok(Command::Pwm { + channel: 0, + setup: PwmSetup::MaxIPos(PwmConfig { + width: 7, + total: 13, + }), + })); + } + + #[test] + fn parse_pwm_max_i_neg() { + let command = Command::parse(b"pwm 0 max_i_neg 128 65535"); + assert_eq!(command, Ok(Command::Pwm { + channel: 0, + setup: PwmSetup::MaxINeg(PwmConfig { + width: 128, + total: 65535, + }), + })); + } + + #[test] + fn parse_pwm_max_v() { + let command = Command::parse(b"pwm 0 max_v 32768 65535"); + assert_eq!(command, Ok(Command::Pwm { + channel: 0, + setup: PwmSetup::MaxV(PwmConfig { + width: 32768, + total: 65535, + }), + })); + } + + #[test] + fn parse_pid() { + let command = Command::parse(b"pid"); + assert_eq!(command, Ok(Command::Show(ShowCommand::Pid))); + } + + #[test] + fn parse_pid_target() { + let command = Command::parse(b"pid 0 target 36.5"); + assert_eq!(command, Ok(Command::Pid { + channel: 0, + parameter: PidParameter::Target, + value: 36.5, + })); + } + + #[test] + fn parse_pid_integral_max() { + let command = Command::parse(b"pid 1 integral_max 2000"); + assert_eq!(command, Ok(Command::Pid { + channel: 1, + parameter: PidParameter::IntegralMax, + value: 2000.0, + })); + } + + #[test] + fn parse_steinhart_hart() { + let command = Command::parse(b"s-h"); + assert_eq!(command, Ok(Command::Show(ShowCommand::SteinhartHart))); + } + + #[test] + fn parse_steinhart_hart_parallel_r() { + let command = Command::parse(b"s-h 1 parallel_r 23.05"); + assert_eq!(command, Ok(Command::SteinhartHart { + channel: 1, + parameter: ShParameter::ParallelR, + value: 23.05, + })); + } + + #[test] + fn parse_postfilter_rate() { + let command = Command::parse(b"postfilter 0 rate 21"); + assert_eq!(command, Ok(Command::PostFilter { + channel: 0, + rate: 21.0, + })); + } +} diff --git a/src/main.rs b/src/main.rs index ed0cf05..428ea8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,13 +33,28 @@ mod ad5680; mod net; mod server; use server::Server; +mod session; +use session::{CHANNELS, Session, SessionOutput}; +mod command_parser; +use command_parser::{Command, ShowCommand, PwmSetup, PwmMode}; mod timer; -/// Interval at which to sample the ADC input and broadcast to all -/// clients. -/// -/// This should be a multiple of the `TIMER_RATE`. -const OUTPUT_INTERVAL: u32 = 1000; + +#[derive(Clone, Copy, Debug)] +struct ChannelState { + adc_data: Option, + adc_time: Instant, +} + +impl Default for ChannelState { + fn default() -> Self { + ChannelState { + adc_data: None, + adc_time: Instant::from_secs(0), + } + } +} + #[cfg(not(feature = "semihosting"))] const WATCHDOG_INTERVAL: u32 = 100; @@ -49,15 +64,16 @@ const WATCHDOG_INTERVAL: u32 = 10_000; #[cfg(not(feature = "generate-hwaddr"))] const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; +const TCP_PORT: u16 = 23; + const HSE: MegaHertz = MegaHertz(8); - /// Initialization and main loop #[entry] fn main() -> ! { init_log(); - info!("Thermostat firmware"); + info!("tecpak"); let mut cp = CorePeripherals::take().unwrap(); cp.SCB.enable_icache(); @@ -102,10 +118,10 @@ fn main() -> ! { }; info!("Net hwaddr: {}", hwaddr); - info!("Net startup"); + let mut channel_states = [ChannelState::default(); CHANNELS]; + net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| { - Server::run(iface, |server| { - let mut last_output = 0_u32; + Server::::run(iface, |server| { loop { let now = timer::now().0; let instant = Instant::from_millis(i64::from(now)); @@ -115,17 +131,224 @@ fn main() -> ! { warn!("poll: {:?}", e); }); - let now = timer::now().0; - if now - last_output >= OUTPUT_INTERVAL { - // let adc_value = adc_input.read(); - writeln!(server, "t={},pa3={}\r", now, 0.0 /*adc_value*/).unwrap(); - last_output = now; - } - - if let Some(channel) = adc.data_ready().unwrap() { + // ADC input + adc.data_ready().unwrap().map(|channel| { let data = adc.read_data().unwrap(); - info!("ADC {}: {:08X}", channel, data); - } + + let state = &mut channel_states[usize::from(channel)]; + state.adc_data = Some(data); + state.adc_time = instant; + server.for_each(|_, session| session.set_report_pending(channel.into())); + }); + + // TCP protocol handling + server.for_each(|mut socket, session| { + if ! socket.is_open() { + let _ = socket.listen(TCP_PORT); + session.reset(); + } else if socket.can_send() && socket.can_recv() && socket.send_capacity() - socket.send_queue() > 128 { + match socket.recv(|buf| session.feed(buf)) { + Ok(SessionOutput::Nothing) => {} + Ok(SessionOutput::Command(command)) => match command { + Command::Quit => + socket.close(), + Command::Reporting(reporting) => { + let _ = writeln!(socket, "report={}", if reporting { "on" } else { "off" }); + } + Command::Show(ShowCommand::Reporting) => { + let _ = writeln!(socket, "report={}", if session.reporting() { "on" } else { "off" }); + } + Command::Show(ShowCommand::Input) => { + for (channel, state) in channel_states.iter().enumerate() { + if let Some(adc_data) = state.adc_data { + let _ = writeln!( + socket, "t={} raw{}=0x{:06X}", + state.adc_time, channel, adc_data + ); + // TODO: show pwm status et al + } + } + } + Command::Show(ShowCommand::Pid) => { + // for (channel, state) in states.iter().enumerate() { + // let _ = writeln!(socket, "PID settings for channel {}", channel); + // let pid = &state.pid; + // let _ = writeln!(socket, "- target={:.4}", pid.get_target()); + // let p = pid.get_parameters(); + // macro_rules! out { + // ($p: tt) => { + // let _ = writeln!(socket, "- {}={:.4}", stringify!($p), p.$p); + // }; + // } + // out!(kp); + // out!(ki); + // out!(kd); + // out!(output_min); + // out!(output_max); + // out!(integral_min); + // out!(integral_max); + // let _ = writeln!(socket, ""); + // } + } + Command::Show(ShowCommand::Pwm) => { + // for (channel, state) in states.iter().enumerate() { + // let _ = writeln!( + // socket, "channel {}: PID={}", + // channel, + // if state.pid_enabled { "engaged" } else { "disengaged" } + // ); + // for pin in TecPin::VALID_VALUES { + // let (width, total) = match channel { + // 0 => tec0.get(*pin), + // 1 => tec1.get(*pin), + // _ => unreachable!(), + // }; + // let _ = writeln!(socket, "- {}={}/{}", pin, width, total); + // } + // let _ = writeln!(socket, ""); + // } + } + Command::Show(ShowCommand::SteinhartHart) => { + // for (channel, state) in states.iter().enumerate() { + // let _ = writeln!( + // socket, "channel {}: Steinhart-Hart equation parameters", + // channel, + // ); + // let _ = writeln!(socket, "- a={}", state.sh.a); + // let _ = writeln!(socket, "- b={}", state.sh.b); + // let _ = writeln!(socket, "- c={}", state.sh.c); + // let _ = writeln!(socket, "- parallel_r={}", state.sh.parallel_r); + // let _ = writeln!(socket, ""); + // } + } + Command::Show(ShowCommand::PostFilter) => { + // for (channel, _) in states.iter().enumerate() { + // match adc.get_postfilter(channel as u8).unwrap() { + // Some(filter) => { + // let _ = writeln!( + // socket, "channel {}: postfilter={:.2} SPS", + // channel, filter.output_rate().unwrap() + // ); + // } + // None => { + // let _ = writeln!( + // socket, "channel {}: no postfilter", + // channel + // ); + // } + // } + // } + } + Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Pid) } => { + // states[channel].pid_enabled = true; + // let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel + // ); + } + Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Manual(config))} => { + // states[channel].pid_enabled = false; + // let PwmConfig { width, total } = config; + // match channel { + // 0 => tec0.set(TecPin::ISet, width, total), + // 1 => tec1.set(TecPin::ISet, width, total), + // _ => unreachable!(), + // } + // let _ = writeln!( + // socket, "channel {}: PWM duty cycle manually set to {}/{}", + // channel, config.width, config.total + // ); + } + Command::Pwm { channel, setup } => { + // let (pin, config) = match setup { + // PwmSetup::ISet(_) => + // // Handled above + // unreachable!(), + // PwmSetup::MaxIPos(config) => + // (TecPin::MaxIPos, config), + // PwmSetup::MaxINeg(config) => + // (TecPin::MaxINeg, config), + // PwmSetup::MaxV(config) => + // (TecPin::MaxV, config), + // }; + // let PwmConfig { width, total } = config; + // match channel { + // 0 => tec0.set(pin, width, total), + // 1 => tec1.set(pin, width, total), + // _ => unreachable!(), + // } + // let _ = writeln!( + // socket, "channel {}: PWM {} reconfigured to {}/{}", + // channel, pin, width, total + // ); + } + Command::Pid { channel, parameter, value } => { + // let pid = &mut states[channel].pid; + // use command_parser::PidParameter::*; + // match parameter { + // Target => + // pid.set_target(value), + // KP => + // pid.update_parameters(|parameters| parameters.kp = value), + // KI => + // pid.update_parameters(|parameters| parameters.ki = value), + // KD => + // pid.update_parameters(|parameters| parameters.kd = value), + // OutputMin => + // pid.update_parameters(|parameters| parameters.output_min = value), + // OutputMax => + // pid.update_parameters(|parameters| parameters.output_max = value), + // IntegralMin => + // pid.update_parameters(|parameters| parameters.integral_min = value), + // IntegralMax => + // pid.update_parameters(|parameters| parameters.integral_max = value), + // } + // pid.reset(); + // let _ = writeln!(socket, "PID parameter updated"); + } + Command::SteinhartHart { channel, parameter, value } => { + // let sh = &mut states[channel].sh; + // use command_parser::ShParameter::*; + // match parameter { + // A => sh.a = value, + // B => sh.b = value, + // C => sh.c = value, + // ParallelR => sh.parallel_r = value, + // } + // let _ = writeln!(socket, "Steinhart-Hart equation parameter updated"); + } + Command::PostFilter { channel, rate } => { + // let filter = ad7172::PostFilter::closest(rate); + // match filter { + // Some(filter) => { + // adc.set_postfilter(channel as u8, Some(filter)).unwrap(); + // let _ = writeln!( + // socket, "channel {}: postfilter set to {:.2} SPS", + // channel, filter.output_rate().unwrap() + // ); + // } + // None => { + // let _ = writeln!(socket, "Unable to choose postfilter"); + // } + // } + } + } + Ok(SessionOutput::Error(e)) => { + let _ = writeln!(socket, "Command error: {:?}", e); + } + Err(_) => + socket.close(), + } + } else if socket.can_send() && socket.send_capacity() - socket.send_queue() > 256 { + while let Some(channel) = session.is_report_pending() { + let state = &mut channel_states[usize::from(channel)]; + let _ = writeln!( + socket, "t={} raw{}=0x{:04X}", + state.adc_time, channel, state.adc_data.unwrap_or(0) + ).map(|_| { + session.mark_report_sent(channel); + }); + } + } + }); // Update watchdog wd.feed(); diff --git a/src/net.rs b/src/net.rs index 86234f6..b42349a 100644 --- a/src/net.rs +++ b/src/net.rs @@ -43,7 +43,7 @@ pub fn run( eth_dev.enable_interrupt(); // IP stack - let local_addr = IpAddress::v4(192, 168, 69, 3); + let local_addr = IpAddress::v4(192, 168, 1, 26); let mut ip_addrs = [IpCidr::new(local_addr, 24)]; let mut neighbor_storage = [None; 16]; let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]); diff --git a/src/server.rs b/src/server.rs index c6f407b..b48b230 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,12 +2,16 @@ use core::fmt; use core::mem::MaybeUninit; use smoltcp::{ iface::EthernetInterface, - socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer}, + socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef}, time::Instant, }; -const TCP_PORT: u16 = 23; +pub struct SocketState { + handle: SocketHandle, + state: S, +} + /// Number of server sockets and therefore concurrent client /// sessions. Many data structures in `Server::run()` correspond to /// this const. @@ -16,45 +20,49 @@ const SOCKET_COUNT: usize = 8; const TCP_RX_BUFFER_SIZE: usize = 2048; const TCP_TX_BUFFER_SIZE: usize = 2048; -macro_rules! create_socket { - ($set:ident, $rx_storage:ident, $tx_storage:ident, $target:expr) => { - let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE]; - let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE]; - let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]); - let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]); - let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - $target = $set.add(tcp_socket); - } -} - /// Contains a number of server sockets that get all sent the same /// data (through `fmt::Write`). -pub struct Server<'a, 'b> { +pub struct Server<'a, 'b, S> { net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>, sockets: SocketSet<'b, 'b, 'b>, - handles: [SocketHandle; SOCKET_COUNT], + states: [SocketState; SOCKET_COUNT], } -impl<'a, 'b> Server<'a, 'b> { +impl<'a, 'b, S: Default> Server<'a, 'b, S> { /// Run a server with stack-allocated sockets pub fn run(net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>, f: F) where - F: FnOnce(&mut Server<'a, '_>), + F: FnOnce(&mut Server<'a, '_, S>), { let mut sockets_storage: [_; SOCKET_COUNT] = Default::default(); let mut sockets = SocketSet::new(&mut sockets_storage[..]); - let mut handles: [SocketHandle; SOCKET_COUNT] = unsafe { MaybeUninit::uninit().assume_init() }; - create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, handles[0]); - create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, handles[1]); - create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, handles[2]); - create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, handles[3]); - create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, handles[4]); - create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, handles[5]); - create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, handles[6]); - create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, handles[7]); + let mut states: [SocketState; SOCKET_COUNT] = unsafe { MaybeUninit::uninit().assume_init() }; + + macro_rules! create_socket { + ($set:ident, $rx_storage:ident, $tx_storage:ident, $target:expr) => { + let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE]; + let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE]; + let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]); + let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]); + let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + $target = $set.add(tcp_socket); + } + } + create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, states[0].handle); + create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, states[1].handle); + create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, states[2].handle); + create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, states[3].handle); + create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, states[4].handle); + create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, states[5].handle); + create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, states[6].handle); + create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, states[7].handle); + + for state in &mut states { + state.state = S::default(); + } let mut server = Server { - handles, + states, sockets, net, }; @@ -63,46 +71,21 @@ impl<'a, 'b> Server<'a, 'b> { /// Poll the interface and the sockets pub fn poll(&mut self, now: Instant) -> Result<(), smoltcp::Error> { - // Poll smoltcp EthernetInterface - let mut poll_error = None; - let activity = self.net.poll(&mut self.sockets, now) - .unwrap_or_else(|e| { - poll_error = Some(e); - true - }); - - if activity { - // Listen on all sockets - for handle in &self.handles { - let mut socket = self.sockets.get::(*handle); - if ! socket.is_open() { - let _ = socket.listen(TCP_PORT); - } - } + // Poll smoltcp EthernetInterface, + // pass only unexpected smoltcp errors to the caller + match self.net.poll(&mut self.sockets, now) { + Ok(_) => Ok(()), + Err(smoltcp::Error::Malformed) => Ok(()), + Err(smoltcp::Error::Unrecognized) => Ok(()), + Err(e) => Err(e), } + } - // Pass some smoltcp errors to the caller - match poll_error { - None => Ok(()), - Some(smoltcp::Error::Malformed) => Ok(()), - Some(smoltcp::Error::Unrecognized) => Ok(()), - Some(e) => Err(e), + /// Iterate over all sockets managed by this server + pub fn for_each, &mut S)>(&mut self, mut callback: F) { + for state in &mut self.states { + let socket = self.sockets.get::(state.handle); + callback(socket, &mut state.state); } } } - -/// Reusing the `fmt::Write` trait just for `write!()` convenience -impl<'a, 's> fmt::Write for Server<'a, 's> { - /// Write to all connected clients - fn write_str(&mut self, slice: &str) -> fmt::Result { - for handle in &self.handles { - let mut socket = self.sockets.get::(*handle); - if socket.can_send() { - // Ignore errors, proceed with next client - let _ = socket.write_str(slice); - } - } - - Ok(()) - } -} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..82be344 --- /dev/null +++ b/src/session.rs @@ -0,0 +1,151 @@ +use core::ops::Deref; +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 { + if c == 13 || c == 10 { + // Enter + if self.pos > 0 { + let len = self.pos; + self.pos = 0; + Some(LineResult { + buf: self.buf.clone(), + len, + }) + } 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 + } + } +} + +pub struct LineResult { + buf: [u8; MAX_LINE_LEN], + len: usize, +} + +impl Deref for LineResult { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.buf[..self.len] + } +} + +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 const CHANNELS: usize = 2; + +pub struct Session { + reader: LineReader, + reporting: bool, + report_pending: [bool; CHANNELS], +} + +impl Default for Session { + fn default() -> Self { + Session::new() + } +} + +impl Session { + pub fn new() -> Self { + Session { + reader: LineReader::new(), + reporting: false, + report_pending: [false; CHANNELS], + } + } + + pub fn reset(&mut self) { + self.reader = LineReader::new(); + self.reporting = false; + self.report_pending = [false; CHANNELS]; + } + + pub fn is_dirty(&self) -> bool { + self.reader.pos > 0 + } + + pub fn reporting(&self) -> bool { + self.reporting + } + + pub fn set_report_pending(&mut self, channel: usize) { + if self.reporting { + self.report_pending[channel] = true; + } + } + + pub fn is_report_pending(&self) -> Option { + if ! self.reporting { + None + } else { + self.report_pending.iter() + .enumerate() + .fold(None, |result, (channel, report_pending)| { + result.or_else(|| { + if *report_pending { Some(channel) } else { None } + }) + }) + } + } + + pub fn mark_report_sent(&mut self, channel: usize) { + self.report_pending[channel] = false; + } + + 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; + let line = self.reader.feed(*b); + match line { + Some(line) => { + let command = Command::parse(&line); + match command { + Ok(Command::Reporting(reporting)) => { + self.reporting = reporting; + } + _ => {} + } + return (buf_bytes, command.into()); + } + None => {} + } + } + (buf_bytes, SessionOutput::Nothing) + } +}