forked from M-Labs/thermostat
merge the network code
This commit is contained in:
parent
09dbb7d495
commit
69a4f5a5d2
|
@ -148,6 +148,16 @@ version = "0.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "857afb5ee9e767c3a73b2ad7212b6deea0c3761a27db1e20ea0ed57ee352cfef"
|
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]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -163,12 +173,28 @@ version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
|
checksum = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
|
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]]
|
[[package]]
|
||||||
name = "panic-abort"
|
name = "panic-abort"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -312,7 +338,9 @@ dependencies = [
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
"hash2hwaddr",
|
"hash2hwaddr",
|
||||||
|
"lexical-core",
|
||||||
"log",
|
"log",
|
||||||
|
"nom",
|
||||||
"panic-abort",
|
"panic-abort",
|
||||||
"panic-semihosting",
|
"panic-semihosting",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
|
@ -338,6 +366,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "void"
|
name = "void"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
|
@ -29,6 +29,8 @@ smoltcp = { version = "0.6.0", default-features = false, features = ["proto-ipv4
|
||||||
hash2hwaddr = { version = "0.0", optional = true }
|
hash2hwaddr = { version = "0.0", optional = true }
|
||||||
bit_field = "0.10"
|
bit_field = "0.10"
|
||||||
byteorder = { version = "1", default-features = false }
|
byteorder = { version = "1", default-features = false }
|
||||||
|
nom = { version = "5", default-features = false }
|
||||||
|
lexical-core = { version = "0.7", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
||||||
|
|
|
@ -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<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<lexical::Error> 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<u16, Error>> {
|
||||||
|
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<f32, Error>> {
|
||||||
|
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 <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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `pwm ... <width> <total>` - Set pwm duty cycle
|
||||||
|
fn pwm_config(input: &[u8]) -> IResult<&[u8], Result<PwmConfig, Error>> {
|
||||||
|
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<PwmSetup, Error>> {
|
||||||
|
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<PwmSetup, Error>> {
|
||||||
|
value(Ok(PwmSetup::ISet(PwmMode::Pid)), tag("pid"))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
|
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> <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) =
|
||||||
|
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 <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,
|
||||||
|
});
|
||||||
|
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() {
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
261
src/main.rs
261
src/main.rs
|
@ -33,13 +33,28 @@ mod ad5680;
|
||||||
mod net;
|
mod net;
|
||||||
mod server;
|
mod server;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
|
mod session;
|
||||||
|
use session::{CHANNELS, Session, SessionOutput};
|
||||||
|
mod command_parser;
|
||||||
|
use command_parser::{Command, ShowCommand, PwmSetup, PwmMode};
|
||||||
mod timer;
|
mod timer;
|
||||||
|
|
||||||
/// Interval at which to sample the ADC input and broadcast to all
|
|
||||||
/// clients.
|
#[derive(Clone, Copy, Debug)]
|
||||||
///
|
struct ChannelState {
|
||||||
/// This should be a multiple of the `TIMER_RATE`.
|
adc_data: Option<i32>,
|
||||||
const OUTPUT_INTERVAL: u32 = 1000;
|
adc_time: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChannelState {
|
||||||
|
fn default() -> Self {
|
||||||
|
ChannelState {
|
||||||
|
adc_data: None,
|
||||||
|
adc_time: Instant::from_secs(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(feature = "semihosting"))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
const WATCHDOG_INTERVAL: u32 = 100;
|
const WATCHDOG_INTERVAL: u32 = 100;
|
||||||
|
@ -49,15 +64,16 @@ const WATCHDOG_INTERVAL: u32 = 10_000;
|
||||||
#[cfg(not(feature = "generate-hwaddr"))]
|
#[cfg(not(feature = "generate-hwaddr"))]
|
||||||
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
|
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
|
||||||
|
|
||||||
|
const TCP_PORT: u16 = 23;
|
||||||
|
|
||||||
|
|
||||||
const HSE: MegaHertz = MegaHertz(8);
|
const HSE: MegaHertz = MegaHertz(8);
|
||||||
|
|
||||||
|
|
||||||
/// Initialization and main loop
|
/// Initialization and main loop
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
init_log();
|
init_log();
|
||||||
info!("Thermostat firmware");
|
info!("tecpak");
|
||||||
|
|
||||||
let mut cp = CorePeripherals::take().unwrap();
|
let mut cp = CorePeripherals::take().unwrap();
|
||||||
cp.SCB.enable_icache();
|
cp.SCB.enable_icache();
|
||||||
|
@ -102,10 +118,10 @@ fn main() -> ! {
|
||||||
};
|
};
|
||||||
info!("Net hwaddr: {}", hwaddr);
|
info!("Net hwaddr: {}", hwaddr);
|
||||||
|
|
||||||
info!("Net startup");
|
let mut channel_states = [ChannelState::default(); CHANNELS];
|
||||||
|
|
||||||
net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
|
net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
|
||||||
Server::run(iface, |server| {
|
Server::<Session>::run(iface, |server| {
|
||||||
let mut last_output = 0_u32;
|
|
||||||
loop {
|
loop {
|
||||||
let now = timer::now().0;
|
let now = timer::now().0;
|
||||||
let instant = Instant::from_millis(i64::from(now));
|
let instant = Instant::from_millis(i64::from(now));
|
||||||
|
@ -115,17 +131,224 @@ fn main() -> ! {
|
||||||
warn!("poll: {:?}", e);
|
warn!("poll: {:?}", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
let now = timer::now().0;
|
// ADC input
|
||||||
if now - last_output >= OUTPUT_INTERVAL {
|
adc.data_ready().unwrap().map(|channel| {
|
||||||
// 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() {
|
|
||||||
let data = adc.read_data().unwrap();
|
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
|
// Update watchdog
|
||||||
wd.feed();
|
wd.feed();
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub fn run<F>(
|
||||||
eth_dev.enable_interrupt();
|
eth_dev.enable_interrupt();
|
||||||
|
|
||||||
// IP stack
|
// 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 ip_addrs = [IpCidr::new(local_addr, 24)];
|
||||||
let mut neighbor_storage = [None; 16];
|
let mut neighbor_storage = [None; 16];
|
||||||
let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]);
|
let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]);
|
||||||
|
|
113
src/server.rs
113
src/server.rs
|
@ -2,12 +2,16 @@ use core::fmt;
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
iface::EthernetInterface,
|
iface::EthernetInterface,
|
||||||
socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer},
|
socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const TCP_PORT: u16 = 23;
|
pub struct SocketState<S> {
|
||||||
|
handle: SocketHandle,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of server sockets and therefore concurrent client
|
/// Number of server sockets and therefore concurrent client
|
||||||
/// sessions. Many data structures in `Server::run()` correspond to
|
/// sessions. Many data structures in `Server::run()` correspond to
|
||||||
/// this const.
|
/// this const.
|
||||||
|
@ -16,6 +20,24 @@ const SOCKET_COUNT: usize = 8;
|
||||||
const TCP_RX_BUFFER_SIZE: usize = 2048;
|
const TCP_RX_BUFFER_SIZE: usize = 2048;
|
||||||
const TCP_TX_BUFFER_SIZE: usize = 2048;
|
const TCP_TX_BUFFER_SIZE: usize = 2048;
|
||||||
|
|
||||||
|
/// Contains a number of server sockets that get all sent the same
|
||||||
|
/// data (through `fmt::Write`).
|
||||||
|
pub struct Server<'a, 'b, S> {
|
||||||
|
net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>,
|
||||||
|
sockets: SocketSet<'b, 'b, 'b>,
|
||||||
|
states: [SocketState<S>; SOCKET_COUNT],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, S: Default> Server<'a, 'b, S> {
|
||||||
|
/// Run a server with stack-allocated sockets
|
||||||
|
pub fn run<F>(net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Server<'a, '_, S>),
|
||||||
|
{
|
||||||
|
let mut sockets_storage: [_; SOCKET_COUNT] = Default::default();
|
||||||
|
let mut sockets = SocketSet::new(&mut sockets_storage[..]);
|
||||||
|
let mut states: [SocketState<S>; SOCKET_COUNT] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
||||||
macro_rules! create_socket {
|
macro_rules! create_socket {
|
||||||
($set:ident, $rx_storage:ident, $tx_storage:ident, $target:expr) => {
|
($set:ident, $rx_storage:ident, $tx_storage:ident, $target:expr) => {
|
||||||
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
|
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
|
||||||
|
@ -26,35 +48,21 @@ macro_rules! create_socket {
|
||||||
$target = $set.add(tcp_socket);
|
$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);
|
||||||
|
|
||||||
/// Contains a number of server sockets that get all sent the same
|
for state in &mut states {
|
||||||
/// data (through `fmt::Write`).
|
state.state = S::default();
|
||||||
pub struct Server<'a, 'b> {
|
|
||||||
net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>,
|
|
||||||
sockets: SocketSet<'b, 'b, 'b>,
|
|
||||||
handles: [SocketHandle; SOCKET_COUNT],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Server<'a, 'b> {
|
|
||||||
/// Run a server with stack-allocated sockets
|
|
||||||
pub fn run<F>(net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Server<'a, '_>),
|
|
||||||
{
|
|
||||||
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 server = Server {
|
let mut server = Server {
|
||||||
handles,
|
states,
|
||||||
sockets,
|
sockets,
|
||||||
net,
|
net,
|
||||||
};
|
};
|
||||||
|
@ -63,46 +71,21 @@ impl<'a, 'b> Server<'a, 'b> {
|
||||||
|
|
||||||
/// Poll the interface and the sockets
|
/// Poll the interface and the sockets
|
||||||
pub fn poll(&mut self, now: Instant) -> Result<(), smoltcp::Error> {
|
pub fn poll(&mut self, now: Instant) -> Result<(), smoltcp::Error> {
|
||||||
// Poll smoltcp EthernetInterface
|
// Poll smoltcp EthernetInterface,
|
||||||
let mut poll_error = None;
|
// pass only unexpected smoltcp errors to the caller
|
||||||
let activity = self.net.poll(&mut self.sockets, now)
|
match self.net.poll(&mut self.sockets, now) {
|
||||||
.unwrap_or_else(|e| {
|
Ok(_) => Ok(()),
|
||||||
poll_error = Some(e);
|
Err(smoltcp::Error::Malformed) => Ok(()),
|
||||||
true
|
Err(smoltcp::Error::Unrecognized) => Ok(()),
|
||||||
});
|
Err(e) => Err(e),
|
||||||
|
|
||||||
if activity {
|
|
||||||
// Listen on all sockets
|
|
||||||
for handle in &self.handles {
|
|
||||||
let mut socket = self.sockets.get::<TcpSocket>(*handle);
|
|
||||||
if ! socket.is_open() {
|
|
||||||
let _ = socket.listen(TCP_PORT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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::<TcpSocket>(*handle);
|
|
||||||
if socket.can_send() {
|
|
||||||
// Ignore errors, proceed with next client
|
|
||||||
let _ = socket.write_str(slice);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
/// Iterate over all sockets managed by this server
|
||||||
|
pub fn for_each<F: FnMut(SocketRef<TcpSocket>, &mut S)>(&mut self, mut callback: F) {
|
||||||
|
for state in &mut self.states {
|
||||||
|
let socket = self.sockets.get::<TcpSocket>(state.handle);
|
||||||
|
callback(socket, &mut state.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<LineResult> {
|
||||||
|
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<Result<Command, ParserError>> for SessionOutput {
|
||||||
|
fn from(input: Result<Command, ParserError>) -> 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<usize> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue