Skip to main content

rs_modbus/
vars.rs

1//! Modbus protocol constants — function codes, exception offsets, MEI types,
2//! and PDU quantity limits. Mirrors njs-modbus `vars.ts`.
3//!
4//! These exist as named constants instead of hex literals so call sites read as
5//! "WRITE_MULTIPLE_COILS_MAX" instead of "0x07b0", matching the Modbus V1.1b3
6//! spec wording.
7
8/// Standard Modbus function codes (V1.1b3 §6).
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10#[repr(u8)]
11pub enum FunctionCode {
12    ReadCoils = 0x01,
13    ReadDiscreteInputs = 0x02,
14    ReadHoldingRegisters = 0x03,
15    ReadInputRegisters = 0x04,
16    WriteSingleCoil = 0x05,
17    WriteSingleRegister = 0x06,
18    WriteMultipleCoils = 0x0f,
19    WriteMultipleRegisters = 0x10,
20    ReportServerId = 0x11,
21    MaskWriteRegister = 0x16,
22    ReadWriteMultipleRegisters = 0x17,
23    ReadDeviceIdentification = 0x2b,
24}
25
26impl FunctionCode {
27    pub const fn as_u8(self) -> u8 {
28        self as u8
29    }
30}
31
32/// Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7).
33pub const EXCEPTION_OFFSET: u8 = 0x80;
34
35/// Coil ON value for FC 5 / FC 15 (V1.1b3 §6.5/§6.11).
36pub const COIL_ON: u16 = 0xff00;
37/// Coil OFF value for FC 5 / FC 15.
38pub const COIL_OFF: u16 = 0x0000;
39
40/// FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21).
41pub const MEI_READ_DEVICE_ID: u8 = 0x0e;
42
43/// Read Device ID code values inside an FC 0x2B / MEI 0x0E request.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45#[repr(u8)]
46pub enum ReadDeviceIdCode {
47    BasicStream = 0x01,
48    RegularStream = 0x02,
49    ExtendedStream = 0x03,
50    SpecificAccess = 0x04,
51}
52
53/// Conformity level reported in an FC 0x2B / MEI 0x0E response.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55#[repr(u8)]
56pub enum ConformityLevel {
57    Basic = 0x81,
58    Regular = 0x82,
59    Extended = 0x83,
60}
61
62/// Modbus V1.1b3 PDU quantity limits.
63pub mod limits {
64    pub const READ_COILS_MIN: u16 = 0x0001;
65    pub const READ_COILS_MAX: u16 = 0x07d0;
66    pub const READ_REGISTERS_MIN: u16 = 0x0001;
67    pub const READ_REGISTERS_MAX: u16 = 0x007d;
68    pub const WRITE_COILS_MAX: u16 = 0x07b0;
69    pub const WRITE_REGISTERS_MAX: u16 = 0x007b;
70    pub const RW_REGISTERS_WRITE_MAX: u16 = 0x0079;
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn function_code_values_match_spec() {
79        assert_eq!(FunctionCode::ReadCoils as u8, 0x01);
80        assert_eq!(FunctionCode::ReadDiscreteInputs as u8, 0x02);
81        assert_eq!(FunctionCode::ReadHoldingRegisters as u8, 0x03);
82        assert_eq!(FunctionCode::ReadInputRegisters as u8, 0x04);
83        assert_eq!(FunctionCode::WriteSingleCoil as u8, 0x05);
84        assert_eq!(FunctionCode::WriteSingleRegister as u8, 0x06);
85        assert_eq!(FunctionCode::WriteMultipleCoils as u8, 0x0f);
86        assert_eq!(FunctionCode::WriteMultipleRegisters as u8, 0x10);
87        assert_eq!(FunctionCode::ReportServerId as u8, 0x11);
88        assert_eq!(FunctionCode::MaskWriteRegister as u8, 0x16);
89        assert_eq!(FunctionCode::ReadWriteMultipleRegisters as u8, 0x17);
90        assert_eq!(FunctionCode::ReadDeviceIdentification as u8, 0x2b);
91    }
92
93    #[test]
94    fn exception_offset_is_high_bit() {
95        assert_eq!(EXCEPTION_OFFSET, 0x80);
96    }
97
98    #[test]
99    fn coil_constants_match_spec() {
100        assert_eq!(COIL_ON, 0xff00);
101        assert_eq!(COIL_OFF, 0x0000);
102    }
103
104    #[test]
105    fn limits_match_spec() {
106        assert_eq!(limits::READ_COILS_MIN, 1);
107        assert_eq!(limits::READ_COILS_MAX, 2000);
108        assert_eq!(limits::READ_REGISTERS_MIN, 1);
109        assert_eq!(limits::READ_REGISTERS_MAX, 125);
110        assert_eq!(limits::WRITE_COILS_MAX, 1968);
111        assert_eq!(limits::WRITE_REGISTERS_MAX, 123);
112        assert_eq!(limits::RW_REGISTERS_WRITE_MAX, 121);
113    }
114
115    #[test]
116    fn conformity_levels_match_spec() {
117        assert_eq!(ConformityLevel::Basic as u8, 0x81);
118        assert_eq!(ConformityLevel::Regular as u8, 0x82);
119        assert_eq!(ConformityLevel::Extended as u8, 0x83);
120    }
121
122    #[test]
123    fn mei_read_device_id_is_0x0e() {
124        assert_eq!(MEI_READ_DEVICE_ID, 0x0e);
125    }
126}