diff --git a/src/codec/mod.rs b/src/codec/mod.rs index 057927ea..6e641287 100644 --- a/src/codec/mod.rs +++ b/src/codec/mod.rs @@ -81,6 +81,7 @@ impl<'a> TryFrom> for Bytes { data.put_u16(*w); } } + ReportServerId => {} MaskWriteRegister(address, and_mask, or_mask) => { data.put_u16(address); data.put_u16(and_mask); @@ -146,6 +147,14 @@ impl From for Bytes { data.put_u16(address); data.put_u16(quantity); } + ReportServerId(server_id, run_indication, additional_data) => { + data.put_u8(2 + u8_len(additional_data.len())); + data.put_u8(server_id); + data.put_u8(if run_indication { 0xFF } else { 0x00 }); + for b in additional_data { + data.put_u8(b); + } + } WriteSingleRegister(address, word) => { data.put_u16(address); data.put_u16(word); @@ -224,6 +233,7 @@ impl TryFrom for Request<'static> { } WriteMultipleRegisters(address, data.into()) } + 0x11 => ReportServerId, 0x16 => { let address = rdr.read_u16::()?; let and_mask = rdr.read_u16::()?; @@ -318,6 +328,26 @@ impl TryFrom for Response { 0x10 => { WriteMultipleRegisters(rdr.read_u16::()?, rdr.read_u16::()?) } + 0x11 => { + let byte_count = rdr.read_u8()?; + let server_id = rdr.read_u8()?; + let run_indication_status = match rdr.read_u8()? { + 0x00 => false, + 0xFF => true, + status => { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Invalid run indication status value: {status}"), + )); + } + }; + let data_len = byte_count.saturating_sub(2).into(); + let mut data = Vec::with_capacity(data_len); + for _ in 0..data_len { + data.push(rdr.read_u8()?); + } + ReportServerId(server_id, run_indication_status, data) + } 0x16 => { let address = rdr.read_u16::()?; let and_mask = rdr.read_u16::()?; @@ -449,6 +479,7 @@ fn request_byte_count(req: &Request<'_>) -> usize { | WriteSingleCoil(_, _) => 5, WriteMultipleCoils(_, ref coils) => 6 + packed_coils_len(coils.len()), WriteMultipleRegisters(_, ref data) => 6 + data.len() * 2, + ReportServerId => 1, MaskWriteRegister(_, _, _) => 7, ReadWriteMultipleRegisters(_, _, _, ref data) => 10 + data.len() * 2, Custom(_, ref data) => 1 + data.len(), @@ -467,6 +498,7 @@ fn response_byte_count(rsp: &Response) -> usize { ReadInputRegisters(ref data) | ReadHoldingRegisters(ref data) | ReadWriteMultipleRegisters(ref data) => 2 + data.len() * 2, + ReportServerId(_, _, ref data) => 3 + data.len(), MaskWriteRegister(_, _, _) => 7, Custom(_, ref data) => 1 + data.len(), } @@ -690,6 +722,12 @@ mod tests { assert_eq!(bytes[9], 0x12); } + #[test] + fn report_server_id() { + let bytes: Bytes = Request::ReportServerId.try_into().unwrap(); + assert_eq!(bytes[0], 0x11); + } + #[test] fn masked_write_register() { let bytes: Bytes = Request::MaskWriteRegister(0xABCD, 0xEF12, 0x2345) @@ -854,6 +892,13 @@ mod tests { ); } + #[test] + fn report_server_id() { + let bytes = Bytes::from(vec![0x11]); + let req = Request::try_from(bytes).unwrap(); + assert_eq!(req, Request::ReportServerId); + } + #[test] fn masked_write_register() { let bytes = Bytes::from(vec![0x16, 0xAB, 0xCD, 0xEF, 0x12, 0x23, 0x45]); @@ -973,6 +1018,17 @@ mod tests { assert_eq!(bytes[4], 0x02); } + #[test] + fn report_server_id() { + let bytes: Bytes = Response::ReportServerId(0x42, true, vec![0x10, 0x20]).into(); + assert_eq!(bytes[0], 0x11); + assert_eq!(bytes[1], 0x04); + assert_eq!(bytes[2], 0x42); + assert_eq!(bytes[3], 0xFF); + assert_eq!(bytes[4], 0x10); + assert_eq!(bytes[5], 0x20); + } + #[test] fn masked_write_register() { let bytes: Bytes = Response::MaskWriteRegister(0x06, 0x8001, 0x4002).into(); @@ -1101,6 +1157,13 @@ mod tests { assert_eq!(rsp, Response::WriteMultipleRegisters(0x06, 2)); } + #[test] + fn report_server_id() { + let bytes = Bytes::from(vec![0x11, 0x04, 0x042, 0xFF, 0x10, 0x20]); + let rsp = Response::try_from(bytes).unwrap(); + assert_eq!(rsp, Response::ReportServerId(0x42, true, vec![0x10, 0x20])); + } + #[test] fn masked_write_register() { let bytes = Bytes::from(vec![0x16, 0x00, 0x06, 0x80, 0x01, 0x40, 0x02]); diff --git a/src/codec/rtu.rs b/src/codec/rtu.rs index d1f0a57a..4628b005 100644 --- a/src/codec/rtu.rs +++ b/src/codec/rtu.rs @@ -157,7 +157,7 @@ fn get_response_pdu_len(adu_buf: &BytesMut) -> Result> { if let Some(fn_code) = adu_buf.get(1) { #[allow(clippy::match_same_arms)] let len = match fn_code { - 0x01..=0x04 | 0x0C | 0x17 => { + 0x01..=0x04 | 0x0C | 0x11 | 0x17 => { return Ok(adu_buf .get(2) .map(|&byte_count| 2 + usize::from(byte_count))); diff --git a/src/frame/mod.rs b/src/frame/mod.rs index f3952e7d..69db93ce 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -38,6 +38,9 @@ pub enum FunctionCode { /// Modbus Function Code: `16` (`0x10`). WriteMultipleRegisters, + /// Modbus Function Code: `17` (`0x11`). + ReportServerId, + /// Modbus Function Code: `22` (`0x16`). MaskWriteRegister, @@ -63,6 +66,7 @@ impl FunctionCode { 0x04 => Self::ReadInputRegisters, 0x0F => Self::WriteMultipleCoils, 0x10 => Self::WriteMultipleRegisters, + 0x11 => Self::ReportServerId, 0x16 => Self::MaskWriteRegister, 0x17 => Self::ReadWriteMultipleRegisters, code => Self::Custom(code), @@ -85,6 +89,7 @@ impl FunctionCode { Self::ReadInputRegisters => 0x04, Self::WriteMultipleCoils => 0x0F, Self::WriteMultipleRegisters => 0x10, + Self::ReportServerId => 0x11, Self::MaskWriteRegister => 0x16, Self::ReadWriteMultipleRegisters => 0x17, Self::Custom(code) => code, @@ -164,6 +169,9 @@ pub enum Request<'a> { /// The second parameter is the vector of values to write to the registers. WriteMultipleRegisters(Address, Cow<'a, [Word]>), + /// A request to report server ID (Serial Line only). + ReportServerId, + /// A request to set or clear individual bits of a holding register. /// The first parameter is the address of the holding register. /// The second parameter is the AND mask. @@ -213,6 +221,7 @@ impl<'a> Request<'a> { WriteMultipleRegisters(addr, words) => { WriteMultipleRegisters(addr, Cow::Owned(words.into_owned())) } + ReportServerId => ReportServerId, MaskWriteRegister(addr, and_mask, or_mask) => { MaskWriteRegister(addr, and_mask, or_mask) } @@ -242,6 +251,8 @@ impl<'a> Request<'a> { WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister, WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters, + ReportServerId => FunctionCode::ReportServerId, + MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister, ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters, @@ -322,6 +333,12 @@ pub enum Response { /// The second parameter contains the amount of register that have been written WriteMultipleRegisters(Address, Quantity), + /// Response to a `ReportServerId` request + /// The first parameter contains the server ID + /// The second parameter indicates whether the server is running + /// The third parameter contains additional data from the server + ReportServerId(u8, bool, Vec), + /// Response `MaskWriteRegister` /// The first parameter is the address of the holding register. /// The second parameter is the AND mask. @@ -357,6 +374,8 @@ impl Response { WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister, WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters, + ReportServerId(_, _, _) => FunctionCode::ReportServerId, + MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister, ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,