use snafu::Snafu;
use core::fmt::{self, Debug, Formatter};
use crate::ascii::*;
use crate::bcc;
use crate::buffer::Buffer;
use crate::nom_parser::master::{parse_read_response, parse_write_response, ResponseToken};
use crate::types::{Address, Parameter, Value};
pub struct Master {
read_again: Option<(Address, Parameter)>,
}
impl Debug for Master {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Master {{ read_again: {:?}, nodes: [..]}}",
self.read_again
)
}
}
impl Default for Master {
fn default() -> Self {
Self::new()
}
}
impl Master {
pub const fn new() -> Self {
Self { read_again: None }
}
pub fn write_parameter(
&mut self,
address: Address,
parameter: Parameter,
value: Value,
) -> impl SendData<Response = ()> + '_ {
self.read_again = None;
let mut data = Buffer::new();
data.push(EOT);
data.write(&address.to_bytes());
data.push(STX);
data.write(¶meter.to_bytes());
data.write(&value.to_bytes());
data.push(ETX);
data.push(bcc(&data.as_ref()[6..]));
WriteCmd { data }
}
pub fn read_parameter(
&mut self,
address: Address,
parameter: Parameter,
) -> impl SendData<Response = Value> + '_ {
let mut buffer = Buffer::new();
self.read_again.take(); buffer.push(EOT);
buffer.write(&address.to_bytes());
buffer.write(¶meter.to_bytes());
buffer.push(ENQ);
ReadCmd {
master: self,
buffer,
parameter,
read_again: None,
}
}
pub fn read_parameter_again(
&mut self,
address: Address,
parameter: Parameter,
) -> impl SendData<Response = Value> + '_ {
let mut buffer = Buffer::new();
if let Some(again) = self.try_read_again(address, parameter) {
buffer.push(again);
} else {
buffer.push(EOT);
buffer.write(&address.to_bytes());
buffer.write(¶meter.to_bytes());
buffer.push(ENQ);
}
ReadCmd {
master: self,
buffer,
parameter,
read_again: Some(address),
}
}
fn try_read_again(&mut self, address: Address, parameter: Parameter) -> Option<u8> {
let (old_addr, old_param) = self.read_again.take()?;
if old_addr == address {
match *parameter - *old_param {
0 => Some(NAK),
1 => Some(ACK),
-1 => Some(BS),
_ => None,
}
} else {
None
}
}
}
pub trait SendData {
type Response;
fn get_data(&self) -> &[u8];
fn data_sent(&mut self) -> &mut dyn ReceiveData<Response = Self::Response>;
}
pub trait ReceiveData {
type Response;
fn receive_data(&mut self, data: &[u8]) -> Option<Result<Self::Response, Error>>;
}
const WRITE_BUF_LEN: usize = 1 + 4 + 1 + 4 + 6 + 1 + 1; struct WriteCmd {
data: Buffer<WRITE_BUF_LEN>,
}
impl SendData for WriteCmd {
type Response = ();
fn get_data(&self) -> &[u8] {
self.data.as_ref()
}
fn data_sent(&mut self) -> &mut dyn ReceiveData<Response = Self::Response> {
self.data.clear();
self
}
}
impl ReceiveData for WriteCmd {
type Response = ();
fn receive_data(&mut self, data: &[u8]) -> Option<Result<Self::Response, Error>> {
Some(match parse_write_response(data) {
ResponseToken::WriteOk => Ok(()),
ResponseToken::WriteFailed | ResponseToken::InvalidParameter => {
CommandFailedSnafu.fail()
}
_ => ProtocolSnafu.fail(),
})
}
}
const READ_CMD_BUF_LEN: usize = 1 + 4 + 6 + 1 + 1; struct ReadCmd<'a> {
master: &'a mut Master,
buffer: Buffer<READ_CMD_BUF_LEN>,
parameter: Parameter,
read_again: Option<Address>,
}
impl SendData for ReadCmd<'_> {
type Response = Value;
fn get_data(&self) -> &[u8] {
self.buffer.as_ref()
}
fn data_sent(&mut self) -> &mut dyn ReceiveData<Response = Self::Response> {
self.buffer.clear();
self
}
}
impl ReceiveData for ReadCmd<'_> {
type Response = Value;
fn receive_data(&mut self, data: &[u8]) -> Option<Result<Self::Response, Error>> {
self.buffer.write(data);
Some(match parse_read_response(self.buffer.as_ref()) {
ResponseToken::NeedData => return None,
ResponseToken::ReadOk { parameter, value } if (parameter == self.parameter) => {
self.master.read_again = self.read_again.map(|addr| (addr, self.parameter));
Ok(value)
}
ResponseToken::InvalidParameter => InvalidParameterSnafu.fail(),
_ => ProtocolSnafu.fail(),
})
}
}
#[derive(Debug, Clone, Snafu)]
pub enum Error {
#[snafu(display("Invalid parameter, EOT received."))]
InvalidParameter,
#[snafu(display("Command failed, NAK received."))]
CommandFailed,
#[snafu(display("Invalid response from node."))]
ProtocolError,
}
#[cfg(any(feature = "std", test))]
pub mod io {
use snafu::{ResultExt, Snafu};
use crate::master::{Error as X328Error, ReceiveData, SendData};
use crate::types::{self, IntoAddress, IntoParameter, IntoValue, Value};
use crate::{Address, Parameter};
use std::io::{Read, Write};
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Invalid argument"))]
InvalidArgument {
source: types::Error,
},
#[snafu(display("X3.28 command error"))]
ProtocolError {
source: X328Error,
},
#[snafu(display("X3.28 IO error: {}", source))]
IoError {
source: std::io::Error,
},
}
#[derive(Debug)]
pub struct Master<IO>
where
IO: std::io::Read + std::io::Write,
{
proto: super::Master,
stream: IO,
}
impl<IO> Master<IO>
where
IO: std::io::Read + std::io::Write,
{
pub fn new(io: IO) -> Self {
Self {
proto: super::Master::new(),
stream: io,
}
}
pub fn write_parameter(
&mut self,
address: impl IntoAddress,
parameter: impl IntoParameter,
value: impl IntoValue,
) -> Result<(), Error> {
let (address, parameter) = check_addr_param(address, parameter)?;
let value = value.into_value().context(InvalidArgumentSnafu)?;
let s = self.proto.write_parameter(address, parameter, value);
Self::send_recv(s, &mut self.stream)
}
pub fn read_parameter(
&mut self,
address: impl IntoAddress,
parameter: impl IntoParameter,
) -> Result<Value, Error> {
let (address, parameter) = check_addr_param(address, parameter)?;
let s = self.proto.read_parameter(address, parameter);
Self::send_recv(s, &mut self.stream)
}
pub fn read_parameter_again(
&mut self,
address: impl IntoAddress,
parameter: impl IntoParameter,
) -> Result<Value, Error> {
let (address, parameter) = check_addr_param(address, parameter)?;
let s = self.proto.read_parameter_again(address, parameter);
Self::send_recv(s, &mut self.stream)
}
fn send_recv<R>(
mut send: impl SendData<Response = R>,
mut io: impl Read + Write,
) -> Result<R, Error> {
let r = Self::send_data(&mut send, &mut io)?;
Self::recv_response(r, io)
}
fn send_data<R>(
send: &mut dyn SendData<Response = R>,
mut writer: impl Write,
) -> Result<&mut dyn ReceiveData<Response = R>, Error> {
log::trace!("Sending {:?}", send.get_data());
match writer
.write_all(send.get_data())
.and_then(|_| writer.flush())
{
Ok(_) => Ok(send.data_sent()),
Err(err) => Err(err),
}
.context(IoSnafu {})
}
fn recv_response<R>(
recv: &mut dyn ReceiveData<Response = R>,
mut reader: impl Read,
) -> Result<R, Error> {
let mut data = [0];
loop {
let len = match reader.read(&mut data) {
Ok(0) => Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Read returned Ok(0)",
)),
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
x => x,
}
.context(IoSnafu {})?;
log::trace!("Received {:?}", &data[..len]);
if let Some(r) = recv.receive_data(&data[..len]) {
return r.context(ProtocolSnafu);
}
}
}
}
fn check_addr_param(
addr: impl IntoAddress,
param: impl IntoParameter,
) -> Result<(Address, Parameter), Error> {
Ok((
addr.into_address().context(InvalidArgumentSnafu)?,
param.into_parameter().context(InvalidArgumentSnafu)?,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
fn addr_param_val(addr: usize, param: usize, val: i32) -> (Address, Parameter, Value) {
(
addr.try_into().unwrap(),
param.try_into().unwrap(),
val.try_into().unwrap(),
)
}
#[test]
fn write_parameter() {
let (addr, param, val) = addr_param_val(43, 1234, 56);
let mut master = Master::new();
let x = master.write_parameter(addr, param, val);
assert_eq!(x.get_data(), b"\x044433\x021234+56\x03\x2F");
}
#[test]
fn read_parameter() {
let (addr, param, val) = addr_param_val(43, 1234, 12345);
let mut master = Master::new();
let mut x = master.read_parameter(addr, param);
assert_eq!(x.get_data(), b"\x0444331234\x05");
let recv = x.data_sent();
assert_eq!(
recv.receive_data(b"\x02123412345\x03\x36")
.unwrap()
.unwrap(),
val
);
}
#[test]
fn read_again() {
let (addr, param, _) = addr_param_val(10, 20, 56);
let mut idle = Master::new();
idle.read_again = Some((addr, param));
let send = idle.read_parameter_again(addr, param.next().unwrap());
assert_eq!(send.get_data(), [ACK]);
}
}