diff --git a/src/codec/mod.rs b/src/codec/mod.rs index 9580c4f..2fd94f7 100644 --- a/src/codec/mod.rs +++ b/src/codec/mod.rs @@ -38,7 +38,7 @@ impl TryFrom for Exception { impl From for [u8; 2] { fn from(ex: ExceptionResponse) -> [u8; 2] { let data = &mut [0; 2]; - let fn_code: u8 = ex.function.into(); + let fn_code: u8 = ex.function.value(); debug_assert!(fn_code < 0x80); data[0] = fn_code + 0x80; data[1] = ex.exception as u8; @@ -54,7 +54,7 @@ impl TryFrom<&[u8]> for ExceptionResponse { if fn_err_code < 0x80 { return Err(Error::ExceptionFnCode(fn_err_code)); } - let function = (fn_err_code - 0x80).into(); + let function = FunctionCode::new(fn_err_code - 0x80); let exception = Exception::try_from(bytes[1])?; Ok(ExceptionResponse { function, @@ -67,7 +67,7 @@ impl<'r> TryFrom<&'r [u8]> for Request<'r> { type Error = Error; fn try_from(bytes: &'r [u8]) -> Result { - use FnCode as F; + use FunctionCode as F; if bytes.is_empty() { return Err(Error::BufferSize); @@ -75,11 +75,11 @@ impl<'r> TryFrom<&'r [u8]> for Request<'r> { let fn_code = bytes[0]; - if bytes.len() < min_request_pdu_len(fn_code.into()) { + if bytes.len() < min_request_pdu_len(FunctionCode::new(fn_code)) { return Err(Error::BufferSize); } - let req = match FnCode::from(fn_code) { + let req = match FunctionCode::new(fn_code) { F::ReadCoils | F::ReadDiscreteInputs | F::ReadInputRegisters @@ -88,7 +88,7 @@ impl<'r> TryFrom<&'r [u8]> for Request<'r> { let addr = BigEndian::read_u16(&bytes[1..3]); let quantity = BigEndian::read_u16(&bytes[3..5]); - match FnCode::from(fn_code) { + match FunctionCode::new(fn_code) { F::ReadCoils => Self::ReadCoils(addr, quantity), F::ReadDiscreteInputs => Self::ReadDiscreteInputs(addr, quantity), F::ReadInputRegisters => Self::ReadInputRegisters(addr, quantity), @@ -141,7 +141,9 @@ impl<'r> TryFrom<&'r [u8]> for Request<'r> { Self::ReadWriteMultipleRegisters(read_address, read_quantity, write_address, data) } _ => match fn_code { - fn_code if fn_code < 0x80 => Self::Custom(FnCode::Custom(fn_code), &bytes[1..]), + fn_code if fn_code < 0x80 => { + Self::Custom(FunctionCode::Custom(fn_code), &bytes[1..]) + } _ => return Err(Error::FnCode(fn_code)), }, }; @@ -153,14 +155,14 @@ impl<'r> TryFrom<&'r [u8]> for Response<'r> { type Error = Error; fn try_from(bytes: &'r [u8]) -> Result { - use FnCode as F; + use FunctionCode as F; let fn_code = bytes[0]; - if bytes.len() < min_response_pdu_len(fn_code.into()) { + if bytes.len() < min_response_pdu_len(FunctionCode::new(fn_code)) { return Err(Error::BufferSize); } - let rsp = match FnCode::from(fn_code) { - F::ReadCoils | FnCode::ReadDiscreteInputs => { + let rsp = match FunctionCode::new(fn_code) { + F::ReadCoils | FunctionCode::ReadDiscreteInputs => { let byte_count = bytes[1] as usize; if byte_count + 2 > bytes.len() { return Err(Error::BufferSize); @@ -170,9 +172,9 @@ impl<'r> TryFrom<&'r [u8]> for Response<'r> { // therefore we just assume that the whole byte is meant. let quantity = byte_count * 8; - match FnCode::from(fn_code) { - FnCode::ReadCoils => Self::ReadCoils(Coils { data, quantity }), - FnCode::ReadDiscreteInputs => { + match FunctionCode::new(fn_code) { + FunctionCode::ReadCoils => Self::ReadCoils(Coils { data, quantity }), + FunctionCode::ReadDiscreteInputs => { Self::ReadDiscreteInputs(Coils { data, quantity }) } _ => unreachable!(), @@ -183,7 +185,7 @@ impl<'r> TryFrom<&'r [u8]> for Response<'r> { F::WriteMultipleCoils | F::WriteSingleRegister | F::WriteMultipleRegisters => { let addr = BigEndian::read_u16(&bytes[1..]); let payload = BigEndian::read_u16(&bytes[3..]); - match FnCode::from(fn_code) { + match FunctionCode::new(fn_code) { F::WriteMultipleCoils => Self::WriteMultipleCoils(addr, payload), F::WriteSingleRegister => Self::WriteSingleRegister(addr, payload), F::WriteMultipleRegisters => Self::WriteMultipleRegisters(addr, payload), @@ -199,14 +201,14 @@ impl<'r> TryFrom<&'r [u8]> for Response<'r> { let data = &bytes[2..2 + byte_count]; let data = Data { data, quantity }; - match FnCode::from(fn_code) { + match FunctionCode::new(fn_code) { F::ReadInputRegisters => Self::ReadInputRegisters(data), F::ReadHoldingRegisters => Self::ReadHoldingRegisters(data), F::ReadWriteMultipleRegisters => Self::ReadWriteMultipleRegisters(data), _ => unreachable!(), } } - _ => Self::Custom(FnCode::from(fn_code), &bytes[1..]), + _ => Self::Custom(FunctionCode::new(fn_code), &bytes[1..]), }; Ok(rsp) } @@ -222,7 +224,7 @@ impl<'r> Encode for Request<'r> { if buf.len() < self.pdu_len() { return Err(Error::BufferSize); } - buf[0] = FnCode::from(*self).into(); + buf[0] = FunctionCode::from(*self).value(); match self { Self::ReadCoils(address, payload) | Self::ReadDiscreteInputs(address, payload) @@ -281,7 +283,7 @@ impl<'r> Encode for Response<'r> { return Err(Error::BufferSize); } - buf[0] = FnCode::from(*self).into(); + buf[0] = FunctionCode::from(*self).value(); match self { Self::ReadCoils(coils) | Self::ReadDiscreteInputs(coils) => { buf[1] = coils.packed_len() as u8; @@ -341,8 +343,8 @@ impl Encode for ExceptionResponse { } } -const fn min_request_pdu_len(fn_code: FnCode) -> usize { - use FnCode as F; +const fn min_request_pdu_len(fn_code: FunctionCode) -> usize { + use FunctionCode as F; match fn_code { F::ReadCoils | F::ReadDiscreteInputs @@ -356,8 +358,8 @@ const fn min_request_pdu_len(fn_code: FnCode) -> usize { } } -const fn min_response_pdu_len(fn_code: FnCode) -> usize { - use FnCode as F; +const fn min_response_pdu_len(fn_code: FunctionCode) -> usize { + use FunctionCode as F; match fn_code { F::ReadCoils | F::ReadDiscreteInputs @@ -377,7 +379,7 @@ mod tests { #[test] fn exception_response_into_bytes() { let bytes: [u8; 2] = ExceptionResponse { - function: 0x03.into(), + function: FunctionCode::new(0x03), exception: Exception::IllegalDataAddress, } .into(); @@ -395,7 +397,7 @@ mod tests { assert_eq!( rsp, ExceptionResponse { - function: 0x03.into(), + function: FunctionCode::new(0x03), exception: Exception::IllegalDataAddress, } ); @@ -403,7 +405,7 @@ mod tests { #[test] fn test_min_request_pdu_len() { - use FnCode::*; + use FunctionCode::*; assert_eq!(min_request_pdu_len(ReadCoils), 5); assert_eq!(min_request_pdu_len(ReadDiscreteInputs), 5); @@ -418,7 +420,7 @@ mod tests { #[test] fn test_min_response_pdu_len() { - use FnCode::*; + use FunctionCode::*; assert_eq!(min_response_pdu_len(ReadCoils), 2); assert_eq!(min_response_pdu_len(ReadDiscreteInputs), 2); @@ -599,7 +601,7 @@ mod tests { #[test] fn custom() { let bytes = &mut [0; 5]; - Request::Custom(FnCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) + Request::Custom(FunctionCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) .encode(bytes) .unwrap(); assert_eq!(bytes[0], 0x55); @@ -747,7 +749,7 @@ mod tests { let req = Request::try_from(bytes).unwrap(); assert_eq!( req, - Request::Custom(FnCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) + Request::Custom(FunctionCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) ); } } @@ -877,7 +879,7 @@ mod tests { #[test] fn custom() { - let res = Response::Custom(FnCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]); + let res = Response::Custom(FunctionCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]); let bytes = &mut [0; 5]; res.encode(bytes).unwrap(); assert_eq!(bytes[0], 0x55); @@ -1020,11 +1022,11 @@ mod tests { let rsp = Response::try_from(bytes).unwrap(); assert_eq!( rsp, - Response::Custom(FnCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) + Response::Custom(FunctionCode::Custom(0x55), &[0xCC, 0x88, 0xAA, 0xFF]) ); let bytes: &[u8] = &[0x66]; let rsp = Response::try_from(bytes).unwrap(); - assert_eq!(rsp, Response::Custom(FnCode::Custom(0x66), &[])); + assert_eq!(rsp, Response::Custom(FunctionCode::Custom(0x66), &[])); } } } diff --git a/src/codec/rtu/server.rs b/src/codec/rtu/server.rs index 8621f93..f5a6afe 100644 --- a/src/codec/rtu/server.rs +++ b/src/codec/rtu/server.rs @@ -80,7 +80,7 @@ mod tests { let RequestAdu { hdr, pdu } = adu; let RequestPdu(pdu) = pdu; assert_eq!(hdr.slave, 0x12); - assert_eq!(FnCode::from(pdu), FnCode::WriteSingleRegister); + assert_eq!(FunctionCode::from(pdu), FunctionCode::WriteSingleRegister); } #[test] diff --git a/src/codec/tcp/server.rs b/src/codec/tcp/server.rs index dda3959..af22792 100644 --- a/src/codec/tcp/server.rs +++ b/src/codec/tcp/server.rs @@ -143,7 +143,7 @@ mod tests { let RequestPdu(pdu) = pdu; assert_eq!(hdr.transaction_id, 42); assert_eq!(hdr.unit_id, 0x12); - assert_eq!(FnCode::from(pdu), FnCode::WriteSingleRegister); + assert_eq!(FunctionCode::from(pdu), FunctionCode::WriteSingleRegister); } #[test] diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 0e9f232..b6df4ae 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -1,3 +1,5 @@ +use core::fmt; + mod coils; mod data; pub(crate) mod rtu; @@ -5,55 +7,83 @@ pub(crate) mod tcp; pub use self::{coils::*, data::*}; use byteorder::{BigEndian, ByteOrder}; -use core::fmt; /// A Modbus function code. /// /// It is represented by an unsigned 8 bit integer. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FnCode { +pub enum FunctionCode { + /// Modbus Function Code: `01` (`0x01`). ReadCoils, + + /// Modbus Function Code: `02` (`0x02`). ReadDiscreteInputs, + + /// Modbus Function Code: `05` (`0x05`). WriteSingleCoil, - WriteMultipleCoils, - ReadInputRegisters, - ReadHoldingRegisters, + + /// Modbus Function Code: `06` (`0x06`). WriteSingleRegister, + + /// Modbus Function Code: `03` (`0x03`). + ReadHoldingRegisters, + + /// Modbus Function Code: `04` (`0x04`). + ReadInputRegisters, + + /// Modbus Function Code: `15` (`0x0F`). + WriteMultipleCoils, + + /// Modbus Function Code: `16` (`0x10`). WriteMultipleRegisters, + + /// Modbus Function Code: `22` (`0x16`). + MaskWriteRegister, + + /// Modbus Function Code: `23` (`0x17`). ReadWriteMultipleRegisters, + #[cfg(feature = "rtu")] ReadExceptionStatus, + #[cfg(feature = "rtu")] Diagnostics, + #[cfg(feature = "rtu")] GetCommEventCounter, + #[cfg(feature = "rtu")] GetCommEventLog, + #[cfg(feature = "rtu")] ReportServerId, - //TODO: - //- ReadFileRecord - //- WriteFileRecord - //- MaskWriteRegiger - //TODO: - //- Read FifoQueue - //- EncapsulatedInterfaceTransport - //- CanOpenGeneralReferenceRequestAndResponsePdu - //- ReadDeviceIdentification + + // TODO: + // - ReadFileRecord + // - WriteFileRecord + // TODO: + // - Read FifoQueue + // - EncapsulatedInterfaceTransport + // - CanOpenGeneralReferenceRequestAndResponsePdu + // - ReadDeviceIdentification + /// Custom Modbus Function Code. Custom(u8), } -impl From for FnCode { - fn from(c: u8) -> Self { - match c { +impl FunctionCode { + /// Create a new [`FunctionCode`] with `value`. + #[must_use] + pub const fn new(value: u8) -> Self { + match value { 0x01 => Self::ReadCoils, 0x02 => Self::ReadDiscreteInputs, 0x05 => Self::WriteSingleCoil, - 0x0F => Self::WriteMultipleCoils, - 0x04 => Self::ReadInputRegisters, - 0x03 => Self::ReadHoldingRegisters, 0x06 => Self::WriteSingleRegister, + 0x03 => Self::ReadHoldingRegisters, + 0x04 => Self::ReadInputRegisters, + 0x0F => Self::WriteMultipleCoils, 0x10 => Self::WriteMultipleRegisters, + 0x16 => Self::MaskWriteRegister, 0x17 => Self::ReadWriteMultipleRegisters, #[cfg(feature = "rtu")] 0x07 => Self::ReadExceptionStatus, @@ -65,41 +95,48 @@ impl From for FnCode { 0x0C => Self::GetCommEventLog, #[cfg(feature = "rtu")] 0x11 => Self::ReportServerId, - _ => Self::Custom(c), + code => FunctionCode::Custom(code), } } -} -impl From for u8 { - fn from(code: FnCode) -> Self { - match code { - FnCode::ReadCoils => 0x01, - FnCode::ReadDiscreteInputs => 0x02, - FnCode::WriteSingleCoil => 0x05, - FnCode::WriteMultipleCoils => 0x0F, - FnCode::ReadInputRegisters => 0x04, - FnCode::ReadHoldingRegisters => 0x03, - FnCode::WriteSingleRegister => 0x06, - FnCode::WriteMultipleRegisters => 0x10, - FnCode::ReadWriteMultipleRegisters => 0x17, + /// Get the [`u8`] value of the current [`FunctionCode`]. + #[must_use] + pub const fn value(self) -> u8 { + match self { + Self::ReadCoils => 0x01, + Self::ReadDiscreteInputs => 0x02, + Self::WriteSingleCoil => 0x05, + Self::WriteSingleRegister => 0x06, + Self::ReadHoldingRegisters => 0x03, + Self::ReadInputRegisters => 0x04, + Self::WriteMultipleCoils => 0x0F, + Self::WriteMultipleRegisters => 0x10, + Self::MaskWriteRegister => 0x16, + Self::ReadWriteMultipleRegisters => 0x17, #[cfg(feature = "rtu")] - FnCode::ReadExceptionStatus => 0x07, + Self::ReadExceptionStatus => 0x07, #[cfg(feature = "rtu")] - FnCode::Diagnostics => 0x08, + Self::Diagnostics => 0x08, #[cfg(feature = "rtu")] - FnCode::GetCommEventCounter => 0x0B, + Self::GetCommEventCounter => 0x0B, #[cfg(feature = "rtu")] - FnCode::GetCommEventLog => 0x0C, + Self::GetCommEventLog => 0x0C, #[cfg(feature = "rtu")] - FnCode::ReportServerId => 0x11, - FnCode::Custom(c) => c, + Self::ReportServerId => 0x11, + Self::Custom(code) => code, } } } +impl fmt::Display for FunctionCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.value().fmt(f) + } +} + /// A Modbus sub-function code is represented by an unsigned 16 bit integer. #[cfg(feature = "rtu")] -pub(crate) type SubFnCode = u16; +pub(crate) type SubFunctionCode = u16; /// A Modbus address is represented by 16 bit (from `0` to `65535`). pub(crate) type Address = u16; @@ -134,7 +171,7 @@ pub enum Request<'r> { #[cfg(feature = "rtu")] ReadExceptionStatus, #[cfg(feature = "rtu")] - Diagnostics(SubFnCode, Data<'r>), + Diagnostics(SubFunctionCode, Data<'r>), #[cfg(feature = "rtu")] GetCommEventCounter, #[cfg(feature = "rtu")] @@ -150,13 +187,13 @@ pub enum Request<'r> { //- EncapsulatedInterfaceTransport //- CanOpenGeneralReferenceRequestAndResponsePdu //- ReadDeviceIdentification - Custom(FnCode, &'r [u8]), + Custom(FunctionCode, &'r [u8]), } /// A server (slave) exception response. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExceptionResponse { - pub function: FnCode, + pub function: FunctionCode, pub exception: Exception, } @@ -206,10 +243,10 @@ pub enum Response<'r> { //- EncapsulatedInterfaceTransport //- CanOpenGeneralReferenceRequestAndResponsePdu //- ReadDeviceIdentification - Custom(FnCode, &'r [u8]), + Custom(FunctionCode, &'r [u8]), } -impl<'r> From> for FnCode { +impl<'r> From> for FunctionCode { fn from(r: Request<'r>) -> Self { use Request as R; @@ -238,7 +275,7 @@ impl<'r> From> for FnCode { } } -impl<'r> From> for FnCode { +impl<'r> From> for FunctionCode { fn from(r: Response<'r>) -> Self { use Response as R; @@ -346,16 +383,16 @@ mod tests { #[test] fn function_code_into_u8() { - let x: u8 = FnCode::WriteMultipleCoils.into(); + let x: u8 = FunctionCode::WriteMultipleCoils.value(); assert_eq!(x, 15); - let x: u8 = FnCode::Custom(0xBB).into(); + let x: u8 = FunctionCode::Custom(0xBB).value(); assert_eq!(x, 0xBB); } #[test] fn function_code_from_u8() { - assert_eq!(FnCode::from(15), FnCode::WriteMultipleCoils); - assert_eq!(FnCode::from(0xBB), FnCode::Custom(0xBB)); + assert_eq!(FunctionCode::new(15), FunctionCode::WriteMultipleCoils); + assert_eq!(FunctionCode::new(0xBB), FunctionCode::Custom(0xBB)); } #[test] @@ -400,10 +437,10 @@ mod tests { ), 0x17, ), - (Custom(FnCode::Custom(88), &[]), 88), + (Custom(FunctionCode::Custom(88), &[]), 88), ]; for (req, expected) in requests { - let code: u8 = FnCode::from(*req).into(); + let code: u8 = FunctionCode::from(*req).value(); assert_eq!(*expected, code); } } @@ -451,10 +488,10 @@ mod tests { }), 0x17, ), - (Custom(FnCode::Custom(99), &[]), 99), + (Custom(FunctionCode::Custom(99), &[]), 99), ]; for (req, expected) in responses { - let code: u8 = FnCode::from(*req).into(); + let code: u8 = FunctionCode::from(*req).value(); assert_eq!(*expected, code); } }