#[cfg(feature = "rtu")]
pub(crate) mod rtu;
#[cfg(feature = "tcp")]
pub(crate) mod tcp;
use std::{
borrow::Cow,
error,
fmt::{self, Display},
};
use crate::bytes::Bytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FunctionCode {
ReadCoils,
ReadDiscreteInputs,
WriteSingleCoil,
WriteSingleRegister,
ReadHoldingRegisters,
ReadInputRegisters,
WriteMultipleCoils,
WriteMultipleRegisters,
MaskWriteRegister,
ReadWriteMultipleRegisters,
Custom(u8),
}
impl FunctionCode {
#[must_use]
pub const fn new(value: u8) -> Self {
match value {
0x01 => FunctionCode::ReadCoils,
0x02 => FunctionCode::ReadDiscreteInputs,
0x05 => FunctionCode::WriteSingleCoil,
0x06 => FunctionCode::WriteSingleRegister,
0x03 => FunctionCode::ReadHoldingRegisters,
0x04 => FunctionCode::ReadInputRegisters,
0x0F => FunctionCode::WriteMultipleCoils,
0x10 => FunctionCode::WriteMultipleRegisters,
0x16 => FunctionCode::MaskWriteRegister,
0x17 => FunctionCode::ReadWriteMultipleRegisters,
code => FunctionCode::Custom(code),
}
}
#[must_use]
pub const fn value(self) -> u8 {
match self {
FunctionCode::ReadCoils => 0x01,
FunctionCode::ReadDiscreteInputs => 0x02,
FunctionCode::WriteSingleCoil => 0x05,
FunctionCode::WriteSingleRegister => 0x06,
FunctionCode::ReadHoldingRegisters => 0x03,
FunctionCode::ReadInputRegisters => 0x04,
FunctionCode::WriteMultipleCoils => 0x0F,
FunctionCode::WriteMultipleRegisters => 0x10,
FunctionCode::MaskWriteRegister => 0x16,
FunctionCode::ReadWriteMultipleRegisters => 0x17,
FunctionCode::Custom(code) => code,
}
}
}
impl Display for FunctionCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value().fmt(f)
}
}
pub type Address = u16;
pub(crate) type Coil = bool;
pub(crate) type Word = u16;
pub type Quantity = u16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Request<'a> {
ReadCoils(Address, Quantity),
ReadDiscreteInputs(Address, Quantity),
WriteSingleCoil(Address, Coil),
WriteMultipleCoils(Address, Cow<'a, [Coil]>),
ReadInputRegisters(Address, Quantity),
ReadHoldingRegisters(Address, Quantity),
WriteSingleRegister(Address, Word),
WriteMultipleRegisters(Address, Cow<'a, [Word]>),
MaskWriteRegister(Address, Word, Word),
ReadWriteMultipleRegisters(Address, Quantity, Address, Cow<'a, [Word]>),
Custom(u8, Cow<'a, [u8]>),
Disconnect,
}
impl<'a> Request<'a> {
#[must_use]
pub fn into_owned(self) -> Request<'static> {
use Request::*;
match self {
ReadCoils(addr, qty) => ReadCoils(addr, qty),
ReadDiscreteInputs(addr, qty) => ReadDiscreteInputs(addr, qty),
WriteSingleCoil(addr, coil) => WriteSingleCoil(addr, coil),
WriteMultipleCoils(addr, coils) => {
WriteMultipleCoils(addr, Cow::Owned(coils.into_owned()))
}
ReadInputRegisters(addr, qty) => ReadInputRegisters(addr, qty),
ReadHoldingRegisters(addr, qty) => ReadHoldingRegisters(addr, qty),
WriteSingleRegister(addr, word) => WriteSingleRegister(addr, word),
WriteMultipleRegisters(addr, words) => {
WriteMultipleRegisters(addr, Cow::Owned(words.into_owned()))
}
MaskWriteRegister(addr, and_mask, or_mask) => {
MaskWriteRegister(addr, and_mask, or_mask)
}
ReadWriteMultipleRegisters(addr, qty, write_addr, words) => {
ReadWriteMultipleRegisters(addr, qty, write_addr, Cow::Owned(words.into_owned()))
}
Custom(func, bytes) => Custom(func, Cow::Owned(bytes.into_owned())),
Disconnect => Disconnect,
}
}
#[must_use]
pub const fn function_code(&self) -> FunctionCode {
use Request::*;
match self {
ReadCoils(_, _) => FunctionCode::ReadCoils,
ReadDiscreteInputs(_, _) => FunctionCode::ReadDiscreteInputs,
WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
ReadInputRegisters(_, _) => FunctionCode::ReadInputRegisters,
ReadHoldingRegisters(_, _) => FunctionCode::ReadHoldingRegisters,
WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters,
Custom(code, _) => FunctionCode::Custom(*code),
Disconnect => unreachable!(),
}
}
}
#[cfg(feature = "server")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlaveRequest<'a> {
pub slave: crate::slave::SlaveId,
pub request: Request<'a>,
}
#[cfg(feature = "server")]
impl<'a> SlaveRequest<'a> {
#[must_use]
pub fn into_owned(self) -> SlaveRequest<'static> {
let Self { slave, request } = self;
SlaveRequest {
slave,
request: request.into_owned(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Response {
ReadCoils(Vec<Coil>),
ReadDiscreteInputs(Vec<Coil>),
WriteSingleCoil(Address, Coil),
WriteMultipleCoils(Address, Quantity),
ReadInputRegisters(Vec<Word>),
ReadHoldingRegisters(Vec<Word>),
WriteSingleRegister(Address, Word),
WriteMultipleRegisters(Address, Quantity),
MaskWriteRegister(Address, Word, Word),
ReadWriteMultipleRegisters(Vec<Word>),
Custom(u8, Bytes),
}
impl Response {
#[must_use]
pub const fn function_code(&self) -> FunctionCode {
use Response::*;
match self {
ReadCoils(_) => FunctionCode::ReadCoils,
ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs,
WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
ReadInputRegisters(_) => FunctionCode::ReadInputRegisters,
ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters,
WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
Custom(code, _) => FunctionCode::Custom(*code),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Exception {
IllegalFunction = 0x01,
IllegalDataAddress = 0x02,
IllegalDataValue = 0x03,
ServerDeviceFailure = 0x04,
Acknowledge = 0x05,
ServerDeviceBusy = 0x06,
MemoryParityError = 0x08,
GatewayPathUnavailable = 0x0A,
GatewayTargetDevice = 0x0B,
}
impl From<Exception> for u8 {
fn from(from: Exception) -> Self {
from as u8
}
}
impl Exception {
pub(crate) fn description(&self) -> &str {
use crate::frame::Exception::*;
match *self {
IllegalFunction => "Illegal function",
IllegalDataAddress => "Illegal data address",
IllegalDataValue => "Illegal data value",
ServerDeviceFailure => "Server device failure",
Acknowledge => "Acknowledge",
ServerDeviceBusy => "Server device busy",
MemoryParityError => "Memory parity error",
GatewayPathUnavailable => "Gateway path unavailable",
GatewayTargetDevice => "Gateway target device failed to respond",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExceptionResponse {
pub function: FunctionCode,
pub exception: Exception,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RequestPdu<'a>(pub(crate) Request<'a>);
impl<'a> From<Request<'a>> for RequestPdu<'a> {
fn from(from: Request<'a>) -> Self {
RequestPdu(from)
}
}
impl<'a> From<RequestPdu<'a>> for Request<'a> {
fn from(from: RequestPdu<'a>) -> Self {
from.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ResponsePdu(pub(crate) Result<Response, ExceptionResponse>);
impl From<Response> for ResponsePdu {
fn from(from: Response) -> Self {
ResponsePdu(Ok(from))
}
}
impl From<ExceptionResponse> for ResponsePdu {
fn from(from: ExceptionResponse) -> Self {
ResponsePdu(Err(from))
}
}
impl From<Result<Response, ExceptionResponse>> for ResponsePdu {
fn from(from: Result<Response, ExceptionResponse>) -> Self {
ResponsePdu(from.map(Into::into).map_err(Into::into))
}
}
#[cfg(any(
feature = "rtu-over-tcp-server",
feature = "rtu-server",
feature = "tcp-server"
))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OptionalResponsePdu(pub(crate) Option<ResponsePdu>);
#[cfg(any(
feature = "rtu-over-tcp-server",
feature = "rtu-server",
feature = "tcp-server"
))]
impl<T> From<Option<T>> for OptionalResponsePdu
where
T: Into<ResponsePdu>,
{
fn from(from: Option<T>) -> Self {
Self(from.map(Into::into))
}
}
#[cfg(any(feature = "rtu-server", feature = "tcp-server"))]
impl<T> From<T> for OptionalResponsePdu
where
T: Into<ResponsePdu>,
{
fn from(from: T) -> Self {
Self(Some(from.into()))
}
}
impl From<ResponsePdu> for Result<Response, ExceptionResponse> {
fn from(from: ResponsePdu) -> Self {
from.0
}
}
impl fmt::Display for Exception {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl error::Error for Exception {
fn description(&self) -> &str {
self.description()
}
}
impl fmt::Display for ExceptionResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Modbus function {}: {}", self.function, self.exception)
}
}
impl error::Error for ExceptionResponse {
fn description(&self) -> &str {
self.exception.description()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_function_code() {
assert_eq!(FunctionCode::ReadCoils, FunctionCode::new(0x01));
assert_eq!(FunctionCode::ReadDiscreteInputs, FunctionCode::new(0x02));
assert_eq!(FunctionCode::WriteSingleCoil, FunctionCode::new(0x05));
assert_eq!(FunctionCode::WriteSingleRegister, FunctionCode::new(0x06));
assert_eq!(FunctionCode::ReadHoldingRegisters, FunctionCode::new(0x03));
assert_eq!(FunctionCode::ReadInputRegisters, FunctionCode::new(0x04));
assert_eq!(FunctionCode::WriteMultipleCoils, FunctionCode::new(0x0F));
assert_eq!(
FunctionCode::WriteMultipleRegisters,
FunctionCode::new(0x10)
);
assert_eq!(FunctionCode::MaskWriteRegister, FunctionCode::new(0x016));
assert_eq!(
FunctionCode::ReadWriteMultipleRegisters,
FunctionCode::new(0x017)
);
assert_eq!(FunctionCode::Custom(70), FunctionCode::new(70));
}
#[test]
fn function_code_values() {
assert_eq!(FunctionCode::ReadCoils.value(), 0x01);
assert_eq!(FunctionCode::ReadDiscreteInputs.value(), 0x02);
assert_eq!(FunctionCode::WriteSingleCoil.value(), 0x05);
assert_eq!(FunctionCode::WriteSingleRegister.value(), 0x06);
assert_eq!(FunctionCode::ReadHoldingRegisters.value(), 0x03);
assert_eq!(FunctionCode::ReadInputRegisters.value(), 0x04);
assert_eq!(FunctionCode::WriteMultipleCoils.value(), 0x0F);
assert_eq!(FunctionCode::WriteMultipleRegisters.value(), 0x10);
assert_eq!(FunctionCode::MaskWriteRegister.value(), 0x016);
assert_eq!(FunctionCode::ReadWriteMultipleRegisters.value(), 0x017);
assert_eq!(FunctionCode::Custom(70).value(), 70);
}
#[test]
fn function_code_from_request() {
use Request::*;
assert_eq!(ReadCoils(0, 0).function_code(), FunctionCode::ReadCoils);
assert_eq!(
ReadDiscreteInputs(0, 0).function_code(),
FunctionCode::ReadDiscreteInputs
);
assert_eq!(
WriteSingleCoil(0, true).function_code(),
FunctionCode::WriteSingleCoil
);
assert_eq!(
WriteMultipleCoils(0, Cow::Borrowed(&[])).function_code(),
FunctionCode::WriteMultipleCoils
);
assert_eq!(
ReadInputRegisters(0, 0).function_code(),
FunctionCode::ReadInputRegisters
);
assert_eq!(
ReadHoldingRegisters(0, 0).function_code(),
FunctionCode::ReadHoldingRegisters
);
assert_eq!(
WriteSingleRegister(0, 0).function_code(),
FunctionCode::WriteSingleRegister
);
assert_eq!(
WriteMultipleRegisters(0, Cow::Borrowed(&[])).function_code(),
FunctionCode::WriteMultipleRegisters
);
assert_eq!(
MaskWriteRegister(0, 0, 0).function_code(),
FunctionCode::MaskWriteRegister
);
assert_eq!(
ReadWriteMultipleRegisters(0, 0, 0, Cow::Borrowed(&[])).function_code(),
FunctionCode::ReadWriteMultipleRegisters
);
assert_eq!(Custom(88, Cow::Borrowed(&[])).function_code().value(), 88);
}
#[test]
fn function_code_from_response() {
use Response::*;
assert_eq!(ReadCoils(vec![]).function_code(), FunctionCode::ReadCoils);
assert_eq!(
ReadDiscreteInputs(vec![]).function_code(),
FunctionCode::ReadDiscreteInputs
);
assert_eq!(
WriteSingleCoil(0x0, false).function_code(),
FunctionCode::WriteSingleCoil
);
assert_eq!(
WriteMultipleCoils(0x0, 0x0).function_code(),
FunctionCode::WriteMultipleCoils
);
assert_eq!(
ReadInputRegisters(vec![]).function_code(),
FunctionCode::ReadInputRegisters
);
assert_eq!(
ReadHoldingRegisters(vec![]).function_code(),
FunctionCode::ReadHoldingRegisters
);
assert_eq!(
WriteSingleRegister(0, 0).function_code(),
FunctionCode::WriteSingleRegister
);
assert_eq!(
WriteMultipleRegisters(0, 0).function_code(),
FunctionCode::WriteMultipleRegisters
);
assert_eq!(
MaskWriteRegister(0, 0, 0).function_code(),
FunctionCode::MaskWriteRegister
);
assert_eq!(
ReadWriteMultipleRegisters(vec![]).function_code(),
FunctionCode::ReadWriteMultipleRegisters
);
assert_eq!(
Custom(99, Bytes::from_static(&[])).function_code().value(),
99
);
}
}