From 2723204e204d04c9fcf1dce7109387099711c263 Mon Sep 17 00:00:00 2001 From: thr2240 Date: Wed, 6 Dec 2023 21:46:30 +0300 Subject: [PATCH 001/122] feat: implement send/receive custom buffer --- rodbus/examples/client.rs | 14 ++++ rodbus/examples/server.rs | 14 ++++ rodbus/src/client/channel.rs | 16 ++++ rodbus/src/client/message.rs | 9 +++ rodbus/src/client/requests/mod.rs | 1 + rodbus/src/client/requests/send_buffer.rs | 94 +++++++++++++++++++++++ rodbus/src/common/function.rs | 6 ++ rodbus/src/serial/frame.rs | 25 ++++++ rodbus/src/server/handler.rs | 13 ++++ rodbus/src/server/request.rs | 16 ++++ rodbus/src/server/task.rs | 1 + 11 files changed, 209 insertions(+) create mode 100644 rodbus/src/client/requests/send_buffer.rs diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 9403fb87..94c58fd5 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -267,6 +267,20 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { + // ANCHOR: send_custom_buffer + let result = channel + .send_custom_buffer( + params, + Indexed { + index: (0x1), + value: (0xAB), + }, + ) + .await; + print_write_result(result); + // ANCHOR_END: write_multiple_registers + } _ => println!("unknown command"), } } diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 4fc490cb..913961ad 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -78,6 +78,20 @@ impl RequestHandler for SimpleHandler { } } + fn receive_custom_buffer(&self, value: Indexed) -> Result { + tracing::info!( + "receive custom buffer, index: {} value: {}", + value.index, + value.value + ); + + if value.value == 0xAB { + Ok(0xCD) + } else { + Err(ExceptionCode::IllegalFunction) + } + } + fn write_single_register(&mut self, value: Indexed) -> Result<(), ExceptionCode> { tracing::info!( "write single register, index: {} value: {}", diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 3b58340e..58f35bfa 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -5,6 +5,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::send_buffer::SendBuffer; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId}; use crate::DecodeLevel; @@ -163,6 +164,21 @@ impl Channel { rx.await? } + /// Send buffer to the server + pub async fn send_custom_buffer( + &mut self, + param: RequestParam, + request: Indexed, + ) -> Result, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); + let request = wrap( + param, + RequestDetails::SendCustomBuffers(SendBuffer::new(request, Promise::channel(tx))), + ); + self.tx.send(request).await?; + rx.await? + } + /// Write a single coil on the server pub async fn write_single_coil( &mut self, diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index c18abb7e..dc2c39f9 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,6 +10,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::send_buffer::SendBuffer; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId}; @@ -41,6 +42,7 @@ pub(crate) enum RequestDetails { ReadDiscreteInputs(ReadBits), ReadHoldingRegisters(ReadRegisters), ReadInputRegisters(ReadRegisters), + SendCustomBuffers(SendBuffer>), WriteSingleCoil(SingleWrite>), WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), @@ -125,6 +127,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, RequestDetails::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, RequestDetails::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, + RequestDetails::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, RequestDetails::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -138,6 +141,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.failure(err), RequestDetails::ReadHoldingRegisters(x) => x.failure(err), RequestDetails::ReadInputRegisters(x) => x.failure(err), + RequestDetails::SendCustomBuffers(x) => x.failure(err), RequestDetails::WriteSingleCoil(x) => x.failure(err), RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), @@ -156,6 +160,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadHoldingRegisters(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadInputRegisters(x) => x.handle_response(cursor, function, decode), + RequestDetails::SendCustomBuffers(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleCoil(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleRegister(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteMultipleCoils(x) => x.handle_response(cursor, function, decode), @@ -173,6 +178,7 @@ impl Serialize for RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.serialize(cursor), RequestDetails::ReadHoldingRegisters(x) => x.serialize(cursor), RequestDetails::ReadInputRegisters(x) => x.serialize(cursor), + RequestDetails::SendCustomBuffers(x) => x.serialize(cursor), RequestDetails::WriteSingleCoil(x) => x.serialize(cursor), RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), @@ -219,6 +225,9 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { RequestDetails::ReadInputRegisters(details) => { write!(f, "{}", details.request.get())?; } + RequestDetails::SendCustomBuffers(details) => { + write!(f, "{}", details.request)?; + } RequestDetails::WriteSingleCoil(details) => { write!(f, "{}", details.request)?; } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index bfebd0a4..b3e56fb2 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -2,3 +2,4 @@ pub(crate) mod read_bits; pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; +pub(crate) mod send_buffer; diff --git a/rodbus/src/client/requests/send_buffer.rs b/rodbus/src/client/requests/send_buffer.rs new file mode 100644 index 00000000..2e19d731 --- /dev/null +++ b/rodbus/src/client/requests/send_buffer.rs @@ -0,0 +1,94 @@ +use std::fmt::Display; + +use crate::client::message::Promise; +use crate::common::function::FunctionCode; +use crate::decode::AppDecodeLevel; +use crate::error::AduParseError; +use crate::error::RequestError; +use crate::types::{coil_from_u16, coil_to_u16, Indexed}; + +use scursor::{ReadCursor, WriteCursor}; + +pub(crate) trait SendBufferOperation: Sized + PartialEq { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; + fn parse(cursor: &mut ReadCursor) -> Result; +} + +pub(crate) struct SendBuffer +where + T: SendBufferOperation + Display + Send + 'static, +{ + pub(crate) request: T, + promise: Promise, +} + +impl SendBuffer +where + T: SendBufferOperation + Display + Send + 'static, +{ + pub(crate) fn new(request: T, promise: Promise) -> Self { + Self { request, promise } + } + + pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.request.serialize(cursor) + } + + pub(crate) fn failure(&mut self, err: RequestError) { + self.promise.failure(err) + } + + pub(crate) fn handle_response( + &mut self, + cursor: ReadCursor, + function: FunctionCode, + decode: AppDecodeLevel, + ) -> Result<(), RequestError> { + let response = self.parse_all(cursor)?; + + if decode.data_headers() { + tracing::info!("PDU RX - {} {}", function, response); + } else if decode.header() { + tracing::info!("PDU RX - {}", function); + } + + self.promise.success(response); + Ok(()) + } + + fn parse_all(&self, mut cursor: ReadCursor) -> Result { + let response = T::parse(&mut cursor)?; + cursor.expect_empty()?; + if self.request != response { + return Err(AduParseError::ReplyEchoMismatch.into()); + } + Ok(response) + } +} + +impl SendBufferOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(coil_to_u16(self.value))?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new( + cursor.read_u16_be()?, + coil_from_u16(cursor.read_u16_be()?)?, + )) + } +} + +impl SendBufferOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(self.value)?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) + } +} diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index e85bf14a..4052f630 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -9,6 +9,7 @@ mod constants { pub(crate) const WRITE_SINGLE_REGISTER: u8 = 6; pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; + pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; } #[derive(Debug, Copy, Clone, PartialEq)] @@ -22,6 +23,7 @@ pub(crate) enum FunctionCode { WriteSingleRegister = constants::WRITE_SINGLE_REGISTER, WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, + SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, } impl Display for FunctionCode { @@ -49,6 +51,9 @@ impl Display for FunctionCode { FunctionCode::WriteMultipleRegisters => { write!(f, "WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) } + FunctionCode::SendCustomBuffers => { + write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) + } } } } @@ -72,6 +77,7 @@ impl FunctionCode { constants::WRITE_SINGLE_REGISTER => Some(FunctionCode::WriteSingleRegister), constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), + constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), _ => None, } } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index 642f7a8a..586c0ac3 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -83,6 +83,7 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Fixed(4), FunctionCode::ReadHoldingRegisters => LengthMode::Fixed(4), FunctionCode::ReadInputRegisters => LengthMode::Fixed(4), + FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), @@ -93,6 +94,7 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Offset(1), FunctionCode::ReadHoldingRegisters => LengthMode::Offset(1), FunctionCode::ReadInputRegisters => LengthMode::Offset(1), + FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), @@ -345,6 +347,21 @@ mod tests { 0x71, 0x86, // crc ]; + const SEND_CUSTOM_BUFFER_REQUEST: &[u8] = &[ + UNIT_ID, // unit id + 0x44, // function code + 0x02, // byte count + 0x01, 0xAB, // additonal data + 0xC8, 0xD9, // crc + ]; + + const SEND_CUSTOM_BUFFER_RESPONSE: &[u8] = &[ + UNIT_ID, // unit id + 0x44, // function code + 0x01, // byte count + 0xCD, // return value + 0x88, 0x2C, // crc + ]; const WRITE_SINGLE_COIL_REQUEST: &[u8] = &[ UNIT_ID, // unit id 0x05, // function code @@ -427,6 +444,10 @@ mod tests { FunctionCode::ReadInputRegisters, READ_INPUT_REGISTERS_REQUEST, ), + ( + FunctionCode::SendCustomBuffers, + SEND_CUSTOM_BUFFER_REQUEST, + ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_REQUEST), ( FunctionCode::WriteSingleRegister, @@ -456,6 +477,10 @@ mod tests { FunctionCode::ReadInputRegisters, READ_INPUT_REGISTERS_RESPONSE, ), + ( + FunctionCode::SendCustomBuffers, + SEND_CUSTOM_BUFFER_RESPONSE, + ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_RESPONSE), ( FunctionCode::WriteSingleRegister, diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 43658927..db81863e 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -43,6 +43,11 @@ pub trait RequestHandler: Send + 'static { Err(ExceptionCode::IllegalFunction) } + /// Read single custom buffer or return an ExceptionCode + fn receive_custom_buffer(&self, _value: Indexed) -> Result { + Err(ExceptionCode::IllegalFunction) + } + /// Write a single coil value fn write_single_coil(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) @@ -177,6 +182,14 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } + /// Authorize a Read Custom Buffer request + fn receive_custom_buffer( + &self, + _value: Indexed, + _role: &str, + ) -> Authorization { + Authorization::Deny + } /// Authorize a Read Holding Registers request fn read_holding_registers( &self, diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 77843d67..ffd30db8 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -17,6 +17,7 @@ pub(crate) enum Request<'a> { ReadDiscreteInputs(ReadBitsRange), ReadHoldingRegisters(ReadRegistersRange), ReadInputRegisters(ReadRegistersRange), + SendCustomBuffers(Indexed), WriteSingleCoil(Indexed), WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), @@ -60,6 +61,7 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, Request::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, Request::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, + Request::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, Request::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -73,6 +75,7 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => None, Request::ReadHoldingRegisters(_) => None, Request::ReadInputRegisters(_) => None, + Request::SendCustomBuffers(_) => None, Request::WriteSingleCoil(x) => Some(BroadcastRequest::WriteSingleCoil(x)), Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), @@ -123,6 +126,10 @@ impl<'a> Request<'a> { let registers = RegisterWriter::new(*range, |i| handler.read_input_register(i)); writer.format_reply(header, function, ®isters, level) } + Request::SendCustomBuffers(request) => { + let result = handler.receive_custom_buffer(*request).map(|_| *request); + write_result(function, header, writer, result, level) + } Request::WriteSingleCoil(request) => { let result = handler.write_single_coil(*request).map(|_| *request); write_result(function, header, writer, result, level) @@ -172,6 +179,12 @@ impl<'a> Request<'a> { cursor.expect_empty()?; Ok(x) } + FunctionCode::SendCustomBuffers => { + let x = + Request::SendCustomBuffers(Indexed::::parse(cursor)?); + cursor.expect_empty()?; + Ok(x) + } FunctionCode::WriteSingleCoil => { let x = Request::WriteSingleCoil(Indexed::::parse(cursor)?); cursor.expect_empty()?; @@ -233,6 +246,9 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { Request::ReadInputRegisters(range) => { write!(f, " {}", range.get())?; } + Request::SendCustomBuffers(request) => { + write!(f, " {request}")?; + } Request::WriteSingleCoil(request) => { write!(f, " {request}")?; } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index fbb7bfca..28f39149 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -256,6 +256,7 @@ impl AuthorizationType { handler.read_holding_registers(unit_id, x.inner, role) } Request::ReadInputRegisters(x) => handler.read_input_registers(unit_id, x.inner, role), + Request::SendCustomBuffers(x) => handler.receive_custom_buffer(*x, role), Request::WriteSingleCoil(x) => handler.write_single_coil(unit_id, x.index, role), Request::WriteSingleRegister(x) => { handler.write_single_register(unit_id, x.index, role) From 3a07ec15d59fab031797c48242caca3b4010c4b2 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 17 Jan 2024 19:13:07 +0100 Subject: [PATCH 002/122] chore: fix client and server ports for Unix-based architectures with no root privileagues --- rodbus/examples/client.rs | 2 +- rodbus/examples/server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 94c58fd5..46288208 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -61,7 +61,7 @@ where async fn run_tcp() -> Result<(), Box> { // ANCHOR: create_tcp_channel let channel = spawn_tcp_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 502), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 1502), 1, default_retry_strategy(), DecodeLevel::default(), diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 913961ad..6c8815e8 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -181,7 +181,7 @@ async fn run_tcp() -> Result<(), Box> { // ANCHOR: tcp_server_create let server = rodbus::server::spawn_tcp_server_task( 1, - "127.0.0.1:502".parse()?, + "127.0.0.1:1502".parse()?, map, AddressFilter::Any, DecodeLevel::default(), From c9c7923e1d45a1206d4a52c0cb42d81200c8097a Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 16:59:01 +0100 Subject: [PATCH 003/122] chore: Implement placeholder commands for read/write custom function code --- rodbus/examples/client.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 46288208..81d6880e 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -281,6 +281,16 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { + // ANCHOR: write_custom_function_code + println!("write success"); + // ANCHOR_END: write_custom_function_code + } + "rcfc" => { + // ANCHOR: read_custom_function_code + println!("success"); + // ANCHOR_END: read_custom_function_code + } _ => println!("unknown command"), } } From ad140d66e2b5b452d1fc0dd62d7d2ae40bf82d49 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:13:12 +0100 Subject: [PATCH 004/122] feat: Implement request building for custom function code --- rodbus/src/client/requests/write_custom_fc.rs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 rodbus/src/client/requests/write_custom_fc.rs diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs new file mode 100644 index 00000000..4bcd0c22 --- /dev/null +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -0,0 +1,94 @@ +use std::fmt::Display; + +use crate::client::message::Promise; +use crate::common::function::FunctionCode; +use crate::decode::AppDecodeLevel; +use crate::error::AduParseError; +use crate::error::RequestError; +use crate::types::{coil_from_u16, coil_to_u16, Indexed}; + +use scursor::{ReadCursor, WriteCursor}; + +pub(crate) trait WriteCustomFCOperation: Sized + PartialEq { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; + fn parse(cursor: &mut ReadCursor) -> Result; +} + +pub(crate) struct WriteCustomFC +where + T: WriteCustomFCOperation + Display + Send + 'static, +{ + pub(crate) request: T, + promise: Promise, +} + +impl WriteCustomFC +where + T: WriteCustomFCOperation + Display + Send + 'static, +{ + pub(crate) fn new(request: T, promise: Promise) -> Self { + Self { request, promise } + } + + pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.request.serialize(cursor) + } + + pub(crate) fn failure(&mut self, err: RequestError) { + self.promise.failure(err) + } + + pub(crate) fn handle_response( + &mut self, + cursor: ReadCursor, + function: FunctionCode, + decode: AppDecodeLevel, + ) -> Result<(), RequestError> { + let response = self.parse_all(cursor)?; + + if decode.data_headers() { + tracing::info!("PDU RX - {} {}", function, response); + } else if decode.header() { + tracing::info!("PDU RX - {}", function); + } + + self.promise.success(response); + Ok(()) + } + + fn parse_all(&self, mut cursor: ReadCursor) -> Result { + let response = T::parse(&mut cursor)?; + cursor.expect_empty()?; + if self.request != response { + return Err(AduParseError::ReplyEchoMismatch.into()); + } + Ok(response) + } +} + +impl WriteCustomFCOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(coil_to_u16(self.value))?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new( + cursor.read_u16_be()?, + coil_from_u16(cursor.read_u16_be()?)?, + )) + } +} + +impl WriteCustomFCOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(self.value)?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) + } +} From bc84c5ee8dd36a24e483916af38cfcef5fb65e3e Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:14:59 +0100 Subject: [PATCH 005/122] feat: Declare custom function code module --- rodbus/src/client/requests/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index b3e56fb2..7a2bd345 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -3,3 +3,4 @@ pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; pub(crate) mod send_buffer; +pub(crate) mod write_custom_fc; \ No newline at end of file From e42d375b8a48aebcd54890b7a1c9aeda44f8239b Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:26:25 +0100 Subject: [PATCH 006/122] feat: Add 'write custom function code' as function code --- rodbus/src/common/function.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index 4052f630..f30ba928 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -10,6 +10,7 @@ mod constants { pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; + pub(crate) const WRITE_CUSTOM_FUNCTION_CODE: u8 = 69; } #[derive(Debug, Copy, Clone, PartialEq)] @@ -24,6 +25,7 @@ pub(crate) enum FunctionCode { WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, + WriteCustomFunctionCode = constants::WRITE_CUSTOM_FUNCTION_CODE, } impl Display for FunctionCode { @@ -54,6 +56,9 @@ impl Display for FunctionCode { FunctionCode::SendCustomBuffers => { write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) } + FunctionCode::WriteCustomFunctionCode => { + write!(f, "WRITE CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) + } } } } @@ -78,6 +83,7 @@ impl FunctionCode { constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), + constants::WRITE_CUSTOM_FUNCTION_CODE => Some(FunctionCode::WriteCustomFunctionCode), _ => None, } } From 6288b90bfcdf051f300f00eb04bea6dbec654ee0 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:30:04 +0100 Subject: [PATCH 007/122] feat: Implement 'write custom function code' functionality into the message handler --- rodbus/src/client/message.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index dc2c39f9..9d462eaf 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -11,6 +11,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; +use crate::client::requests::write_custom_fc::WriteCustomFC; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId}; @@ -47,6 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), + WriteCustomFunctionCode(WriteCustomFC>), } impl Request { @@ -132,6 +134,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, RequestDetails::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, + RequestDetails::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -146,6 +149,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), RequestDetails::WriteMultipleRegisters(x) => x.failure(err), + RequestDetails::WriteCustomFunctionCode(x) => x.failure(err), } } @@ -166,6 +170,9 @@ impl RequestDetails { RequestDetails::WriteMultipleCoils(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteMultipleRegisters(x) => { x.handle_response(cursor, function, decode) + }, + RequestDetails::WriteCustomFunctionCode(x) => { + x.handle_response(cursor, function, decode) } } } @@ -183,6 +190,7 @@ impl Serialize for RequestDetails { RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), RequestDetails::WriteMultipleRegisters(x) => x.serialize(cursor), + RequestDetails::WriteCustomFunctionCode(x) => x.serialize(cursor), } } } @@ -250,6 +258,9 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } } + RequestDetails::WriteCustomFunctionCode(details) => { + write!(f, "{}", details.request)?; + } } } From 284e8d95fe2dab710939e3d39d5bbbac4d6a9e09 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:38:14 +0100 Subject: [PATCH 008/122] feat: Implement 'write custom function code' in channel handler --- rodbus/src/client/channel.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 58f35bfa..3e1db41c 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -10,6 +10,8 @@ use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId}; use crate::DecodeLevel; +use super::requests::write_custom_fc::WriteCustomFC; + /// Async channel used to make requests #[derive(Debug, Clone)] pub struct Channel { @@ -179,6 +181,21 @@ impl Channel { rx.await? } + /// Write a Custom Function Code to the server + pub async fn write_custom_function_code( + &mut self, + param: RequestParam, + request: Indexed, + ) -> Result, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); + let request = wrap( + param, + RequestDetails::WriteCustomFunctionCode(WriteCustomFC::new(request, Promise::channel(tx))), + ); + self.tx.send(request).await?; + rx.await? + } + /// Write a single coil on the server pub async fn write_single_coil( &mut self, From d0b381f60c9740630f7719fd8deca3260a5bec90 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:40:13 +0100 Subject: [PATCH 009/122] feat: Implement 'write custom function code' in client example --- rodbus/examples/client.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 81d6880e..d732816c 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -283,12 +283,20 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: write_custom_function_code - println!("write success"); + let result: Result, RequestError> = channel + .write_custom_function_code( + params, + Indexed { + index: (0x1), + value: (0x44), + }, + ) + .await; // ANCHOR_END: write_custom_function_code } "rcfc" => { // ANCHOR: read_custom_function_code - println!("success"); + println!("read success"); // ANCHOR_END: read_custom_function_code } _ => println!("unknown command"), From fc3d8b487fbc9e62e9d91577149a7b99370cf088 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:51:23 +0100 Subject: [PATCH 010/122] feat: Implement 'write custom function code' in server request handler --- rodbus/src/server/handler.rs | 5 +++++ rodbus/src/server/request.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index db81863e..40ee7831 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -67,6 +67,11 @@ pub trait RequestHandler: Send + 'static { fn write_multiple_registers(&mut self, _values: WriteRegisters) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } + + /// Write a custom function code + fn write_custom_function_code(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { + Err(ExceptionCode::IllegalFunction) + } } /// Trait useful for converting None into IllegalDataAddress diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index ffd30db8..79854651 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -22,6 +22,7 @@ pub(crate) enum Request<'a> { WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), WriteMultipleRegisters(WriteRegisters<'a>), + WriteCustomFunctionCode(Indexed), } /// All requests that support broadcast @@ -66,6 +67,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, Request::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, + Request::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -80,6 +82,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), Request::WriteMultipleRegisters(x) => Some(BroadcastRequest::WriteMultipleRegisters(x)), + Request::WriteCustomFunctionCode(_) => None, } } @@ -148,6 +151,10 @@ impl<'a> Request<'a> { .map(|_| items.range); write_result(function, header, writer, result, level) } + Request::WriteCustomFunctionCode(request) => { + let result = handler.write_custom_function_code(*request).map(|_| *request); + write_result(function, header, writer, result, level) + } } } @@ -213,6 +220,12 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } + FunctionCode::WriteCustomFunctionCode => { + let x = + Request::WriteCustomFunctionCode(Indexed::::parse(cursor)?); + cursor.expect_empty()?; + Ok(x) + } } } } @@ -269,6 +282,9 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { RegisterIteratorDisplay::new(self.level, items.iterator) )?; } + Request::WriteCustomFunctionCode(request) => { + write!(f, " {request}")?; + } } } From 10c81d91c630f798ee3968094d2b5f7aa400890d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 19:55:02 +0100 Subject: [PATCH 011/122] feat: Implement 'write custom function code' in server handler & server task handler --- rodbus/src/server/handler.rs | 5 +++++ rodbus/src/server/task.rs | 1 + 2 files changed, 6 insertions(+) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 40ee7831..0f460c05 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -244,6 +244,11 @@ pub trait AuthorizationHandler: Send + Sync + 'static { ) -> Authorization { Authorization::Deny } + + /// Authorize a Write Custom Function Code request + fn write_custom_function_code(&self, _value: Indexed, _role: &str) -> Authorization { + Authorization::Deny + } } /// Read-only authorization handler that blindly accepts diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index 28f39149..a96164e7 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -265,6 +265,7 @@ impl AuthorizationType { Request::WriteMultipleRegisters(x) => { handler.write_multiple_registers(unit_id, x.range, role) } + Request::WriteCustomFunctionCode(x) => handler.write_custom_function_code(*x, role), } } From a232ef314fbe94932081f00996ef378ab8654fc6 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 18 Jan 2024 20:05:10 +0100 Subject: [PATCH 012/122] feat: Implement 'write custom function code' in frame handler --- rodbus/examples/client.rs | 1 + rodbus/src/serial/frame.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index d732816c..3292cbe6 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -292,6 +292,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index 586c0ac3..cbe54da7 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -88,6 +88,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), FunctionCode::WriteMultipleRegisters => LengthMode::Offset(5), + FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, ParserType::Response => match function_code { FunctionCode::ReadCoils => LengthMode::Offset(1), @@ -99,6 +100,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), FunctionCode::WriteMultipleRegisters => LengthMode::Fixed(4), + FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, } } From d6c7a31d2aca9b454ce03b9833258d5bae1f7e52 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 19 Jan 2024 18:11:33 +0100 Subject: [PATCH 013/122] feat: Add CustomFC type; Implement client request for CustomFC type --- rodbus/src/client/requests/write_custom_fc.rs | 31 ++++++-------- rodbus/src/types.rs | 40 ++++++++++--------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs index 4bcd0c22..5a46e396 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -1,11 +1,11 @@ use std::fmt::Display; +use crate::CustomFunctionCode; use crate::client::message::Promise; use crate::common::function::FunctionCode; use crate::decode::AppDecodeLevel; use crate::error::AduParseError; use crate::error::RequestError; -use crate::types::{coil_from_u16, coil_to_u16, Indexed}; use scursor::{ReadCursor, WriteCursor}; @@ -66,29 +66,22 @@ where } } -impl WriteCustomFCOperation for Indexed { +impl WriteCustomFCOperation for CustomFunctionCode { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(coil_to_u16(self.value))?; - Ok(()) - } + cursor.write_u16_be(self.len() as u16)?; - fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new( - cursor.read_u16_be()?, - coil_from_u16(cursor.read_u16_be()?)?, - )) - } -} - -impl WriteCustomFCOperation for Indexed { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(self.value)?; + for &item in self.iter() { + cursor.write_u16_be(item)?; + } Ok(()) } fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) + let len = cursor.read_u16_be()? as usize; + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(cursor.read_u16_be()?); + } + Ok(CustomFunctionCode::new(vec)) } } diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 0f9d94eb..a538e6b2 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -85,25 +85,10 @@ pub(crate) struct RegisterIteratorDisplay<'a> { level: AppDecodeLevel, } -impl std::fmt::Display for UnitId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:#04X}", self.value) - } -} - -impl<'a> BitIterator<'a> { - pub(crate) fn parse_all( - range: AddressRange, - cursor: &'a mut ReadCursor, - ) -> Result { - let bytes = cursor.read_bytes(crate::common::bits::num_bytes_for_bits(range.count))?; - cursor.expect_empty()?; - Ok(Self { - bytes, - range, - pos: 0, - }) - } +/// Custom buffer +#[derive(Clone, Debug, PartialEq)] +pub struct CustomFunctionCode { + data: Vec, } impl<'a> BitIteratorDisplay<'a> { @@ -367,6 +352,23 @@ impl Default for UnitId { } } +impl CustomFunctionCode { + /// Create a new custom function code + pub fn new(data: Vec) -> Self { + Self { data } + } + + /// Get the length of the underlying vector + pub fn len(&self) -> usize { + self.data.len() + } + + // Iterate over the underlying vector + pub fn iter(&self) -> std::slice::Iter { + self.data.iter() + } +} + #[cfg(test)] mod tests { use crate::error::*; From 6d6d91657d781c5c9d84a1db1ad678ea8045dcbc Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 19 Jan 2024 18:28:13 +0100 Subject: [PATCH 014/122] feat: Implement CustomFC Display trait --- rodbus/src/types.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index a538e6b2..81f69321 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -369,6 +369,19 @@ impl CustomFunctionCode { } } +impl std::fmt::Display for CustomFunctionCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, val) in self.data.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", val)?; + } + write!(f, "]") + } +} + #[cfg(test)] mod tests { use crate::error::*; From 90744c1763fcfdb1aaea32410a41ca15f25da58e Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 19 Jan 2024 18:31:18 +0100 Subject: [PATCH 015/122] feat: Implement CustomFC type in client-side request --- rodbus/examples/client.rs | 2 +- rodbus/src/client/channel.rs | 8 ++++---- rodbus/src/client/message.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 3292cbe6..98c1c9c0 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -283,7 +283,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: write_custom_function_code - let result: Result, RequestError> = channel + let result: Result = channel .write_custom_function_code( params, Indexed { diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 3e1db41c..dc1d0a03 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -7,7 +7,7 @@ use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultipl use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; use crate::error::*; -use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId}; +use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; use crate::DecodeLevel; use super::requests::write_custom_fc::WriteCustomFC; @@ -185,9 +185,9 @@ impl Channel { pub async fn write_custom_function_code( &mut self, param: RequestParam, - request: Indexed, - ) -> Result, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); + request: CustomFunctionCode, + ) -> Result { + let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, RequestDetails::WriteCustomFunctionCode(WriteCustomFC::new(request, Promise::channel(tx))), diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 9d462eaf..e581fecc 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -13,7 +13,7 @@ use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::write_custom_fc::WriteCustomFC; use crate::common::traits::Serialize; -use crate::types::{Indexed, UnitId}; +use crate::types::{Indexed, UnitId, CustomFunctionCode}; use scursor::{ReadCursor, WriteCursor}; use std::time::Duration; @@ -48,7 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), - WriteCustomFunctionCode(WriteCustomFC>), + WriteCustomFunctionCode(WriteCustomFC), } impl Request { From 38b3404dc72d2455dfaec3d82507564694436174 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 16:57:20 +0100 Subject: [PATCH 016/122] fix: Implement UnitId Display trait --- rodbus/src/types.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 81f69321..17a59154 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -91,6 +91,12 @@ pub struct CustomFunctionCode { data: Vec, } +impl std::fmt::Display for UnitId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:#04X}", self.value) + } +} + impl<'a> BitIteratorDisplay<'a> { pub(crate) fn new(level: AppDecodeLevel, iterator: BitIterator<'a>) -> Self { Self { iterator, level } From fa71af4737293ab53ec55ab025478d4c8e662fb2 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 16:59:15 +0100 Subject: [PATCH 017/122] chore: Replace write custom FC client request parameter --- rodbus/examples/client.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 98c1c9c0..ab80ef59 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -286,10 +286,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box = channel .write_custom_function_code( params, - Indexed { - index: (0x1), - value: (0x44), - }, + CustomFunctionCode::new(vec![0x01, 0x02, 0x03, 0x04]) ) .await; print_write_result(result); From 788be9ff939873f91dabbf410889021298aab029 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 17:05:22 +0100 Subject: [PATCH 018/122] fix: Implement BitIterator parse_all fn --- rodbus/src/types.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 17a59154..fc70359f 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -97,6 +97,21 @@ impl std::fmt::Display for UnitId { } } +impl<'a> BitIterator<'a> { + pub(crate) fn parse_all( + range: AddressRange, + cursor: &'a mut ReadCursor, + ) -> Result { + let bytes = cursor.read_bytes(crate::common::bits::num_bytes_for_bits(range.count))?; + cursor.expect_empty()?; + Ok(Self { + bytes, + range, + pos: 0, + }) + } +} + impl<'a> BitIteratorDisplay<'a> { pub(crate) fn new(level: AppDecodeLevel, iterator: BitIterator<'a>) -> Self { Self { iterator, level } From 1934f3dace72b9a87d78605a9a86d0b6e1e10d81 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 17:07:05 +0100 Subject: [PATCH 019/122] fix: Implement missing documentation for CustomFunctionCode fn --- rodbus/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index fc70359f..cd0b0bbe 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -384,7 +384,7 @@ impl CustomFunctionCode { self.data.len() } - // Iterate over the underlying vector + /// Iterate over the underlying vector pub fn iter(&self) -> std::slice::Iter { self.data.iter() } From 476e5ea9347eafbd0bf2d18f34f3c905ffd0ae25 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 20:53:38 +0100 Subject: [PATCH 020/122] chore: Set tracing level to DEBUG --- rodbus/examples/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index ab80ef59..afcbe682 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box> { // ANCHOR: logging tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) + .with_max_level(tracing::Level::DEBUG) .with_target(false) .init(); // ANCHOR_END: logging From ea4f641e363101f0ab3ab999553796d3c04f47fb Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 20:55:10 +0100 Subject: [PATCH 021/122] fix: Implement customFC type in server-side request --- rodbus/src/server/handler.rs | 4 ++-- rodbus/src/server/request.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 0f460c05..813c5db0 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -69,7 +69,7 @@ pub trait RequestHandler: Send + 'static { } /// Write a custom function code - fn write_custom_function_code(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, _value: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } } @@ -246,7 +246,7 @@ pub trait AuthorizationHandler: Send + Sync + 'static { } /// Authorize a Write Custom Function Code request - fn write_custom_function_code(&self, _value: Indexed, _role: &str) -> Authorization { + fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Deny } } diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 79854651..0b34d196 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -22,7 +22,7 @@ pub(crate) enum Request<'a> { WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), WriteMultipleRegisters(WriteRegisters<'a>), - WriteCustomFunctionCode(Indexed), + WriteCustomFunctionCode(CustomFunctionCode), } /// All requests that support broadcast @@ -222,7 +222,7 @@ impl<'a> Request<'a> { } FunctionCode::WriteCustomFunctionCode => { let x = - Request::WriteCustomFunctionCode(Indexed::::parse(cursor)?); + Request::WriteCustomFunctionCode(CustomFunctionCode::parse(cursor)?); cursor.expect_empty()?; Ok(x) } From debeaa1f2efb6c4177b39a4cd603126e508f463c Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 20:57:07 +0100 Subject: [PATCH 022/122] feat: Implement custom function code parser --- rodbus/src/common/parse.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 8b6dce04..8e705815 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -1,6 +1,6 @@ use crate::common::traits::Parse; use crate::error::*; -use crate::types::{coil_from_u16, AddressRange, Indexed}; +use crate::types::{coil_from_u16, AddressRange, Indexed, CustomFunctionCode}; use scursor::ReadCursor; @@ -28,6 +28,17 @@ impl Parse for Indexed { } } +impl Parse for CustomFunctionCode { + fn parse(cursor: &mut ReadCursor) -> Result { + let len = cursor.read_u16_be()? as usize; + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(cursor.read_u16_be()?); + } + Ok(CustomFunctionCode::new(vec)) // Construct a U16Vec from the parsed vector + } +} + #[cfg(test)] mod coils { use crate::common::traits::Parse; From 9a9eaf460aa3dd12fea5d52c6cc337acb82d53da Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 22 Jan 2024 21:15:45 +0100 Subject: [PATCH 023/122] feat: Implement Serialize & Loggable traits for CustomFunctionCode --- rodbus/src/common/serialize.rs | 45 +++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 905cfe01..db6a00f7 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -8,7 +8,7 @@ use crate::error::{InternalError, RequestError}; use crate::server::response::{BitWriter, RegisterWriter}; use crate::types::{ coil_from_u16, coil_to_u16, AddressRange, BitIterator, BitIteratorDisplay, Indexed, - RegisterIterator, RegisterIteratorDisplay, + RegisterIterator, RegisterIteratorDisplay, CustomFunctionCode, }; use scursor::{ReadCursor, WriteCursor}; @@ -290,6 +290,49 @@ impl Serialize for WriteMultiple { } } +impl Serialize for CustomFunctionCode { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.len() as u16)?; + + for &item in self.iter() { + cursor.write_u16_be(item)?; + } + Ok(()) + } +} + +impl Loggable for CustomFunctionCode { + fn log( + &self, + payload: &[u8], + level: crate::decode::AppDecodeLevel, + f: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + if level.data_headers() { + let mut cursor = ReadCursor::new(payload); + + let len = match cursor.read_u16_be() { + Ok(len) => len, + Err(_) => return Ok(()), + }; + + let mut vec = Vec::with_capacity(len as usize); + for _ in 0..len { + match cursor.read_u16_be() { + Ok(value) => vec.push(value), + Err(_) => return Ok(()), + } + } + + let value = CustomFunctionCode::new(vec); + + write!(f, "{value}")?; + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; From c1d5e68484c6d51d723b80a4b6b12f34c6ec2e3e Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 23 Jan 2024 19:54:36 +0100 Subject: [PATCH 024/122] fix: Replace Vec with a fixed-size array for now, to work around the server-side request error (missing Copy trait) --- rodbus/examples/client.rs | 4 ++-- rodbus/src/client/requests/write_custom_fc.rs | 16 ++++++++++------ rodbus/src/common/parse.rs | 13 +++++++++++-- rodbus/src/common/serialize.rs | 13 +++++++++++-- rodbus/src/types.rs | 8 ++++---- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index afcbe682..067557f4 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -283,10 +283,10 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: write_custom_function_code - let result: Result = channel + let result = channel .write_custom_function_code( params, - CustomFunctionCode::new(vec![0x01, 0x02, 0x03, 0x04]) + CustomFunctionCode::new([0x01, 0x02, 0x03, 0x04]) ) .await; print_write_result(result); diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs index 5a46e396..3f359498 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -77,11 +77,15 @@ impl WriteCustomFCOperation for CustomFunctionCode { } fn parse(cursor: &mut ReadCursor) -> Result { - let len = cursor.read_u16_be()? as usize; - let mut vec = Vec::with_capacity(len); - for _ in 0..len { - vec.push(cursor.read_u16_be()?); - } - Ok(CustomFunctionCode::new(vec)) + //let len = cursor.read_u16_be()? as usize; + + let val1 = cursor.read_u16_be()?; + let val2 = cursor.read_u16_be()?; + let val3 = cursor.read_u16_be()?; + let val4 = cursor.read_u16_be()?; + + let values = [val1, val2, val3, val4]; + + Ok(CustomFunctionCode::new(values)) } } diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 8e705815..4bcbb790 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -30,12 +30,21 @@ impl Parse for Indexed { impl Parse for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { - let len = cursor.read_u16_be()? as usize; + /*let len = cursor.read_u16_be()? as usize; let mut vec = Vec::with_capacity(len); for _ in 0..len { vec.push(cursor.read_u16_be()?); } - Ok(CustomFunctionCode::new(vec)) // Construct a U16Vec from the parsed vector + Ok(CustomFunctionCode::new(vec)) // Construct a U16Vec from the parsed vector*/ + + let val1 = cursor.read_u16_be()?; + let val2 = cursor.read_u16_be()?; + let val3 = cursor.read_u16_be()?; + let val4 = cursor.read_u16_be()?; + + let values = [val1, val2, val3, val4]; + + Ok(CustomFunctionCode::new(values)) } } diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index db6a00f7..3c9e3c39 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -311,7 +311,7 @@ impl Loggable for CustomFunctionCode { if level.data_headers() { let mut cursor = ReadCursor::new(payload); - let len = match cursor.read_u16_be() { + /*let len = match cursor.read_u16_be() { Ok(len) => len, Err(_) => return Ok(()), }; @@ -322,9 +322,18 @@ impl Loggable for CustomFunctionCode { Ok(value) => vec.push(value), Err(_) => return Ok(()), } + }*/ + + let mut data = [0_u16; 4]; + + for i in 0..4 { + data[i] = match cursor.read_u16_be() { + Ok(value) => value, + Err(_) => return Ok(()), + }; } - let value = CustomFunctionCode::new(vec); + let value = CustomFunctionCode::new(data); write!(f, "{value}")?; } diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index cd0b0bbe..dfc832b1 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -86,9 +86,9 @@ pub(crate) struct RegisterIteratorDisplay<'a> { } /// Custom buffer -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Copy, PartialEq)] pub struct CustomFunctionCode { - data: Vec, + data: [u16; 4], } impl std::fmt::Display for UnitId { @@ -375,7 +375,7 @@ impl Default for UnitId { impl CustomFunctionCode { /// Create a new custom function code - pub fn new(data: Vec) -> Self { + pub fn new(data: [u16; 4]) -> Self { Self { data } } @@ -392,7 +392,7 @@ impl CustomFunctionCode { impl std::fmt::Display for CustomFunctionCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; + write!(f, "value: [")?; for (i, val) in self.data.iter().enumerate() { if i != 0 { write!(f, ", ")?; From d71bf865f11e22c27ea6b25d8a89b16ab28e9342 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 23 Jan 2024 20:34:57 +0100 Subject: [PATCH 025/122] fix: Implement missing customFC request in server-side request handling --- rodbus/examples/server.rs | 6 ++++++ rodbus/src/server/handler.rs | 2 +- rodbus/src/types.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 6c8815e8..6efee4a9 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -92,6 +92,12 @@ impl RequestHandler for SimpleHandler { } } + fn write_custom_function_code(&self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + tracing::info!("processing custom function code, {:?}", values); + + Ok(()) + } + fn write_single_register(&mut self, value: Indexed) -> Result<(), ExceptionCode> { tracing::info!( "write single register, index: {} value: {}", diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 813c5db0..36496cfa 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -69,7 +69,7 @@ pub trait RequestHandler: Send + 'static { } /// Write a custom function code - fn write_custom_function_code(&mut self, _value: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } } diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index dfc832b1..205d709e 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -392,7 +392,7 @@ impl CustomFunctionCode { impl std::fmt::Display for CustomFunctionCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "value: [")?; + write!(f, "values: [")?; for (i, val) in self.data.iter().enumerate() { if i != 0 { write!(f, ", ")?; From 5bd36f17c2a1f84710533a20936a59fdee96320a Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 24 Jan 2024 14:24:39 +0100 Subject: [PATCH 026/122] fix: Implement correct CustomFunctionCode value parsing --- rodbus/src/client/requests/write_custom_fc.rs | 1 + rodbus/src/common/parse.rs | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs index 3f359498..6247b0e4 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -78,6 +78,7 @@ impl WriteCustomFCOperation for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { //let len = cursor.read_u16_be()? as usize; + cursor.read_u16_be()?; // Skip the length field let val1 = cursor.read_u16_be()?; let val2 = cursor.read_u16_be()?; diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 4bcbb790..0c22d8ae 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -30,12 +30,8 @@ impl Parse for Indexed { impl Parse for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { - /*let len = cursor.read_u16_be()? as usize; - let mut vec = Vec::with_capacity(len); - for _ in 0..len { - vec.push(cursor.read_u16_be()?); - } - Ok(CustomFunctionCode::new(vec)) // Construct a U16Vec from the parsed vector*/ + //let len = cursor.read_u16_be()? as usize; + cursor.read_u16_be()?; // Skip the length field let val1 = cursor.read_u16_be()?; let val2 = cursor.read_u16_be()?; From bff9685ddecdc0c3a74870bb09c16b91ce91deac Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 24 Jan 2024 17:45:27 +0100 Subject: [PATCH 027/122] fix: Implement customFC length attribute --- rodbus/src/client/requests/write_custom_fc.rs | 5 ++--- rodbus/src/common/parse.rs | 5 ++--- rodbus/src/common/serialize.rs | 2 +- rodbus/src/types.rs | 5 +++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs index 6247b0e4..c3b3abbd 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -77,8 +77,7 @@ impl WriteCustomFCOperation for CustomFunctionCode { } fn parse(cursor: &mut ReadCursor) -> Result { - //let len = cursor.read_u16_be()? as usize; - cursor.read_u16_be()?; // Skip the length field + let len = cursor.read_u16_be()? as usize; let val1 = cursor.read_u16_be()?; let val2 = cursor.read_u16_be()?; @@ -87,6 +86,6 @@ impl WriteCustomFCOperation for CustomFunctionCode { let values = [val1, val2, val3, val4]; - Ok(CustomFunctionCode::new(values)) + Ok(CustomFunctionCode::new(len, values)) } } diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 0c22d8ae..e9d767e1 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -30,8 +30,7 @@ impl Parse for Indexed { impl Parse for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { - //let len = cursor.read_u16_be()? as usize; - cursor.read_u16_be()?; // Skip the length field + let len = cursor.read_u16_be()? as usize; let val1 = cursor.read_u16_be()?; let val2 = cursor.read_u16_be()?; @@ -40,7 +39,7 @@ impl Parse for CustomFunctionCode { let values = [val1, val2, val3, val4]; - Ok(CustomFunctionCode::new(values)) + Ok(CustomFunctionCode::new(len, values)) } } diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 3c9e3c39..077f5825 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -333,7 +333,7 @@ impl Loggable for CustomFunctionCode { }; } - let value = CustomFunctionCode::new(data); + let value = CustomFunctionCode::new(0x04, data); write!(f, "{value}")?; } diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 205d709e..1ec44898 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -88,6 +88,7 @@ pub(crate) struct RegisterIteratorDisplay<'a> { /// Custom buffer #[derive(Clone, Debug, Copy, PartialEq)] pub struct CustomFunctionCode { + len: usize, data: [u16; 4], } @@ -375,8 +376,8 @@ impl Default for UnitId { impl CustomFunctionCode { /// Create a new custom function code - pub fn new(data: [u16; 4]) -> Self { - Self { data } + pub fn new(len: usize, data: [u16; 4]) -> Self { + Self { len, data } } /// Get the length of the underlying vector From 8954e364fb38d0a3f1076ab27abe94123ca90d19 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 24 Jan 2024 18:11:33 +0100 Subject: [PATCH 028/122] feat: Improved server and client examples for custom function code feature --- rodbus/examples/client.rs | 6 ++++-- rodbus/examples/server.rs | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 067557f4..57817776 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -198,7 +198,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Result<(), Box { // ANCHOR: write_custom_function_code + let length = 0x04 as usize; + let values = [0x2, 0x3, 0x4, 0x5]; let result = channel .write_custom_function_code( params, - CustomFunctionCode::new([0x01, 0x02, 0x03, 0x04]) + CustomFunctionCode::new(length, values) ) .await; print_write_result(result); diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 6efee4a9..aed1da88 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -93,7 +93,11 @@ impl RequestHandler for SimpleHandler { } fn write_custom_function_code(&self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { - tracing::info!("processing custom function code, {:?}", values); + let mut custom_fc_args = [0_u16; 4]; + for (i, &value) in values.iter().enumerate() { + custom_fc_args[i] = value; + } + tracing::info!("processing custom function code values: {:?}", custom_fc_args); Ok(()) } From 2719e7499fe25add940c693682d52e5ca1417041 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 24 Jan 2024 18:13:33 +0100 Subject: [PATCH 029/122] chore: Improve type naming of the custom function code feature --- rodbus/src/client/channel.rs | 4 ++-- rodbus/src/client/message.rs | 4 ++-- rodbus/src/client/requests/write_custom_fc.rs | 13 ++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index dc1d0a03..80737db0 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -6,11 +6,11 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; +use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; use crate::DecodeLevel; -use super::requests::write_custom_fc::WriteCustomFC; /// Async channel used to make requests #[derive(Debug, Clone)] @@ -190,7 +190,7 @@ impl Channel { let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, - RequestDetails::WriteCustomFunctionCode(WriteCustomFC::new(request, Promise::channel(tx))), + RequestDetails::WriteCustomFunctionCode(WriteCustomFunctionCode::new(request, Promise::channel(tx))), ); self.tx.send(request).await?; rx.await? diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index e581fecc..e4238b31 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -11,7 +11,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; -use crate::client::requests::write_custom_fc::WriteCustomFC; +use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -48,7 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), - WriteCustomFunctionCode(WriteCustomFC), + WriteCustomFunctionCode(WriteCustomFunctionCode), } impl Request { diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs index c3b3abbd..5bb7f9fa 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -9,22 +9,22 @@ use crate::error::RequestError; use scursor::{ReadCursor, WriteCursor}; -pub(crate) trait WriteCustomFCOperation: Sized + PartialEq { +pub(crate) trait WriteCustomFunctionCodeOperation: Sized + PartialEq { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; fn parse(cursor: &mut ReadCursor) -> Result; } -pub(crate) struct WriteCustomFC +pub(crate) struct WriteCustomFunctionCode where - T: WriteCustomFCOperation + Display + Send + 'static, + T: WriteCustomFunctionCodeOperation + Display + Send + 'static, { pub(crate) request: T, promise: Promise, } -impl WriteCustomFC +impl WriteCustomFunctionCode where - T: WriteCustomFCOperation + Display + Send + 'static, + T: WriteCustomFunctionCodeOperation + Display + Send + 'static, { pub(crate) fn new(request: T, promise: Promise) -> Self { Self { request, promise } @@ -66,7 +66,7 @@ where } } -impl WriteCustomFCOperation for CustomFunctionCode { +impl WriteCustomFunctionCodeOperation for CustomFunctionCode { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { cursor.write_u16_be(self.len() as u16)?; @@ -78,7 +78,6 @@ impl WriteCustomFCOperation for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { let len = cursor.read_u16_be()? as usize; - let val1 = cursor.read_u16_be()?; let val2 = cursor.read_u16_be()?; let val3 = cursor.read_u16_be()?; From be8eba72e09f6ae6a9886eee4ebefb0fafbd6678 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 24 Jan 2024 18:59:26 +0100 Subject: [PATCH 030/122] chore: Improve server and client examples for write custom function code feature --- rodbus/examples/client.rs | 5 +++-- rodbus/examples/server.rs | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 57817776..3acdc0c9 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -199,7 +199,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Result<(), Box { // ANCHOR: write_custom_function_code let length = 0x04 as usize; - let values = [0x2, 0x3, 0x4, 0x5]; + let values = [0x2, 0x3, 0x4, 0x5]; // i.e.: Voltage Hi = 0x02 / Voltage Lo = 0x03 / Current Hi = 0x04 / Current Lo = 0x05 + let result = channel .write_custom_function_code( params, diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index aed1da88..781c4157 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -93,11 +93,11 @@ impl RequestHandler for SimpleHandler { } fn write_custom_function_code(&self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { - let mut custom_fc_args = [0_u16; 4]; + let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { custom_fc_args[i] = value; } - tracing::info!("processing custom function code values: {:?}", custom_fc_args); + tracing::info!("processing custom function code arguments: {:?}", custom_fc_args); Ok(()) } @@ -155,7 +155,7 @@ impl RequestHandler for SimpleHandler { async fn main() -> Result<(), Box> { // initialize logging tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) + .with_max_level(tracing::Level::DEBUG) .with_target(false) .init(); @@ -305,8 +305,8 @@ async fn run_server( server .set_decode_level(DecodeLevel::new( AppDecodeLevel::DataValues, - FrameDecodeLevel::Header, - PhysDecodeLevel::Length, + FrameDecodeLevel::Payload, + PhysDecodeLevel::Data, )) .await?; } From 6438e99fc01006bfe28d21aab051c092b097304d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 17:32:25 +0100 Subject: [PATCH 031/122] chore: Set server and client port in examples >1024 for non-root linux users --- rodbus/examples/client.rs | 4 ++-- rodbus/examples/server.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 3acdc0c9..65843496 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -96,7 +96,7 @@ async fn run_rtu() -> Result<(), Box> { async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box> { // ANCHOR: create_tls_channel let channel = spawn_tls_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 802), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 1802), 1, default_retry_strategy(), tls_config, @@ -178,7 +178,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Result<(), Box Date: Thu, 25 Jan 2024 17:33:36 +0100 Subject: [PATCH 032/122] fix: Authorize Write Custom Function Code Request in server handler (required for TLS communication) --- rodbus/src/server/handler.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 36496cfa..f4a347a6 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -327,6 +327,11 @@ impl AuthorizationHandler for ReadOnlyAuthorizationHandler { ) -> Authorization { Authorization::Deny } + + /// Authorize a Write Custom Function Code request + fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { + Authorization::Allow + } } #[cfg(test)] From 3504b1d19b2cbb1969797e4698d4ae0cbe429fd9 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 17:35:52 +0100 Subject: [PATCH 033/122] fix: Correct logged offset server PDU TX values for the Custom Function Code request --- rodbus/src/common/serialize.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 077f5825..3e751de7 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -311,21 +311,13 @@ impl Loggable for CustomFunctionCode { if level.data_headers() { let mut cursor = ReadCursor::new(payload); - /*let len = match cursor.read_u16_be() { - Ok(len) => len, + let len = match cursor.read_u16_be() { + Ok(value) => value as usize, Err(_) => return Ok(()), }; - let mut vec = Vec::with_capacity(len as usize); - for _ in 0..len { - match cursor.read_u16_be() { - Ok(value) => vec.push(value), - Err(_) => return Ok(()), - } - }*/ - let mut data = [0_u16; 4]; - + for i in 0..4 { data[i] = match cursor.read_u16_be() { Ok(value) => value, @@ -333,9 +325,10 @@ impl Loggable for CustomFunctionCode { }; } - let value = CustomFunctionCode::new(0x04, data); + let custom_fc = CustomFunctionCode::new(len, data); + + write!(f, "{:?}", custom_fc)?; - write!(f, "{value}")?; } Ok(()) From 21413afd958e2e22f4bf06926e636679887fc0ac Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 19:09:53 +0100 Subject: [PATCH 034/122] test: Add Custom Function Code feature parsing unit test --- rodbus/src/common/parse.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index e9d767e1..b5faa97d 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -78,4 +78,11 @@ mod coils { let result = Indexed::::parse(&mut cursor); assert_eq!(result, Ok(Indexed::new(1, 0xCAFE))); } + + #[test] + fn parse_succeeds_for_valid_custom_function_code() { + let mut cursor = ReadCursor::new(&[0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); + let result = crate::types::CustomFunctionCode::parse(&mut cursor); + assert_eq!(result, Ok(crate::types::CustomFunctionCode::new(4, [0xCAFE, 0xC0DE, 0xCAFE, 0xC0DE]))); + } } From 570aefb856e6bd0952b32122fe02e357d4c409c6 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 19:17:54 +0100 Subject: [PATCH 035/122] test: Add Custom Function Code parsing unit test for invalid values --- rodbus/src/common/parse.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index b5faa97d..272ca1e4 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -85,4 +85,11 @@ mod coils { let result = crate::types::CustomFunctionCode::parse(&mut cursor); assert_eq!(result, Ok(crate::types::CustomFunctionCode::new(4, [0xCAFE, 0xC0DE, 0xCAFE, 0xC0DE]))); } + + #[test] + fn parse_fails_for_invalid_custom_function_code() { + let mut cursor = ReadCursor::new(&[0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0]); + let result = crate::types::CustomFunctionCode::parse(&mut cursor); + assert_eq!(result, Err(AduParseError::InsufficientBytes.into())); + } } From 74794615a8e47df01441c3bbc17cfedf225eb3b8 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 19:23:55 +0100 Subject: [PATCH 036/122] test: Add Custom Function Code serializing unit test --- rodbus/src/common/serialize.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 3e751de7..9bf8b7c7 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -347,4 +347,13 @@ mod tests { range.serialize(&mut cursor).unwrap(); assert_eq!(buffer, [0x00, 0x03, 0x02, 0x00]); } + + #[test] + fn serializes_valid_custom_function_code() { + let custom_fc = CustomFunctionCode::new(4, [0xCAFE, 0xC0DE, 0xCAFE, 0xC0DE]); + let mut buffer = [0u8; 10]; + let mut cursor = WriteCursor::new(&mut buffer); + custom_fc.serialize(&mut cursor).unwrap(); + assert_eq!(buffer, [0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); + } } From 7248688eb2baf0f3855ab6903d519dd40973d051 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 25 Jan 2024 20:05:31 +0100 Subject: [PATCH 037/122] test: Add Custom Function Code integration test --- rodbus/src/types.rs | 2 +- rodbus/tests/integration_test.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 1ec44898..a952384d 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -85,7 +85,7 @@ pub(crate) struct RegisterIteratorDisplay<'a> { level: AppDecodeLevel, } -/// Custom buffer +/// Custom Function Code #[derive(Clone, Debug, Copy, PartialEq)] pub struct CustomFunctionCode { len: usize, diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index d49b9ca5..40ff421d 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -222,6 +222,13 @@ async fn test_requests_and_responses() { Indexed::new(2, 0x0506) ] ); + assert_eq!( + channel + .write_custom_function_code(params, CustomFunctionCode::new(4, [0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE])) + .await + .unwrap(), + CustomFunctionCode::new(4, [0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE]) + ) } #[test] From 116c97b18e5f46d210c85fdcb90c385c38dc7924 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:13:30 +0100 Subject: [PATCH 038/122] refactor: Improve client example script for the Custom Function Code feature --- rodbus/examples/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 65843496..3914090f 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -284,7 +284,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: write_custom_function_code let length = 0x04 as usize; - let values = [0x2, 0x3, 0x4, 0x5]; // i.e.: Voltage Hi = 0x02 / Voltage Lo = 0x03 / Current Hi = 0x04 / Current Lo = 0x05 + let values = [0xC0, 0xDE, 0xCA, 0xFE]; // i.e.: Voltage Hi = 0xC0 / Voltage Lo = 0xDE / Current Hi = 0xCA / Current Lo = 0xFE let result = channel .write_custom_function_code( From 51cd99908f7b852e5619c91923fe7c1c51d34f9a Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:14:14 +0100 Subject: [PATCH 039/122] test: Add integration test for the Custom Function Code feature --- rodbus/tests/integration_test.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index 40ff421d..00b23664 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -13,6 +13,7 @@ struct Handler { pub discrete_inputs: [bool; 10], pub holding_registers: [u16; 10], pub input_registers: [u16; 10], + pub custom_function_code: [u16; 5], } impl Handler { @@ -22,6 +23,7 @@ impl Handler { discrete_inputs: [false; 10], holding_registers: [0; 10], input_registers: [0; 10], + custom_function_code: [0; 5], } } } @@ -94,7 +96,17 @@ impl RequestHandler for Handler { } Ok(()) } -} + + fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + for (i, &value) in values.iter().enumerate() { + match self.custom_function_code.get_mut(i) { + Some(c) => *c = value, + None => return Err(ExceptionCode::IllegalDataAddress), + } + } + Ok(()) + } + } async fn test_requests_and_responses() { let handler = Handler::new().wrap(); @@ -224,11 +236,11 @@ async fn test_requests_and_responses() { ); assert_eq!( channel - .write_custom_function_code(params, CustomFunctionCode::new(4, [0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE])) + .write_custom_function_code(params, CustomFunctionCode::new(0x04, [0xC0, 0xDE, 0xCA, 0xFE])) .await .unwrap(), - CustomFunctionCode::new(4, [0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE]) - ) + CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) + ); } #[test] From e2c80ceaa57f6010cbaeb80897e6f2376c76b97f Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:15:07 +0100 Subject: [PATCH 040/122] fix: Resolve immutable server handler value for the Custom Function Code request --- rodbus/src/server/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index f4a347a6..d189e8aa 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -69,7 +69,7 @@ pub trait RequestHandler: Send + 'static { } /// Write a custom function code - fn write_custom_function_code(&self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } } From 15dd7e612e51e1901c2971e1eedf61a3c0163594 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:20:39 +0100 Subject: [PATCH 041/122] refactor(client): code cleanup --- rodbus/examples/client.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 3914090f..f0841854 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box> { // ANCHOR: logging tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) + .with_max_level(tracing::Level::INFO) .with_target(false) .init(); // ANCHOR_END: logging @@ -61,7 +61,7 @@ where async fn run_tcp() -> Result<(), Box> { // ANCHOR: create_tcp_channel let channel = spawn_tcp_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 1502), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 502), 1, default_retry_strategy(), DecodeLevel::default(), @@ -96,7 +96,7 @@ async fn run_rtu() -> Result<(), Box> { async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box> { // ANCHOR: create_tls_channel let channel = spawn_tls_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 1802), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 802), 1, default_retry_strategy(), tls_config, @@ -178,7 +178,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Result<(), Box Result<(), Box { - // ANCHOR: read_custom_function_code - println!("read success"); - // ANCHOR_END: read_custom_function_code - } _ => println!("unknown command"), } } From 6e4f06848a6e3248048ba1a763eb7665968addb0 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:23:03 +0100 Subject: [PATCH 042/122] refactor(server): code cleanup --- rodbus/examples/server.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 102f06e3..7fbaf4e2 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -92,7 +92,7 @@ impl RequestHandler for SimpleHandler { } } - fn write_custom_function_code(&self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { custom_fc_args[i] = value; @@ -155,7 +155,7 @@ impl RequestHandler for SimpleHandler { async fn main() -> Result<(), Box> { // initialize logging tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) + .with_max_level(tracing::Level::INFO) .with_target(false) .init(); @@ -191,7 +191,7 @@ async fn run_tcp() -> Result<(), Box> { // ANCHOR: tcp_server_create let server = rodbus::server::spawn_tcp_server_task( 1, - "127.0.0.1:1502".parse()?, + "127.0.0.1:502".parse()?, map, AddressFilter::Any, DecodeLevel::default(), @@ -230,7 +230,7 @@ async fn run_tls(tls_config: TlsServerConfig) -> Result<(), Box Date: Fri, 26 Jan 2024 17:26:16 +0100 Subject: [PATCH 043/122] refactor(parse): code cleanup --- rodbus/src/common/parse.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 272ca1e4..73e7bcc3 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -30,14 +30,8 @@ impl Parse for Indexed { impl Parse for CustomFunctionCode { fn parse(cursor: &mut ReadCursor) -> Result { - let len = cursor.read_u16_be()? as usize; - - let val1 = cursor.read_u16_be()?; - let val2 = cursor.read_u16_be()?; - let val3 = cursor.read_u16_be()?; - let val4 = cursor.read_u16_be()?; - - let values = [val1, val2, val3, val4]; + let len = cursor.read_u16_be()? as usize; + let values = [cursor.read_u16_be()?, cursor.read_u16_be()?, cursor.read_u16_be()?, cursor.read_u16_be()?]; Ok(CustomFunctionCode::new(len, values)) } From 81f6188fe6dbd6da1400c228f537f7525c313a2d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 26 Jan 2024 17:44:21 +0100 Subject: [PATCH 044/122] docs(write_custom_function_code): Add documentation for write custom function code feature --- rodbus/WCFC_README.md | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 rodbus/WCFC_README.md diff --git a/rodbus/WCFC_README.md b/rodbus/WCFC_README.md new file mode 100644 index 00000000..1867d359 --- /dev/null +++ b/rodbus/WCFC_README.md @@ -0,0 +1,64 @@ +# 69 (0x45) Write Custom Function Code + +This document provides a detailed overview of the custom function code (0x45) used in the MODBUS Application Protocol. This function code is user-defined and falls within the range of 65 to 72, as specified in the MODBUS Application Protocol Specification V1.1b3 (Page 10, Section 5: Function Code Categories). + + +## Introduction +The 0x45 function code enables the implementation of user-defined logic on a remote server device. It facilitates the transmission, reception, and processing of a custom function code with a fixed-size data buffer. This buffer currently supports 4 arguments, each 2 bytes (u16) in size, allowing for the execution of custom logic remotely. + +**Note:** To increase flexibility, support for a variable-length data buffer will be included in a future update. + + +## Request Structure +| Parameter | Size | Range / Value | +|--------------------|----------|-----------------------| +| Function code | 1 Byte | 0x45 | +| Length | 2 Bytes | 0x0004 | +| Data | 8 Bytes | 0x0000 to 0xFFFF | + + +## Response Structure +| Parameter | Size | Value/Description | +|---------------|---------|----------------------| +| Function code | 1 Byte | 0x45 | +| Length | 2 Bytes | 0x0004 | +| Data | 8 Bytes | 0x0000 to 0xFFFF | + + +## Error Handling +| Parameter | Size | Description | +|----------------|---------|-----------------------------------| +| Function code | 1 Byte | Function code + 0x80 = 0xC5 (197) | +| Exception code | 1 Byte | 01 or 02 or 03 or 04 | + +### Error Codes: +- **01**: Illegal Function +- **02**: Illegal Data Address +- **03**: Illegal Data Value +- **04**: Server Device Failure + + +## Usage Example +### Request to send the custom buffer [0xC0, 0xDE, 0xCA, 0xFE]: + +| Request Field | Hex | Response Field | Hex | +|---------------------------|-----|------------------------|-----| +| Function | 45 | Function | 45 | +| Length | 04 | Byte Count | 04 | +| Arg1 | C0 | Arg1 | C0 | +| Arg2 | DE | Arg2 | DE | +| Arg3 | CA | Arg3 | CA | +| Arg4 | FE | Arg4 | FE | + + +## Modify and Test Server-Side Buffer Handling +The server currently forwards the Custom Function Code buffer to the client again without alteration. To test modifying or processing the buffer on the remote server device, edit the `write_custom_function_code()` function in `src/examples/client.rs` and `src/examples/server.rs` as needed. + + +## Troubleshooting Tips +- Ensure the server and client are using the same communication and are connected to each other. +- Check for any error codes in the response and refer to the error handling section for resolution. + + +## Additional Resources +- For more information on the MODBUS protocol and function codes, refer to the [MODBUS Application Protocol Specification V1.1b3](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf). From 1926d686baeb7b8a35b6df8d2bdadf12d7a76054 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 29 Jan 2024 17:39:52 +0100 Subject: [PATCH 045/122] chore(send_cfc): Rename CFC README.me --- rodbus/{WCFC_README.md => SCFC_README.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename rodbus/{WCFC_README.md => SCFC_README.md} (93%) diff --git a/rodbus/WCFC_README.md b/rodbus/SCFC_README.md similarity index 93% rename from rodbus/WCFC_README.md rename to rodbus/SCFC_README.md index 1867d359..7e2e1692 100644 --- a/rodbus/WCFC_README.md +++ b/rodbus/SCFC_README.md @@ -1,4 +1,4 @@ -# 69 (0x45) Write Custom Function Code +# 69 (0x45) Send Custom Function Code This document provides a detailed overview of the custom function code (0x45) used in the MODBUS Application Protocol. This function code is user-defined and falls within the range of 65 to 72, as specified in the MODBUS Application Protocol Specification V1.1b3 (Page 10, Section 5: Function Code Categories). @@ -52,7 +52,7 @@ The 0x45 function code enables the implementation of user-defined logic on a rem ## Modify and Test Server-Side Buffer Handling -The server currently forwards the Custom Function Code buffer to the client again without alteration. To test modifying or processing the buffer on the remote server device, edit the `write_custom_function_code()` function in `src/examples/client.rs` and `src/examples/server.rs` as needed. +The server currently forwards the Custom Function Code buffer to the client again without alteration. To test modifying or processing the buffer on the remote server device, edit the `send_custom_function_code()` function in `src/examples/client.rs` and `src/examples/server.rs` as needed. ## Troubleshooting Tips From cf2a350fae1f09be169e54a9deda77804624167c Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 29 Jan 2024 17:43:34 +0100 Subject: [PATCH 046/122] fix(examples): Adjust TCP & TLS port for non-root Unix users --- rodbus/examples/client.rs | 4 ++-- rodbus/examples/server.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index f0841854..0d55f646 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -61,7 +61,7 @@ where async fn run_tcp() -> Result<(), Box> { // ANCHOR: create_tcp_channel let channel = spawn_tcp_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 502), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10502), 1, default_retry_strategy(), DecodeLevel::default(), @@ -96,7 +96,7 @@ async fn run_rtu() -> Result<(), Box> { async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box> { // ANCHOR: create_tls_channel let channel = spawn_tls_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 802), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10802), 1, default_retry_strategy(), tls_config, diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 7fbaf4e2..897f6859 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -191,7 +191,7 @@ async fn run_tcp() -> Result<(), Box> { // ANCHOR: tcp_server_create let server = rodbus::server::spawn_tcp_server_task( 1, - "127.0.0.1:502".parse()?, + "127.0.0.1:10502".parse()?, map, AddressFilter::Any, DecodeLevel::default(), @@ -230,7 +230,7 @@ async fn run_tls(tls_config: TlsServerConfig) -> Result<(), Box Date: Mon, 29 Jan 2024 19:59:10 +0100 Subject: [PATCH 047/122] refactor(custom_fc): Rename WriteCustomFC to SendCustomFC --- rodbus/examples/client.rs | 8 ++++---- rodbus/examples/server.rs | 2 +- rodbus/src/client/channel.rs | 8 ++++---- rodbus/src/client/message.rs | 14 +++++++------- rodbus/src/client/requests/mod.rs | 2 +- .../{write_custom_fc.rs => send_custom_fc.rs} | 12 ++++++------ rodbus/src/common/function.rs | 10 +++++----- rodbus/src/serial/frame.rs | 4 ++-- rodbus/src/server/handler.rs | 10 +++++----- rodbus/src/server/request.rs | 16 ++++++++-------- rodbus/src/server/task.rs | 2 +- rodbus/tests/integration_test.rs | 4 ++-- 12 files changed, 46 insertions(+), 46 deletions(-) rename rodbus/src/client/requests/{write_custom_fc.rs => send_custom_fc.rs} (86%) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 0d55f646..4292b649 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -281,19 +281,19 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { - // ANCHOR: write_custom_function_code + "scfc" => { + // ANCHOR: send_custom_function_code let length = 0x04 as usize; let values = [0xC0, 0xDE, 0xCA, 0xFE]; // i.e.: Voltage Hi = 0xC0 / Voltage Lo = 0xDE / Current Hi = 0xCA / Current Lo = 0xFE let result = channel - .write_custom_function_code( + .send_custom_function_code( params, CustomFunctionCode::new(length, values) ) .await; print_write_result(result); - // ANCHOR_END: write_custom_function_code + // ANCHOR_END: send_custom_function_code } _ => println!("unknown command"), } diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 897f6859..eace3f4f 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -92,7 +92,7 @@ impl RequestHandler for SimpleHandler { } } - fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { custom_fc_args[i] = value; diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 80737db0..05726bdb 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -6,7 +6,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; -use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; +use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; use crate::DecodeLevel; @@ -181,8 +181,8 @@ impl Channel { rx.await? } - /// Write a Custom Function Code to the server - pub async fn write_custom_function_code( + /// Send a Custom Function Code to the server + pub async fn send_custom_function_code( &mut self, param: RequestParam, request: CustomFunctionCode, @@ -190,7 +190,7 @@ impl Channel { let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, - RequestDetails::WriteCustomFunctionCode(WriteCustomFunctionCode::new(request, Promise::channel(tx))), + RequestDetails::SendCustomFunctionCode(CustomFCRequest::new(request, Promise::channel(tx))), ); self.tx.send(request).await?; rx.await? diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index e4238b31..8ee4db5d 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -11,7 +11,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; -use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; +use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -48,7 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), - WriteCustomFunctionCode(WriteCustomFunctionCode), + SendCustomFunctionCode(CustomFCRequest), } impl Request { @@ -134,7 +134,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, RequestDetails::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, - RequestDetails::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, + RequestDetails::SendCustomFunctionCode(_) => FunctionCode::SendCustomFunctionCode, } } @@ -149,7 +149,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), RequestDetails::WriteMultipleRegisters(x) => x.failure(err), - RequestDetails::WriteCustomFunctionCode(x) => x.failure(err), + RequestDetails::SendCustomFunctionCode(x) => x.failure(err), } } @@ -171,7 +171,7 @@ impl RequestDetails { RequestDetails::WriteMultipleRegisters(x) => { x.handle_response(cursor, function, decode) }, - RequestDetails::WriteCustomFunctionCode(x) => { + RequestDetails::SendCustomFunctionCode(x) => { x.handle_response(cursor, function, decode) } } @@ -190,7 +190,7 @@ impl Serialize for RequestDetails { RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), RequestDetails::WriteMultipleRegisters(x) => x.serialize(cursor), - RequestDetails::WriteCustomFunctionCode(x) => x.serialize(cursor), + RequestDetails::SendCustomFunctionCode(x) => x.serialize(cursor), } } } @@ -258,7 +258,7 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } } - RequestDetails::WriteCustomFunctionCode(details) => { + RequestDetails::SendCustomFunctionCode(details) => { write!(f, "{}", details.request)?; } } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 7a2bd345..6260c59b 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -3,4 +3,4 @@ pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; pub(crate) mod send_buffer; -pub(crate) mod write_custom_fc; \ No newline at end of file +pub(crate) mod send_custom_fc; \ No newline at end of file diff --git a/rodbus/src/client/requests/write_custom_fc.rs b/rodbus/src/client/requests/send_custom_fc.rs similarity index 86% rename from rodbus/src/client/requests/write_custom_fc.rs rename to rodbus/src/client/requests/send_custom_fc.rs index 5bb7f9fa..3a7f1a61 100644 --- a/rodbus/src/client/requests/write_custom_fc.rs +++ b/rodbus/src/client/requests/send_custom_fc.rs @@ -9,22 +9,22 @@ use crate::error::RequestError; use scursor::{ReadCursor, WriteCursor}; -pub(crate) trait WriteCustomFunctionCodeOperation: Sized + PartialEq { +pub(crate) trait CustomFCOperation: Sized + PartialEq { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; fn parse(cursor: &mut ReadCursor) -> Result; } -pub(crate) struct WriteCustomFunctionCode +pub(crate) struct CustomFCRequest where - T: WriteCustomFunctionCodeOperation + Display + Send + 'static, + T: CustomFCOperation + Display + Send + 'static, { pub(crate) request: T, promise: Promise, } -impl WriteCustomFunctionCode +impl CustomFCRequest where - T: WriteCustomFunctionCodeOperation + Display + Send + 'static, + T: CustomFCOperation + Display + Send + 'static, { pub(crate) fn new(request: T, promise: Promise) -> Self { Self { request, promise } @@ -66,7 +66,7 @@ where } } -impl WriteCustomFunctionCodeOperation for CustomFunctionCode { +impl CustomFCOperation for CustomFunctionCode { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { cursor.write_u16_be(self.len() as u16)?; diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index f30ba928..8874fb41 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -10,7 +10,7 @@ mod constants { pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; - pub(crate) const WRITE_CUSTOM_FUNCTION_CODE: u8 = 69; + pub(crate) const SEND_CUSTOM_FUNCTION_CODE: u8 = 69; } #[derive(Debug, Copy, Clone, PartialEq)] @@ -25,7 +25,7 @@ pub(crate) enum FunctionCode { WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, - WriteCustomFunctionCode = constants::WRITE_CUSTOM_FUNCTION_CODE, + SendCustomFunctionCode = constants::SEND_CUSTOM_FUNCTION_CODE, } impl Display for FunctionCode { @@ -56,8 +56,8 @@ impl Display for FunctionCode { FunctionCode::SendCustomBuffers => { write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) } - FunctionCode::WriteCustomFunctionCode => { - write!(f, "WRITE CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) + FunctionCode::SendCustomFunctionCode => { + write!(f, "SEND CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } } } @@ -83,7 +83,7 @@ impl FunctionCode { constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), - constants::WRITE_CUSTOM_FUNCTION_CODE => Some(FunctionCode::WriteCustomFunctionCode), + constants::SEND_CUSTOM_FUNCTION_CODE => Some(FunctionCode::SendCustomFunctionCode), _ => None, } } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index cbe54da7..07697695 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -88,7 +88,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), FunctionCode::WriteMultipleRegisters => LengthMode::Offset(5), - FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), + FunctionCode::SendCustomFunctionCode => LengthMode::Offset(1), }, ParserType::Response => match function_code { FunctionCode::ReadCoils => LengthMode::Offset(1), @@ -100,7 +100,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), FunctionCode::WriteMultipleRegisters => LengthMode::Fixed(4), - FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), + FunctionCode::SendCustomFunctionCode => LengthMode::Offset(1), }, } } diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index d189e8aa..a660052e 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -69,7 +69,7 @@ pub trait RequestHandler: Send + 'static { } /// Write a custom function code - fn write_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn process_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } } @@ -245,8 +245,8 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } - /// Authorize a Write Custom Function Code request - fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { + /// Authorize a Send Custom Function Code request + fn process_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Deny } } @@ -328,8 +328,8 @@ impl AuthorizationHandler for ReadOnlyAuthorizationHandler { Authorization::Deny } - /// Authorize a Write Custom Function Code request - fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { + /// Authorize a Send Custom Function Code request + fn process_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Allow } } diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 0b34d196..752eb6e2 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -22,7 +22,7 @@ pub(crate) enum Request<'a> { WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), WriteMultipleRegisters(WriteRegisters<'a>), - WriteCustomFunctionCode(CustomFunctionCode), + SendCustomFunctionCode(CustomFunctionCode), } /// All requests that support broadcast @@ -67,7 +67,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, Request::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, - Request::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, + Request::SendCustomFunctionCode(_) => FunctionCode::SendCustomFunctionCode, } } @@ -82,7 +82,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), Request::WriteMultipleRegisters(x) => Some(BroadcastRequest::WriteMultipleRegisters(x)), - Request::WriteCustomFunctionCode(_) => None, + Request::SendCustomFunctionCode(_) => None, } } @@ -151,8 +151,8 @@ impl<'a> Request<'a> { .map(|_| items.range); write_result(function, header, writer, result, level) } - Request::WriteCustomFunctionCode(request) => { - let result = handler.write_custom_function_code(*request).map(|_| *request); + Request::SendCustomFunctionCode(request) => { + let result = handler.process_custom_function_code(*request).map(|_| *request); write_result(function, header, writer, result, level) } } @@ -220,9 +220,9 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } - FunctionCode::WriteCustomFunctionCode => { + FunctionCode::SendCustomFunctionCode => { let x = - Request::WriteCustomFunctionCode(CustomFunctionCode::parse(cursor)?); + Request::SendCustomFunctionCode(CustomFunctionCode::parse(cursor)?); cursor.expect_empty()?; Ok(x) } @@ -282,7 +282,7 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { RegisterIteratorDisplay::new(self.level, items.iterator) )?; } - Request::WriteCustomFunctionCode(request) => { + Request::SendCustomFunctionCode(request) => { write!(f, " {request}")?; } } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index a96164e7..8171a329 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -265,7 +265,7 @@ impl AuthorizationType { Request::WriteMultipleRegisters(x) => { handler.write_multiple_registers(unit_id, x.range, role) } - Request::WriteCustomFunctionCode(x) => handler.write_custom_function_code(*x, role), + Request::SendCustomFunctionCode(x) => handler.process_custom_function_code(*x, role), } } diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index 00b23664..0bc423cf 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -97,7 +97,7 @@ impl RequestHandler for Handler { Ok(()) } - fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { for (i, &value) in values.iter().enumerate() { match self.custom_function_code.get_mut(i) { Some(c) => *c = value, @@ -236,7 +236,7 @@ async fn test_requests_and_responses() { ); assert_eq!( channel - .write_custom_function_code(params, CustomFunctionCode::new(0x04, [0xC0, 0xDE, 0xCA, 0xFE])) + .send_custom_function_code(params, CustomFunctionCode::new(0x04, [0xC0, 0xDE, 0xCA, 0xFE])) .await .unwrap(), CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) From 70547300599bec048c5d85a4de99df79fa5ae011 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 29 Jan 2024 20:16:09 +0100 Subject: [PATCH 048/122] refactor(custom_fc): Remove SendCustomBuffer functionality (PR 2 on Iniationware/rodbus) --- rodbus/examples/client.rs | 14 ---- rodbus/examples/server.rs | 14 ---- rodbus/src/client/channel.rs | 16 ---- rodbus/src/client/message.rs | 9 --- rodbus/src/client/requests/mod.rs | 1 - rodbus/src/client/requests/send_buffer.rs | 94 ----------------------- rodbus/src/common/function.rs | 6 -- rodbus/src/serial/frame.rs | 22 +++--- rodbus/src/server/handler.rs | 13 ---- rodbus/src/server/request.rs | 16 ---- rodbus/src/server/task.rs | 1 - 11 files changed, 10 insertions(+), 196 deletions(-) delete mode 100644 rodbus/src/client/requests/send_buffer.rs diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 4292b649..a2e15b25 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -267,20 +267,6 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { - // ANCHOR: send_custom_buffer - let result = channel - .send_custom_buffer( - params, - Indexed { - index: (0x1), - value: (0xAB), - }, - ) - .await; - print_write_result(result); - // ANCHOR_END: write_multiple_registers - } "scfc" => { // ANCHOR: send_custom_function_code let length = 0x04 as usize; diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index eace3f4f..0f87c235 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -78,20 +78,6 @@ impl RequestHandler for SimpleHandler { } } - fn receive_custom_buffer(&self, value: Indexed) -> Result { - tracing::info!( - "receive custom buffer, index: {} value: {}", - value.index, - value.value - ); - - if value.value == 0xAB { - Ok(0xCD) - } else { - Err(ExceptionCode::IllegalFunction) - } - } - fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 05726bdb..9a143896 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -5,7 +5,6 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; @@ -166,21 +165,6 @@ impl Channel { rx.await? } - /// Send buffer to the server - pub async fn send_custom_buffer( - &mut self, - param: RequestParam, - request: Indexed, - ) -> Result, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); - let request = wrap( - param, - RequestDetails::SendCustomBuffers(SendBuffer::new(request, Promise::channel(tx))), - ); - self.tx.send(request).await?; - rx.await? - } - /// Send a Custom Function Code to the server pub async fn send_custom_function_code( &mut self, diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 8ee4db5d..5adb335e 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,7 +10,6 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -43,7 +42,6 @@ pub(crate) enum RequestDetails { ReadDiscreteInputs(ReadBits), ReadHoldingRegisters(ReadRegisters), ReadInputRegisters(ReadRegisters), - SendCustomBuffers(SendBuffer>), WriteSingleCoil(SingleWrite>), WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), @@ -129,7 +127,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, RequestDetails::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, RequestDetails::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, - RequestDetails::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, RequestDetails::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -144,7 +141,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.failure(err), RequestDetails::ReadHoldingRegisters(x) => x.failure(err), RequestDetails::ReadInputRegisters(x) => x.failure(err), - RequestDetails::SendCustomBuffers(x) => x.failure(err), RequestDetails::WriteSingleCoil(x) => x.failure(err), RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), @@ -164,7 +160,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadHoldingRegisters(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadInputRegisters(x) => x.handle_response(cursor, function, decode), - RequestDetails::SendCustomBuffers(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleCoil(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleRegister(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteMultipleCoils(x) => x.handle_response(cursor, function, decode), @@ -185,7 +180,6 @@ impl Serialize for RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.serialize(cursor), RequestDetails::ReadHoldingRegisters(x) => x.serialize(cursor), RequestDetails::ReadInputRegisters(x) => x.serialize(cursor), - RequestDetails::SendCustomBuffers(x) => x.serialize(cursor), RequestDetails::WriteSingleCoil(x) => x.serialize(cursor), RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), @@ -233,9 +227,6 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { RequestDetails::ReadInputRegisters(details) => { write!(f, "{}", details.request.get())?; } - RequestDetails::SendCustomBuffers(details) => { - write!(f, "{}", details.request)?; - } RequestDetails::WriteSingleCoil(details) => { write!(f, "{}", details.request)?; } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 6260c59b..2e5e306a 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -2,5 +2,4 @@ pub(crate) mod read_bits; pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; -pub(crate) mod send_buffer; pub(crate) mod send_custom_fc; \ No newline at end of file diff --git a/rodbus/src/client/requests/send_buffer.rs b/rodbus/src/client/requests/send_buffer.rs deleted file mode 100644 index 2e19d731..00000000 --- a/rodbus/src/client/requests/send_buffer.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt::Display; - -use crate::client::message::Promise; -use crate::common::function::FunctionCode; -use crate::decode::AppDecodeLevel; -use crate::error::AduParseError; -use crate::error::RequestError; -use crate::types::{coil_from_u16, coil_to_u16, Indexed}; - -use scursor::{ReadCursor, WriteCursor}; - -pub(crate) trait SendBufferOperation: Sized + PartialEq { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; - fn parse(cursor: &mut ReadCursor) -> Result; -} - -pub(crate) struct SendBuffer -where - T: SendBufferOperation + Display + Send + 'static, -{ - pub(crate) request: T, - promise: Promise, -} - -impl SendBuffer -where - T: SendBufferOperation + Display + Send + 'static, -{ - pub(crate) fn new(request: T, promise: Promise) -> Self { - Self { request, promise } - } - - pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - self.request.serialize(cursor) - } - - pub(crate) fn failure(&mut self, err: RequestError) { - self.promise.failure(err) - } - - pub(crate) fn handle_response( - &mut self, - cursor: ReadCursor, - function: FunctionCode, - decode: AppDecodeLevel, - ) -> Result<(), RequestError> { - let response = self.parse_all(cursor)?; - - if decode.data_headers() { - tracing::info!("PDU RX - {} {}", function, response); - } else if decode.header() { - tracing::info!("PDU RX - {}", function); - } - - self.promise.success(response); - Ok(()) - } - - fn parse_all(&self, mut cursor: ReadCursor) -> Result { - let response = T::parse(&mut cursor)?; - cursor.expect_empty()?; - if self.request != response { - return Err(AduParseError::ReplyEchoMismatch.into()); - } - Ok(response) - } -} - -impl SendBufferOperation for Indexed { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(coil_to_u16(self.value))?; - Ok(()) - } - - fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new( - cursor.read_u16_be()?, - coil_from_u16(cursor.read_u16_be()?)?, - )) - } -} - -impl SendBufferOperation for Indexed { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(self.value)?; - Ok(()) - } - - fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) - } -} diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index 8874fb41..d53c45b1 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -9,7 +9,6 @@ mod constants { pub(crate) const WRITE_SINGLE_REGISTER: u8 = 6; pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; - pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; pub(crate) const SEND_CUSTOM_FUNCTION_CODE: u8 = 69; } @@ -24,7 +23,6 @@ pub(crate) enum FunctionCode { WriteSingleRegister = constants::WRITE_SINGLE_REGISTER, WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, - SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, SendCustomFunctionCode = constants::SEND_CUSTOM_FUNCTION_CODE, } @@ -53,9 +51,6 @@ impl Display for FunctionCode { FunctionCode::WriteMultipleRegisters => { write!(f, "WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) } - FunctionCode::SendCustomBuffers => { - write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) - } FunctionCode::SendCustomFunctionCode => { write!(f, "SEND CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } @@ -82,7 +77,6 @@ impl FunctionCode { constants::WRITE_SINGLE_REGISTER => Some(FunctionCode::WriteSingleRegister), constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), - constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), constants::SEND_CUSTOM_FUNCTION_CODE => Some(FunctionCode::SendCustomFunctionCode), _ => None, } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index 07697695..0331fd58 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -83,7 +83,6 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Fixed(4), FunctionCode::ReadHoldingRegisters => LengthMode::Fixed(4), FunctionCode::ReadInputRegisters => LengthMode::Fixed(4), - FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), @@ -95,7 +94,6 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Offset(1), FunctionCode::ReadHoldingRegisters => LengthMode::Offset(1), FunctionCode::ReadInputRegisters => LengthMode::Offset(1), - FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), @@ -349,19 +347,19 @@ mod tests { 0x71, 0x86, // crc ]; - const SEND_CUSTOM_BUFFER_REQUEST: &[u8] = &[ + const SEND_CUSTOM_FUNCTION_CODE_REQUEST: &[u8] = &[ UNIT_ID, // unit id 0x44, // function code - 0x02, // byte count - 0x01, 0xAB, // additonal data + 0x08, // byte count (length of data) + 0xC0, 0xDE, 0xCA, 0xFE, // data 0xC8, 0xD9, // crc ]; - const SEND_CUSTOM_BUFFER_RESPONSE: &[u8] = &[ + const SEND_CUSTOM_FUNCTION_CODE_RESPONSE: &[u8] = &[ UNIT_ID, // unit id 0x44, // function code - 0x01, // byte count - 0xCD, // return value + 0x08, // byte count + 0xC0, 0xDE, 0xCA, 0xFE, // data 0x88, 0x2C, // crc ]; const WRITE_SINGLE_COIL_REQUEST: &[u8] = &[ @@ -447,8 +445,8 @@ mod tests { READ_INPUT_REGISTERS_REQUEST, ), ( - FunctionCode::SendCustomBuffers, - SEND_CUSTOM_BUFFER_REQUEST, + FunctionCode::SendCustomFunctionCode, + SEND_CUSTOM_FUNCTION_CODE_REQUEST, ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_REQUEST), ( @@ -480,8 +478,8 @@ mod tests { READ_INPUT_REGISTERS_RESPONSE, ), ( - FunctionCode::SendCustomBuffers, - SEND_CUSTOM_BUFFER_RESPONSE, + FunctionCode::SendCustomFunctionCode, + SEND_CUSTOM_FUNCTION_CODE_RESPONSE, ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_RESPONSE), ( diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index a660052e..32af45a3 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -43,11 +43,6 @@ pub trait RequestHandler: Send + 'static { Err(ExceptionCode::IllegalFunction) } - /// Read single custom buffer or return an ExceptionCode - fn receive_custom_buffer(&self, _value: Indexed) -> Result { - Err(ExceptionCode::IllegalFunction) - } - /// Write a single coil value fn write_single_coil(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) @@ -187,14 +182,6 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } - /// Authorize a Read Custom Buffer request - fn receive_custom_buffer( - &self, - _value: Indexed, - _role: &str, - ) -> Authorization { - Authorization::Deny - } /// Authorize a Read Holding Registers request fn read_holding_registers( &self, diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 752eb6e2..5df56f7f 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -17,7 +17,6 @@ pub(crate) enum Request<'a> { ReadDiscreteInputs(ReadBitsRange), ReadHoldingRegisters(ReadRegistersRange), ReadInputRegisters(ReadRegistersRange), - SendCustomBuffers(Indexed), WriteSingleCoil(Indexed), WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), @@ -62,7 +61,6 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, Request::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, Request::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, - Request::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, Request::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -77,7 +75,6 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => None, Request::ReadHoldingRegisters(_) => None, Request::ReadInputRegisters(_) => None, - Request::SendCustomBuffers(_) => None, Request::WriteSingleCoil(x) => Some(BroadcastRequest::WriteSingleCoil(x)), Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), @@ -129,10 +126,6 @@ impl<'a> Request<'a> { let registers = RegisterWriter::new(*range, |i| handler.read_input_register(i)); writer.format_reply(header, function, ®isters, level) } - Request::SendCustomBuffers(request) => { - let result = handler.receive_custom_buffer(*request).map(|_| *request); - write_result(function, header, writer, result, level) - } Request::WriteSingleCoil(request) => { let result = handler.write_single_coil(*request).map(|_| *request); write_result(function, header, writer, result, level) @@ -186,12 +179,6 @@ impl<'a> Request<'a> { cursor.expect_empty()?; Ok(x) } - FunctionCode::SendCustomBuffers => { - let x = - Request::SendCustomBuffers(Indexed::::parse(cursor)?); - cursor.expect_empty()?; - Ok(x) - } FunctionCode::WriteSingleCoil => { let x = Request::WriteSingleCoil(Indexed::::parse(cursor)?); cursor.expect_empty()?; @@ -259,9 +246,6 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { Request::ReadInputRegisters(range) => { write!(f, " {}", range.get())?; } - Request::SendCustomBuffers(request) => { - write!(f, " {request}")?; - } Request::WriteSingleCoil(request) => { write!(f, " {request}")?; } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index 8171a329..95d6f368 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -256,7 +256,6 @@ impl AuthorizationType { handler.read_holding_registers(unit_id, x.inner, role) } Request::ReadInputRegisters(x) => handler.read_input_registers(unit_id, x.inner, role), - Request::SendCustomBuffers(x) => handler.receive_custom_buffer(*x, role), Request::WriteSingleCoil(x) => handler.write_single_coil(unit_id, x.index, role), Request::WriteSingleRegister(x) => { handler.write_single_register(unit_id, x.index, role) From 699c3d4295bd9f4b6da2179640f43595cb88f00f Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 16:52:31 +0100 Subject: [PATCH 049/122] Revert "refactor(custom_fc): Remove SendCustomBuffer functionality (PR 2 on Iniationware/rodbus)" This reverts commit 70547300599bec048c5d85a4de99df79fa5ae011. --- rodbus/examples/client.rs | 14 ++++ rodbus/examples/server.rs | 14 ++++ rodbus/src/client/channel.rs | 16 ++++ rodbus/src/client/message.rs | 9 +++ rodbus/src/client/requests/mod.rs | 1 + rodbus/src/client/requests/send_buffer.rs | 94 +++++++++++++++++++++++ rodbus/src/common/function.rs | 6 ++ rodbus/src/serial/frame.rs | 22 +++--- rodbus/src/server/handler.rs | 13 ++++ rodbus/src/server/request.rs | 16 ++++ rodbus/src/server/task.rs | 1 + 11 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 rodbus/src/client/requests/send_buffer.rs diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index a2e15b25..4292b649 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -267,6 +267,20 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { + // ANCHOR: send_custom_buffer + let result = channel + .send_custom_buffer( + params, + Indexed { + index: (0x1), + value: (0xAB), + }, + ) + .await; + print_write_result(result); + // ANCHOR_END: write_multiple_registers + } "scfc" => { // ANCHOR: send_custom_function_code let length = 0x04 as usize; diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 0f87c235..eace3f4f 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -78,6 +78,20 @@ impl RequestHandler for SimpleHandler { } } + fn receive_custom_buffer(&self, value: Indexed) -> Result { + tracing::info!( + "receive custom buffer, index: {} value: {}", + value.index, + value.value + ); + + if value.value == 0xAB { + Ok(0xCD) + } else { + Err(ExceptionCode::IllegalFunction) + } + } + fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 9a143896..05726bdb 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -5,6 +5,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; @@ -165,6 +166,21 @@ impl Channel { rx.await? } + /// Send buffer to the server + pub async fn send_custom_buffer( + &mut self, + param: RequestParam, + request: Indexed, + ) -> Result, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); + let request = wrap( + param, + RequestDetails::SendCustomBuffers(SendBuffer::new(request, Promise::channel(tx))), + ); + self.tx.send(request).await?; + rx.await? + } + /// Send a Custom Function Code to the server pub async fn send_custom_function_code( &mut self, diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 5adb335e..8ee4db5d 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,6 +10,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::send_custom_fc::CustomFCRequest; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -42,6 +43,7 @@ pub(crate) enum RequestDetails { ReadDiscreteInputs(ReadBits), ReadHoldingRegisters(ReadRegisters), ReadInputRegisters(ReadRegisters), + SendCustomBuffers(SendBuffer>), WriteSingleCoil(SingleWrite>), WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), @@ -127,6 +129,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, RequestDetails::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, RequestDetails::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, + RequestDetails::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, RequestDetails::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -141,6 +144,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.failure(err), RequestDetails::ReadHoldingRegisters(x) => x.failure(err), RequestDetails::ReadInputRegisters(x) => x.failure(err), + RequestDetails::SendCustomBuffers(x) => x.failure(err), RequestDetails::WriteSingleCoil(x) => x.failure(err), RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), @@ -160,6 +164,7 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadHoldingRegisters(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadInputRegisters(x) => x.handle_response(cursor, function, decode), + RequestDetails::SendCustomBuffers(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleCoil(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleRegister(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteMultipleCoils(x) => x.handle_response(cursor, function, decode), @@ -180,6 +185,7 @@ impl Serialize for RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.serialize(cursor), RequestDetails::ReadHoldingRegisters(x) => x.serialize(cursor), RequestDetails::ReadInputRegisters(x) => x.serialize(cursor), + RequestDetails::SendCustomBuffers(x) => x.serialize(cursor), RequestDetails::WriteSingleCoil(x) => x.serialize(cursor), RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), @@ -227,6 +233,9 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { RequestDetails::ReadInputRegisters(details) => { write!(f, "{}", details.request.get())?; } + RequestDetails::SendCustomBuffers(details) => { + write!(f, "{}", details.request)?; + } RequestDetails::WriteSingleCoil(details) => { write!(f, "{}", details.request)?; } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 2e5e306a..6260c59b 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -2,4 +2,5 @@ pub(crate) mod read_bits; pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; +pub(crate) mod send_buffer; pub(crate) mod send_custom_fc; \ No newline at end of file diff --git a/rodbus/src/client/requests/send_buffer.rs b/rodbus/src/client/requests/send_buffer.rs new file mode 100644 index 00000000..2e19d731 --- /dev/null +++ b/rodbus/src/client/requests/send_buffer.rs @@ -0,0 +1,94 @@ +use std::fmt::Display; + +use crate::client::message::Promise; +use crate::common::function::FunctionCode; +use crate::decode::AppDecodeLevel; +use crate::error::AduParseError; +use crate::error::RequestError; +use crate::types::{coil_from_u16, coil_to_u16, Indexed}; + +use scursor::{ReadCursor, WriteCursor}; + +pub(crate) trait SendBufferOperation: Sized + PartialEq { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; + fn parse(cursor: &mut ReadCursor) -> Result; +} + +pub(crate) struct SendBuffer +where + T: SendBufferOperation + Display + Send + 'static, +{ + pub(crate) request: T, + promise: Promise, +} + +impl SendBuffer +where + T: SendBufferOperation + Display + Send + 'static, +{ + pub(crate) fn new(request: T, promise: Promise) -> Self { + Self { request, promise } + } + + pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.request.serialize(cursor) + } + + pub(crate) fn failure(&mut self, err: RequestError) { + self.promise.failure(err) + } + + pub(crate) fn handle_response( + &mut self, + cursor: ReadCursor, + function: FunctionCode, + decode: AppDecodeLevel, + ) -> Result<(), RequestError> { + let response = self.parse_all(cursor)?; + + if decode.data_headers() { + tracing::info!("PDU RX - {} {}", function, response); + } else if decode.header() { + tracing::info!("PDU RX - {}", function); + } + + self.promise.success(response); + Ok(()) + } + + fn parse_all(&self, mut cursor: ReadCursor) -> Result { + let response = T::parse(&mut cursor)?; + cursor.expect_empty()?; + if self.request != response { + return Err(AduParseError::ReplyEchoMismatch.into()); + } + Ok(response) + } +} + +impl SendBufferOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(coil_to_u16(self.value))?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new( + cursor.read_u16_be()?, + coil_from_u16(cursor.read_u16_be()?)?, + )) + } +} + +impl SendBufferOperation for Indexed { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + cursor.write_u16_be(self.index)?; + cursor.write_u16_be(self.value)?; + Ok(()) + } + + fn parse(cursor: &mut ReadCursor) -> Result { + Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) + } +} diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index d53c45b1..8874fb41 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -9,6 +9,7 @@ mod constants { pub(crate) const WRITE_SINGLE_REGISTER: u8 = 6; pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; + pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; pub(crate) const SEND_CUSTOM_FUNCTION_CODE: u8 = 69; } @@ -23,6 +24,7 @@ pub(crate) enum FunctionCode { WriteSingleRegister = constants::WRITE_SINGLE_REGISTER, WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, + SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, SendCustomFunctionCode = constants::SEND_CUSTOM_FUNCTION_CODE, } @@ -51,6 +53,9 @@ impl Display for FunctionCode { FunctionCode::WriteMultipleRegisters => { write!(f, "WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) } + FunctionCode::SendCustomBuffers => { + write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) + } FunctionCode::SendCustomFunctionCode => { write!(f, "SEND CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } @@ -77,6 +82,7 @@ impl FunctionCode { constants::WRITE_SINGLE_REGISTER => Some(FunctionCode::WriteSingleRegister), constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), + constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), constants::SEND_CUSTOM_FUNCTION_CODE => Some(FunctionCode::SendCustomFunctionCode), _ => None, } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index 0331fd58..07697695 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -83,6 +83,7 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Fixed(4), FunctionCode::ReadHoldingRegisters => LengthMode::Fixed(4), FunctionCode::ReadInputRegisters => LengthMode::Fixed(4), + FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), @@ -94,6 +95,7 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Offset(1), FunctionCode::ReadHoldingRegisters => LengthMode::Offset(1), FunctionCode::ReadInputRegisters => LengthMode::Offset(1), + FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), @@ -347,19 +349,19 @@ mod tests { 0x71, 0x86, // crc ]; - const SEND_CUSTOM_FUNCTION_CODE_REQUEST: &[u8] = &[ + const SEND_CUSTOM_BUFFER_REQUEST: &[u8] = &[ UNIT_ID, // unit id 0x44, // function code - 0x08, // byte count (length of data) - 0xC0, 0xDE, 0xCA, 0xFE, // data + 0x02, // byte count + 0x01, 0xAB, // additonal data 0xC8, 0xD9, // crc ]; - const SEND_CUSTOM_FUNCTION_CODE_RESPONSE: &[u8] = &[ + const SEND_CUSTOM_BUFFER_RESPONSE: &[u8] = &[ UNIT_ID, // unit id 0x44, // function code - 0x08, // byte count - 0xC0, 0xDE, 0xCA, 0xFE, // data + 0x01, // byte count + 0xCD, // return value 0x88, 0x2C, // crc ]; const WRITE_SINGLE_COIL_REQUEST: &[u8] = &[ @@ -445,8 +447,8 @@ mod tests { READ_INPUT_REGISTERS_REQUEST, ), ( - FunctionCode::SendCustomFunctionCode, - SEND_CUSTOM_FUNCTION_CODE_REQUEST, + FunctionCode::SendCustomBuffers, + SEND_CUSTOM_BUFFER_REQUEST, ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_REQUEST), ( @@ -478,8 +480,8 @@ mod tests { READ_INPUT_REGISTERS_RESPONSE, ), ( - FunctionCode::SendCustomFunctionCode, - SEND_CUSTOM_FUNCTION_CODE_RESPONSE, + FunctionCode::SendCustomBuffers, + SEND_CUSTOM_BUFFER_RESPONSE, ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_RESPONSE), ( diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 32af45a3..a660052e 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -43,6 +43,11 @@ pub trait RequestHandler: Send + 'static { Err(ExceptionCode::IllegalFunction) } + /// Read single custom buffer or return an ExceptionCode + fn receive_custom_buffer(&self, _value: Indexed) -> Result { + Err(ExceptionCode::IllegalFunction) + } + /// Write a single coil value fn write_single_coil(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) @@ -182,6 +187,14 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } + /// Authorize a Read Custom Buffer request + fn receive_custom_buffer( + &self, + _value: Indexed, + _role: &str, + ) -> Authorization { + Authorization::Deny + } /// Authorize a Read Holding Registers request fn read_holding_registers( &self, diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 5df56f7f..752eb6e2 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -17,6 +17,7 @@ pub(crate) enum Request<'a> { ReadDiscreteInputs(ReadBitsRange), ReadHoldingRegisters(ReadRegistersRange), ReadInputRegisters(ReadRegistersRange), + SendCustomBuffers(Indexed), WriteSingleCoil(Indexed), WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), @@ -61,6 +62,7 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, Request::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, Request::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, + Request::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, Request::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -75,6 +77,7 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => None, Request::ReadHoldingRegisters(_) => None, Request::ReadInputRegisters(_) => None, + Request::SendCustomBuffers(_) => None, Request::WriteSingleCoil(x) => Some(BroadcastRequest::WriteSingleCoil(x)), Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), @@ -126,6 +129,10 @@ impl<'a> Request<'a> { let registers = RegisterWriter::new(*range, |i| handler.read_input_register(i)); writer.format_reply(header, function, ®isters, level) } + Request::SendCustomBuffers(request) => { + let result = handler.receive_custom_buffer(*request).map(|_| *request); + write_result(function, header, writer, result, level) + } Request::WriteSingleCoil(request) => { let result = handler.write_single_coil(*request).map(|_| *request); write_result(function, header, writer, result, level) @@ -179,6 +186,12 @@ impl<'a> Request<'a> { cursor.expect_empty()?; Ok(x) } + FunctionCode::SendCustomBuffers => { + let x = + Request::SendCustomBuffers(Indexed::::parse(cursor)?); + cursor.expect_empty()?; + Ok(x) + } FunctionCode::WriteSingleCoil => { let x = Request::WriteSingleCoil(Indexed::::parse(cursor)?); cursor.expect_empty()?; @@ -246,6 +259,9 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { Request::ReadInputRegisters(range) => { write!(f, " {}", range.get())?; } + Request::SendCustomBuffers(request) => { + write!(f, " {request}")?; + } Request::WriteSingleCoil(request) => { write!(f, " {request}")?; } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index 95d6f368..8171a329 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -256,6 +256,7 @@ impl AuthorizationType { handler.read_holding_registers(unit_id, x.inner, role) } Request::ReadInputRegisters(x) => handler.read_input_registers(unit_id, x.inner, role), + Request::SendCustomBuffers(x) => handler.receive_custom_buffer(*x, role), Request::WriteSingleCoil(x) => handler.write_single_coil(unit_id, x.index, role), Request::WriteSingleRegister(x) => { handler.write_single_register(unit_id, x.index, role) From a74d50fbcd20616b375bfdd90ef1562c8f69f6ec Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 16:54:24 +0100 Subject: [PATCH 050/122] Revert "refactor(custom_fc): Rename WriteCustomFC to SendCustomFC" This reverts commit fcb4beb2d310872ba75f23b12a90652f05bea58f. --- rodbus/examples/client.rs | 8 ++++---- rodbus/examples/server.rs | 2 +- rodbus/src/client/channel.rs | 8 ++++---- rodbus/src/client/message.rs | 14 +++++++------- rodbus/src/client/requests/mod.rs | 2 +- .../{send_custom_fc.rs => write_custom_fc.rs} | 12 ++++++------ rodbus/src/common/function.rs | 10 +++++----- rodbus/src/serial/frame.rs | 4 ++-- rodbus/src/server/handler.rs | 10 +++++----- rodbus/src/server/request.rs | 16 ++++++++-------- rodbus/src/server/task.rs | 2 +- rodbus/tests/integration_test.rs | 4 ++-- 12 files changed, 46 insertions(+), 46 deletions(-) rename rodbus/src/client/requests/{send_custom_fc.rs => write_custom_fc.rs} (86%) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 4292b649..0d55f646 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -281,19 +281,19 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { - // ANCHOR: send_custom_function_code + "wcfc" => { + // ANCHOR: write_custom_function_code let length = 0x04 as usize; let values = [0xC0, 0xDE, 0xCA, 0xFE]; // i.e.: Voltage Hi = 0xC0 / Voltage Lo = 0xDE / Current Hi = 0xCA / Current Lo = 0xFE let result = channel - .send_custom_function_code( + .write_custom_function_code( params, CustomFunctionCode::new(length, values) ) .await; print_write_result(result); - // ANCHOR_END: send_custom_function_code + // ANCHOR_END: write_custom_function_code } _ => println!("unknown command"), } diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index eace3f4f..897f6859 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -92,7 +92,7 @@ impl RequestHandler for SimpleHandler { } } - fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { custom_fc_args[i] = value; diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 05726bdb..80737db0 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -6,7 +6,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; -use crate::client::requests::send_custom_fc::CustomFCRequest; +use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; use crate::DecodeLevel; @@ -181,8 +181,8 @@ impl Channel { rx.await? } - /// Send a Custom Function Code to the server - pub async fn send_custom_function_code( + /// Write a Custom Function Code to the server + pub async fn write_custom_function_code( &mut self, param: RequestParam, request: CustomFunctionCode, @@ -190,7 +190,7 @@ impl Channel { let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, - RequestDetails::SendCustomFunctionCode(CustomFCRequest::new(request, Promise::channel(tx))), + RequestDetails::WriteCustomFunctionCode(WriteCustomFunctionCode::new(request, Promise::channel(tx))), ); self.tx.send(request).await?; rx.await? diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 8ee4db5d..e4238b31 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -11,7 +11,7 @@ use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; use crate::client::requests::send_buffer::SendBuffer; -use crate::client::requests::send_custom_fc::CustomFCRequest; +use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -48,7 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), - SendCustomFunctionCode(CustomFCRequest), + WriteCustomFunctionCode(WriteCustomFunctionCode), } impl Request { @@ -134,7 +134,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, RequestDetails::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, - RequestDetails::SendCustomFunctionCode(_) => FunctionCode::SendCustomFunctionCode, + RequestDetails::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -149,7 +149,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), RequestDetails::WriteMultipleRegisters(x) => x.failure(err), - RequestDetails::SendCustomFunctionCode(x) => x.failure(err), + RequestDetails::WriteCustomFunctionCode(x) => x.failure(err), } } @@ -171,7 +171,7 @@ impl RequestDetails { RequestDetails::WriteMultipleRegisters(x) => { x.handle_response(cursor, function, decode) }, - RequestDetails::SendCustomFunctionCode(x) => { + RequestDetails::WriteCustomFunctionCode(x) => { x.handle_response(cursor, function, decode) } } @@ -190,7 +190,7 @@ impl Serialize for RequestDetails { RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), RequestDetails::WriteMultipleRegisters(x) => x.serialize(cursor), - RequestDetails::SendCustomFunctionCode(x) => x.serialize(cursor), + RequestDetails::WriteCustomFunctionCode(x) => x.serialize(cursor), } } } @@ -258,7 +258,7 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } } - RequestDetails::SendCustomFunctionCode(details) => { + RequestDetails::WriteCustomFunctionCode(details) => { write!(f, "{}", details.request)?; } } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 6260c59b..7a2bd345 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -3,4 +3,4 @@ pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; pub(crate) mod send_buffer; -pub(crate) mod send_custom_fc; \ No newline at end of file +pub(crate) mod write_custom_fc; \ No newline at end of file diff --git a/rodbus/src/client/requests/send_custom_fc.rs b/rodbus/src/client/requests/write_custom_fc.rs similarity index 86% rename from rodbus/src/client/requests/send_custom_fc.rs rename to rodbus/src/client/requests/write_custom_fc.rs index 3a7f1a61..5bb7f9fa 100644 --- a/rodbus/src/client/requests/send_custom_fc.rs +++ b/rodbus/src/client/requests/write_custom_fc.rs @@ -9,22 +9,22 @@ use crate::error::RequestError; use scursor::{ReadCursor, WriteCursor}; -pub(crate) trait CustomFCOperation: Sized + PartialEq { +pub(crate) trait WriteCustomFunctionCodeOperation: Sized + PartialEq { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; fn parse(cursor: &mut ReadCursor) -> Result; } -pub(crate) struct CustomFCRequest +pub(crate) struct WriteCustomFunctionCode where - T: CustomFCOperation + Display + Send + 'static, + T: WriteCustomFunctionCodeOperation + Display + Send + 'static, { pub(crate) request: T, promise: Promise, } -impl CustomFCRequest +impl WriteCustomFunctionCode where - T: CustomFCOperation + Display + Send + 'static, + T: WriteCustomFunctionCodeOperation + Display + Send + 'static, { pub(crate) fn new(request: T, promise: Promise) -> Self { Self { request, promise } @@ -66,7 +66,7 @@ where } } -impl CustomFCOperation for CustomFunctionCode { +impl WriteCustomFunctionCodeOperation for CustomFunctionCode { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { cursor.write_u16_be(self.len() as u16)?; diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index 8874fb41..f30ba928 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -10,7 +10,7 @@ mod constants { pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; - pub(crate) const SEND_CUSTOM_FUNCTION_CODE: u8 = 69; + pub(crate) const WRITE_CUSTOM_FUNCTION_CODE: u8 = 69; } #[derive(Debug, Copy, Clone, PartialEq)] @@ -25,7 +25,7 @@ pub(crate) enum FunctionCode { WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, - SendCustomFunctionCode = constants::SEND_CUSTOM_FUNCTION_CODE, + WriteCustomFunctionCode = constants::WRITE_CUSTOM_FUNCTION_CODE, } impl Display for FunctionCode { @@ -56,8 +56,8 @@ impl Display for FunctionCode { FunctionCode::SendCustomBuffers => { write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) } - FunctionCode::SendCustomFunctionCode => { - write!(f, "SEND CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) + FunctionCode::WriteCustomFunctionCode => { + write!(f, "WRITE CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } } } @@ -83,7 +83,7 @@ impl FunctionCode { constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), - constants::SEND_CUSTOM_FUNCTION_CODE => Some(FunctionCode::SendCustomFunctionCode), + constants::WRITE_CUSTOM_FUNCTION_CODE => Some(FunctionCode::WriteCustomFunctionCode), _ => None, } } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index 07697695..cbe54da7 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -88,7 +88,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), FunctionCode::WriteMultipleRegisters => LengthMode::Offset(5), - FunctionCode::SendCustomFunctionCode => LengthMode::Offset(1), + FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, ParserType::Response => match function_code { FunctionCode::ReadCoils => LengthMode::Offset(1), @@ -100,7 +100,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), FunctionCode::WriteMultipleRegisters => LengthMode::Fixed(4), - FunctionCode::SendCustomFunctionCode => LengthMode::Offset(1), + FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, } } diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index a660052e..d189e8aa 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -69,7 +69,7 @@ pub trait RequestHandler: Send + 'static { } /// Write a custom function code - fn process_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } } @@ -245,8 +245,8 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } - /// Authorize a Send Custom Function Code request - fn process_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { + /// Authorize a Write Custom Function Code request + fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Deny } } @@ -328,8 +328,8 @@ impl AuthorizationHandler for ReadOnlyAuthorizationHandler { Authorization::Deny } - /// Authorize a Send Custom Function Code request - fn process_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { + /// Authorize a Write Custom Function Code request + fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Allow } } diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 752eb6e2..0b34d196 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -22,7 +22,7 @@ pub(crate) enum Request<'a> { WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), WriteMultipleRegisters(WriteRegisters<'a>), - SendCustomFunctionCode(CustomFunctionCode), + WriteCustomFunctionCode(CustomFunctionCode), } /// All requests that support broadcast @@ -67,7 +67,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, Request::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, - Request::SendCustomFunctionCode(_) => FunctionCode::SendCustomFunctionCode, + Request::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -82,7 +82,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), Request::WriteMultipleRegisters(x) => Some(BroadcastRequest::WriteMultipleRegisters(x)), - Request::SendCustomFunctionCode(_) => None, + Request::WriteCustomFunctionCode(_) => None, } } @@ -151,8 +151,8 @@ impl<'a> Request<'a> { .map(|_| items.range); write_result(function, header, writer, result, level) } - Request::SendCustomFunctionCode(request) => { - let result = handler.process_custom_function_code(*request).map(|_| *request); + Request::WriteCustomFunctionCode(request) => { + let result = handler.write_custom_function_code(*request).map(|_| *request); write_result(function, header, writer, result, level) } } @@ -220,9 +220,9 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } - FunctionCode::SendCustomFunctionCode => { + FunctionCode::WriteCustomFunctionCode => { let x = - Request::SendCustomFunctionCode(CustomFunctionCode::parse(cursor)?); + Request::WriteCustomFunctionCode(CustomFunctionCode::parse(cursor)?); cursor.expect_empty()?; Ok(x) } @@ -282,7 +282,7 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { RegisterIteratorDisplay::new(self.level, items.iterator) )?; } - Request::SendCustomFunctionCode(request) => { + Request::WriteCustomFunctionCode(request) => { write!(f, " {request}")?; } } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index 8171a329..a96164e7 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -265,7 +265,7 @@ impl AuthorizationType { Request::WriteMultipleRegisters(x) => { handler.write_multiple_registers(unit_id, x.range, role) } - Request::SendCustomFunctionCode(x) => handler.process_custom_function_code(*x, role), + Request::WriteCustomFunctionCode(x) => handler.write_custom_function_code(*x, role), } } diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index 0bc423cf..00b23664 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -97,7 +97,7 @@ impl RequestHandler for Handler { Ok(()) } - fn process_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { + fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { for (i, &value) in values.iter().enumerate() { match self.custom_function_code.get_mut(i) { Some(c) => *c = value, @@ -236,7 +236,7 @@ async fn test_requests_and_responses() { ); assert_eq!( channel - .send_custom_function_code(params, CustomFunctionCode::new(0x04, [0xC0, 0xDE, 0xCA, 0xFE])) + .write_custom_function_code(params, CustomFunctionCode::new(0x04, [0xC0, 0xDE, 0xCA, 0xFE])) .await .unwrap(), CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) From 07e0703f7d12b72b7f1959c0e97b54b3cb91411b Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 16:54:31 +0100 Subject: [PATCH 051/122] Revert "fix(examples): Adjust TCP & TLS port for non-root Unix users" This reverts commit cf2a350fae1f09be169e54a9deda77804624167c. --- rodbus/examples/client.rs | 4 ++-- rodbus/examples/server.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 0d55f646..f0841854 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -61,7 +61,7 @@ where async fn run_tcp() -> Result<(), Box> { // ANCHOR: create_tcp_channel let channel = spawn_tcp_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10502), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 502), 1, default_retry_strategy(), DecodeLevel::default(), @@ -96,7 +96,7 @@ async fn run_rtu() -> Result<(), Box> { async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box> { // ANCHOR: create_tls_channel let channel = spawn_tls_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10802), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 802), 1, default_retry_strategy(), tls_config, diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 897f6859..7fbaf4e2 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -191,7 +191,7 @@ async fn run_tcp() -> Result<(), Box> { // ANCHOR: tcp_server_create let server = rodbus::server::spawn_tcp_server_task( 1, - "127.0.0.1:10502".parse()?, + "127.0.0.1:502".parse()?, map, AddressFilter::Any, DecodeLevel::default(), @@ -230,7 +230,7 @@ async fn run_tls(tls_config: TlsServerConfig) -> Result<(), Box Date: Tue, 30 Jan 2024 16:54:34 +0100 Subject: [PATCH 052/122] Revert "chore(send_cfc): Rename CFC README.me" This reverts commit 1926d686baeb7b8a35b6df8d2bdadf12d7a76054. --- rodbus/{SCFC_README.md => WCFC_README.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename rodbus/{SCFC_README.md => WCFC_README.md} (93%) diff --git a/rodbus/SCFC_README.md b/rodbus/WCFC_README.md similarity index 93% rename from rodbus/SCFC_README.md rename to rodbus/WCFC_README.md index 7e2e1692..1867d359 100644 --- a/rodbus/SCFC_README.md +++ b/rodbus/WCFC_README.md @@ -1,4 +1,4 @@ -# 69 (0x45) Send Custom Function Code +# 69 (0x45) Write Custom Function Code This document provides a detailed overview of the custom function code (0x45) used in the MODBUS Application Protocol. This function code is user-defined and falls within the range of 65 to 72, as specified in the MODBUS Application Protocol Specification V1.1b3 (Page 10, Section 5: Function Code Categories). @@ -52,7 +52,7 @@ The 0x45 function code enables the implementation of user-defined logic on a rem ## Modify and Test Server-Side Buffer Handling -The server currently forwards the Custom Function Code buffer to the client again without alteration. To test modifying or processing the buffer on the remote server device, edit the `send_custom_function_code()` function in `src/examples/client.rs` and `src/examples/server.rs` as needed. +The server currently forwards the Custom Function Code buffer to the client again without alteration. To test modifying or processing the buffer on the remote server device, edit the `write_custom_function_code()` function in `src/examples/client.rs` and `src/examples/server.rs` as needed. ## Troubleshooting Tips From 068b7af33926187144f7ccb4d831185fc66e87fa Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 17:25:47 +0100 Subject: [PATCH 053/122] refactor(send_custom_buffer): Remove Send Custom Buffer functionality --- rodbus/examples/client.rs | 14 ---- rodbus/examples/server.rs | 14 ---- rodbus/src/client/channel.rs | 16 ---- rodbus/src/client/message.rs | 9 --- rodbus/src/client/requests/mod.rs | 1 - rodbus/src/client/requests/send_buffer.rs | 94 ----------------------- rodbus/src/common/function.rs | 6 -- rodbus/src/serial/frame.rs | 25 ------ rodbus/src/server/handler.rs | 15 +--- rodbus/src/server/request.rs | 16 ---- rodbus/src/server/task.rs | 1 - 11 files changed, 1 insertion(+), 210 deletions(-) delete mode 100644 rodbus/src/client/requests/send_buffer.rs diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index f0841854..340a4bfc 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -267,20 +267,6 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { - // ANCHOR: send_custom_buffer - let result = channel - .send_custom_buffer( - params, - Indexed { - index: (0x1), - value: (0xAB), - }, - ) - .await; - print_write_result(result); - // ANCHOR_END: write_multiple_registers - } "wcfc" => { // ANCHOR: write_custom_function_code let length = 0x04 as usize; diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 7fbaf4e2..7e348b10 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -78,20 +78,6 @@ impl RequestHandler for SimpleHandler { } } - fn receive_custom_buffer(&self, value: Indexed) -> Result { - tracing::info!( - "receive custom buffer, index: {} value: {}", - value.index, - value.value - ); - - if value.value == 0xAB { - Ok(0xCD) - } else { - Err(ExceptionCode::IllegalFunction) - } - } - fn write_custom_function_code(&mut self, values: CustomFunctionCode) -> Result<(), ExceptionCode> { let mut custom_fc_args = [0_u16; 4]; // i.e.: Voltage Hi = 0x02, Voltage Lo = 0x03, Current Hi = 0x04, Current Lo = 0x05 for (i, &value) in values.iter().enumerate() { diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 80737db0..9baa4708 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -5,7 +5,6 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; @@ -166,21 +165,6 @@ impl Channel { rx.await? } - /// Send buffer to the server - pub async fn send_custom_buffer( - &mut self, - param: RequestParam, - request: Indexed, - ) -> Result, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); - let request = wrap( - param, - RequestDetails::SendCustomBuffers(SendBuffer::new(request, Promise::channel(tx))), - ); - self.tx.send(request).await?; - rx.await? - } - /// Write a Custom Function Code to the server pub async fn write_custom_function_code( &mut self, diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index e4238b31..89e84ac8 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,7 +10,6 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::send_buffer::SendBuffer; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -43,7 +42,6 @@ pub(crate) enum RequestDetails { ReadDiscreteInputs(ReadBits), ReadHoldingRegisters(ReadRegisters), ReadInputRegisters(ReadRegisters), - SendCustomBuffers(SendBuffer>), WriteSingleCoil(SingleWrite>), WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), @@ -129,7 +127,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, RequestDetails::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, RequestDetails::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, - RequestDetails::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, RequestDetails::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -144,7 +141,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.failure(err), RequestDetails::ReadHoldingRegisters(x) => x.failure(err), RequestDetails::ReadInputRegisters(x) => x.failure(err), - RequestDetails::SendCustomBuffers(x) => x.failure(err), RequestDetails::WriteSingleCoil(x) => x.failure(err), RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), @@ -164,7 +160,6 @@ impl RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadHoldingRegisters(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadInputRegisters(x) => x.handle_response(cursor, function, decode), - RequestDetails::SendCustomBuffers(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleCoil(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteSingleRegister(x) => x.handle_response(cursor, function, decode), RequestDetails::WriteMultipleCoils(x) => x.handle_response(cursor, function, decode), @@ -185,7 +180,6 @@ impl Serialize for RequestDetails { RequestDetails::ReadDiscreteInputs(x) => x.serialize(cursor), RequestDetails::ReadHoldingRegisters(x) => x.serialize(cursor), RequestDetails::ReadInputRegisters(x) => x.serialize(cursor), - RequestDetails::SendCustomBuffers(x) => x.serialize(cursor), RequestDetails::WriteSingleCoil(x) => x.serialize(cursor), RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), @@ -233,9 +227,6 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { RequestDetails::ReadInputRegisters(details) => { write!(f, "{}", details.request.get())?; } - RequestDetails::SendCustomBuffers(details) => { - write!(f, "{}", details.request)?; - } RequestDetails::WriteSingleCoil(details) => { write!(f, "{}", details.request)?; } diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 7a2bd345..04337758 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -2,5 +2,4 @@ pub(crate) mod read_bits; pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; -pub(crate) mod send_buffer; pub(crate) mod write_custom_fc; \ No newline at end of file diff --git a/rodbus/src/client/requests/send_buffer.rs b/rodbus/src/client/requests/send_buffer.rs deleted file mode 100644 index 2e19d731..00000000 --- a/rodbus/src/client/requests/send_buffer.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt::Display; - -use crate::client::message::Promise; -use crate::common::function::FunctionCode; -use crate::decode::AppDecodeLevel; -use crate::error::AduParseError; -use crate::error::RequestError; -use crate::types::{coil_from_u16, coil_to_u16, Indexed}; - -use scursor::{ReadCursor, WriteCursor}; - -pub(crate) trait SendBufferOperation: Sized + PartialEq { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError>; - fn parse(cursor: &mut ReadCursor) -> Result; -} - -pub(crate) struct SendBuffer -where - T: SendBufferOperation + Display + Send + 'static, -{ - pub(crate) request: T, - promise: Promise, -} - -impl SendBuffer -where - T: SendBufferOperation + Display + Send + 'static, -{ - pub(crate) fn new(request: T, promise: Promise) -> Self { - Self { request, promise } - } - - pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - self.request.serialize(cursor) - } - - pub(crate) fn failure(&mut self, err: RequestError) { - self.promise.failure(err) - } - - pub(crate) fn handle_response( - &mut self, - cursor: ReadCursor, - function: FunctionCode, - decode: AppDecodeLevel, - ) -> Result<(), RequestError> { - let response = self.parse_all(cursor)?; - - if decode.data_headers() { - tracing::info!("PDU RX - {} {}", function, response); - } else if decode.header() { - tracing::info!("PDU RX - {}", function); - } - - self.promise.success(response); - Ok(()) - } - - fn parse_all(&self, mut cursor: ReadCursor) -> Result { - let response = T::parse(&mut cursor)?; - cursor.expect_empty()?; - if self.request != response { - return Err(AduParseError::ReplyEchoMismatch.into()); - } - Ok(response) - } -} - -impl SendBufferOperation for Indexed { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(coil_to_u16(self.value))?; - Ok(()) - } - - fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new( - cursor.read_u16_be()?, - coil_from_u16(cursor.read_u16_be()?)?, - )) - } -} - -impl SendBufferOperation for Indexed { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - cursor.write_u16_be(self.index)?; - cursor.write_u16_be(self.value)?; - Ok(()) - } - - fn parse(cursor: &mut ReadCursor) -> Result { - Ok(Indexed::new(cursor.read_u16_be()?, cursor.read_u16_be()?)) - } -} diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index f30ba928..d017c29a 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -9,7 +9,6 @@ mod constants { pub(crate) const WRITE_SINGLE_REGISTER: u8 = 6; pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; - pub(crate) const SEND_CUSTOM_BUFFERS: u8 = 68; pub(crate) const WRITE_CUSTOM_FUNCTION_CODE: u8 = 69; } @@ -24,7 +23,6 @@ pub(crate) enum FunctionCode { WriteSingleRegister = constants::WRITE_SINGLE_REGISTER, WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, - SendCustomBuffers = constants::SEND_CUSTOM_BUFFERS, WriteCustomFunctionCode = constants::WRITE_CUSTOM_FUNCTION_CODE, } @@ -53,9 +51,6 @@ impl Display for FunctionCode { FunctionCode::WriteMultipleRegisters => { write!(f, "WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) } - FunctionCode::SendCustomBuffers => { - write!(f, "SEND CUSTOM BUFFER ({:#04X})", self.get_value()) - } FunctionCode::WriteCustomFunctionCode => { write!(f, "WRITE CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } @@ -82,7 +77,6 @@ impl FunctionCode { constants::WRITE_SINGLE_REGISTER => Some(FunctionCode::WriteSingleRegister), constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), - constants::SEND_CUSTOM_BUFFERS => Some(FunctionCode::SendCustomBuffers), constants::WRITE_CUSTOM_FUNCTION_CODE => Some(FunctionCode::WriteCustomFunctionCode), _ => None, } diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index cbe54da7..c502e9d3 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -83,7 +83,6 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Fixed(4), FunctionCode::ReadHoldingRegisters => LengthMode::Fixed(4), FunctionCode::ReadInputRegisters => LengthMode::Fixed(4), - FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), @@ -95,7 +94,6 @@ impl RtuParser { FunctionCode::ReadDiscreteInputs => LengthMode::Offset(1), FunctionCode::ReadHoldingRegisters => LengthMode::Offset(1), FunctionCode::ReadInputRegisters => LengthMode::Offset(1), - FunctionCode::SendCustomBuffers => LengthMode::Offset(1), FunctionCode::WriteSingleCoil => LengthMode::Fixed(4), FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), @@ -349,21 +347,6 @@ mod tests { 0x71, 0x86, // crc ]; - const SEND_CUSTOM_BUFFER_REQUEST: &[u8] = &[ - UNIT_ID, // unit id - 0x44, // function code - 0x02, // byte count - 0x01, 0xAB, // additonal data - 0xC8, 0xD9, // crc - ]; - - const SEND_CUSTOM_BUFFER_RESPONSE: &[u8] = &[ - UNIT_ID, // unit id - 0x44, // function code - 0x01, // byte count - 0xCD, // return value - 0x88, 0x2C, // crc - ]; const WRITE_SINGLE_COIL_REQUEST: &[u8] = &[ UNIT_ID, // unit id 0x05, // function code @@ -446,10 +429,6 @@ mod tests { FunctionCode::ReadInputRegisters, READ_INPUT_REGISTERS_REQUEST, ), - ( - FunctionCode::SendCustomBuffers, - SEND_CUSTOM_BUFFER_REQUEST, - ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_REQUEST), ( FunctionCode::WriteSingleRegister, @@ -479,10 +458,6 @@ mod tests { FunctionCode::ReadInputRegisters, READ_INPUT_REGISTERS_RESPONSE, ), - ( - FunctionCode::SendCustomBuffers, - SEND_CUSTOM_BUFFER_RESPONSE, - ), (FunctionCode::WriteSingleCoil, WRITE_SINGLE_COIL_RESPONSE), ( FunctionCode::WriteSingleRegister, diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index d189e8aa..2d67b200 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -43,11 +43,6 @@ pub trait RequestHandler: Send + 'static { Err(ExceptionCode::IllegalFunction) } - /// Read single custom buffer or return an ExceptionCode - fn receive_custom_buffer(&self, _value: Indexed) -> Result { - Err(ExceptionCode::IllegalFunction) - } - /// Write a single coil value fn write_single_coil(&mut self, _value: Indexed) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) @@ -186,15 +181,7 @@ pub trait AuthorizationHandler: Send + Sync + 'static { ) -> Authorization { Authorization::Deny } - - /// Authorize a Read Custom Buffer request - fn receive_custom_buffer( - &self, - _value: Indexed, - _role: &str, - ) -> Authorization { - Authorization::Deny - } + /// Authorize a Read Holding Registers request fn read_holding_registers( &self, diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 0b34d196..d5a0be84 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -17,7 +17,6 @@ pub(crate) enum Request<'a> { ReadDiscreteInputs(ReadBitsRange), ReadHoldingRegisters(ReadRegistersRange), ReadInputRegisters(ReadRegistersRange), - SendCustomBuffers(Indexed), WriteSingleCoil(Indexed), WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), @@ -62,7 +61,6 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs, Request::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters, Request::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters, - Request::SendCustomBuffers(_) => FunctionCode::SendCustomBuffers, Request::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil, Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, @@ -77,7 +75,6 @@ impl<'a> Request<'a> { Request::ReadDiscreteInputs(_) => None, Request::ReadHoldingRegisters(_) => None, Request::ReadInputRegisters(_) => None, - Request::SendCustomBuffers(_) => None, Request::WriteSingleCoil(x) => Some(BroadcastRequest::WriteSingleCoil(x)), Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), @@ -129,10 +126,6 @@ impl<'a> Request<'a> { let registers = RegisterWriter::new(*range, |i| handler.read_input_register(i)); writer.format_reply(header, function, ®isters, level) } - Request::SendCustomBuffers(request) => { - let result = handler.receive_custom_buffer(*request).map(|_| *request); - write_result(function, header, writer, result, level) - } Request::WriteSingleCoil(request) => { let result = handler.write_single_coil(*request).map(|_| *request); write_result(function, header, writer, result, level) @@ -186,12 +179,6 @@ impl<'a> Request<'a> { cursor.expect_empty()?; Ok(x) } - FunctionCode::SendCustomBuffers => { - let x = - Request::SendCustomBuffers(Indexed::::parse(cursor)?); - cursor.expect_empty()?; - Ok(x) - } FunctionCode::WriteSingleCoil => { let x = Request::WriteSingleCoil(Indexed::::parse(cursor)?); cursor.expect_empty()?; @@ -259,9 +246,6 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { Request::ReadInputRegisters(range) => { write!(f, " {}", range.get())?; } - Request::SendCustomBuffers(request) => { - write!(f, " {request}")?; - } Request::WriteSingleCoil(request) => { write!(f, " {request}")?; } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index a96164e7..86ce2754 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -256,7 +256,6 @@ impl AuthorizationType { handler.read_holding_registers(unit_id, x.inner, role) } Request::ReadInputRegisters(x) => handler.read_input_registers(unit_id, x.inner, role), - Request::SendCustomBuffers(x) => handler.receive_custom_buffer(*x, role), Request::WriteSingleCoil(x) => handler.write_single_coil(unit_id, x.index, role), Request::WriteSingleRegister(x) => { handler.write_single_register(unit_id, x.index, role) From 112facec83638d3a911fa2be2531bbfd007a2e15 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 18:13:59 +0100 Subject: [PATCH 054/122] feat(function): add FC23 to FunctionCode enum --- rodbus/src/common/function.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rodbus/src/common/function.rs b/rodbus/src/common/function.rs index d017c29a..0359eba5 100644 --- a/rodbus/src/common/function.rs +++ b/rodbus/src/common/function.rs @@ -9,6 +9,7 @@ mod constants { pub(crate) const WRITE_SINGLE_REGISTER: u8 = 6; pub(crate) const WRITE_MULTIPLE_COILS: u8 = 15; pub(crate) const WRITE_MULTIPLE_REGISTERS: u8 = 16; + pub(crate) const READ_WRITE_MULTIPLE_REGISTERS: u8 = 23; pub(crate) const WRITE_CUSTOM_FUNCTION_CODE: u8 = 69; } @@ -23,6 +24,7 @@ pub(crate) enum FunctionCode { WriteSingleRegister = constants::WRITE_SINGLE_REGISTER, WriteMultipleCoils = constants::WRITE_MULTIPLE_COILS, WriteMultipleRegisters = constants::WRITE_MULTIPLE_REGISTERS, + ReadWriteMultipleRegisters = constants::READ_WRITE_MULTIPLE_REGISTERS, WriteCustomFunctionCode = constants::WRITE_CUSTOM_FUNCTION_CODE, } @@ -51,6 +53,9 @@ impl Display for FunctionCode { FunctionCode::WriteMultipleRegisters => { write!(f, "WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) } + FunctionCode::ReadWriteMultipleRegisters => { + write!(f, "READ/WRITE MULTIPLE REGISTERS ({:#04X})", self.get_value()) + } FunctionCode::WriteCustomFunctionCode => { write!(f, "WRITE CUSTOM FUNCTION CODE ({:#04X})", self.get_value()) } @@ -77,6 +82,7 @@ impl FunctionCode { constants::WRITE_SINGLE_REGISTER => Some(FunctionCode::WriteSingleRegister), constants::WRITE_MULTIPLE_COILS => Some(FunctionCode::WriteMultipleCoils), constants::WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::WriteMultipleRegisters), + constants::READ_WRITE_MULTIPLE_REGISTERS => Some(FunctionCode::ReadWriteMultipleRegisters), constants::WRITE_CUSTOM_FUNCTION_CODE => Some(FunctionCode::WriteCustomFunctionCode), _ => None, } From ec9b8035446c43aa52c662f04e0d3ae237220512 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:34:40 +0100 Subject: [PATCH 055/122] feat(client): add FC23 feature to client example --- rodbus/examples/client.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 340a4bfc..e86318f3 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -267,6 +267,18 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { + // ANCHOR: read_write_multiple_registers + let result = channel + .read_write_multiple_registers( + params, + AddressRange::try_from(0, 5).unwrap(), + WriteMultiple::from(0, vec![0xCA, 0xFE]).unwrap(), + ) + .await; + print_read_result(result); + // ANCHOR_END: read_write_multiple_registers + } "wcfc" => { // ANCHOR: write_custom_function_code let length = 0x04 as usize; From 684229adf04d2b6789d6f780a7a5cd695d2d2614 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:36:31 +0100 Subject: [PATCH 056/122] feat(message): add FC23 request to client message handler --- rodbus/src/client/message.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 89e84ac8..6ba65ced 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,6 +10,8 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::read_write_multiple::ReadWriteMultiple; +use crate::client::requests::read_write_multiple::MultipleReadWriteRequest; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -46,6 +48,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), + ReadWriteMultipleRegisters(MultipleReadWriteRequest>), WriteCustomFunctionCode(WriteCustomFunctionCode), } @@ -131,6 +134,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, RequestDetails::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, RequestDetails::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, + RequestDetails::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters, RequestDetails::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -145,6 +149,7 @@ impl RequestDetails { RequestDetails::WriteSingleRegister(x) => x.failure(err), RequestDetails::WriteMultipleCoils(x) => x.failure(err), RequestDetails::WriteMultipleRegisters(x) => x.failure(err), + RequestDetails::ReadWriteMultipleRegisters(x) => x.failure(err), RequestDetails::WriteCustomFunctionCode(x) => x.failure(err), } } @@ -166,6 +171,9 @@ impl RequestDetails { RequestDetails::WriteMultipleRegisters(x) => { x.handle_response(cursor, function, decode) }, + RequestDetails::ReadWriteMultipleRegisters(x) => { + x.handle_response(cursor, function, decode) + }, RequestDetails::WriteCustomFunctionCode(x) => { x.handle_response(cursor, function, decode) } @@ -184,6 +192,7 @@ impl Serialize for RequestDetails { RequestDetails::WriteSingleRegister(x) => x.serialize(cursor), RequestDetails::WriteMultipleCoils(x) => x.serialize(cursor), RequestDetails::WriteMultipleRegisters(x) => x.serialize(cursor), + RequestDetails::ReadWriteMultipleRegisters(x) => x.serialize(cursor), RequestDetails::WriteCustomFunctionCode(x) => x.serialize(cursor), } } @@ -249,6 +258,15 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } } + RequestDetails::ReadWriteMultipleRegisters(details) => { + write!(f, "{}", details.request.read_range)?; + write!(f, "{}", details.request.write_range)?; + if self.level.data_values() { + for x in details.request.iter() { + write!(f, "\n{x}")?; + } + } + } RequestDetails::WriteCustomFunctionCode(details) => { write!(f, "{}", details.request)?; } From 47ffc89c119f2dfc5b2c6bd46225c4c55aa74bb2 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:37:06 +0100 Subject: [PATCH 057/122] feat(mod): add FC23 to client requests mod file --- rodbus/src/client/requests/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rodbus/src/client/requests/mod.rs b/rodbus/src/client/requests/mod.rs index 04337758..a1980d13 100644 --- a/rodbus/src/client/requests/mod.rs +++ b/rodbus/src/client/requests/mod.rs @@ -2,4 +2,5 @@ pub(crate) mod read_bits; pub(crate) mod read_registers; pub(crate) mod write_multiple; pub(crate) mod write_single; +pub(crate) mod read_write_multiple; pub(crate) mod write_custom_fc; \ No newline at end of file From 82537ba2b37ce3e22d17c02ee77a603ce6f04405 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:38:31 +0100 Subject: [PATCH 058/122] feat(channel): add FC23 feature to channel request --- rodbus/src/client/channel.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 9baa4708..b738dc9d 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -246,6 +246,30 @@ impl Channel { rx.await? } + /// Read & Write multiple contiguous registers on the server + pub async fn read_write_multiple_registers( + &mut self, + param: RequestParam, + read_range: AddressRange, + write_request: WriteMultiple, + ) -> Result>, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::>, RequestError>>(); + let request = wrap( + param, + RequestDetails::ReadWriteMultipleRegisters( + MultipleWriteRequest::new( + write_request, + Promise::channel(tx), + ), + ReadRegisters::channel( + read_range.of_read_registers()?, + tx, + )), + ); + self.tx.send(request).await?; + rx.await? + } + /// Dynamically change the protocol decoding level of the channel pub async fn set_decode_level(&mut self, level: DecodeLevel) -> Result<(), Shutdown> { self.tx From 47d3affb9fd0e9e2fbf4cbe12e30fc318193dda9 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:38:59 +0100 Subject: [PATCH 059/122] feat(read_write_multiple): add FC23 requests file --- .../client/requests/read_write_multiple.rs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 rodbus/src/client/requests/read_write_multiple.rs diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs new file mode 100644 index 00000000..82e15aee --- /dev/null +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -0,0 +1,71 @@ +use crate::client::message::Promise; +use crate::common::function::FunctionCode; +use crate::common::traits::{Parse, Serialize}; +use crate::types::AddressRange; +use crate::decode::AppDecodeLevel; +use crate::error::{InvalidRequest, RequestError, AduParseError}; + +use scursor::{ReadCursor, WriteCursor}; + +#[derive(Debug, Clone)] +pub struct ReadWriteMultiple { + pub(crate) read_range: AddressRange, + pub(crate) write_range: AddressRange, + pub(crate) write_values: Vec, +} + +impl ReadWriteMultiple { + pub fn new(read_start: u16, read_count: u16, write_start: u16, write_count: u16, write_values: Vec) -> Result { + let read_range = AddressRange::try_from(read_start, read_count)?; + let write_range = AddressRange::try_from(write_start, write_count)?; + Ok(Self { read_range, write_range, write_values }) + } +} + +pub(crate) struct MultipleReadWriteRequest +where + ReadWriteMultiple: Serialize, +{ + pub(crate) request: ReadWriteMultiple, + promise: Promise, +} + +impl MultipleReadWriteRequest +where + ReadWriteMultiple: Serialize, +{ + pub(crate) fn new(request: ReadWriteMultiple, promise: Promise) -> Self { + Self { request, promise } + } + + pub(crate) fn serialize(self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.request.serialize(cursor)?; + Ok(()) + } + + pub(crate) fn failure(&mut self, err: RequestError) { + self.promise.failure(err) + } + + pub(crate) fn handle_response(&mut self, cursor: ReadCursor, function: FunctionCode, decode: AppDecodeLevel) -> Result<(), RequestError> { + let response = self.parse_all(cursor)?; + + if decode.data_headers() { + tracing::info!("PDU RX - {} {}", function, response); + } else if decode.header() { + tracing::info!("PDU RX - {}", function); + } + + self.promise.success(response); + Ok(()) + } + + fn parse_all(&self, mut cursor: ReadCursor) -> Result{ + let response = AddressRange::parse(&mut cursor)?; + cursor.expect_empty()?; + if self.request.read_range != response { + return Err(AduParseError::ReplyEchoMismatch.into()); + } + Ok(response) + } +} From 8a071b8ddac72ca6c280503ab5285b7ebc73fefd Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 20:39:39 +0100 Subject: [PATCH 060/122] feat(serialize): add serialize trait for FC23 --- rodbus/src/common/serialize.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 9bf8b7c7..cba69012 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; +use crate::client::requests::read_write_multiple::ReadWriteMultiple; use crate::client::WriteMultiple; use crate::common::traits::Loggable; use crate::common::traits::Parse; @@ -290,6 +291,21 @@ impl Serialize for WriteMultiple { } } +impl Serialize for ReadWriteMultiple> { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + // Implement the serialization logic for ReadWriteMultiple> here + Ok(()) + } +} + +impl Serialize for ReadWriteMultiple { + fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.read_range.serialize(cursor)?; + self.write_range.serialize(cursor)?; + self.write_values.as_slice().serialize(cursor) + } +} + impl Serialize for CustomFunctionCode { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { cursor.write_u16_be(self.len() as u16)?; From 3aa288a916983bd295e1b4084a5757231393f2d3 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 21:24:34 +0100 Subject: [PATCH 061/122] refactor(channel): correct FC23 channel request parameters --- rodbus/src/client/channel.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index b738dc9d..a61bd736 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -5,6 +5,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::{MultipleWriteRequest, WriteMultiple}; use crate::client::requests::write_single::SingleWrite; +use crate::client::requests::read_write_multiple::{MultipleReadWriteRequest, ReadWriteMultiple}; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::error::*; use crate::types::{AddressRange, BitIterator, Indexed, RegisterIterator, UnitId, CustomFunctionCode}; @@ -252,19 +253,21 @@ impl Channel { param: RequestParam, read_range: AddressRange, write_request: WriteMultiple, - ) -> Result>, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::>, RequestError>>(); + ) -> Result { + let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, RequestDetails::ReadWriteMultipleRegisters( - MultipleWriteRequest::new( - write_request, - Promise::channel(tx), + MultipleReadWriteRequest::new( + ReadWriteMultiple::new( + read_range.start, + read_range.count, + write_request.range.start, + write_request.range.count, + write_request.values, + ), Promise::channel(tx) ), - ReadRegisters::channel( - read_range.of_read_registers()?, - tx, - )), + ), ); self.tx.send(request).await?; rx.await? From 99f9e09994255c8509fbc1e8ef07d2b0c8faab8d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 21:25:24 +0100 Subject: [PATCH 062/122] fix(message): resolve client message request error (iter() not defined) --- rodbus/src/client/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 6ba65ced..565432d8 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -262,7 +262,7 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { write!(f, "{}", details.request.read_range)?; write!(f, "{}", details.request.write_range)?; if self.level.data_values() { - for x in details.request.iter() { + for x in details.request.read_range.iter() { write!(f, "\n{x}")?; } } From 6c5abc21f66ff16817e3ee5062ece7f23e8825e1 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 30 Jan 2024 21:26:18 +0100 Subject: [PATCH 063/122] chore(serialize): Remove serialization logic for ReadWriteMultiple> --- rodbus/src/common/serialize.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index cba69012..608162bb 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -291,13 +291,6 @@ impl Serialize for WriteMultiple { } } -impl Serialize for ReadWriteMultiple> { - fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - // Implement the serialization logic for ReadWriteMultiple> here - Ok(()) - } -} - impl Serialize for ReadWriteMultiple { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { self.read_range.serialize(cursor)?; From 611d8729b5e800c66ffcabe56618ee12a19c2c36 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 17:55:13 +0100 Subject: [PATCH 064/122] feat(types): add ReadWriteMultipleRegistersRange type --- rodbus/src/types.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index a952384d..42dd2d41 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -92,6 +92,31 @@ pub struct CustomFunctionCode { data: [u16; 4], } +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct ReadWriteMultipleRegistersRange { + pub(crate) read_range: AddressRange, + pub(crate) write_range: AddressRange, + pub(crate) write_values: Vec, +} + +impl ReadWriteMultipleRegistersRange { + pub(crate) fn new( + read_start: u16, + read_count: u16, + write_start: u16, + write_count: u16, + write_values: Vec, + ) -> Result { + let read_range = AddressRange::try_from(read_start, read_count)?; + let write_range = AddressRange::try_from(write_start, write_count)?; + Ok(Self { + read_range, + write_range, + write_values, + }) + } +} + impl std::fmt::Display for UnitId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:#04X}", self.value) @@ -404,6 +429,12 @@ impl std::fmt::Display for CustomFunctionCode { } } +impl std::fmt::Display for ReadWriteMultipleRegistersRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "read: {}, write: {}", self.read_range, self.write_range) + } +} + #[cfg(test)] mod tests { use crate::error::*; From c192005983051eaf983a1e943ab0060c5b2c4c2f Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 17:56:00 +0100 Subject: [PATCH 065/122] refactor(message): refactor read_write_multiple imports --- rodbus/src/client/message.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 565432d8..38897182 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,8 +10,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::read_write_multiple::ReadWriteMultiple; -use crate::client::requests::read_write_multiple::MultipleReadWriteRequest; +use crate::client::requests::read_write_multiple::{ReadWriteMultiple, MultipleReadWriteRequest}; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; From 6c8ac0a509f8e81c1b1128bd34c2045346248740 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 17:56:38 +0100 Subject: [PATCH 066/122] fix(read_write_multiple): fix read_write_multiple client request types --- .../client/requests/read_write_multiple.rs | 107 +++++++++++++++--- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 82e15aee..381bfa6c 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -1,24 +1,91 @@ use crate::client::message::Promise; use crate::common::function::FunctionCode; use crate::common::traits::{Parse, Serialize}; -use crate::types::AddressRange; use crate::decode::AppDecodeLevel; -use crate::error::{InvalidRequest, RequestError, AduParseError}; +use crate::error::RequestError; +use crate::error::{AduParseError, InvalidRequest}; +use crate::types::{AddressRange, Indexed}; use scursor::{ReadCursor, WriteCursor}; +use std::convert::TryFrom; +/// Collection of values and starting address +/// +/// Used when making write multiple coil/register requests #[derive(Debug, Clone)] pub struct ReadWriteMultiple { + /// starting address pub(crate) read_range: AddressRange, + /// starting address pub(crate) write_range: AddressRange, - pub(crate) write_values: Vec, + /// vector of values + pub(crate) values: Vec, +} + +pub(crate) struct ReadWriteMultipleIterator<'a, T> { + range: AddressRange, + pos: u16, + iter: std::slice::Iter<'a, T>, } impl ReadWriteMultiple { - pub fn new(read_start: u16, read_count: u16, write_start: u16, write_count: u16, write_values: Vec) -> Result { - let read_range = AddressRange::try_from(read_start, read_count)?; - let write_range = AddressRange::try_from(write_start, write_count)?; - Ok(Self { read_range, write_range, write_values }) + /// Create new collection of values + pub fn new( + read_start: u16, + write_start: u16, + values: Vec, + ) -> Result { + let count = match u16::try_from(values.len()) { + Ok(x) => x, + Err(_) => return Err(InvalidRequest::CountTooBigForU16(values.len())), + }; + let read_range = AddressRange::try_from(read_start, count)?; + let write_range = AddressRange::try_from(write_start, count)?; + Ok(Self { + read_range, + write_range, + values, + }) + } + + pub(crate) fn iter(&self) -> ReadWriteMultipleIterator<'_, T> { + ReadWriteMultipleIterator::new(self.read_range, self.values.iter()) + } +} + +impl<'a, T> ReadWriteMultipleIterator<'a, T> { + fn new(range: AddressRange, iter: std::slice::Iter<'a, T>) -> Self { + Self { + range, + pos: 0, + iter, + } + } +} + +impl Iterator for ReadWriteMultipleIterator<'_, T> +where + T: Copy, +{ + type Item = Indexed; + + fn next(&mut self) -> Option { + let next = self.iter.next(); + + match next { + Some(next) => { + let result = Indexed::new(self.range.start + self.pos, *next); + self.pos += 1; + Some(result) + } + None => None, + } + } + + // implementing this allows collect to optimize the vector capacity + fn size_hint(&self) -> (usize, Option) { + let remaining = (self.range.count - self.pos) as usize; + (remaining, Some(remaining)) } } @@ -38,18 +105,22 @@ where Self { request, promise } } - pub(crate) fn serialize(self, cursor: &mut WriteCursor) -> Result<(), RequestError> { - self.request.serialize(cursor)?; - Ok(()) + pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { + self.request.serialize(cursor) } pub(crate) fn failure(&mut self, err: RequestError) { self.promise.failure(err) } - pub(crate) fn handle_response(&mut self, cursor: ReadCursor, function: FunctionCode, decode: AppDecodeLevel) -> Result<(), RequestError> { + pub(crate) fn handle_response( + &mut self, + cursor: ReadCursor, + function: FunctionCode, + decode: AppDecodeLevel, + ) -> Result<(), RequestError> { let response = self.parse_all(cursor)?; - + if decode.data_headers() { tracing::info!("PDU RX - {} {}", function, response); } else if decode.header() { @@ -60,12 +131,12 @@ where Ok(()) } - fn parse_all(&self, mut cursor: ReadCursor) -> Result{ - let response = AddressRange::parse(&mut cursor)?; - cursor.expect_empty()?; - if self.request.read_range != response { - return Err(AduParseError::ReplyEchoMismatch.into()); + fn parse_all(&self, mut cursor: ReadCursor) -> Result { + let range = AddressRange::parse(&mut cursor)?; + if range != self.request.read_range { + return Err(RequestError::BadResponse(AduParseError::ReplyEchoMismatch)); } - Ok(response) + cursor.expect_empty()?; + Ok(range) } } From bc321384d408115c5d9707c2c749f24af43ab9ab Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 17:58:04 +0100 Subject: [PATCH 067/122] fix(channel): fix channel input parameters for read_write_multiple request --- rodbus/src/client/channel.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index a61bd736..f7f8dca0 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -252,7 +252,7 @@ impl Channel { &mut self, param: RequestParam, read_range: AddressRange, - write_request: WriteMultiple, + write_request: WriteMultiple, ) -> Result { let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( @@ -260,10 +260,8 @@ impl Channel { RequestDetails::ReadWriteMultipleRegisters( MultipleReadWriteRequest::new( ReadWriteMultiple::new( - read_range.start, - read_range.count, - write_request.range.start, - write_request.range.count, + read_range, + write_request.range, write_request.values, ), Promise::channel(tx) ), From c52d33c536736b1bd0adbb1d99f1e6a80ce0c2eb Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 18:10:14 +0100 Subject: [PATCH 068/122] fix(serialize): resolve wrong variable call when serializing read_write_multiple --- rodbus/src/common/serialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 608162bb..5065f2a0 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -295,7 +295,7 @@ impl Serialize for ReadWriteMultiple { fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { self.read_range.serialize(cursor)?; self.write_range.serialize(cursor)?; - self.write_values.as_slice().serialize(cursor) + self.values.as_slice().serialize(cursor) } } From 1c44640e15de071de7afdb85ffdf0a87c7641d1a Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 18:11:14 +0100 Subject: [PATCH 069/122] fix(message): resolve type mismatch in ReadWriteMultipleRegisters request details --- rodbus/src/client/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 38897182..0738cc02 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -10,7 +10,7 @@ use crate::client::requests::read_bits::ReadBits; use crate::client::requests::read_registers::ReadRegisters; use crate::client::requests::write_multiple::MultipleWriteRequest; use crate::client::requests::write_single::SingleWrite; -use crate::client::requests::read_write_multiple::{ReadWriteMultiple, MultipleReadWriteRequest}; +use crate::client::requests::read_write_multiple::MultipleReadWriteRequest; use crate::client::requests::write_custom_fc::WriteCustomFunctionCode; use crate::common::traits::Serialize; use crate::types::{Indexed, UnitId, CustomFunctionCode}; @@ -47,7 +47,7 @@ pub(crate) enum RequestDetails { WriteSingleRegister(SingleWrite>), WriteMultipleCoils(MultipleWriteRequest), WriteMultipleRegisters(MultipleWriteRequest), - ReadWriteMultipleRegisters(MultipleReadWriteRequest>), + ReadWriteMultipleRegisters(MultipleReadWriteRequest), WriteCustomFunctionCode(WriteCustomFunctionCode), } From 69a0af7c41a963d7e8e10248e29d9ee4f6e9541d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 18:12:18 +0100 Subject: [PATCH 070/122] fix(read_write_multiple): Fix incorrect ReadWriteMultiple initialization --- .../src/client/requests/read_write_multiple.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 381bfa6c..65f52b79 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -5,6 +5,7 @@ use crate::decode::AppDecodeLevel; use crate::error::RequestError; use crate::error::{AduParseError, InvalidRequest}; use crate::types::{AddressRange, Indexed}; +use crate::InvalidRange; use scursor::{ReadCursor, WriteCursor}; use std::convert::TryFrom; @@ -31,16 +32,16 @@ pub(crate) struct ReadWriteMultipleIterator<'a, T> { impl ReadWriteMultiple { /// Create new collection of values pub fn new( - read_start: u16, - write_start: u16, + read_range: AddressRange, + write_range: AddressRange, values: Vec, ) -> Result { - let count = match u16::try_from(values.len()) { - Ok(x) => x, - Err(_) => return Err(InvalidRequest::CountTooBigForU16(values.len())), - }; - let read_range = AddressRange::try_from(read_start, count)?; - let write_range = AddressRange::try_from(write_start, count)?; + let count = u16::try_from(values.len()).map_err(|_| InvalidRequest::BadRange(InvalidRange::CountOfZero))?; + + if read_range.count != count { + return Err(InvalidRequest::BadRange(InvalidRange::CountTooLargeForType(read_range.count, count))); + } + Ok(Self { read_range, write_range, From 3cb58e596a0c9f011ef42dfca28c45ae0399d728 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 18:12:40 +0100 Subject: [PATCH 071/122] fix(channel): fix read_write_multiple channel request parameters --- rodbus/src/client/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index f7f8dca0..f4dd8743 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -252,7 +252,7 @@ impl Channel { &mut self, param: RequestParam, read_range: AddressRange, - write_request: WriteMultiple, + write_request: WriteMultiple, ) -> Result { let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( @@ -263,7 +263,7 @@ impl Channel { read_range, write_request.range, write_request.values, - ), Promise::channel(tx) + ).unwrap(), Promise::channel(tx) ), ), ); From cc76e500d5fb96122d5ba25412a980b5993f7242 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 19:33:49 +0100 Subject: [PATCH 072/122] fix(serial frame): Implement serial request/response lengthmode for ReadWriteMultipleRegisters function --- rodbus/src/serial/frame.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rodbus/src/serial/frame.rs b/rodbus/src/serial/frame.rs index c502e9d3..da8c3200 100644 --- a/rodbus/src/serial/frame.rs +++ b/rodbus/src/serial/frame.rs @@ -87,6 +87,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Offset(5), FunctionCode::WriteMultipleRegisters => LengthMode::Offset(5), + FunctionCode::ReadWriteMultipleRegisters => LengthMode::Offset(9), FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, ParserType::Response => match function_code { @@ -98,6 +99,7 @@ impl RtuParser { FunctionCode::WriteSingleRegister => LengthMode::Fixed(4), FunctionCode::WriteMultipleCoils => LengthMode::Fixed(4), FunctionCode::WriteMultipleRegisters => LengthMode::Fixed(4), + FunctionCode::ReadWriteMultipleRegisters => LengthMode::Fixed(4), FunctionCode::WriteCustomFunctionCode => LengthMode::Offset(1), }, } From 9d4139ea2168b4a244ec0b11f594712d5b17c8e7 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 19:34:46 +0100 Subject: [PATCH 073/122] feat(request): add ReadWriteMultipleRegisters FC to server request handler --- rodbus/src/server/request.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index d5a0be84..1f3ecab3 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -207,6 +207,15 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } + FunctionCode::ReadWriteMultipleRegisters => { + let range = AddressRange::parse(cursor)?; + // don't care about the count, validated b/c all bytes are consumed + cursor.read_u8()?; + Ok(Request::WriteMultipleRegisters(WriteRegisters::new( + range, + RegisterIterator::parse_all(range, cursor)?, + ))) + } FunctionCode::WriteCustomFunctionCode => { let x = Request::WriteCustomFunctionCode(CustomFunctionCode::parse(cursor)?); From 049f32164be35836f7c3e15292588998b9d9fb85 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 19:36:22 +0100 Subject: [PATCH 074/122] fix(types): remove ReadWriteMultipleRegistersRange from types (new fn not used yet) --- rodbus/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 42dd2d41..7db529b0 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -99,7 +99,7 @@ pub(crate) struct ReadWriteMultipleRegistersRange { pub(crate) write_values: Vec, } -impl ReadWriteMultipleRegistersRange { +/*impl ReadWriteMultipleRegistersRange { pub(crate) fn new( read_start: u16, read_count: u16, @@ -115,7 +115,7 @@ impl ReadWriteMultipleRegistersRange { write_values, }) } -} +}*/ impl std::fmt::Display for UnitId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { From 0295bcaa219555e2dbbc8c555f9e9b6fe9aedd37 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 19:37:55 +0100 Subject: [PATCH 075/122] fix(read_write_multiple): remove ReadWriteMultipleIterator implementation (not used yet) --- rodbus/src/client/requests/read_write_multiple.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 65f52b79..0b933dd6 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -14,7 +14,7 @@ use std::convert::TryFrom; /// /// Used when making write multiple coil/register requests #[derive(Debug, Clone)] -pub struct ReadWriteMultiple { +pub(crate) struct ReadWriteMultiple { /// starting address pub(crate) read_range: AddressRange, /// starting address @@ -31,7 +31,7 @@ pub(crate) struct ReadWriteMultipleIterator<'a, T> { impl ReadWriteMultiple { /// Create new collection of values - pub fn new( + pub(crate) fn new( read_range: AddressRange, write_range: AddressRange, values: Vec, @@ -49,12 +49,12 @@ impl ReadWriteMultiple { }) } - pub(crate) fn iter(&self) -> ReadWriteMultipleIterator<'_, T> { + /*pub(crate) fn iter(&self) -> ReadWriteMultipleIterator<'_, T> { ReadWriteMultipleIterator::new(self.read_range, self.values.iter()) - } + }*/ } -impl<'a, T> ReadWriteMultipleIterator<'a, T> { +/*impl<'a, T> ReadWriteMultipleIterator<'a, T> { fn new(range: AddressRange, iter: std::slice::Iter<'a, T>) -> Self { Self { range, @@ -62,7 +62,7 @@ impl<'a, T> ReadWriteMultipleIterator<'a, T> { iter, } } -} +}*/ impl Iterator for ReadWriteMultipleIterator<'_, T> where From 545a393eb01b76d3e24dd2b0f452d363b26a5e83 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 20:00:49 +0100 Subject: [PATCH 076/122] fix(client): fix incorrect request parameters for ReadWriteMultipleRegisters function --- rodbus/examples/client.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index e86318f3..b8068cee 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,11 +269,16 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers + let read_range = AddressRange::try_from(0, 5).unwrap(); + let write_range = AddressRange::try_from(0, 5).unwrap(); + let write_values = vec![0xCA, 0xFE]; + + let request = ReadWriteMultiple::new(read_range, write_range, write_values).unwrap(); + let result = channel .read_write_multiple_registers( params, - AddressRange::try_from(0, 5).unwrap(), - WriteMultiple::from(0, vec![0xCA, 0xFE]).unwrap(), + request, ) .await; print_read_result(result); From 1bcec6f28f480ad95843d0074e2602801be2e34b Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 20:01:53 +0100 Subject: [PATCH 077/122] fix(read_write_multiple): fix return type and struct access for ReadWriteMultipleRegisters feature --- .../src/client/requests/read_write_multiple.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 0b933dd6..0552dc12 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -14,7 +14,7 @@ use std::convert::TryFrom; /// /// Used when making write multiple coil/register requests #[derive(Debug, Clone)] -pub(crate) struct ReadWriteMultiple { +pub struct ReadWriteMultiple { /// starting address pub(crate) read_range: AddressRange, /// starting address @@ -31,7 +31,7 @@ pub(crate) struct ReadWriteMultipleIterator<'a, T> { impl ReadWriteMultiple { /// Create new collection of values - pub(crate) fn new( + pub fn new( read_range: AddressRange, write_range: AddressRange, values: Vec, @@ -95,14 +95,14 @@ where ReadWriteMultiple: Serialize, { pub(crate) request: ReadWriteMultiple, - promise: Promise, + promise: Promise>, } impl MultipleReadWriteRequest where ReadWriteMultiple: Serialize, { - pub(crate) fn new(request: ReadWriteMultiple, promise: Promise) -> Self { + pub(crate) fn new(request: ReadWriteMultiple, promise: Promise>) -> Self { Self { request, promise } } @@ -132,11 +132,11 @@ where Ok(()) } - fn parse_all(&self, mut cursor: ReadCursor) -> Result { - let range = AddressRange::parse(&mut cursor)?; - if range != self.request.read_range { + fn parse_all(&self, mut cursor: ReadCursor) -> Result, RequestError> { + let range = Indexed::::parse(&mut cursor)?; + /*if range != self.request.read_range { return Err(RequestError::BadResponse(AduParseError::ReplyEchoMismatch)); - } + }*/ cursor.expect_empty()?; Ok(range) } From 1f61df702af3416fcd993f978beb6ae3fafccbb9 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 31 Jan 2024 20:02:40 +0100 Subject: [PATCH 078/122] fix(client): correct channel request parameters and return values for ReadWriteMultipleRegisters function --- rodbus/src/client/channel.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index f4dd8743..26f97f9d 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -251,21 +251,15 @@ impl Channel { pub async fn read_write_multiple_registers( &mut self, param: RequestParam, - read_range: AddressRange, - write_request: WriteMultiple, - ) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel::>(); + request: ReadWriteMultiple, + ) -> Result, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); let request = wrap( param, - RequestDetails::ReadWriteMultipleRegisters( - MultipleReadWriteRequest::new( - ReadWriteMultiple::new( - read_range, - write_request.range, - write_request.values, - ).unwrap(), Promise::channel(tx) - ), - ), + RequestDetails::ReadWriteMultipleRegisters(MultipleReadWriteRequest::new( + request, + Promise::channel(tx) + )), ); self.tx.send(request).await?; rx.await? From 451c64f5e1227b4fb39db9cbede65207fd5d1c08 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 15:26:59 +0100 Subject: [PATCH 079/122] refactor(channel): change channel Result type from Indexed to Vec> for FC23 (ReadWriteMultipleRegisters) --- rodbus/src/client/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 26f97f9d..840342c8 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -252,8 +252,8 @@ impl Channel { &mut self, param: RequestParam, request: ReadWriteMultiple, - ) -> Result, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::, RequestError>>(); + ) -> Result>, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::>, RequestError>>(); let request = wrap( param, RequestDetails::ReadWriteMultipleRegisters(MultipleReadWriteRequest::new( From 020ae4585b4c9239ffae720484605ede785f2161 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 15:30:09 +0100 Subject: [PATCH 080/122] refactor(read_write_multiple): change Promise and Result type from Indexed to Vec> for ReadWriteMultipleRegisters request --- .../client/requests/read_write_multiple.rs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 0552dc12..60e76bfa 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -95,14 +95,14 @@ where ReadWriteMultiple: Serialize, { pub(crate) request: ReadWriteMultiple, - promise: Promise>, + promise: Promise>>, } impl MultipleReadWriteRequest where ReadWriteMultiple: Serialize, { - pub(crate) fn new(request: ReadWriteMultiple, promise: Promise>) -> Self { + pub(crate) fn new(request: ReadWriteMultiple, promise: Promise>>) -> Self { Self { request, promise } } @@ -123,7 +123,7 @@ where let response = self.parse_all(cursor)?; if decode.data_headers() { - tracing::info!("PDU RX - {} {}", function, response); + tracing::info!("PDU RX - {} {:?}", function, response); } else if decode.header() { tracing::info!("PDU RX - {}", function); } @@ -132,12 +132,15 @@ where Ok(()) } - fn parse_all(&self, mut cursor: ReadCursor) -> Result, RequestError> { - let range = Indexed::::parse(&mut cursor)?; - /*if range != self.request.read_range { - return Err(RequestError::BadResponse(AduParseError::ReplyEchoMismatch)); - }*/ - cursor.expect_empty()?; - Ok(range) + fn parse_all(&self, mut cursor: ReadCursor) -> Result>, RequestError> { + let mut result = Vec::with_capacity(self.request.values.len()); + + for _ in 0..self.request.values.len() { + let value = cursor.read_u16_be().map_err(|_| AduParseError::InsufficientBytes)?; + + result.push(Indexed::new(self.request.write_range.start, value)); + } + + Ok(result) } } From ab2daf029d9660bfbf83a7bc93dd4ffeb7ab2945 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 18:50:17 +0100 Subject: [PATCH 081/122] feat(handler): add FC23 request authorization to server handler --- rodbus/src/server/handler.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 2d67b200..8b8ad866 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -63,6 +63,16 @@ pub trait RequestHandler: Send + 'static { Err(ExceptionCode::IllegalFunction) } + /// Read and write multiple registers + fn read_write_multiple_registers( + &mut self, + _read_range: AddressRange, + _write_range: AddressRange, + _values: Vec>, + ) -> Result<(), ExceptionCode> { + Err(ExceptionCode::IllegalFunction) + } + /// Write a custom function code fn write_custom_function_code(&mut self, _values: CustomFunctionCode) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) @@ -232,6 +242,17 @@ pub trait AuthorizationHandler: Send + Sync + 'static { Authorization::Deny } + /// Authorize a Read Write Multiple Registers request + fn read_write_multiple_registers( + &self, + _unit_id: UnitId, + _read_range: AddressRange, + _write_range: AddressRange, + _role: &str, + ) -> Authorization { + Authorization::Deny + } + /// Authorize a Write Custom Function Code request fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Deny @@ -315,6 +336,17 @@ impl AuthorizationHandler for ReadOnlyAuthorizationHandler { Authorization::Deny } + /// Authorize a Read Write Multiple Registers request + fn read_write_multiple_registers( + &self, + _unit_id: UnitId, + _read_range: AddressRange, + _write_range: AddressRange, + _role: &str, + ) -> Authorization { + Authorization::Deny + } + /// Authorize a Write Custom Function Code request fn write_custom_function_code(&self, _value: CustomFunctionCode, _role: &str) -> Authorization { Authorization::Allow From 0d153989038e95bd3c4a13960e4be6dedc308795 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 19:22:38 +0100 Subject: [PATCH 082/122] feat(server types): add ReadWriteRegisters struct to server-side types (FC23) --- rodbus/src/server/types.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rodbus/src/server/types.rs b/rodbus/src/server/types.rs index 9e7fad72..4d9971b5 100644 --- a/rodbus/src/server/types.rs +++ b/rodbus/src/server/types.rs @@ -29,3 +29,21 @@ impl<'a> WriteRegisters<'a> { Self { range, iterator } } } + + +/// Request to write registers received by the server +#[derive(Debug, Copy, Clone)] +pub struct ReadWriteRegisters<'a> { + /// address range of the read request + pub read_range: AddressRange, + /// address range of the write request + pub write_range: AddressRange, + /// lazy iterator over the register values to write + pub iterator: RegisterIterator<'a>, +} + +impl<'a> ReadWriteRegisters<'a> { + pub(crate) fn new(read_range: AddressRange, write_range: AddressRange, iterator: RegisterIterator<'a>) -> Self { + Self { read_range, write_range, iterator } + } +} \ No newline at end of file From 7994ed2061c4669f1ffbce984e56f58f2de12090 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 19:23:15 +0100 Subject: [PATCH 083/122] feat(server types): add ReadWriteRegisters type to server handler (FC23) --- rodbus/src/server/handler.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 8b8ad866..05bff766 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; use crate::exception::ExceptionCode; -use crate::server::{WriteCoils, WriteRegisters}; +use crate::server::{WriteCoils, WriteRegisters, ReadWriteRegisters}; use crate::types::*; /// Trait implemented by the user to process requests received from the client @@ -64,12 +64,7 @@ pub trait RequestHandler: Send + 'static { } /// Read and write multiple registers - fn read_write_multiple_registers( - &mut self, - _read_range: AddressRange, - _write_range: AddressRange, - _values: Vec>, - ) -> Result<(), ExceptionCode> { + fn read_write_multiple_registers(&mut self, _values: ReadWriteRegisters) -> Result<(), ExceptionCode> { Err(ExceptionCode::IllegalFunction) } From b8f21e33eb752f824626edf3b85305030ad53db2 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 1 Feb 2024 19:24:24 +0100 Subject: [PATCH 084/122] feat(server types): add ReadWriteRegisters type to server-side request handling (FC23) --- rodbus/src/server/request.rs | 22 +++++++++++++++++++++- rodbus/src/server/task.rs | 3 +++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 1f3ecab3..21fe5f54 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -21,6 +21,7 @@ pub(crate) enum Request<'a> { WriteSingleRegister(Indexed), WriteMultipleCoils(WriteCoils<'a>), WriteMultipleRegisters(WriteRegisters<'a>), + ReadWriteMultipleRegisters(ReadWriteRegisters<'a>), WriteCustomFunctionCode(CustomFunctionCode), } @@ -65,6 +66,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister, Request::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils, Request::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters, + Request::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters, Request::WriteCustomFunctionCode(_) => FunctionCode::WriteCustomFunctionCode, } } @@ -79,6 +81,7 @@ impl<'a> Request<'a> { Request::WriteSingleRegister(x) => Some(BroadcastRequest::WriteSingleRegister(x)), Request::WriteMultipleCoils(x) => Some(BroadcastRequest::WriteMultipleCoils(x)), Request::WriteMultipleRegisters(x) => Some(BroadcastRequest::WriteMultipleRegisters(x)), + Request::ReadWriteMultipleRegisters(_) => None, Request::WriteCustomFunctionCode(_) => None, } } @@ -144,6 +147,12 @@ impl<'a> Request<'a> { .map(|_| items.range); write_result(function, header, writer, result, level) } + Request::ReadWriteMultipleRegisters(request) => { + let result = handler + .read_write_multiple_registers(*request) + .map(|_| request.read_range); + write_result(function, header, writer, result, level) + } Request::WriteCustomFunctionCode(request) => { let result = handler.write_custom_function_code(*request).map(|_| *request); write_result(function, header, writer, result, level) @@ -207,11 +216,13 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } + //TODO: parsing logic is not done yet, it's only a placeholder for now FunctionCode::ReadWriteMultipleRegisters => { let range = AddressRange::parse(cursor)?; // don't care about the count, validated b/c all bytes are consumed cursor.read_u8()?; - Ok(Request::WriteMultipleRegisters(WriteRegisters::new( + Ok(Request::ReadWriteMultipleRegisters(ReadWriteRegisters::new( + range, range, RegisterIterator::parse_all(range, cursor)?, ))) @@ -275,6 +286,15 @@ impl std::fmt::Display for RequestDisplay<'_, '_> { RegisterIteratorDisplay::new(self.level, items.iterator) )?; } + Request::ReadWriteMultipleRegisters(request) => { + write!( + f, + " {} {} {}", + request.read_range, + request.write_range, + RegisterIteratorDisplay::new(self.level, request.iterator) + )?; + } Request::WriteCustomFunctionCode(request) => { write!(f, " {request}")?; } diff --git a/rodbus/src/server/task.rs b/rodbus/src/server/task.rs index 86ce2754..18068866 100644 --- a/rodbus/src/server/task.rs +++ b/rodbus/src/server/task.rs @@ -264,6 +264,9 @@ impl AuthorizationType { Request::WriteMultipleRegisters(x) => { handler.write_multiple_registers(unit_id, x.range, role) } + Request::ReadWriteMultipleRegisters(x) => { + handler.read_write_multiple_registers(unit_id, x.read_range, x.write_range, role) + } Request::WriteCustomFunctionCode(x) => handler.write_custom_function_code(*x, role), } } From b77d5dc5ac955379aeaff05e41d7a244197d2efe Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 12:21:21 +0100 Subject: [PATCH 085/122] fix(client): fix missing ReadWriteMultiple import in client mod --- rodbus/src/client/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rodbus/src/client/mod.rs b/rodbus/src/client/mod.rs index 368e5e42..6a497d41 100644 --- a/rodbus/src/client/mod.rs +++ b/rodbus/src/client/mod.rs @@ -12,6 +12,7 @@ pub(crate) mod task; pub use crate::client::channel::*; pub use crate::client::listener::*; pub use crate::client::requests::write_multiple::WriteMultiple; +pub use crate::client::requests::read_write_multiple::ReadWriteMultiple; pub use crate::retry::*; #[cfg(feature = "tls")] From 10dd7f8b6c2a85fc6df9350da6a05523f049b29f Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 14:42:23 +0100 Subject: [PATCH 086/122] feat(examples): set TCP port to 10502 and TLS port to 10802 for non-root execution --- rodbus/examples/client.rs | 4 ++-- rodbus/examples/server.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index b8068cee..0612c055 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -61,7 +61,7 @@ where async fn run_tcp() -> Result<(), Box> { // ANCHOR: create_tcp_channel let channel = spawn_tcp_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 502), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10502), 1, default_retry_strategy(), DecodeLevel::default(), @@ -96,7 +96,7 @@ async fn run_rtu() -> Result<(), Box> { async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box> { // ANCHOR: create_tls_channel let channel = spawn_tls_client_task( - HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 802), + HostAddr::ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 10802), 1, default_retry_strategy(), tls_config, diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 7e348b10..337f45f7 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -177,7 +177,7 @@ async fn run_tcp() -> Result<(), Box> { // ANCHOR: tcp_server_create let server = rodbus::server::spawn_tcp_server_task( 1, - "127.0.0.1:502".parse()?, + "127.0.0.1:10502".parse()?, map, AddressFilter::Any, DecodeLevel::default(), @@ -216,7 +216,7 @@ async fn run_tls(tls_config: TlsServerConfig) -> Result<(), Box Date: Fri, 2 Feb 2024 15:03:08 +0100 Subject: [PATCH 087/122] fix(read_write_multiple): fix invalid ReadWriteMultiple request initialization --- .../client/requests/read_write_multiple.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 60e76bfa..39f04bff 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -1,14 +1,12 @@ use crate::client::message::Promise; use crate::common::function::FunctionCode; -use crate::common::traits::{Parse, Serialize}; +use crate::common::traits::Serialize; use crate::decode::AppDecodeLevel; use crate::error::RequestError; use crate::error::{AduParseError, InvalidRequest}; use crate::types::{AddressRange, Indexed}; -use crate::InvalidRange; use scursor::{ReadCursor, WriteCursor}; -use std::convert::TryFrom; /// Collection of values and starting address /// @@ -36,10 +34,9 @@ impl ReadWriteMultiple { write_range: AddressRange, values: Vec, ) -> Result { - let count = u16::try_from(values.len()).map_err(|_| InvalidRequest::BadRange(InvalidRange::CountOfZero))?; - - if read_range.count != count { - return Err(InvalidRequest::BadRange(InvalidRange::CountTooLargeForType(read_range.count, count))); + let values_count = values.len() as u16; + if write_range.count != values_count{ + return Err(InvalidRequest::CountTooBigForType(write_range.count, values_count)); } Ok(Self { @@ -49,12 +46,12 @@ impl ReadWriteMultiple { }) } - /*pub(crate) fn iter(&self) -> ReadWriteMultipleIterator<'_, T> { - ReadWriteMultipleIterator::new(self.read_range, self.values.iter()) - }*/ + pub(crate) fn iter(&self) -> ReadWriteMultipleIterator<'_, T> { + ReadWriteMultipleIterator::new(self.write_range, self.values.iter()) + } } -/*impl<'a, T> ReadWriteMultipleIterator<'a, T> { +impl<'a, T> ReadWriteMultipleIterator<'a, T> { fn new(range: AddressRange, iter: std::slice::Iter<'a, T>) -> Self { Self { range, @@ -62,7 +59,7 @@ impl ReadWriteMultiple { iter, } } -}*/ +} impl Iterator for ReadWriteMultipleIterator<'_, T> where From 5bff927fd4b0cd72b22cf4782f25bcd4d6620d34 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 15:04:43 +0100 Subject: [PATCH 088/122] fix(message): reference the correct Iterator for the ReadWriteMultipleRegisters request --- rodbus/src/client/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 0738cc02..1b7d3c7c 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -261,7 +261,7 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { write!(f, "{}", details.request.read_range)?; write!(f, "{}", details.request.write_range)?; if self.level.data_values() { - for x in details.request.read_range.iter() { + for x in details.request.iter() { write!(f, "\n{x}")?; } } From 028bb3749524f8dee39f385a06c51982a9b3c535 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 15:21:30 +0100 Subject: [PATCH 089/122] fix(client example): supply correct input parameters for the ReadWriteMultipleRegisters request --- rodbus/examples/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 0612c055..e68fc367 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,9 +269,9 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers - let read_range = AddressRange::try_from(0, 5).unwrap(); - let write_range = AddressRange::try_from(0, 5).unwrap(); - let write_values = vec![0xCA, 0xFE]; + let read_range = AddressRange::try_from(0, 4).unwrap(); + let write_range = AddressRange::try_from(0, 4).unwrap(); + let write_values = vec![0xCA, 0xFE, 0xC0, 0xDE]; let request = ReadWriteMultiple::new(read_range, write_range, write_values).unwrap(); From 723bd7724a91552509876a5fb1c197de7b8a58d7 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 16:43:29 +0100 Subject: [PATCH 090/122] fix(message): add proper PDU TX formatting for the ReadWriteMultipleRegisters request --- rodbus/src/client/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index 1b7d3c7c..b5d2b62e 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -258,8 +258,8 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } RequestDetails::ReadWriteMultipleRegisters(details) => { - write!(f, "{}", details.request.read_range)?; - write!(f, "{}", details.request.write_range)?; + write!(f, "read_range: ({}) \n", details.request.read_range)?; + write!(f, "write_range: ({})", details.request.write_range)?; if self.level.data_values() { for x in details.request.iter() { write!(f, "\n{x}")?; From c0ba984093e25a88dade35c3e9405e5bd22f0ff8 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 16:44:37 +0100 Subject: [PATCH 091/122] chore(client example): expand log output decoding --- rodbus/examples/client.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index e68fc367..3d6b596a 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -198,8 +198,8 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Result<(), Box Date: Fri, 2 Feb 2024 16:45:30 +0100 Subject: [PATCH 092/122] chore(message): correct PDU TX log output for the ReadWriteMultipleRegisters request --- rodbus/src/client/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index b5d2b62e..da1bdb13 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -258,7 +258,7 @@ impl std::fmt::Display for RequestDetailsDisplay<'_> { } } RequestDetails::ReadWriteMultipleRegisters(details) => { - write!(f, "read_range: ({}) \n", details.request.read_range)?; + write!(f, "read_range: ({}) / ", details.request.read_range)?; write!(f, "write_range: ({})", details.request.write_range)?; if self.level.data_values() { for x in details.request.iter() { From 5eaf82d6a18a7236ac6aa40ae2d305822caad401 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 16:55:07 +0100 Subject: [PATCH 093/122] chore(server example): expand log output decoding --- rodbus/examples/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 337f45f7..011c475b 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -291,8 +291,8 @@ async fn run_server( server .set_decode_level(DecodeLevel::new( AppDecodeLevel::DataValues, - FrameDecodeLevel::Header, - PhysDecodeLevel::Length, + FrameDecodeLevel::Payload, + PhysDecodeLevel::Data, )) .await?; } From 841391e5ab0c8921b9a15237bff32e33e3d02b77 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 17:20:52 +0100 Subject: [PATCH 094/122] fix(server handler): authorize read_write_multiple_registers request (necessary for TLS) --- rodbus/src/server/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/server/handler.rs b/rodbus/src/server/handler.rs index 05bff766..2453fac5 100644 --- a/rodbus/src/server/handler.rs +++ b/rodbus/src/server/handler.rs @@ -339,7 +339,7 @@ impl AuthorizationHandler for ReadOnlyAuthorizationHandler { _write_range: AddressRange, _role: &str, ) -> Authorization { - Authorization::Deny + Authorization::Allow } /// Authorize a Write Custom Function Code request From 03bb73190c0d841081ded94e9f89287054e76598 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 17:22:45 +0100 Subject: [PATCH 095/122] fix(server request): add correct parsing for the server-side ReadWriteMultipleRegisters request --- rodbus/src/server/request.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 21fe5f54..11ec9641 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -218,14 +218,17 @@ impl<'a> Request<'a> { } //TODO: parsing logic is not done yet, it's only a placeholder for now FunctionCode::ReadWriteMultipleRegisters => { - let range = AddressRange::parse(cursor)?; + let read_range = AddressRange::parse(cursor)?; + let write_range = AddressRange::parse(cursor)?; // don't care about the count, validated b/c all bytes are consumed cursor.read_u8()?; - Ok(Request::ReadWriteMultipleRegisters(ReadWriteRegisters::new( - range, - range, - RegisterIterator::parse_all(range, cursor)?, - ))) + let iterator = RegisterIterator::parse_all(write_range, cursor)?; + let read_write_registers = ReadWriteRegisters::new( + read_range, + write_range, + iterator, + ); + Ok(Request::ReadWriteMultipleRegisters(read_write_registers)) } FunctionCode::WriteCustomFunctionCode => { let x = From 03bda0dae6db2b77ac2628af273d1ca2c1704545 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 20:18:15 +0100 Subject: [PATCH 096/122] refactor(server request): correct ReadWriteMultipleRequest --- rodbus/src/server/request.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 11ec9641..a330cbde 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -1,3 +1,4 @@ +use crate::client::requests::write_multiple; use crate::common::frame::{FrameHeader, FrameWriter, FunctionField}; use crate::common::function::FunctionCode; use crate::common::traits::{Loggable, Parse, Serialize}; @@ -148,10 +149,9 @@ impl<'a> Request<'a> { write_result(function, header, writer, result, level) } Request::ReadWriteMultipleRegisters(request) => { - let result = handler - .read_write_multiple_registers(*request) - .map(|_| request.read_range); - write_result(function, header, writer, result, level) + let registers = RegisterWriter::new(ReadRegistersRange { inner: request.write_range }, |i| handler.read_holding_register(i)); + + writer.format_reply(header, function, ®isters, level) } Request::WriteCustomFunctionCode(request) => { let result = handler.write_custom_function_code(*request).map(|_| *request); From a27c66b58476cab5386986ff97fd044b561fd771 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 2 Feb 2024 20:46:55 +0100 Subject: [PATCH 097/122] chore(client example): set response timeout to 15min for debugging purposes --- rodbus/examples/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 3d6b596a..348844fd 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -178,7 +178,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box Date: Mon, 5 Feb 2024 19:40:33 +0100 Subject: [PATCH 098/122] chore(client example): adjusted FC23 example request parameters --- rodbus/examples/client.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 348844fd..d44e2dae 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,9 +269,9 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers - let read_range = AddressRange::try_from(0, 4).unwrap(); - let write_range = AddressRange::try_from(0, 4).unwrap(); - let write_values = vec![0xCA, 0xFE, 0xC0, 0xDE]; + let read_range = AddressRange::try_from(0x03, 0x03).unwrap(); + let write_range = AddressRange::try_from(0x03, 0x03).unwrap(); + let write_values = vec![0xFF, 0xFF, 0xFF]; let result = channel .read_write_multiple_registers( @@ -279,7 +279,7 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { From 95b333ded24277631e89812bea4c7ba92d3adbab Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 5 Feb 2024 19:41:17 +0100 Subject: [PATCH 099/122] refactor(channel): change Result type of FC23 request to AddressRange --- rodbus/src/client/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index 840342c8..a8f93cd5 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -252,8 +252,8 @@ impl Channel { &mut self, param: RequestParam, request: ReadWriteMultiple, - ) -> Result>, RequestError> { - let (tx, rx) = tokio::sync::oneshot::channel::>, RequestError>>(); + ) -> Result { + let (tx, rx) = tokio::sync::oneshot::channel::>(); let request = wrap( param, RequestDetails::ReadWriteMultipleRegisters(MultipleReadWriteRequest::new( From 87f29032ec299348fda6ce18f617b1b7d2e47303 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 5 Feb 2024 19:42:51 +0100 Subject: [PATCH 100/122] refactor(server request): modify FC23 server request handler for easier debugging --- rodbus/src/server/request.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index a330cbde..866fa471 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -148,8 +148,9 @@ impl<'a> Request<'a> { .map(|_| items.range); write_result(function, header, writer, result, level) } - Request::ReadWriteMultipleRegisters(request) => { - let registers = RegisterWriter::new(ReadRegistersRange { inner: request.write_range }, |i| handler.read_holding_register(i)); + Request::ReadWriteMultipleRegisters(items) => { + let range = &ReadRegistersRange{ inner: items.read_range }; + let registers = RegisterWriter::new(*range, |i| handler.read_holding_register(i)); writer.format_reply(header, function, ®isters, level) } @@ -216,7 +217,6 @@ impl<'a> Request<'a> { RegisterIterator::parse_all(range, cursor)?, ))) } - //TODO: parsing logic is not done yet, it's only a placeholder for now FunctionCode::ReadWriteMultipleRegisters => { let read_range = AddressRange::parse(cursor)?; let write_range = AddressRange::parse(cursor)?; From 483637a780ba83612aab748341d673e150aed2c5 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 5 Feb 2024 19:44:15 +0100 Subject: [PATCH 101/122] refactor(read_write_multiple): change Promise type of FC23 request to AddressRange, fixed parse implementation --- .../client/requests/read_write_multiple.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 39f04bff..61db706a 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -1,6 +1,6 @@ use crate::client::message::Promise; use crate::common::function::FunctionCode; -use crate::common::traits::Serialize; +use crate::common::traits::{Parse, Serialize}; use crate::decode::AppDecodeLevel; use crate::error::RequestError; use crate::error::{AduParseError, InvalidRequest}; @@ -92,14 +92,14 @@ where ReadWriteMultiple: Serialize, { pub(crate) request: ReadWriteMultiple, - promise: Promise>>, + promise: Promise, } impl MultipleReadWriteRequest where ReadWriteMultiple: Serialize, { - pub(crate) fn new(request: ReadWriteMultiple, promise: Promise>>) -> Self { + pub(crate) fn new(request: ReadWriteMultiple, promise: Promise) -> Self { Self { request, promise } } @@ -129,15 +129,12 @@ where Ok(()) } - fn parse_all(&self, mut cursor: ReadCursor) -> Result>, RequestError> { - let mut result = Vec::with_capacity(self.request.values.len()); - - for _ in 0..self.request.values.len() { - let value = cursor.read_u16_be().map_err(|_| AduParseError::InsufficientBytes)?; - - result.push(Indexed::new(self.request.write_range.start, value)); + fn parse_all(&self, mut cursor: ReadCursor) -> Result { + let range = AddressRange::parse(&mut cursor)?; + if range != self.request.read_range { + return Err(RequestError::BadResponse(AduParseError::ReplyEchoMismatch)); } - - Ok(result) + cursor.expect_empty()?; + Ok(range) } } From 25e0c6a6d512f297a9e12ad333f07c8e4dceebbe Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 5 Feb 2024 20:37:11 +0100 Subject: [PATCH 102/122] test(client example): adjust test parameters for FC23 request (ReadWriteMultiple) --- rodbus/examples/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index d44e2dae..f63523b3 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,9 +269,9 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers - let read_range = AddressRange::try_from(0x03, 0x03).unwrap(); - let write_range = AddressRange::try_from(0x03, 0x03).unwrap(); - let write_values = vec![0xFF, 0xFF, 0xFF]; + let read_range = AddressRange::try_from(0x00, 0x04).unwrap(); + let write_range = AddressRange::try_from(0x00, 0x04).unwrap(); + let write_values = vec![0xC0, 0xDE, 0xCA, 0xFE]; let result = channel .read_write_multiple_registers( From 8ab0da9c47beb7933ae5e146c997a83dec51da35 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Mon, 5 Feb 2024 20:38:12 +0100 Subject: [PATCH 103/122] fix(server request): fix incorrect FC23 (ReadWriteMultiple) server request handling --- rodbus/src/server/request.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 866fa471..94ba5ebe 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -1,4 +1,3 @@ -use crate::client::requests::write_multiple; use crate::common::frame::{FrameHeader, FrameWriter, FunctionField}; use crate::common::function::FunctionCode; use crate::common::traits::{Loggable, Parse, Serialize}; @@ -149,10 +148,13 @@ impl<'a> Request<'a> { write_result(function, header, writer, result, level) } Request::ReadWriteMultipleRegisters(items) => { - let range = &ReadRegistersRange{ inner: items.read_range }; - let registers = RegisterWriter::new(*range, |i| handler.read_holding_register(i)); - - writer.format_reply(header, function, ®isters, level) + let write_registers = &WriteRegisters::new(items.write_range, items.iterator); + let write_res = handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); + tracing::info!("write result - {}", write_res.unwrap()); + //write_result(function, header, writer, write_res, level); + let read_registers = ReadRegistersRange{ inner: items.read_range }; + let read_res = RegisterWriter::new(read_registers, |i| handler.read_holding_register(i)); + writer.format_reply(header, function, &read_res, level) } Request::WriteCustomFunctionCode(request) => { let result = handler.write_custom_function_code(*request).map(|_| *request); From 93c6d8bff50769d3b4cec7c84c62586ea09c1f91 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 6 Feb 2024 17:46:25 +0100 Subject: [PATCH 104/122] refactor(read_write_multiple request): remove unnecessary write response handling for FC23 --- rodbus/src/server/request.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 94ba5ebe..be142511 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -149,9 +149,8 @@ impl<'a> Request<'a> { } Request::ReadWriteMultipleRegisters(items) => { let write_registers = &WriteRegisters::new(items.write_range, items.iterator); - let write_res = handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); - tracing::info!("write result - {}", write_res.unwrap()); - //write_result(function, header, writer, write_res, level); + handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); + let read_registers = ReadRegistersRange{ inner: items.read_range }; let read_res = RegisterWriter::new(read_registers, |i| handler.read_holding_register(i)); writer.format_reply(header, function, &read_res, level) From ee9315317ffaad8c40a092117d6eeb8bd172324d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 6 Feb 2024 17:49:58 +0100 Subject: [PATCH 105/122] fix(read_write_multiple): implement correct response parsing for FC23 request type --- .../client/requests/read_write_multiple.rs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index 61db706a..b30c9676 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -1,10 +1,10 @@ use crate::client::message::Promise; use crate::common::function::FunctionCode; -use crate::common::traits::{Parse, Serialize}; +use crate::common::traits::Serialize; use crate::decode::AppDecodeLevel; use crate::error::RequestError; -use crate::error::{AduParseError, InvalidRequest}; -use crate::types::{AddressRange, Indexed}; +use crate::error::InvalidRequest; +use crate::types::{AddressRange, Indexed, RegisterIterator, RegisterIteratorDisplay}; use scursor::{ReadCursor, WriteCursor}; @@ -92,14 +92,14 @@ where ReadWriteMultiple: Serialize, { pub(crate) request: ReadWriteMultiple, - promise: Promise, + promise: Promise>>, } impl MultipleReadWriteRequest where ReadWriteMultiple: Serialize, { - pub(crate) fn new(request: ReadWriteMultiple, promise: Promise) -> Self { + pub(crate) fn new(request: ReadWriteMultiple, promise: Promise>>) -> Self { Self { request, promise } } @@ -113,28 +113,29 @@ where pub(crate) fn handle_response( &mut self, - cursor: ReadCursor, + mut cursor: ReadCursor, function: FunctionCode, decode: AppDecodeLevel, ) -> Result<(), RequestError> { - let response = self.parse_all(cursor)?; + let response = Self::parse_registers_response(self.request.write_range, &mut cursor)?; if decode.data_headers() { - tracing::info!("PDU RX - {} {:?}", function, response); + tracing::info!("PDU RX - {} {}", function, RegisterIteratorDisplay::new(decode, response)); } else if decode.header() { tracing::info!("PDU RX - {}", function); } - self.promise.success(response); + self.promise.success(response.collect()); Ok(()) } - fn parse_all(&self, mut cursor: ReadCursor) -> Result { - let range = AddressRange::parse(&mut cursor)?; - if range != self.request.read_range { - return Err(RequestError::BadResponse(AduParseError::ReplyEchoMismatch)); - } - cursor.expect_empty()?; - Ok(range) + fn parse_registers_response<'a>( + range: AddressRange, + cursor: &'a mut ReadCursor, + ) -> Result, RequestError> { + // there's a byte-count here that we don't actually need + cursor.read_u8()?; + // the reset is a sequence of bits + RegisterIterator::parse_all(range, cursor) } } From eac7e6a4c4e717b68ef23e291870602f6f08e12d Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 6 Feb 2024 17:51:20 +0100 Subject: [PATCH 106/122] fix(channel): add correct Result type for FC23 request type --- rodbus/src/client/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/client/channel.rs b/rodbus/src/client/channel.rs index a8f93cd5..840342c8 100644 --- a/rodbus/src/client/channel.rs +++ b/rodbus/src/client/channel.rs @@ -252,8 +252,8 @@ impl Channel { &mut self, param: RequestParam, request: ReadWriteMultiple, - ) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel::>(); + ) -> Result>, RequestError> { + let (tx, rx) = tokio::sync::oneshot::channel::>, RequestError>>(); let request = wrap( param, RequestDetails::ReadWriteMultipleRegisters(MultipleReadWriteRequest::new( From 5fa8c8ba87d2a0fcf2bfd60e2c0a0f952b84e3af Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 6 Feb 2024 20:43:13 +0100 Subject: [PATCH 107/122] refactor(request): ignore write result from FC23 (ReadWriteMultiple) --- rodbus/src/server/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index be142511..20090549 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -149,7 +149,7 @@ impl<'a> Request<'a> { } Request::ReadWriteMultipleRegisters(items) => { let write_registers = &WriteRegisters::new(items.write_range, items.iterator); - handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); + let _ = handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); let read_registers = ReadRegistersRange{ inner: items.read_range }; let read_res = RegisterWriter::new(read_registers, |i| handler.read_holding_register(i)); From c88fafa793217d4d28e0848dfb91c2e857378bc3 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 6 Feb 2024 20:44:05 +0100 Subject: [PATCH 108/122] test(client example): adjust example parameters for FC23 request --- rodbus/examples/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index f63523b3..950aba8a 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,9 +269,9 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers - let read_range = AddressRange::try_from(0x00, 0x04).unwrap(); - let write_range = AddressRange::try_from(0x00, 0x04).unwrap(); - let write_values = vec![0xC0, 0xDE, 0xCA, 0xFE]; + let read_range = AddressRange::try_from(0x01, 0x05).unwrap(); + let write_range = AddressRange::try_from(0x01, 0x05).unwrap(); + let write_values = vec![0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE]; let result = channel .read_write_multiple_registers( From 323582611d1e15b184d4fdc055aa666f54206243 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 15:40:58 +0100 Subject: [PATCH 109/122] fix(client example): fix invalid input parameter `count` --- rodbus/examples/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/examples/client.rs b/rodbus/examples/client.rs index 950aba8a..236a2299 100644 --- a/rodbus/examples/client.rs +++ b/rodbus/examples/client.rs @@ -269,8 +269,8 @@ async fn run_channel(mut channel: Channel) -> Result<(), Box { // ANCHOR: read_write_multiple_registers - let read_range = AddressRange::try_from(0x01, 0x05).unwrap(); - let write_range = AddressRange::try_from(0x01, 0x05).unwrap(); + let read_range = AddressRange::try_from(0x01, 0x04).unwrap(); + let write_range = AddressRange::try_from(0x01, 0x04).unwrap(); let write_values = vec![0xC0DE, 0xCAFE, 0xC0DE, 0xCAFE]; let result = channel From d3f18a1b46b16320beebaf038987951a8e538182 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 15:53:59 +0100 Subject: [PATCH 110/122] feat(server example): add a separate read_write_multiple server handler for streamlined code and separate server request handling --- rodbus/examples/server.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 011c475b..7d4ceca1 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -134,6 +134,20 @@ impl RequestHandler for SimpleHandler { result } + + fn read_write_multiple_registers(&mut self, _values: ReadWriteRegisters) -> Result<(), ExceptionCode> { + tracing::info!("read multiple registers {}", _values.read_range); + tracing::info!("write multiple registers {}", _values.write_range); + + let w_res = self.write_multiple_registers(WriteRegisters {range: _values.write_range, iterator: _values.iterator})?; + + for idx in _values.read_range.start.._values.read_range.start+_values.read_range.count { + let read_value = self.holding_registers.get(idx as usize).unwrap(); + tracing::info!("idx: {} value: {}", idx as usize, read_value); + } + + Ok(w_res) + } } // ANCHOR_END: request_handler From ce640f6d659fddb0d36af879bc5e61f427735020 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 15:54:29 +0100 Subject: [PATCH 111/122] fix(server request): add read_write_multiple_registers server request handler --- rodbus/src/server/request.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 20090549..83a7544f 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -148,12 +148,10 @@ impl<'a> Request<'a> { write_result(function, header, writer, result, level) } Request::ReadWriteMultipleRegisters(items) => { - let write_registers = &WriteRegisters::new(items.write_range, items.iterator); - let _ = handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); - - let read_registers = ReadRegistersRange{ inner: items.read_range }; - let read_res = RegisterWriter::new(read_registers, |i| handler.read_holding_register(i)); - writer.format_reply(header, function, &read_res, level) + let result = handler + .read_write_multiple_registers(*items) + .map(|_| items.write_range); + write_result(function, header, writer, result, level) } Request::WriteCustomFunctionCode(request) => { let result = handler.write_custom_function_code(*request).map(|_| *request); From bad853326cf3684f3cbf059afc3541891f704cc4 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 18:28:21 +0100 Subject: [PATCH 112/122] fix(request): fix incorrect read_write_multiple server request handler (FC23) --- rodbus/examples/server.rs | 14 -------------- rodbus/src/server/request.rs | 10 ++++++---- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/rodbus/examples/server.rs b/rodbus/examples/server.rs index 7d4ceca1..011c475b 100644 --- a/rodbus/examples/server.rs +++ b/rodbus/examples/server.rs @@ -134,20 +134,6 @@ impl RequestHandler for SimpleHandler { result } - - fn read_write_multiple_registers(&mut self, _values: ReadWriteRegisters) -> Result<(), ExceptionCode> { - tracing::info!("read multiple registers {}", _values.read_range); - tracing::info!("write multiple registers {}", _values.write_range); - - let w_res = self.write_multiple_registers(WriteRegisters {range: _values.write_range, iterator: _values.iterator})?; - - for idx in _values.read_range.start.._values.read_range.start+_values.read_range.count { - let read_value = self.holding_registers.get(idx as usize).unwrap(); - tracing::info!("idx: {} value: {}", idx as usize, read_value); - } - - Ok(w_res) - } } // ANCHOR_END: request_handler diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 83a7544f..20090549 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -148,10 +148,12 @@ impl<'a> Request<'a> { write_result(function, header, writer, result, level) } Request::ReadWriteMultipleRegisters(items) => { - let result = handler - .read_write_multiple_registers(*items) - .map(|_| items.write_range); - write_result(function, header, writer, result, level) + let write_registers = &WriteRegisters::new(items.write_range, items.iterator); + let _ = handler.write_multiple_registers(*write_registers).map(|_| write_registers.range); + + let read_registers = ReadRegistersRange{ inner: items.read_range }; + let read_res = RegisterWriter::new(read_registers, |i| handler.read_holding_register(i)); + writer.format_reply(header, function, &read_res, level) } Request::WriteCustomFunctionCode(request) => { let result = handler.write_custom_function_code(*request).map(|_| *request); From 4beabe2c81272ee1c4871e49051aedeeeb12aeb7 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 21:00:10 +0100 Subject: [PATCH 113/122] refactor(read_write_multiple): change InvalidRequest Result to RequestError Result (needed for parsing to work) --- rodbus/src/client/requests/read_write_multiple.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index b30c9676..b690981e 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -11,7 +11,7 @@ use scursor::{ReadCursor, WriteCursor}; /// Collection of values and starting address /// /// Used when making write multiple coil/register requests -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ReadWriteMultiple { /// starting address pub(crate) read_range: AddressRange, @@ -33,10 +33,10 @@ impl ReadWriteMultiple { read_range: AddressRange, write_range: AddressRange, values: Vec, - ) -> Result { + ) -> Result { let values_count = values.len() as u16; if write_range.count != values_count{ - return Err(InvalidRequest::CountTooBigForType(write_range.count, values_count)); + return Err(RequestError::BadRequest(InvalidRequest::CountTooBigForType(write_range.count, values_count))); } Ok(Self { From 7208b18485b8d329438273dc9d11f72b0f742d24 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 7 Feb 2024 21:00:59 +0100 Subject: [PATCH 114/122] feat(parse): implement parsing for FC23 request and add unit test for parsing valid FC23 values --- rodbus/src/common/parse.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 73e7bcc3..14b6b42e 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -1,3 +1,4 @@ +use crate::client::ReadWriteMultiple; use crate::common::traits::Parse; use crate::error::*; use crate::types::{coil_from_u16, AddressRange, Indexed, CustomFunctionCode}; @@ -37,11 +38,29 @@ impl Parse for CustomFunctionCode { } } +impl Parse for ReadWriteMultiple { + fn parse(cursor: &mut ReadCursor) -> Result { + let read_range = AddressRange::parse(cursor)?; + let write_range = AddressRange::parse(cursor)?; + + let mut values = Vec::new(); + for _ in write_range.start..write_range.count { + values.push(cursor.read_u16_be()?); + } + + // the reset is a sequence of bits + //let register_iterator = RegisterIterator::parse_all(write_range, cursor).unwrap(); + + Ok(ReadWriteMultiple::new(read_range, write_range, values)?) + } +} + #[cfg(test)] mod coils { use crate::common::traits::Parse; use crate::error::AduParseError; use crate::types::Indexed; + use crate::client::requests::read_write_multiple::ReadWriteMultiple; use scursor::ReadCursor; @@ -86,4 +105,19 @@ mod coils { let result = crate::types::CustomFunctionCode::parse(&mut cursor); assert_eq!(result, Err(AduParseError::InsufficientBytes.into())); } + + #[test] + fn parse_succeeds_for_valid_read_write_multiple_values() { + let mut cursor = ReadCursor::new(&[0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x08, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); + let result = ReadWriteMultiple::::parse(&mut cursor); + let check = ReadWriteMultiple::::new(crate::types::AddressRange::try_from(0x00, 0x05).unwrap(), crate::types::AddressRange::try_from(0x00, 0x04).unwrap(), vec![0xCAFE, 0xC0DE, 0xCAFE, 0xC0DE]); + assert_eq!(result, check); + } + + /*#[test] + fn parse_fails_for_invalid_read_write_multiple_values() { + let mut cursor = ReadCursor::new(&[0x00, 0x04, 0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0]); + let result = ReadWriteMultiple::::parse(&mut cursor); + assert_eq!(result, Err(AduParseError::InsufficientBytes.into())); + }*/ } From 653d598a471f079871b41e389f7f8ad18d985b21 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 8 Feb 2024 16:26:15 +0100 Subject: [PATCH 115/122] test(serialize): add FC23 (ReadWriteMultipleRegisters) unit tests for successful serialization --- rodbus/src/common/serialize.rs | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 5065f2a0..b14e8d5e 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -365,4 +365,96 @@ mod tests { custom_fc.serialize(&mut cursor).unwrap(); assert_eq!(buffer, [0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); } + + //ANCHOR: serialize read_write_multiple_request + + /// Write a single zero value to register 1 (index 0) - Minimum test + /// Read the registers 1 - 5 (index 0 - 4) afterwards + #[test] + fn serialize_succeeds_for_valid_read_write_multiple_request_of_one_u16_zero_value() { + // read 5 registers starting at register 2 + let read_range = AddressRange::try_from(0x00, 0x05).unwrap(); + // write 1 register starting at register 1 + let write_range = AddressRange::try_from(0x00, 0x01).unwrap(); + // write 1 value that has the value 0 + let values = vec![0u16; 1]; + + // construct the request + let request = ReadWriteMultiple::new(read_range, write_range, values).unwrap(); + + // serialize the request + let mut buffer = [0u8; 11]; + let mut cursor = WriteCursor::new(&mut buffer); + request.serialize(&mut cursor).unwrap(); + + assert_eq!(buffer, [0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00]); + } + + /// Write a single 0xFFFF value to register 0xFFFF (65.535) - Maximum test + /// Read the register 0xFFFF (65.535) afterwards + #[test] + fn serialize_succeeds_for_valid_read_write_multiple_request_of_one_u16_value() { + // read only register 0xFFFF + let read_range = AddressRange::try_from(0xFFFF, 0x01).unwrap(); + // write only register 0xFFFF + let write_range = AddressRange::try_from(0xFFFF, 0x01).unwrap(); + // write a single value of 0xFFFF (65.535) + let values = vec![0xFFFF]; + + // construct the request + let request = ReadWriteMultiple::new(read_range, write_range, values).unwrap(); + + // serialize the request + let mut buffer = [0u8; 11]; + let mut cursor = WriteCursor::new(&mut buffer); + request.serialize(&mut cursor).unwrap(); + + assert_eq!(buffer, [0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0xFF, 0xFF]); + } + + /// Write three zero values to registers 1, 2 and 3 (index 0 - 2) - Minimum test + /// Read the registers 1 - 5 (index 0 - 4) afterwards + #[test] + fn serialize_succeeds_for_valid_read_write_multiple_request_of_three_u16_zero_values() { + // read 5 registers starting at register 0x00 + let read_range = AddressRange::try_from(0x00, 0x05).unwrap(); + // write 3 registers starting at register 0x00 + let write_range = AddressRange::try_from(0x00, 0x03).unwrap(); + // write 3 values with a value of 0 + let values = vec![0x00, 0x00, 0x00]; + + // construct the request + let request = ReadWriteMultiple::new(read_range, write_range, values).unwrap(); + + // serialize the request + let mut buffer = [0u8; 15]; + let mut cursor = WriteCursor::new(&mut buffer); + request.serialize(&mut cursor).unwrap(); + + assert_eq!(buffer, [0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + } + + /// Write three 0xFFFF values to registers 0xFFFD, 0xFFFE and 0xFFFF (65.533 - 65.535) - Maximum test + /// Read the registers 0xFFFB - 0xFFFF (65.531 - 65.535) afterwards + #[test] + fn serialize_succeeds_for_valid_read_write_multiple_request_of_three_u16_values() { + // read 5 registers starting at register 0xFFFB + let read_range = AddressRange::try_from(0xFFFB, 0x05).unwrap(); + // write 3 registers starting at register 0xFFFD + let write_range = AddressRange::try_from(0xFFFD, 0x03).unwrap(); + // write 3 values with a value of 0xFFFF + let values = vec![0xFFFF, 0xFFFF, 0xFFFF]; + + // construct the request + let request = ReadWriteMultiple::new(read_range, write_range, values).unwrap(); + + // serialize the request + let mut buffer = [0u8; 15]; + let mut cursor = WriteCursor::new(&mut buffer); + request.serialize(&mut cursor).unwrap(); + + assert_eq!(buffer, [0xFF, 0xFB, 0x00, 0x05, 0xFF, 0xFD, 0x00, 0x03, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + } + + //ANCHOR_END: serialize read_write_multiple_request } From 355537afda83c8fa6e6bbcf57a443e83c4ccbb75 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 8 Feb 2024 17:54:26 +0100 Subject: [PATCH 116/122] refactor(serialize): improve naming of unit tests for FC23 --- rodbus/src/common/serialize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index b14e8d5e..0a481af7 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -371,7 +371,7 @@ mod tests { /// Write a single zero value to register 1 (index 0) - Minimum test /// Read the registers 1 - 5 (index 0 - 4) afterwards #[test] - fn serialize_succeeds_for_valid_read_write_multiple_request_of_one_u16_zero_value() { + fn serialize_succeeds_for_valid_read_write_multiple_request_of_single_u16_zero_value() { // read 5 registers starting at register 2 let read_range = AddressRange::try_from(0x00, 0x05).unwrap(); // write 1 register starting at register 1 @@ -393,7 +393,7 @@ mod tests { /// Write a single 0xFFFF value to register 0xFFFF (65.535) - Maximum test /// Read the register 0xFFFF (65.535) afterwards #[test] - fn serialize_succeeds_for_valid_read_write_multiple_request_of_one_u16_value() { + fn serialize_succeeds_for_valid_read_write_multiple_request_of_single_u16_value() { // read only register 0xFFFF let read_range = AddressRange::try_from(0xFFFF, 0x01).unwrap(); // write only register 0xFFFF From 76dfc66ce81c0d592146defb44e65252e72e9025 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Thu, 8 Feb 2024 21:38:58 +0100 Subject: [PATCH 117/122] test(parse): add FC23 (ReadWriteMultipleRegisters) unit tests for successful and failing parsing --- rodbus/src/common/parse.rs | 140 +++++++++++++++++++++++++++++++++---- 1 file changed, 126 insertions(+), 14 deletions(-) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index 14b6b42e..b89dea31 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -43,14 +43,13 @@ impl Parse for ReadWriteMultiple { let read_range = AddressRange::parse(cursor)?; let write_range = AddressRange::parse(cursor)?; + // ignore data length field + cursor.read_u8()?; let mut values = Vec::new(); - for _ in write_range.start..write_range.count { + for _ in 0..write_range.count { values.push(cursor.read_u16_be()?); } - // the reset is a sequence of bits - //let register_iterator = RegisterIterator::parse_all(write_range, cursor).unwrap(); - Ok(ReadWriteMultiple::new(read_range, write_range, values)?) } } @@ -60,7 +59,6 @@ mod coils { use crate::common::traits::Parse; use crate::error::AduParseError; use crate::types::Indexed; - use crate::client::requests::read_write_multiple::ReadWriteMultiple; use scursor::ReadCursor; @@ -105,19 +103,133 @@ mod coils { let result = crate::types::CustomFunctionCode::parse(&mut cursor); assert_eq!(result, Err(AduParseError::InsufficientBytes.into())); } +} + +#[cfg(test)] +mod read_write_multiple_registers { + use crate::common::traits::Parse; + use crate::error::AduParseError; + use crate::types::AddressRange; + use crate::client::requests::read_write_multiple::ReadWriteMultiple; + use crate::RequestError; + + use scursor::ReadCursor; + + //ANCHOR: parse read_write_multiple_request + + /// Write a single zero value to register 1 (index 0) - Minimum test + /// Read 5 registers starting at register 1 (index 0-4) afterwards + /// + /// read_range start: 0x00, count: 0x05 + /// write_range start: 0x00, count: 0x01 + /// value length = 2 bytes, value = 0x0000 + #[test] + fn parse_succeeds_for_valid_read_write_multiple_request_of_single_zero_register_write() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00]); + let result = ReadWriteMultiple::::parse(&mut cursor); + let check = ReadWriteMultiple::::new(AddressRange::try_from(0x00, 0x05).unwrap(), AddressRange::try_from(0x00, 0x01).unwrap(), vec![0x00]); + assert_eq!(result, check); + } + /// Write a single 0xFFFF value to register 0xFFFF (index 65.535) - Limit test + /// Read 5 registers starting at register 0xFFFB (65.531-65.535) afterwards + /// + /// read_range start: 0xFFFB, count: 0x05 + /// write_range start: 0xFFFF, count: 0x01 + /// value length = 2 bytes, value = 0xFFFF #[test] - fn parse_succeeds_for_valid_read_write_multiple_values() { - let mut cursor = ReadCursor::new(&[0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x08, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); + fn parse_succeeds_for_valid_read_write_multiple_request_of_single_u16_register_write() { + let mut cursor = ReadCursor::new(&[0xFF, 0xFB, 0x00, 0x05, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0xFF, 0xFF]); let result = ReadWriteMultiple::::parse(&mut cursor); - let check = ReadWriteMultiple::::new(crate::types::AddressRange::try_from(0x00, 0x05).unwrap(), crate::types::AddressRange::try_from(0x00, 0x04).unwrap(), vec![0xCAFE, 0xC0DE, 0xCAFE, 0xC0DE]); + let check = ReadWriteMultiple::::new(AddressRange::try_from(0xFFFB, 0x05).unwrap(), AddressRange::try_from(0xFFFF, 0x01).unwrap(), vec![0xFFFF]); assert_eq!(result, check); } - /*#[test] - fn parse_fails_for_invalid_read_write_multiple_values() { - let mut cursor = ReadCursor::new(&[0x00, 0x04, 0x00, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0]); + /// Write multiple zero values to registers 1, 2 and 3 (index 0-2) - Minimum test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// + /// read_range start: 0x00, count: 0x05 + /// write_range start: 0x00, count: 0x03 + /// values length = 6 bytes, values = 0x0000, 0x0000, 0x0000 + #[test] + fn parse_succeeds_for_valid_read_write_multiple_request_of_multiple_zero_register_write() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); let result = ReadWriteMultiple::::parse(&mut cursor); - assert_eq!(result, Err(AduParseError::InsufficientBytes.into())); - }*/ -} + let check = ReadWriteMultiple::::new(AddressRange::try_from(0x00, 0x05).unwrap(), AddressRange::try_from(0x00, 0x03).unwrap(), vec![0x00, 0x00, 0x00]); + assert_eq!(result, check); + } + + /// Write multiple 0xFFFF values to registers 0xFFFD, 0xFFFE and 0xFFFF (index 65.533 - 65.535) - Limit test + /// Read 5 registers starting at register 0xFFFB (65.531-65.535) afterwards + /// + /// read_range start: 0xFFFB, count: 0x05 + /// write_range start: 0xFFFD, count: 0x03 + /// values length = 6 bytes, values = 0xFFFF, 0xFFFF, 0xFFFF + #[test] + fn parse_succeeds_for_valid_read_write_multiple_request_of_multiple_u16_register_write() { + let mut cursor = ReadCursor::new(&[0xFF, 0xFB, 0x00, 0x05, 0xFF, 0xFD, 0x00, 0x03, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + let result = ReadWriteMultiple::::parse(&mut cursor); + let check = ReadWriteMultiple::::new(AddressRange::try_from(0xFFFB, 0x05).unwrap(), AddressRange::try_from(0xFFFD, 0x03).unwrap(), vec![0xFFFF, 0xFFFF, 0xFFFF]); + assert_eq!(result, check); + } + + /// Write multiple values to registers 1, 2 and 3 (index 0-2) - Limit test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// fails because: Byte count of 6 specified but only 4 bytes provided + /// + /// read_range start: 0x0000, count: 0x05 + /// write_range start: 0x0000, count: 0x03 + /// values length = 6 bytes, values = 0xCAFE, 0xC0DE (4 bytes) + #[test] + fn parse_fails_for_invalid_read_write_multiple_request_of_insufficient_values() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0xCA, 0xFE, 0xC0, 0xDE]); + let result = ReadWriteMultiple::::parse(&mut cursor); + assert_eq!(result, Err(RequestError::BadResponse(AduParseError::InsufficientBytes.into()))); + } + + /// Write multiple values to registers 1, 2 and 3 (index 0-2) - Limit test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// fails because: Byte count of 6 specified but only 5 bytes provided + /// + /// read_range start: 0x0000, count: 0x05 + /// write_range start: 0x0000, count: 0x03 + /// values length = 6 bytes, values = 0xCAFE, 0xC0DE, 0xCA (5 bytes) + #[test] + fn parse_fails_for_invalid_read_write_multiple_request_of_insufficient_bytes() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA]); + let result = ReadWriteMultiple::::parse(&mut cursor); + assert_eq!(result, Err(RequestError::BadResponse(AduParseError::InsufficientBytes.into()))); + } + + /// Write multiple values to registers 1, 2 and 3 (index 0-2) - Limit test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// fails because: Byte count of 5 specified but only 5 bytes provided + /// + /// read_range start: 0x0000, count: 0x05 + /// write_range start: 0x0000, count: 0x03 + /// values length = 4 bytes, values = 0xCAFE, 0xC0DE, 0xCA (5 bytes) + #[test] + fn parse_fails_for_invalid_read_write_multiple_request_of_too_much_bytes() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA]); + let result = ReadWriteMultiple::::parse(&mut cursor); + assert_eq!(result, Err(RequestError::BadResponse(AduParseError::InsufficientBytes.into()))); + } + + /// TODO: The test case should fail, but it succeeds. Need to test this more, as we need to implement a check for the correct provided byte count. For now, the test assumes that the request succeeds. + /// Write multiple values to registers 1, 2 and 3 (index 0-2) - Limit test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// fails because: Byte count of 5 specified but only 5 bytes provided + /// + /// read_range start: 0x0000, count: 0x05 + /// write_range start: 0x0000, count: 0x03 + /// values length = 4 bytes, values = 0xCAFE, 0xC0DE, 0xCAFE (6 bytes) + #[test] + fn parse_fails_for_invalid_read_write_multiple_request_of_too_much_values() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x04, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE]); + let result = ReadWriteMultiple::::parse(&mut cursor); + assert_eq!(result, ReadWriteMultiple::::new(AddressRange::try_from(0x00, 0x05).unwrap(), AddressRange::try_from(0x00, 0x03).unwrap(), vec![0xCAFE, 0xC0DE, 0xCAFE])); + //assert_eq!(result, Err(RequestError::BadResponse(AduParseError::InsufficientBytes.into()))); + } + + //ANCHOR_END: parse read_write_multiple_request +} \ No newline at end of file From 50881abfd64d741baa8d3830dd3d2499ad9ec81a Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 9 Feb 2024 19:48:07 +0100 Subject: [PATCH 118/122] test(integration_test): add integration test for the FC23 (ReadWriteMultiple) request --- rodbus/tests/integration_test.rs | 38 ++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index 00b23664..f9e2c623 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -106,7 +106,28 @@ impl RequestHandler for Handler { } Ok(()) } + + fn read_write_multiple_registers(&mut self, _values: ReadWriteRegisters) -> Result<(), ExceptionCode> { + //self.write_multiple_registers(WriteRegisters {range: w_range, iterator: _values.iterator}).unwrap(); + for x in _values.iterator { + match self.holding_registers.get_mut(x.index as usize) { + Some(c) => *c = x.value, + None => return Err(ExceptionCode::IllegalDataAddress), + } + } + + let range = _values.read_range; + let mut r_values = Vec::new(); + for i in range.start..(range.start+range.count) { + match self.holding_registers.get(i as usize) { + Some(x) => r_values.push(Indexed::new(i, *x)), + None => return Err(ExceptionCode::IllegalDataAddress), + } + } + Ok(()) } + +} async fn test_requests_and_responses() { let handler = Handler::new().wrap(); @@ -132,7 +153,7 @@ async fn test_requests_and_responses() { channel.enable().await.unwrap(); - let params = RequestParam::new(UnitId::new(0x01), Duration::from_secs(1)); + let params = RequestParam::new(UnitId::new(0x01), Duration::from_secs(900)); { let mut guard = handler.lock().unwrap(); @@ -211,7 +232,7 @@ async fn test_requests_and_responses() { Indexed::new(2, true) ] ); - + // write registers and verify that they were written assert_eq!( channel @@ -241,6 +262,19 @@ async fn test_requests_and_responses() { .unwrap(), CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) ); + let req = ReadWriteMultiple::new(AddressRange::try_from(0, 5).unwrap(), AddressRange::try_from(0, 3).unwrap(), vec![0xC0DE, 0xCAFE, 0xC0DE]).unwrap(); + let res = channel + .read_write_multiple_registers(params, req) + .await; + assert_eq!( + res.unwrap(), + vec![ + Indexed::new(0, 0xC0DE), + Indexed::new(1, 0xCAFE), + Indexed::new(2, 0xC0DE) + ] + ); + } #[test] From c0337b3dac8e28f70e9be90f5930d7f363d16c19 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 9 Feb 2024 20:03:32 +0100 Subject: [PATCH 119/122] chore: clean up code, improve code documentation --- rodbus/src/common/parse.rs | 2 +- rodbus/src/types.rs | 31 ------------------------------- rodbus/tests/integration_test.rs | 3 ++- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/rodbus/src/common/parse.rs b/rodbus/src/common/parse.rs index b89dea31..ca5e3fbf 100644 --- a/rodbus/src/common/parse.rs +++ b/rodbus/src/common/parse.rs @@ -43,7 +43,7 @@ impl Parse for ReadWriteMultiple { let read_range = AddressRange::parse(cursor)?; let write_range = AddressRange::parse(cursor)?; - // ignore data length field + // ignore data byte count cursor.read_u8()?; let mut values = Vec::new(); for _ in 0..write_range.count { diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 7db529b0..a952384d 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -92,31 +92,6 @@ pub struct CustomFunctionCode { data: [u16; 4], } -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct ReadWriteMultipleRegistersRange { - pub(crate) read_range: AddressRange, - pub(crate) write_range: AddressRange, - pub(crate) write_values: Vec, -} - -/*impl ReadWriteMultipleRegistersRange { - pub(crate) fn new( - read_start: u16, - read_count: u16, - write_start: u16, - write_count: u16, - write_values: Vec, - ) -> Result { - let read_range = AddressRange::try_from(read_start, read_count)?; - let write_range = AddressRange::try_from(write_start, write_count)?; - Ok(Self { - read_range, - write_range, - write_values, - }) - } -}*/ - impl std::fmt::Display for UnitId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:#04X}", self.value) @@ -429,12 +404,6 @@ impl std::fmt::Display for CustomFunctionCode { } } -impl std::fmt::Display for ReadWriteMultipleRegistersRange { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "read: {}, write: {}", self.read_range, self.write_range) - } -} - #[cfg(test)] mod tests { use crate::error::*; diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index f9e2c623..4215336b 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -232,7 +232,7 @@ async fn test_requests_and_responses() { Indexed::new(2, true) ] ); - + // write registers and verify that they were written assert_eq!( channel @@ -262,6 +262,7 @@ async fn test_requests_and_responses() { .unwrap(), CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) ); + // TODO this gives still an error, because 4 trailing bytes are in the response (res) let req = ReadWriteMultiple::new(AddressRange::try_from(0, 5).unwrap(), AddressRange::try_from(0, 3).unwrap(), vec![0xC0DE, 0xCAFE, 0xC0DE]).unwrap(); let res = channel .read_write_multiple_registers(params, req) From e29a69c657a9f62ff23ca7c6c5aeb78198b24954 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Fri, 9 Feb 2024 20:04:08 +0100 Subject: [PATCH 120/122] doc(read_write_multiple): add request documentation for FC23 (ReadWriteMultiple) --- rodbus/RWMR_README.md | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 rodbus/RWMR_README.md diff --git a/rodbus/RWMR_README.md b/rodbus/RWMR_README.md new file mode 100644 index 00000000..f46d4b6e --- /dev/null +++ b/rodbus/RWMR_README.md @@ -0,0 +1,77 @@ +# 23 (0x17) Read/Write Multiple registers + +This document provides an overview of the Function Code 23 (0x17) (Read & Write Multiple Registers) request as specified in the MODBUS Application Protocol. + + +## Description +This function code performs a combination of one read operation and one write operation in a single MODBUS transaction. The write operation is performed before the read. + +The request specifies the starting address and number of holding registers to be read as well as the starting address, number of holding registers, and the data to be written. The byte count specifies the number of bytes to follow in the write data field. + +The normal response contains the data from the group of registers that were read. The byte count field specifies the quantity of bytes to follow in the read data field. + + +## Request Structure +| Parameter | Size | Range / Value | +|------------------------|--------------|--------------------------| +| Function code | 1 Byte | 0x17 | +| Read Starting Address | 2 Bytes | 0x0000 to 0xFFFF | +| Quantity to Read | 2 Bytes | 0x0001 to 0x007D | +| Write Starting Address | 2 Bytes | 0x0000 to 0xFFFF | +| Quantity to Write | 2 Bytes | 0x0001 to 0x0079 | +| Write Byte Count | 1 Byte | 2 x N* | +| Write Registers Value | N* x 2 Bytes | | +( N* = Quantity to Write ) + +## Response Structure +| Parameter | Size | Value / Description | +|----------------------|--------------|------------------------| +| Function code | 1 Byte | 0x17 | +| Byte Count | 1 Byte | 2 x N* | +| Read Registers value | N* x 2 Bytes | | +( N* = Quantity to Read ) + +## Error Handling +| Parameter | Size | Description | +|----------------|---------|-----------------------------------| +| Error code | 1 Byte | Function code + 0x80 = 0x97 (151) | +| Exception code | 1 Byte | 01 or 02 or 03 or 04 | + +### Error Codes: +- **01**: Illegal Function +- **02**: Illegal Data Address +- **03**: Illegal Data Value +- **04**: Server Device Failure + + +## Example +Here is an example of a request to read six registers starting at register 4, and to write three +registers starting at register 15: + +| Request Field | Hex | Response Field | Hex | +|--------------------------------|-----|----------------------------|-----| +| Function | 17 | Function | 17 | +| Read Starting Address Hi | 00 | Byte Count | 0C | +| Read Starting Address Lo | 03 | Read Registers value Hi | 00 | +| Quantity to Read Hi | 00 | Read Registers value Lo | FE | +| Quantity to Read Lo | 06 | Read Registers value Hi | 0A | +| Write Starting Address Hi | 00 | Read Registers value Lo | CD | +| Write Starting Address Lo | 0E | Read Registers value Hi | 00 | +| Quantity to Write Hi | 00 | Read Registers value Lo | 01 | +| Quantity to Write Lo | 03 | Read Registers value Hi | 00 | +| Write Byte Count | 06 | Read Registers value Lo | 03 | +| Write Registers Value Hi (1st) | 00 | Read Registers value Hi | 00 | +| Write Registers Value Lo (1st) | FF | Read Registers value Lo | 0D | +| Write Registers Value Hi (2nd) | 00 | Read Registers value Hi | 00 | +| Write Registers Value Lo (2nd) | FF | Read Registers value Lo | FF | +| Write Registers Value Hi (3rd) | 00 | | | +| Write Registers Value Lo (3rd) | FF | | | + + +## Troubleshooting Tips +- Ensure the server and client are using the same communication method and are connected to each other +- Check for any returned error code in the response and refer to the error handling section for resolution + + +## Additional Resources +- For more information on the MODBUS protocol and function codes, refer to the [MODBUS Application Protocol Specification V1.1b3](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf), Page 38 (Read/Write Multiple registers). From aa3e323df0fa11d8748a3707c41bf79b9759affb Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Tue, 13 Feb 2024 21:20:57 +0100 Subject: [PATCH 121/122] test(request): add read_write_multiple request parsing unit test cases --- rodbus/src/server/request.rs | 121 ++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/rodbus/src/server/request.rs b/rodbus/src/server/request.rs index 20090549..2c61e1d1 100644 --- a/rodbus/src/server/request.rs +++ b/rodbus/src/server/request.rs @@ -435,4 +435,123 @@ mod tests { ) } } -} + + mod read_write_multiple_registers { + use scursor::ReadCursor; + + use super::super::*; + use crate::error::AduParseError; + + //ANCHOR: parse read_write_multiple_request + + /// Write a single zero value to register 1 (index 0) - Minimum test + /// Read 5 registers starting at register 1 (index 0-4) afterwards + /// + /// read_range start: 0x00, count: 0x05 + /// write_range start: 0x00, count: 0x01 + /// value length = 2 bytes, value = 0x0000 + #[test] + fn can_parse_read_write_multiple_registers_request_of_single_zero_register_write() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00]); + let registers = match Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor).unwrap() { + Request::ReadWriteMultipleRegisters(registers) => registers, + _ => panic!("bad match"), + }; + assert_eq!(registers.read_range, AddressRange::try_from(0x00, 0x05).unwrap()); + assert_eq!(registers.write_range, AddressRange::try_from(0x00, 0x01).unwrap()); + assert_eq!(registers.iterator.collect::>>(), vec![Indexed::new(0x0000, 0x0000)]); + } + + /// Write a single 0xFFFF value to register 0xFFFF (index 65.535) - Limit test + /// Read 5 registers starting at register 0xFFFB (65.531-65.535) afterwards + /// + /// read_range start: 0xFFFB, count: 0x05 + /// write_range start: 0xFFFF, count: 0x01 + /// value length = 2 bytes, value = 0xFFFF + #[test] + fn can_parse_read_write_multiple_registers_request_of_single_u16_register_write() { + let mut cursor = ReadCursor::new(&[0xFF, 0xFB, 0x00, 0x05, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0xFF, 0xFF]); + let registers = match Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor).unwrap() { + Request::ReadWriteMultipleRegisters(registers) => registers, + _ => panic!("bad match"), + }; + assert_eq!(registers.read_range, AddressRange::try_from(0xFFFB, 0x05).unwrap()); + assert_eq!(registers.write_range, AddressRange::try_from(0xFFFF, 0x01).unwrap()); + assert_eq!(registers.iterator.collect::>>(), vec![Indexed::new(0xFFFF, 0xFFFF)]); + } + + /// Write multiple zero values to registers 1, 2 and 3 (index 0-2) - Minimum test + /// Read 5 registers starting at register 1 (0-4) afterwards + /// + /// read_range start: 0x00, count: 0x05 + /// write_range start: 0x00, count: 0x03 + /// values length = 6 bytes, values = 0x0000, 0x0000, 0x0000 + #[test] + fn can_parse_read_write_multiple_registers_request_of_multiple_zero_register_write() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + let registers = match Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor).unwrap() { + Request::ReadWriteMultipleRegisters(registers) => registers, + _ => panic!("bad match"), + }; + assert_eq!(registers.read_range, AddressRange::try_from(0x00, 0x05).unwrap()); + assert_eq!(registers.write_range, AddressRange::try_from(0x00, 0x03).unwrap()); + assert_eq!(registers.iterator.collect::>>(), vec![Indexed::new(0x0000, 0x0000), Indexed::new(0x0001, 0x0000), Indexed::new(0x0002, 0x0000)]); + } + + /// Write multiple 0xFFFF values to registers 0xFFFD, 0xFFFE and 0xFFFF (index 65.533 - 65.535) - Limit test + /// Read 5 registers starting at register 0xFFFB (65.531-65.535) afterwards + /// + /// read_range start: 0xFFFB, count: 0x05 + /// write_range start: 0xFFFD, count: 0x03 + /// values length = 6 bytes, values = 0xFFFF, 0xFFFF, 0xFFFF + #[test] + fn parse_succeeds_for_valid_read_write_multiple_request_of_multiple_u16_register_write() { + let mut cursor = ReadCursor::new(&[0xFF, 0xFB, 0x00, 0x05, 0xFF, 0xFD, 0x00, 0x03, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + let registers = match Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor).unwrap() { + Request::ReadWriteMultipleRegisters(registers) => registers, + _ => panic!("bad match"), + }; + assert_eq!(registers.read_range, AddressRange::try_from(0xFFFB, 0x05).unwrap()); + assert_eq!(registers.write_range, AddressRange::try_from(0xFFFD, 0x03).unwrap()); + assert_eq!(registers.iterator.collect::>>(), vec![Indexed::new(0xFFFD, 0xFFFF), Indexed::new(0xFFFE, 0xFFFF), Indexed::new(0xFFFF, 0xFFFF)]); + } + + #[test] + fn parse_fails_when_too_few_bytes_for_write_byte_count() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA]); + let err = Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor) + .err() + .unwrap(); + assert_eq!(err, AduParseError::InsufficientBytes.into()); + } + + #[test] + fn parse_fails_when_too_many_bytes_for_write_byte_count() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0]); + let err = Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor) + .err() + .unwrap(); + assert_eq!(err, AduParseError::TrailingBytes(1).into()); + } + + #[test] + fn parse_fails_when_specified_byte_count_not_present() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE]); + let err = Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor) + .err() + .unwrap(); + assert_eq!(err, AduParseError::InsufficientBytes.into()); + } + + #[test] + fn parse_fails_when_too_many_bytes_present() { + let mut cursor = ReadCursor::new(&[0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x06, 0xCA, 0xFE, 0xC0, 0xDE, 0xCA, 0xFE, 0xC0, 0xDE]); + let err = Request::parse(FunctionCode::ReadWriteMultipleRegisters, &mut cursor) + .err() + .unwrap(); + assert_eq!(err, AduParseError::TrailingBytes(2).into()); + } + + //ANCHOR_END: parse read_write_multiple_request + } +} \ No newline at end of file From b6d913f49d6bb9b89f08efc735e54a132ea2c0b5 Mon Sep 17 00:00:00 2001 From: dkoehler-dev Date: Wed, 14 Feb 2024 15:12:29 +0100 Subject: [PATCH 122/122] test(read_write_multiple): fix integration test for FC23 read_write_multiple_registers --- rodbus/src/client/requests/read_write_multiple.rs | 2 +- rodbus/tests/integration_test.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rodbus/src/client/requests/read_write_multiple.rs b/rodbus/src/client/requests/read_write_multiple.rs index b690981e..ac8059ac 100644 --- a/rodbus/src/client/requests/read_write_multiple.rs +++ b/rodbus/src/client/requests/read_write_multiple.rs @@ -117,7 +117,7 @@ where function: FunctionCode, decode: AppDecodeLevel, ) -> Result<(), RequestError> { - let response = Self::parse_registers_response(self.request.write_range, &mut cursor)?; + let response = Self::parse_registers_response(self.request.read_range, &mut cursor)?; if decode.data_headers() { tracing::info!("PDU RX - {} {}", function, RegisterIteratorDisplay::new(decode, response)); diff --git a/rodbus/tests/integration_test.rs b/rodbus/tests/integration_test.rs index 4215336b..b8b586fb 100644 --- a/rodbus/tests/integration_test.rs +++ b/rodbus/tests/integration_test.rs @@ -262,17 +262,17 @@ async fn test_requests_and_responses() { .unwrap(), CustomFunctionCode::new(4, [0xC0, 0xDE, 0xCA, 0xFE]) ); - // TODO this gives still an error, because 4 trailing bytes are in the response (res) - let req = ReadWriteMultiple::new(AddressRange::try_from(0, 5).unwrap(), AddressRange::try_from(0, 3).unwrap(), vec![0xC0DE, 0xCAFE, 0xC0DE]).unwrap(); - let res = channel - .read_write_multiple_registers(params, req) - .await; assert_eq!( - res.unwrap(), + channel + .read_write_multiple_registers(params, ReadWriteMultiple::new(AddressRange::try_from(0, 5).unwrap(), AddressRange::try_from(0, 3).unwrap(), vec![0xC0DE, 0xCAFE, 0xC0DE]).unwrap()) + .await + .unwrap(), vec![ Indexed::new(0, 0xC0DE), Indexed::new(1, 0xCAFE), - Indexed::new(2, 0xC0DE) + Indexed::new(2, 0xC0DE), + Indexed::new(3, 0x0000), + Indexed::new(4, 0x0000) ] );