thermostat/src/command_parser.rs

558 lines
15 KiB
Rust
Raw Normal View History

2020-03-14 06:39:22 +08:00
use core::fmt;
use core::num::ParseIntError;
use core::str::{from_utf8, Utf8Error};
2020-03-14 06:39:22 +08:00
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 num_traits::{Num, ParseFloatError};
2020-03-14 06:39:22 +08:00
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Parser(ErrorKind),
Incomplete,
UnexpectedInput(u8),
Utf8(Utf8Error),
ParseInt(ParseIntError),
// `num_traits::ParseFloatError` does not impl Clone
ParseFloat,
2020-03-14 06:39:22 +08:00
}
impl<'t> From<nom::Err<(&'t [u8], ErrorKind)>> 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<Utf8Error> for Error {
fn from(e: Utf8Error) -> Self {
Error::Utf8(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Error::ParseInt(e)
}
}
impl From<ParseFloatError> for Error {
fn from(_: ParseFloatError) -> Self {
Error::ParseFloat
2020-03-14 06:39:22 +08:00
}
}
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::Utf8(e) => {
"utf8: ".fmt(fmt)?;
(e as &dyn core::fmt::Debug).fmt(fmt)
}
Error::ParseInt(e) => {
"parsing int: ".fmt(fmt)?;
2020-03-14 06:39:22 +08:00
(e as &dyn core::fmt::Debug).fmt(fmt)
}
Error::ParseFloat => {
"parsing float".fmt(fmt)
}
2020-03-14 06:39:22 +08:00
}
}
}
#[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 {
2020-03-20 01:34:57 +08:00
T0,
R,
R0,
2020-03-14 06:39:22 +08:00
}
2020-03-20 01:34:57 +08:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PwmPin {
ISet,
MaxIPos,
MaxINeg,
MaxV,
2020-03-14 06:39:22 +08:00
}
2020-03-20 01:34:57 +08:00
impl PwmPin {
pub fn name(&self) -> &'static str {
match self {
PwmPin::ISet => "i_set",
PwmPin::MaxIPos => "max_i_pos",
PwmPin::MaxINeg => "max_i_neg",
PwmPin::MaxV => "max_v",
}
}
2020-03-14 06:39:22 +08:00
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Quit,
Show(ShowCommand),
Reporting(bool),
2020-03-20 01:34:57 +08:00
/// PWM parameter setting
2020-03-14 06:39:22 +08:00
Pwm {
channel: usize,
2020-03-20 01:34:57 +08:00
pin: PwmPin,
duty: u32,
2020-03-14 06:39:22 +08:00
},
2020-03-20 01:34:57 +08:00
/// Enable PID control for `i_set`
PwmPid {
channel: usize,
},
/// PID parameter setting
2020-03-14 06:39:22 +08:00
Pid {
channel: usize,
parameter: PidParameter,
value: f64,
2020-03-14 06:39:22 +08:00
},
SteinhartHart {
channel: usize,
parameter: ShParameter,
value: f64,
2020-03-14 06:39:22 +08:00
},
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)
}
2020-03-20 01:34:57 +08:00
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u32, Error>> {
2020-03-14 06:39:22 +08:00
take_while1(is_digit)(input)
.map(|(input, digits)| {
let result =
from_utf8(digits)
.map_err(|e| e.into())
.and_then(|digits| u32::from_str_radix(digits, 10)
.map_err(|e| e.into())
);
2020-03-14 06:39:22 +08:00
(input, result)
})
}
fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
2020-03-14 06:39:22 +08:00
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 =
from_utf8(digits)
.map_err(|e| e.into())
.and_then(|digits| f64::from_str_radix(digits, 10)
.map_err(|e| e.into())
)
.map(|result: f64| if negative { -result } else { result });
2020-03-14 06:39:22 +08:00
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 <on | off>` - 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)
}
2020-03-20 01:34:57 +08:00
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, u32), Error>> {
let result_with_pin = |pin: PwmPin|
move |result: Result<u32, Error>|
result.map(|duty| (pin, duty));
2020-03-14 06:39:22 +08:00
alt((
map(
preceded(
tag("max_i_pos"),
preceded(
whitespace,
2020-03-20 01:34:57 +08:00
unsigned
2020-03-14 06:39:22 +08:00
)
),
2020-03-20 01:34:57 +08:00
result_with_pin(PwmPin::MaxIPos)
2020-03-14 06:39:22 +08:00
),
map(
preceded(
tag("max_i_neg"),
preceded(
whitespace,
2020-03-20 01:34:57 +08:00
unsigned
2020-03-14 06:39:22 +08:00
)
),
2020-03-20 01:34:57 +08:00
result_with_pin(PwmPin::MaxINeg)
2020-03-14 06:39:22 +08:00
),
map(
preceded(
tag("max_v"),
preceded(
whitespace,
2020-03-20 01:34:57 +08:00
unsigned
2020-03-14 06:39:22 +08:00
)
),
2020-03-20 01:34:57 +08:00
result_with_pin(PwmPin::MaxV)
2020-03-14 06:39:22 +08:00
),
2020-03-20 01:34:57 +08:00
map(unsigned, result_with_pin(PwmPin::ISet)
))
)(input)
2020-03-14 06:39:22 +08:00
}
/// `pwm <0-1> pid` - Set PWM to be controlled by PID
2020-03-20 01:34:57 +08:00
fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> {
value((), tag("pid"))(input)
2020-03-14 06:39:22 +08:00
}
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pwm")(input)?;
alt((
2020-03-20 01:34:57 +08:00
|input| {
let (input, _) = whitespace(input)?;
let (input, channel) = channel(input)?;
let (input, _) = whitespace(input)?;
let (input, result) = alt((
|input| {
let (input, ()) = pwm_pid(input)?;
Ok((input, Ok(Command::PwmPid { channel })))
},
|input| {
let (input, config) = pwm_setup(input)?;
match config {
Ok((pin, duty)) =>
Ok((input, Ok(Command::Pwm { channel, pin, duty }))),
Err(e) =>
Ok((input, Err(e))),
}
},
))(input)?;
end(input)?;
Ok((input, result))
},
2020-03-14 06:39:22 +08:00
value(Ok(Command::Show(ShowCommand::Pwm)), end)
))(input)
}
/// `pid <0-1> <parameter> <value>`
fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
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 <pid_parameter>`
fn pid(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pid")(input)?;
alt((
preceded(
whitespace,
pid_parameter
),
value(Ok(Command::Show(ShowCommand::Pid)), end)
))(input)
}
/// `s-h <0-1> <parameter> <value>`
fn steinhart_hart_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, channel) = channel(input)?;
let (input, _) = whitespace(input)?;
let (input, parameter) =
2020-03-20 01:34:57 +08:00
alt((value(ShParameter::T0, tag("t0")),
value(ShParameter::R, tag("r")),
value(ShParameter::R0, tag("r0"))
2020-03-14 06:39:22 +08:00
))(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 <steinhart_hart_parameter>`
fn steinhart_hart(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
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<Command, Error>> {
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: rate as f32,
2020-03-14 06:39:22 +08:00
});
Ok((input, result))
}
),
value(Ok(Command::Show(ShowCommand::PostFilter)), end)
))(input)
}
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
alt((value(Ok(Command::Quit), tag("quit")),
map(report, Ok),
pwm,
pid,
steinhart_hart,
postfilter,
))(input)
}
impl Command {
pub fn parse(input: &[u8]) -> Result<Self, Error> {
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() {
2020-03-20 01:34:57 +08:00
let command = Command::parse(b"pwm 1 16383");
2020-03-14 06:39:22 +08:00
assert_eq!(command, Ok(Command::Pwm {
channel: 1,
2020-03-20 01:34:57 +08:00
pin: PwmPin::ISet,
duty: 16383,
2020-03-14 06:39:22 +08:00
}));
}
#[test]
fn parse_pwm_pid() {
let command = Command::parse(b"pwm 0 pid");
2020-03-20 01:34:57 +08:00
assert_eq!(command, Ok(Command::PwmPid {
2020-03-14 06:39:22 +08:00
channel: 0,
2020-03-20 01:34:57 +08:00
pin: PwmPin::ISet,
2020-03-14 06:39:22 +08:00
}));
}
#[test]
fn parse_pwm_max_i_pos() {
2020-03-20 01:34:57 +08:00
let command = Command::parse(b"pwm 0 max_i_pos 7");
2020-03-14 06:39:22 +08:00
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
2020-03-20 01:34:57 +08:00
pin: PwmPin::MaxIPos,
duty: 7,
2020-03-14 06:39:22 +08:00
}));
}
#[test]
fn parse_pwm_max_i_neg() {
2020-03-20 01:34:57 +08:00
let command = Command::parse(b"pwm 0 max_i_neg 128");
2020-03-14 06:39:22 +08:00
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
2020-03-20 01:34:57 +08:00
pin: PwmPin::MaxINeg,
duty: 128,
2020-03-14 06:39:22 +08:00
}));
}
#[test]
fn parse_pwm_max_v() {
2020-03-20 01:34:57 +08:00
let command = Command::parse(b"pwm 0 max_v 32768");
2020-03-14 06:39:22 +08:00
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
2020-03-20 01:34:57 +08:00
pin: PwmPin::MaxV,
duty: 32768,
2020-03-14 06:39:22 +08:00
}));
}
#[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() {
2020-03-20 01:34:57 +08:00
let command = Command::parse(b"s-h 1 t0 23.05");
2020-03-14 06:39:22 +08:00
assert_eq!(command, Ok(Command::SteinhartHart {
channel: 1,
2020-03-20 01:34:57 +08:00
parameter: ShParameter::T0,
2020-03-14 06:39:22 +08:00
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,
}));
}
}