use heapless::Vec;
use mbus_core::data_unit::common::{MAX_ADU_FRAME_LEN, MAX_PDU_DATA_LEN, Pdu, compile_adu_frame};
use mbus_core::errors::{ExceptionCode, MbusError};
#[cfg(feature = "diagnostics")]
use mbus_core::function_codes::public::EncapsulatedInterfaceType;
use mbus_core::function_codes::public::FunctionCode;
#[cfg(feature = "file-record")]
use mbus_core::models::file_record::{FileRecordReadSubRequest, MAX_SUB_REQUESTS_PER_PDU};
use mbus_core::transport::{SerialMode, TransportType, UnitIdOrSlaveAddr, checksum};
use std::future::Future;
#[cfg(feature = "traffic")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsyncTrafficDirection {
Tx,
Rx,
}
#[derive(Debug)]
pub enum AsyncServerError {
Transport(MbusError),
ConnectionClosed,
FramingError(MbusError),
BindFailed(std::io::Error),
}
impl std::fmt::Display for AsyncServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AsyncServerError::Transport(e) => write!(f, "transport error: {e:?}"),
AsyncServerError::ConnectionClosed => write!(f, "connection closed"),
AsyncServerError::FramingError(e) => write!(f, "framing error: {e:?}"),
AsyncServerError::BindFailed(e) => write!(f, "bind failed: {e}"),
}
}
}
impl std::error::Error for AsyncServerError {}
impl From<MbusError> for AsyncServerError {
fn from(e: MbusError) -> Self {
match e {
MbusError::ConnectionClosed => AsyncServerError::ConnectionClosed,
other => AsyncServerError::Transport(other),
}
}
}
#[cfg(feature = "file-record")]
#[derive(Debug, Clone)]
pub struct AsyncFileRecordWriteSubRequest {
pub file_number: u16,
pub record_number: u16,
pub record_length: u16,
pub record_data: Vec<u8, MAX_ADU_FRAME_LEN>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
#[non_exhaustive]
pub enum ModbusRequest {
#[cfg(feature = "coils")]
ReadCoils {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
},
#[cfg(feature = "coils")]
WriteSingleCoil {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
value: bool,
},
#[cfg(feature = "coils")]
WriteMultipleCoils {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "discrete-inputs")]
ReadDiscreteInputs {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
},
#[cfg(feature = "holding-registers")]
ReadHoldingRegisters {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
},
#[cfg(feature = "holding-registers")]
WriteSingleRegister {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
value: u16,
},
#[cfg(feature = "holding-registers")]
WriteMultipleRegisters {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "input-registers")]
ReadInputRegisters {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
count: u16,
},
#[cfg(feature = "holding-registers")]
MaskWriteRegister {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
address: u16,
and_mask: u16,
or_mask: u16,
},
#[cfg(feature = "holding-registers")]
ReadWriteMultipleRegisters {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
read_address: u16,
read_count: u16,
write_address: u16,
write_count: u16,
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "diagnostics")]
ReadExceptionStatus {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
},
#[cfg(feature = "diagnostics")]
Diagnostics {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
sub_function: u16,
data: u16,
},
#[cfg(feature = "diagnostics")]
GetCommEventCounter {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
},
#[cfg(feature = "diagnostics")]
GetCommEventLog {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
},
#[cfg(feature = "diagnostics")]
ReportServerId {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
},
#[cfg(feature = "diagnostics")]
EncapsulatedInterfaceTransport {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
mei_type: u8,
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "fifo")]
ReadFifoQueue {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
pointer_address: u16,
},
#[cfg(feature = "file-record")]
ReadFileRecord {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
sub_requests: Vec<FileRecordReadSubRequest, MAX_SUB_REQUESTS_PER_PDU>,
},
#[cfg(feature = "file-record")]
WriteFileRecord {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
sub_requests: Vec<AsyncFileRecordWriteSubRequest, MAX_SUB_REQUESTS_PER_PDU>,
raw_pdu_data: Vec<u8, MAX_PDU_DATA_LEN>,
},
Unknown {
txn_id: u16,
unit: UnitIdOrSlaveAddr,
function_code: u8,
},
}
impl ModbusRequest {
pub fn txn_id(&self) -> u16 {
match self {
#[cfg(feature = "coils")]
ModbusRequest::ReadCoils { txn_id, .. } => *txn_id,
#[cfg(feature = "coils")]
ModbusRequest::WriteSingleCoil { txn_id, .. } => *txn_id,
#[cfg(feature = "coils")]
ModbusRequest::WriteMultipleCoils { txn_id, .. } => *txn_id,
#[cfg(feature = "discrete-inputs")]
ModbusRequest::ReadDiscreteInputs { txn_id, .. } => *txn_id,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadHoldingRegisters { txn_id, .. } => *txn_id,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteSingleRegister { txn_id, .. } => *txn_id,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteMultipleRegisters { txn_id, .. } => *txn_id,
#[cfg(feature = "input-registers")]
ModbusRequest::ReadInputRegisters { txn_id, .. } => *txn_id,
#[cfg(feature = "holding-registers")]
ModbusRequest::MaskWriteRegister { txn_id, .. } => *txn_id,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadWriteMultipleRegisters { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::ReadExceptionStatus { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::Diagnostics { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventCounter { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventLog { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::ReportServerId { txn_id, .. } => *txn_id,
#[cfg(feature = "diagnostics")]
ModbusRequest::EncapsulatedInterfaceTransport { txn_id, .. } => *txn_id,
#[cfg(feature = "fifo")]
ModbusRequest::ReadFifoQueue { txn_id, .. } => *txn_id,
#[cfg(feature = "file-record")]
ModbusRequest::ReadFileRecord { txn_id, .. } => *txn_id,
#[cfg(feature = "file-record")]
ModbusRequest::WriteFileRecord { txn_id, .. } => *txn_id,
ModbusRequest::Unknown { txn_id, .. } => *txn_id,
}
}
pub fn unit(&self) -> UnitIdOrSlaveAddr {
match self {
#[cfg(feature = "coils")]
ModbusRequest::ReadCoils { unit, .. } => *unit,
#[cfg(feature = "coils")]
ModbusRequest::WriteSingleCoil { unit, .. } => *unit,
#[cfg(feature = "coils")]
ModbusRequest::WriteMultipleCoils { unit, .. } => *unit,
#[cfg(feature = "discrete-inputs")]
ModbusRequest::ReadDiscreteInputs { unit, .. } => *unit,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadHoldingRegisters { unit, .. } => *unit,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteSingleRegister { unit, .. } => *unit,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteMultipleRegisters { unit, .. } => *unit,
#[cfg(feature = "input-registers")]
ModbusRequest::ReadInputRegisters { unit, .. } => *unit,
#[cfg(feature = "holding-registers")]
ModbusRequest::MaskWriteRegister { unit, .. } => *unit,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadWriteMultipleRegisters { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::ReadExceptionStatus { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::Diagnostics { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventCounter { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventLog { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::ReportServerId { unit, .. } => *unit,
#[cfg(feature = "diagnostics")]
ModbusRequest::EncapsulatedInterfaceTransport { unit, .. } => *unit,
#[cfg(feature = "fifo")]
ModbusRequest::ReadFifoQueue { unit, .. } => *unit,
#[cfg(feature = "file-record")]
ModbusRequest::ReadFileRecord { unit, .. } => *unit,
#[cfg(feature = "file-record")]
ModbusRequest::WriteFileRecord { unit, .. } => *unit,
ModbusRequest::Unknown { unit, .. } => *unit,
}
}
pub fn function_code_byte(&self) -> u8 {
match self {
#[cfg(feature = "coils")]
ModbusRequest::ReadCoils { .. } => FunctionCode::ReadCoils as u8,
#[cfg(feature = "coils")]
ModbusRequest::WriteSingleCoil { .. } => FunctionCode::WriteSingleCoil as u8,
#[cfg(feature = "coils")]
ModbusRequest::WriteMultipleCoils { .. } => FunctionCode::WriteMultipleCoils as u8,
#[cfg(feature = "discrete-inputs")]
ModbusRequest::ReadDiscreteInputs { .. } => FunctionCode::ReadDiscreteInputs as u8,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadHoldingRegisters { .. } => FunctionCode::ReadHoldingRegisters as u8,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteSingleRegister { .. } => FunctionCode::WriteSingleRegister as u8,
#[cfg(feature = "holding-registers")]
ModbusRequest::WriteMultipleRegisters { .. } => {
FunctionCode::WriteMultipleRegisters as u8
}
#[cfg(feature = "input-registers")]
ModbusRequest::ReadInputRegisters { .. } => FunctionCode::ReadInputRegisters as u8,
#[cfg(feature = "holding-registers")]
ModbusRequest::MaskWriteRegister { .. } => FunctionCode::MaskWriteRegister as u8,
#[cfg(feature = "holding-registers")]
ModbusRequest::ReadWriteMultipleRegisters { .. } => {
FunctionCode::ReadWriteMultipleRegisters as u8
}
#[cfg(feature = "diagnostics")]
ModbusRequest::ReadExceptionStatus { .. } => FunctionCode::ReadExceptionStatus as u8,
#[cfg(feature = "diagnostics")]
ModbusRequest::Diagnostics { .. } => FunctionCode::Diagnostics as u8,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventCounter { .. } => FunctionCode::GetCommEventCounter as u8,
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventLog { .. } => FunctionCode::GetCommEventLog as u8,
#[cfg(feature = "diagnostics")]
ModbusRequest::ReportServerId { .. } => FunctionCode::ReportServerId as u8,
#[cfg(feature = "diagnostics")]
ModbusRequest::EncapsulatedInterfaceTransport { .. } => {
FunctionCode::EncapsulatedInterfaceTransport as u8
}
#[cfg(feature = "fifo")]
ModbusRequest::ReadFifoQueue { .. } => FunctionCode::ReadFifoQueue as u8,
#[cfg(feature = "file-record")]
ModbusRequest::ReadFileRecord { .. } => FunctionCode::ReadFileRecord as u8,
#[cfg(feature = "file-record")]
ModbusRequest::WriteFileRecord { .. } => FunctionCode::WriteFileRecord as u8,
ModbusRequest::Unknown { function_code, .. } => *function_code,
}
}
}
#[derive(Debug)]
pub enum ModbusResponse {
ByteCountPayload {
fc: FunctionCode,
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
EchoCoil {
address: u16,
raw_value: u16,
},
EchoRegister {
address: u16,
value: u16,
},
EchoMaskWrite {
address: u16,
and_mask: u16,
or_mask: u16,
},
EchoMultiWrite {
fc: FunctionCode,
address: u16,
count: u16,
},
#[cfg(feature = "diagnostics")]
SingleByte {
fc: FunctionCode,
value: u8,
},
#[cfg(feature = "diagnostics")]
DiagnosticsEcho {
sub_function: u16,
result: u16,
},
#[cfg(feature = "diagnostics")]
TwoU16 {
fc: FunctionCode,
first: u16,
second: u16,
},
#[cfg(feature = "fifo")]
FifoData {
data: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "diagnostics")]
ReadDeviceId {
read_device_id_code: u8,
conformity_level: u8,
more_follows: bool,
next_object_id: u8,
objects: Vec<u8, MAX_ADU_FRAME_LEN>,
},
#[cfg(feature = "file-record")]
FileRecordWriteEcho {
pdu_data: Vec<u8, MAX_PDU_DATA_LEN>,
},
Exception {
request_fc: FunctionCode,
code: ExceptionCode,
},
ExceptionRaw {
fc_byte: u8,
code: ExceptionCode,
},
NoResponse,
}
impl ModbusResponse {
pub fn registers(fc: FunctionCode, values: &[u16]) -> Self {
let mut data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
for v in values {
let _ = data.extend_from_slice(&v.to_be_bytes());
}
ModbusResponse::ByteCountPayload { fc, data }
}
pub fn packed_bits(fc: FunctionCode, bytes: &[u8]) -> Self {
let mut data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = data.extend_from_slice(bytes);
ModbusResponse::ByteCountPayload { fc, data }
}
pub fn echo_coil(address: u16, on: bool) -> Self {
ModbusResponse::EchoCoil {
address,
raw_value: if on { 0xFF00 } else { 0x0000 },
}
}
pub fn echo_register(address: u16, value: u16) -> Self {
ModbusResponse::EchoRegister { address, value }
}
pub fn echo_mask_write(address: u16, and_mask: u16, or_mask: u16) -> Self {
ModbusResponse::EchoMaskWrite {
address,
and_mask,
or_mask,
}
}
pub fn echo_multi_write(fc: FunctionCode, address: u16, count: u16) -> Self {
ModbusResponse::EchoMultiWrite { fc, address, count }
}
pub fn exception(request_fc: FunctionCode, code: ExceptionCode) -> Self {
ModbusResponse::Exception { request_fc, code }
}
pub fn exception_raw(fc_byte: u8, code: ExceptionCode) -> Self {
ModbusResponse::ExceptionRaw { fc_byte, code }
}
pub fn invalid_function(request_fc: FunctionCode) -> Self {
Self::exception(request_fc, ExceptionCode::IllegalFunction)
}
#[cfg(feature = "diagnostics")]
pub fn read_exception_status(status: u8) -> Self {
ModbusResponse::SingleByte {
fc: FunctionCode::ReadExceptionStatus,
value: status,
}
}
#[cfg(feature = "diagnostics")]
pub fn diagnostics_echo(sub_function: u16, result: u16) -> Self {
ModbusResponse::DiagnosticsEcho {
sub_function,
result,
}
}
#[cfg(feature = "diagnostics")]
pub fn comm_event_counter(status_word: u16, event_count: u16) -> Self {
ModbusResponse::TwoU16 {
fc: FunctionCode::GetCommEventCounter,
first: status_word,
second: event_count,
}
}
#[cfg(feature = "diagnostics")]
pub fn comm_event_log(payload: &[u8]) -> Self {
let mut data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = data.extend_from_slice(payload);
ModbusResponse::ByteCountPayload {
fc: FunctionCode::GetCommEventLog,
data,
}
}
#[cfg(feature = "diagnostics")]
pub fn report_server_id(payload: &[u8]) -> Self {
let mut data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = data.extend_from_slice(payload);
ModbusResponse::ByteCountPayload {
fc: FunctionCode::ReportServerId,
data,
}
}
#[cfg(feature = "diagnostics")]
pub fn read_device_id(
read_device_id_code: u8,
conformity_level: u8,
more_follows: bool,
next_object_id: u8,
objects: &[u8],
) -> Self {
let mut objects_buf: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = objects_buf.extend_from_slice(objects);
ModbusResponse::ReadDeviceId {
read_device_id_code,
conformity_level,
more_follows,
next_object_id,
objects: objects_buf,
}
}
#[cfg(feature = "fifo")]
pub fn fifo_response(data: &[u8]) -> Self {
let mut buf: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = buf.extend_from_slice(data);
ModbusResponse::FifoData { data: buf }
}
#[cfg(feature = "file-record")]
pub fn read_file_record_response(payload: &[u8]) -> Self {
let mut data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
let _ = data.extend_from_slice(payload);
ModbusResponse::ByteCountPayload {
fc: FunctionCode::ReadFileRecord,
data,
}
}
#[cfg(feature = "file-record")]
pub fn echo_write_file_record(pdu_data: Vec<u8, MAX_PDU_DATA_LEN>) -> Self {
ModbusResponse::FileRecordWriteEcho { pdu_data }
}
pub(crate) fn encode(
self,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
transport_type: TransportType,
) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
match self {
ModbusResponse::ByteCountPayload { fc, data } => {
encode_byte_count_payload(fc, &data, txn_id, unit, transport_type)
}
ModbusResponse::EchoCoil { address, raw_value } => {
encode_echo_coil(address, raw_value, txn_id, unit, transport_type)
}
ModbusResponse::EchoRegister { address, value } => {
encode_echo_register(address, value, txn_id, unit, transport_type)
}
ModbusResponse::EchoMaskWrite {
address,
and_mask,
or_mask,
} => encode_echo_mask_write(address, and_mask, or_mask, txn_id, unit, transport_type),
ModbusResponse::EchoMultiWrite { fc, address, count } => {
encode_echo_multi_write(fc, address, count, txn_id, unit, transport_type)
}
#[cfg(feature = "diagnostics")]
ModbusResponse::SingleByte { fc, value } => {
encode_single_byte(fc, value, txn_id, unit, transport_type)
}
#[cfg(feature = "diagnostics")]
ModbusResponse::DiagnosticsEcho {
sub_function,
result,
} => encode_diagnostics_echo(sub_function, result, txn_id, unit, transport_type),
#[cfg(feature = "diagnostics")]
ModbusResponse::TwoU16 { fc, first, second } => {
encode_two_u16(fc, first, second, txn_id, unit, transport_type)
}
#[cfg(feature = "fifo")]
ModbusResponse::FifoData { data } => {
encode_fifo_data(&data, txn_id, unit, transport_type)
}
#[cfg(feature = "diagnostics")]
ModbusResponse::ReadDeviceId {
read_device_id_code,
conformity_level,
more_follows,
next_object_id,
objects,
} => encode_read_device_id(
read_device_id_code,
conformity_level,
more_follows,
next_object_id,
&objects,
txn_id,
unit,
transport_type,
),
#[cfg(feature = "file-record")]
ModbusResponse::FileRecordWriteEcho { pdu_data } => {
encode_file_record_write_echo(pdu_data, txn_id, unit, transport_type)
}
ModbusResponse::Exception { request_fc, code } => {
encode_exception(request_fc, code, txn_id, unit, transport_type)
}
ModbusResponse::ExceptionRaw { fc_byte, code } => {
encode_exception_raw(fc_byte, code, txn_id, unit, transport_type)
}
ModbusResponse::NoResponse => Err(MbusError::Unexpected), }
}
}
#[cfg(feature = "traffic")]
pub trait AsyncServerTrafficNotifier {
fn on_rx_frame(&mut self, _txn_id: u16, _unit: UnitIdOrSlaveAddr, _frame: &[u8]) {}
fn on_tx_frame(&mut self, _txn_id: u16, _unit: UnitIdOrSlaveAddr, _frame: &[u8]) {}
fn on_tx_error(
&mut self,
_txn_id: u16,
_unit: UnitIdOrSlaveAddr,
_error: MbusError,
_frame: &[u8],
) {
}
fn on_rx_error(
&mut self,
_txn_id: u16,
_unit: UnitIdOrSlaveAddr,
_error: MbusError,
_frame: &[u8],
) {
}
}
#[doc(hidden)]
#[cfg(not(feature = "traffic"))]
pub trait AsyncAppRequirements: Send + 'static {}
#[cfg(not(feature = "traffic"))]
impl<T: Send + 'static> AsyncAppRequirements for T {}
#[doc(hidden)]
#[cfg(feature = "traffic")]
pub trait AsyncAppRequirements: AsyncServerTrafficNotifier + Send + 'static {}
#[cfg(feature = "traffic")]
impl<T: AsyncServerTrafficNotifier + Send + 'static> AsyncAppRequirements for T {}
pub trait AsyncAppHandler: AsyncAppRequirements {
fn handle(&mut self, req: ModbusRequest) -> impl Future<Output = ModbusResponse> + Send;
fn on_exception(
&mut self,
_txn_id: u16,
_unit: UnitIdOrSlaveAddr,
_function_code: FunctionCode,
_exception_code: ExceptionCode,
) {
}
}
impl<APP> AsyncAppHandler for std::sync::Arc<tokio::sync::Mutex<APP>>
where
APP: AsyncAppHandler,
{
fn handle(&mut self, req: ModbusRequest) -> impl Future<Output = ModbusResponse> + Send {
let this = self.clone();
async move { this.lock().await.handle(req).await }
}
fn on_exception(
&mut self,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
function_code: FunctionCode,
exception_code: ExceptionCode,
) {
if let Ok(mut guard) = self.try_lock() {
guard.on_exception(txn_id, unit, function_code, exception_code);
}
}
}
#[cfg(feature = "traffic")]
impl<APP: AsyncAppHandler> AsyncServerTrafficNotifier for std::sync::Arc<tokio::sync::Mutex<APP>> {}
type AduFrame = Vec<u8, MAX_ADU_FRAME_LEN>;
type EncodeResult = Result<AduFrame, MbusError>;
fn encode_byte_count_payload(
fc: FunctionCode,
data: &[u8],
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_byte_count_payload(fc, data)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_echo_coil(
address: u16,
raw_value: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_write_single_u16(FunctionCode::WriteSingleCoil, address, raw_value)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_echo_register(
address: u16,
value: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_write_single_u16(FunctionCode::WriteSingleRegister, address, value)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_echo_mask_write(
address: u16,
and_mask: u16,
or_mask: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_mask_write_register(address, and_mask, or_mask)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_echo_multi_write(
fc: FunctionCode,
address: u16,
count: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_write_single_u16(fc, address, count)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "diagnostics")]
fn encode_single_byte(
fc: FunctionCode,
value: u8,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_byte_payload(fc, value)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "diagnostics")]
fn encode_diagnostics_echo(
sub_function: u16,
result: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_diagnostics(sub_function, result)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "diagnostics")]
fn encode_two_u16(
fc: FunctionCode,
first: u16,
second: u16,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_write_single_u16(fc, first, second)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "fifo")]
fn encode_fifo_data(
data: &[u8],
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let pdu = Pdu::build_fifo_payload(data)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "diagnostics")]
#[allow(clippy::too_many_arguments)]
fn encode_read_device_id(
read_device_id_code: u8,
conformity_level: u8,
more_follows: bool,
next_object_id: u8,
objects: &[u8],
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let n_objects = count_mei_objects(objects).map_err(|_| MbusError::InvalidPduLength)?;
let more_byte: u8 = if more_follows { 0xFF } else { 0x00 };
let header = [
read_device_id_code,
conformity_level,
more_byte,
next_object_id,
n_objects,
];
if header.len() + objects.len() > MAX_ADU_FRAME_LEN - 1 {
return Err(MbusError::BufferTooSmall);
}
let mut mei_data: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
mei_data
.extend_from_slice(&header)
.map_err(|_| MbusError::BufferTooSmall)?;
mei_data
.extend_from_slice(objects)
.map_err(|_| MbusError::BufferTooSmall)?;
let pdu = Pdu::build_mei_type(
FunctionCode::EncapsulatedInterfaceTransport,
EncapsulatedInterfaceType::ReadDeviceIdentification as u8,
&mei_data,
)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
#[cfg(feature = "file-record")]
fn encode_file_record_write_echo(
pdu_data: Vec<u8, MAX_PDU_DATA_LEN>,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let len = pdu_data.len() as u8;
let pdu = Pdu::new(FunctionCode::WriteFileRecord, pdu_data, len);
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_exception(
request_fc: FunctionCode,
code: ExceptionCode,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let exception_fc = request_fc
.exception_response()
.ok_or(MbusError::InvalidFunctionCode)?;
let pdu = Pdu::build_byte_payload(exception_fc, code as u8)?;
compile_adu_frame(txn_id, unit.get(), pdu, tt)
}
fn encode_exception_raw(
fc_byte: u8,
code: ExceptionCode,
txn_id: u16,
unit: UnitIdOrSlaveAddr,
tt: TransportType,
) -> EncodeResult {
let exception_fc_byte = fc_byte | 0x80;
let code_byte = code as u8;
let unit_id = unit.get();
let mut frame: AduFrame = AduFrame::new();
match tt {
TransportType::StdTcp | TransportType::CustomTcp => {
frame
.extend_from_slice(&txn_id.to_be_bytes())
.map_err(|_| MbusError::Unexpected)?;
frame
.extend_from_slice(&0u16.to_be_bytes())
.map_err(|_| MbusError::Unexpected)?;
frame
.extend_from_slice(&3u16.to_be_bytes())
.map_err(|_| MbusError::Unexpected)?;
frame.push(unit_id).map_err(|_| MbusError::Unexpected)?;
frame
.push(exception_fc_byte)
.map_err(|_| MbusError::Unexpected)?;
frame.push(code_byte).map_err(|_| MbusError::Unexpected)?;
}
TransportType::StdSerial(mode) | TransportType::CustomSerial(mode) => match mode {
SerialMode::Rtu => {
let payload = [unit_id, exception_fc_byte, code_byte];
frame
.extend_from_slice(&payload)
.map_err(|_| MbusError::Unexpected)?;
let crc = checksum::crc16(&payload);
frame
.extend_from_slice(&crc.to_le_bytes())
.map_err(|_| MbusError::Unexpected)?;
}
SerialMode::Ascii => {
let binary = [unit_id, exception_fc_byte, code_byte];
let lrc = checksum::lrc(&binary);
frame.push(b':').map_err(|_| MbusError::Unexpected)?;
for &b in &binary {
frame
.push(raw_nibble_to_hex(b >> 4))
.map_err(|_| MbusError::Unexpected)?;
frame
.push(raw_nibble_to_hex(b & 0x0F))
.map_err(|_| MbusError::Unexpected)?;
}
frame
.push(raw_nibble_to_hex(lrc >> 4))
.map_err(|_| MbusError::Unexpected)?;
frame
.push(raw_nibble_to_hex(lrc & 0x0F))
.map_err(|_| MbusError::Unexpected)?;
frame.push(b'\r').map_err(|_| MbusError::Unexpected)?;
frame.push(b'\n').map_err(|_| MbusError::Unexpected)?;
}
},
}
Ok(frame)
}
#[inline]
fn raw_nibble_to_hex(nibble: u8) -> u8 {
if nibble < 10 {
b'0' + nibble
} else {
b'A' + nibble - 10
}
}
#[cfg(feature = "diagnostics")]
fn count_mei_objects(payload: &[u8]) -> Result<u8, MbusError> {
let mut offset = 0usize;
let mut count: u8 = 0;
while offset < payload.len() {
if offset + 2 > payload.len() {
return Err(MbusError::InvalidPduLength);
}
let val_len = payload[offset + 1] as usize;
offset += 2 + val_len;
if offset > payload.len() {
return Err(MbusError::InvalidPduLength);
}
count = count.checked_add(1).ok_or(MbusError::InvalidPduLength)?;
}
Ok(count)
}