Skip to content

Commit

Permalink
Implement Report Server ID (function code 17) (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
snejugal authored Sep 10, 2024
1 parent 92c216a commit abb8539
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 1 deletion.
63 changes: 63 additions & 0 deletions src/codec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl<'a> TryFrom<Request<'a>> for Bytes {
data.put_u16(*w);
}
}
ReportServerId => {}
MaskWriteRegister(address, and_mask, or_mask) => {
data.put_u16(address);
data.put_u16(and_mask);
Expand Down Expand Up @@ -146,6 +147,14 @@ impl From<Response> 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);
Expand Down Expand Up @@ -224,6 +233,7 @@ impl TryFrom<Bytes> for Request<'static> {
}
WriteMultipleRegisters(address, data.into())
}
0x11 => ReportServerId,
0x16 => {
let address = rdr.read_u16::<BigEndian>()?;
let and_mask = rdr.read_u16::<BigEndian>()?;
Expand Down Expand Up @@ -318,6 +328,26 @@ impl TryFrom<Bytes> for Response {
0x10 => {
WriteMultipleRegisters(rdr.read_u16::<BigEndian>()?, rdr.read_u16::<BigEndian>()?)
}
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::<BigEndian>()?;
let and_mask = rdr.read_u16::<BigEndian>()?;
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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]);
Expand Down
2 changes: 1 addition & 1 deletion src/codec/rtu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
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)));
Expand Down
19 changes: 19 additions & 0 deletions src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -242,6 +251,8 @@ impl<'a> Request<'a> {
WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,

ReportServerId => FunctionCode::ReportServerId,

MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,

ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters,
Expand Down Expand Up @@ -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<u8>),

/// Response `MaskWriteRegister`
/// The first parameter is the address of the holding register.
/// The second parameter is the AND mask.
Expand Down Expand Up @@ -357,6 +374,8 @@ impl Response {
WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,

ReportServerId(_, _, _) => FunctionCode::ReportServerId,

MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,

ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
Expand Down

0 comments on commit abb8539

Please sign in to comment.