forked from M-Labs/thermostat
merge the network code
This commit is contained in:
parent
09dbb7d495
commit
69a4f5a5d2
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"]
|
||||
|
533
src/command_parser.rs
Normal file
533
src/command_parser.rs
Normal file
@ -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 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<i32>,
|
||||
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::<Session>::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();
|
||||
|
@ -43,7 +43,7 @@ pub fn run<F>(
|
||||
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[..]);
|
||||
|
117
src/server.rs
117
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<S> {
|
||||
handle: SocketHandle,
|
||||
state: S,
|
||||
}
|
||||
|
||||
/// Number of server sockets and therefore concurrent client
|
||||
/// sessions. Many data structures in `Server::run()` correspond to
|
||||
/// this const.
|
||||
@ -16,7 +20,25 @@ const SOCKET_COUNT: usize = 8;
|
||||
const TCP_RX_BUFFER_SIZE: usize = 2048;
|
||||
const TCP_TX_BUFFER_SIZE: usize = 2048;
|
||||
|
||||
macro_rules! create_socket {
|
||||
/// 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 {
|
||||
($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];
|
||||
@ -25,36 +47,22 @@ macro_rules! create_socket {
|
||||
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);
|
||||
|
||||
/// Contains a number of server sockets that get all sent the same
|
||||
/// data (through `fmt::Write`).
|
||||
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]);
|
||||
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::<TcpSocket>(*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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
|
151
src/session.rs
Normal file
151
src/session.rs
Normal file
@ -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
Block a user