use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ExceptionCode {
IllegalFunction = 0x01,
IllegalDataAddress = 0x02,
IllegalDataValue = 0x03,
SlaveDeviceFailure = 0x04,
Acknowledge = 0x05,
SlaveDeviceBusy = 0x06,
MemoryParityError = 0x08,
GatewayPathUnavailable = 0x0A,
GatewayTargetDeviceFailedToRespond = 0x0B,
}
impl ExceptionCode {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x01 => Some(Self::IllegalFunction),
0x02 => Some(Self::IllegalDataAddress),
0x03 => Some(Self::IllegalDataValue),
0x04 => Some(Self::SlaveDeviceFailure),
0x05 => Some(Self::Acknowledge),
0x06 => Some(Self::SlaveDeviceBusy),
0x08 => Some(Self::MemoryParityError),
0x0A => Some(Self::GatewayPathUnavailable),
0x0B => Some(Self::GatewayTargetDeviceFailedToRespond),
_ => None,
}
}
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn description(&self) -> &'static str {
match self {
Self::IllegalFunction => "The function code is not recognized or allowed",
Self::IllegalDataAddress => "The data address is not allowable",
Self::IllegalDataValue => "The data value is not allowable",
Self::SlaveDeviceFailure => "An unrecoverable error occurred",
Self::Acknowledge => "Request accepted, processing in progress",
Self::SlaveDeviceBusy => "Server is busy processing another request",
Self::MemoryParityError => "Memory parity error detected",
Self::GatewayPathUnavailable => "Gateway path unavailable",
Self::GatewayTargetDeviceFailedToRespond => "Target device failed to respond",
}
}
}
impl fmt::Display for ExceptionCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02X}: {}", self.as_u8(), self.description())
}
}
impl From<ExceptionCode> for u8 {
fn from(code: ExceptionCode) -> Self {
code as u8
}
}
#[derive(Debug, Clone)]
pub struct ExceptionResponse {
pub function_code: u8,
pub exception_code: ExceptionCode,
}
impl ExceptionResponse {
pub fn new(function_code: u8, exception_code: ExceptionCode) -> Self {
Self {
function_code,
exception_code,
}
}
pub fn to_pdu(&self) -> Vec<u8> {
vec![self.function_code | 0x80, self.exception_code.as_u8()]
}
pub fn from_pdu(pdu: &[u8]) -> Option<Self> {
if pdu.len() < 2 {
return None;
}
if pdu[0] & 0x80 == 0 {
return None;
}
let function_code = pdu[0] & 0x7F;
let exception_code = ExceptionCode::from_u8(pdu[1])?;
Some(Self {
function_code,
exception_code,
})
}
pub fn is_exception(pdu: &[u8]) -> bool {
!pdu.is_empty() && (pdu[0] & 0x80) != 0
}
}
pub fn build_exception_pdu(function_code: u8, exception_code: ExceptionCode) -> Vec<u8> {
ExceptionResponse::new(function_code, exception_code).to_pdu()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exception_code_values() {
assert_eq!(ExceptionCode::IllegalFunction as u8, 0x01);
assert_eq!(ExceptionCode::IllegalDataAddress as u8, 0x02);
assert_eq!(ExceptionCode::IllegalDataValue as u8, 0x03);
assert_eq!(ExceptionCode::SlaveDeviceFailure as u8, 0x04);
assert_eq!(
ExceptionCode::GatewayTargetDeviceFailedToRespond as u8,
0x0B
);
}
#[test]
fn test_exception_code_from_u8() {
assert_eq!(
ExceptionCode::from_u8(0x01),
Some(ExceptionCode::IllegalFunction)
);
assert_eq!(
ExceptionCode::from_u8(0x02),
Some(ExceptionCode::IllegalDataAddress)
);
assert_eq!(ExceptionCode::from_u8(0xFF), None);
}
#[test]
fn test_exception_response_to_pdu() {
let response = ExceptionResponse::new(0x03, ExceptionCode::IllegalDataAddress);
let pdu = response.to_pdu();
assert_eq!(pdu, vec![0x83, 0x02]);
}
#[test]
fn test_exception_response_from_pdu() {
let pdu = [0x83, 0x02];
let response = ExceptionResponse::from_pdu(&pdu).unwrap();
assert_eq!(response.function_code, 0x03);
assert_eq!(response.exception_code, ExceptionCode::IllegalDataAddress);
}
#[test]
fn test_is_exception() {
assert!(ExceptionResponse::is_exception(&[0x83, 0x02]));
assert!(!ExceptionResponse::is_exception(&[0x03, 0x02]));
assert!(!ExceptionResponse::is_exception(&[]));
}
}