use core::{convert::From, mem};
pub fn get_function(data: &[u8]) -> (Option<ModbusFunction>, Option<&[u8]>) {
(
data.get(0).map(|byte| ModbusFunction::new(*byte)),
data.get(1..),
)
}
pub unsafe fn get_function_unchecked(data: &[u8]) -> (ModbusFunction, &[u8]) {
(
ModbusFunction::new(*data.get_unchecked(0)),
data.get_unchecked(1..),
)
}
#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ModbusFunction(pub u8);
impl ModbusFunction {
pub const fn new(function_code: u8) -> Self {
Self(function_code)
}
pub const fn new_public(public_function: PublicModbusFunction) -> Self {
Self(public_function as u8)
}
pub const fn is(self, public_function: PublicModbusFunction) -> bool {
self.0 == Self::new_public(public_function).0
}
pub const fn is_valid(self) -> bool {
self.0 != 0
}
pub const fn is_public(self) -> bool {
PublicModbusFunction::is_public_function(self.0)
}
pub const fn is_public_reserved(self) -> bool {
self.is_valid() && !self.is_custom()
}
pub const fn is_custom(self) -> bool {
self.0 >= 65 && self.0 <= 72 || self.0 >= 100 && self.0 <= 110
}
pub const fn is_exception(self) -> bool {
self.0 >= 128
}
pub fn from_data(data: &[u8]) -> (Option<Self>, Option<&[u8]>) {
get_function(data)
}
pub unsafe fn from_data_unchecked(data: &[u8]) -> (Self, &[u8]) {
get_function_unchecked(data)
}
}
impl PartialEq<PublicModbusFunction> for ModbusFunction {
fn eq(&self, other: &PublicModbusFunction) -> bool {
self.is(*other)
}
}
impl From<u8> for ModbusFunction {
fn from(b: u8) -> Self {
Self::new(b)
}
}
impl Into<u8> for ModbusFunction {
fn into(self) -> u8 {
self.0
}
}
impl From<PublicModbusFunction> for ModbusFunction {
fn from(public_function: PublicModbusFunction) -> Self {
Self::new_public(public_function)
}
}
macro_rules! public_modbus_function {
($($name:ident = $fcode:expr,)*) => {
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PublicModbusFunction {
$($name = $fcode,)*
}
impl PublicModbusFunction {
pub const fn is_public_function(code: u8) -> bool {
if code == 0 {
return false;
}
match code {
$($fcode => true,)*
_ => false,
}
}
}
};
}
public_modbus_function! {
Invalid = 0,
ReadCoils = 1,
ReadDiscreteInputs = 2,
ReadHoldingRegisters = 3,
ReadInputRegisters = 4,
WriteSingleCoil = 5,
WriteSingleRegister = 6,
ReadExceptionStatus = 7,
Diagnostics = 8,
GetCommEventCounter = 11,
GetCommEventLog = 12,
WriteMultipleCoils = 15,
WriteMultipleregisters = 16,
ReportServerID = 17,
ReadFileRecord = 20,
WriteFileRecord = 21,
MaskWriteRegister = 22,
ReadWriteMultipleRegisters = 23,
ReadFIFOQueue = 24,
EncapsulatedInterfaceTransport = 43,
}
impl PublicModbusFunction {
pub fn new(code: u8) -> Self {
if Self::is_public_function(code) {
unsafe { Self::new_unchecked(code) }
} else {
Self::Invalid
}
}
pub unsafe fn new_unchecked(code: u8) -> Self {
mem::transmute(code)
}
}
impl PartialEq<ModbusFunction> for PublicModbusFunction {
fn eq(&self, other: &ModbusFunction) -> bool {
other == self
}
}
impl From<ModbusFunction> for PublicModbusFunction {
fn from(mf: ModbusFunction) -> Self {
Self::new(mf.0)
}
}
impl From<u8> for PublicModbusFunction {
fn from(code: u8) -> Self {
Self::new(code)
}
}
impl Into<u8> for PublicModbusFunction {
fn into(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod test {
use super::{get_function, ModbusFunction, PublicModbusFunction};
#[test]
fn invalid() {
assert!(!ModbusFunction::from(0).is_valid())
}
#[test]
fn public() {
assert!(ModbusFunction::from(1).is_public());
}
#[test]
fn custom() {
let mbf = ModbusFunction::from(69);
assert!(mbf.is_custom());
}
#[test]
fn custom_all() {
for i in 65..=72 {
let mbf = ModbusFunction::from(i);
assert!(mbf.is_custom() && mbf.is_valid());
}
for i in 100..=110 {
let mbf = ModbusFunction::from(i);
assert!(mbf.is_custom() && mbf.is_valid());
}
}
#[test]
fn invalid_from_data_no_tail() {
let data = [0];
let (function, tail) = get_function(&data);
assert_eq!(
function,
Some(ModbusFunction::new_public(PublicModbusFunction::Invalid))
);
assert_eq!(tail, Some(&[] as &[u8]));
}
#[test]
fn no_data() {
let data = [];
let (function, tail) = get_function(&data);
assert_eq!(function, None);
assert_eq!(tail, None);
}
#[test]
fn invalid_from_data() {
let data = [0, 1];
let (function, tail) = get_function(&data);
assert_eq!(
function,
Some(ModbusFunction::new_public(PublicModbusFunction::Invalid))
);
assert_eq!(tail, Some(&[1u8] as &[u8]));
}
#[test]
fn read_coils_from_data() {
let data = [1, 1];
let (function, tail) = get_function(&data);
assert_eq!(
function,
Some(ModbusFunction::new_public(PublicModbusFunction::ReadCoils))
);
assert_eq!(tail, Some(&[1u8] as &[u8]));
}
}