modbius_core/
slaveid.rs

1//! Modbus Slave Ids.
2//!
3//! Slave ids are mainly used for Modbus RTU and Modbus ASCII communication but in many cases also have significance
4//! for modbus TCP communication. The [SlaveId] struct is the main way of using slave ids.
5
6/// Gets the slave id of the given modbus data.
7///
8/// None is returned if data contains less than 1 byte
9pub fn get_slaveid(data: &[u8]) -> (Option<SlaveId>, Option<&[u8]>) {
10    (
11        data.get(0).map(|byte| SlaveId::new(*byte)),
12        data.get(1..),
13    )
14}
15
16/// Gets the slave id of the given modbus data.
17///
18/// # Safety
19/// Providing data with less than one byte is undefined behavior
20pub unsafe fn get_slaveid_unchecked(data: &[u8]) -> (SlaveId, &[u8]) {
21    (
22        SlaveId::new(*data.get_unchecked(0)),
23        data.get_unchecked(1..),
24    )
25}
26
27/// A u8 wrapper type to represent slave ids
28#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
29pub struct SlaveId(u8);
30
31impl SlaveId {
32    pub const fn new(id: u8) -> Self {
33        Self(id)
34    }
35
36    /// The default TCP slave id is 255 decimal or 0xFF hex.
37    ///
38    /// Addressing in TCP is normally done through the ip address so the slave id just set to 0xFF.
39    /// In many cases this rule is ignored and a certain slave address is expected.
40    /// See <https://modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf> espacially page 23 for more details.
41    pub const fn new_default_tcp() -> Self {
42        Self(0xFF)
43    }
44
45    /// Creates a new broadcast slave id. The broadcast id is 0 and every slave device MUST react to it.
46    pub const fn new_broadcast() -> Self {
47        Self(0)
48    }
49
50    /// Check if this id is for a normal device. Only false if this is a reserved function code (> 248) or broadcast (0)
51    pub const fn is_device(self) -> bool {
52        !self.is_broadcast() && !self.is_reserved()
53    }
54
55    /// Checks if this function is any reserved slave address other than broadcast. (id > 248)
56    pub const fn is_reserved(self) -> bool {
57        self.0 >= 248
58    }
59
60    /// Checks if this is a broadcast slave id. The broadcast id is 0 and every slave device MUST react to it.
61    pub const fn is_broadcast(self) -> bool {
62        self.0 == 0
63    }
64
65    /// Checks if this is the default TCP slave id (0xFF)
66    pub const fn is_default_tcp(self) -> bool {
67        self.0 == 0xFF
68    }
69
70    /// Checks if a device with this slave id has to react to the given slave id `other`
71    pub const fn must_react(self, other: SlaveId) -> bool {
72        if other.0 == 0 {
73            true
74        } else {
75            self.0 == other.0
76        }
77    }
78
79    /// Gets the slave id of the given modbus data.
80    ///
81    /// None is returned if data contains less than 1 byte
82    pub fn from_data(data: &[u8]) -> (Option<Self>, Option<&[u8]>) {
83        get_slaveid(data)
84    }
85
86   /// Gets the slave id of the given modbus data.
87    ///
88    /// # Safety
89    /// Providing data with less than one byte is undefined behavior
90    pub unsafe fn from_data_unchecked(data: &[u8]) -> (Self, &[u8]) {
91        get_slaveid_unchecked(data)
92    }
93}
94
95impl From<u8> for SlaveId {
96    fn from(sid: u8) -> Self {
97        Self(sid)
98    }
99}
100
101impl Into<u8> for SlaveId {
102    fn into(self) -> u8 {
103        self.0
104    }
105}
106
107#[cfg(test)]
108mod test {
109    use crate::SlaveId;
110
111    #[test]
112    fn new10() {
113        let id = SlaveId::new(10);
114        assert_eq!(id.0, 10);
115        assert!(id.is_device());
116        assert!(!id.is_reserved());
117        assert!(!id.is_default_tcp());
118        assert!(!id.is_broadcast());
119        assert_eq!(id, SlaveId::from(10));
120    }
121
122    #[test]
123    fn device() {
124        assert!(SlaveId::new(20).is_device())
125    }
126
127    #[test]
128    fn broadcast() {
129        assert!(SlaveId::new(0).is_broadcast());
130        assert_eq!(SlaveId::new(0), SlaveId::new_broadcast());
131        assert!(SlaveId::new_broadcast().is_broadcast());
132    }
133
134    #[test]
135    fn tcp() {
136        let tcp = SlaveId::new(0xFF);
137        assert!(tcp.is_default_tcp() && tcp.is_reserved());
138
139        assert_eq!(tcp, SlaveId::new_default_tcp());
140
141        let tcp = SlaveId::new_default_tcp();
142        assert!(tcp.is_default_tcp() && tcp.is_reserved());
143    }
144
145    #[test]
146    fn all_broadcast() {
147        for i in u8::MIN..=u8::MAX {
148            assert!(SlaveId::new(i).must_react(SlaveId::new(0)));
149            assert!(SlaveId::new(i).must_react(SlaveId::new(i)));
150        }
151    }
152
153    #[test]
154    fn all_device() {
155        for i in 1..=247 {
156            assert!(SlaveId::new(i).is_device());
157            assert!(!SlaveId::new(i).is_reserved());
158            assert!(SlaveId::new(i).must_react(SlaveId::new(i)));
159        }
160    }
161
162    #[test]
163    fn all_reserved() {
164        for i in 248..=u8::MAX {
165            assert!(SlaveId::new(i).is_reserved());
166            assert!(!SlaveId::new(i).is_device());
167            assert!(SlaveId::new(i).must_react(SlaveId::new(i)));
168        }
169    }
170}