modbus_core/frame/
coils.rs

1// SPDX-FileCopyrightText: Copyright (c) 2018-2025 slowtec GmbH <post@slowtec.de>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::*;
5use crate::error::*;
6
7/// Packed coils
8#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Coils<'c> {
11    pub(crate) data: RawData<'c>,
12    pub(crate) quantity: usize,
13}
14
15impl<'c> Coils<'c> {
16    /// Pack coils defined by an bool slice into a byte buffer.
17    pub fn from_bools(bools: &[bool], target: &'c mut [u8]) -> Result<Self, Error> {
18        if bools.is_empty() {
19            return Err(Error::BufferSize);
20        }
21        pack_coils(bools, target)?;
22        Ok(Coils {
23            data: target,
24            quantity: bools.len(),
25        })
26    }
27
28    //TODO: add tests
29    pub(crate) fn copy_to(&self, buf: &mut [u8]) {
30        let packed_len = self.packed_len();
31        debug_assert!(buf.len() >= packed_len);
32        (0..packed_len).for_each(|idx| {
33            buf[idx] = self.data[idx];
34        });
35    }
36
37    /// Quantity of coils
38    #[must_use]
39    pub const fn len(&self) -> usize {
40        self.quantity
41    }
42
43    /// Number of bytes required to pack the coils.
44    #[must_use]
45    pub const fn packed_len(&self) -> usize {
46        packed_coils_len(self.quantity)
47    }
48
49    ///  Returns `true` if the container has no items.
50    #[must_use]
51    pub const fn is_empty(&self) -> bool {
52        self.quantity == 0
53    }
54
55    /// Get a specific coil.
56    #[must_use]
57    pub const fn get(&self, idx: usize) -> Option<Coil> {
58        if idx + 1 > self.quantity {
59            return None;
60        }
61        Some((self.data[(idx as u16 / 8u16) as usize] >> (idx % 8)) & 0b1 > 0)
62    }
63}
64
65/// Coils iterator.
66// TODO: crate an generic iterator
67#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct CoilsIter<'c> {
70    cnt: usize,
71    coils: Coils<'c>,
72}
73
74impl Iterator for CoilsIter<'_> {
75    type Item = Coil;
76
77    fn next(&mut self) -> Option<Self::Item> {
78        let result = self.coils.get(self.cnt);
79        self.cnt += 1;
80        result
81    }
82}
83
84impl<'c> IntoIterator for Coils<'c> {
85    type Item = Coil;
86    type IntoIter = CoilsIter<'c>;
87
88    fn into_iter(self) -> Self::IntoIter {
89        CoilsIter {
90            cnt: 0,
91            coils: self,
92        }
93    }
94}
95
96/// Turn a bool into a u16 coil value
97#[must_use]
98pub const fn bool_to_u16_coil(state: bool) -> u16 {
99    if state { 0xFF00 } else { 0x0000 }
100}
101
102/// Turn a u16 coil value into a boolean value.
103pub const fn u16_coil_to_bool(coil: u16) -> Result<bool, Error> {
104    match coil {
105        0xFF00 => Ok(true),
106        0x0000 => Ok(false),
107        _ => Err(Error::CoilValue(coil)),
108    }
109}
110
111/// Calculate the number of bytes required for a given number of coils.
112#[must_use]
113pub const fn packed_coils_len(bitcount: usize) -> usize {
114    bitcount.div_ceil(8)
115}
116
117///  Pack coils into a byte array.
118///
119///  It returns the number of bytes used to pack the coils.
120pub fn pack_coils(coils: &[Coil], bytes: &mut [u8]) -> Result<usize, Error> {
121    let packed_size = packed_coils_len(coils.len());
122    if bytes.len() < packed_size {
123        return Err(Error::BufferSize);
124    }
125    coils.iter().enumerate().for_each(|(i, b)| {
126        let v = u8::from(*b);
127        bytes[i / 8] |= v << (i % 8);
128    });
129    Ok(packed_size)
130}
131
132///  Unpack coils from a byte array.
133pub fn unpack_coils(bytes: &[u8], count: u16, coils: &mut [Coil]) -> Result<(), Error> {
134    if coils.len() < count as usize {
135        return Err(Error::BufferSize);
136    }
137    (0..count).for_each(|i| {
138        coils[i as usize] = (bytes[(i / 8u16) as usize] >> (i % 8)) & 0b1 > 0;
139    });
140    Ok(())
141}
142
143#[cfg(test)]
144mod tests {
145
146    use super::*;
147
148    #[test]
149    fn from_bool_slice() {
150        let bools: &[bool] = &[true, false, true, true];
151        let buff: &mut [u8] = &mut [0];
152        let coils = Coils::from_bools(bools, buff).unwrap();
153        assert_eq!(coils.len(), 4);
154        let mut iter = coils.into_iter();
155        assert_eq!(iter.next(), Some(true));
156        assert_eq!(iter.next(), Some(false));
157        assert_eq!(iter.next(), Some(true));
158        assert_eq!(iter.next(), Some(true));
159        assert_eq!(iter.next(), None);
160    }
161
162    #[test]
163    fn coils_len() {
164        let coils = Coils {
165            data: &[0, 1, 2],
166            quantity: 5,
167        };
168        assert_eq!(coils.len(), 5);
169    }
170
171    #[test]
172    fn coils_empty() {
173        let coils = Coils {
174            data: &[0, 1, 2],
175            quantity: 0,
176        };
177        assert!(coils.is_empty());
178    }
179
180    #[test]
181    fn coils_get() {
182        let coils = Coils {
183            data: &[0b1],
184            quantity: 1,
185        };
186        assert_eq!(coils.get(0), Some(true));
187        assert_eq!(coils.get(1), None);
188
189        let coils = Coils {
190            data: &[0b01],
191            quantity: 2,
192        };
193        assert_eq!(coils.get(0), Some(true));
194        assert_eq!(coils.get(1), Some(false));
195        assert_eq!(coils.get(2), None);
196
197        let coils = Coils {
198            data: &[0xff, 0b11],
199            quantity: 10,
200        };
201        for i in 0..10 {
202            assert_eq!(coils.get(i), Some(true));
203        }
204        assert_eq!(coils.get(11), None);
205    }
206
207    #[test]
208    fn coils_iter() {
209        let coils = Coils {
210            data: &[0b0101_0011],
211            quantity: 5,
212        };
213        let mut coils_iter = CoilsIter { cnt: 0, coils };
214        assert_eq!(coils_iter.next(), Some(true));
215        assert_eq!(coils_iter.next(), Some(true));
216        assert_eq!(coils_iter.next(), Some(false));
217        assert_eq!(coils_iter.next(), Some(false));
218        assert_eq!(coils_iter.next(), Some(true));
219        assert_eq!(coils_iter.next(), None);
220    }
221
222    #[test]
223    fn coils_into_iter() {
224        let coils = Coils {
225            data: &[0b0101_0011],
226            quantity: 3,
227        };
228        let mut coils_iter = coils.into_iter();
229        assert_eq!(coils_iter.next(), Some(true));
230        assert_eq!(coils_iter.next(), Some(true));
231        assert_eq!(coils_iter.next(), Some(false));
232        assert_eq!(coils_iter.next(), None);
233    }
234
235    #[test]
236    fn iter_over_coils() {
237        let coils = Coils {
238            data: &[0b0101_0011],
239            quantity: 3,
240        };
241        let mut cnt = 0;
242        for _ in coils {
243            cnt += 1;
244        }
245        assert_eq!(cnt, 3);
246    }
247
248    #[test]
249    fn convert_bool_to_coil() {
250        assert_eq!(bool_to_u16_coil(true), 0xFF00);
251        assert_eq!(bool_to_u16_coil(false), 0x0000);
252    }
253
254    #[test]
255    fn convert_coil_to_bool() {
256        assert!(u16_coil_to_bool(0xFF00).unwrap());
257        assert!(!u16_coil_to_bool(0x0000).unwrap());
258        assert_eq!(
259            u16_coil_to_bool(0x1234).err().unwrap(),
260            Error::CoilValue(0x1234)
261        );
262    }
263
264    #[test]
265    fn pack_coils_into_byte_array() {
266        assert_eq!(pack_coils(&[], &mut []).unwrap(), 0);
267        assert_eq!(pack_coils(&[], &mut [0, 0]).unwrap(), 0);
268        assert_eq!(
269            pack_coils(&[true; 2], &mut []).err().unwrap(),
270            Error::BufferSize
271        );
272
273        let buff = &mut [0];
274        assert_eq!(pack_coils(&[true], buff).unwrap(), 1);
275        assert_eq!(buff, &[0b_1]);
276
277        let buff = &mut [0];
278        assert_eq!(pack_coils(&[false], buff).unwrap(), 1);
279        assert_eq!(buff, &[0b_0]);
280
281        let buff = &mut [0];
282        assert_eq!(pack_coils(&[true, false], buff).unwrap(), 1);
283        assert_eq!(buff, &[0b_01]);
284
285        let buff = &mut [0];
286        assert_eq!(pack_coils(&[false, true], buff).unwrap(), 1);
287        assert_eq!(buff, &[0b_10]);
288
289        let buff = &mut [0];
290        assert_eq!(pack_coils(&[true, true], buff).unwrap(), 1);
291        assert_eq!(buff, &[0b_11]);
292
293        let buff = &mut [0];
294        assert_eq!(pack_coils(&[true; 8], buff).unwrap(), 1);
295        assert_eq!(buff, &[0b_1111_1111]);
296
297        let buff = &mut [0];
298        assert_eq!(pack_coils(&[false; 8], buff).unwrap(), 1);
299        assert_eq!(buff, &[0]);
300
301        let buff = &mut [0, 0];
302        assert_eq!(pack_coils(&[true; 9], buff).unwrap(), 2);
303        assert_eq!(buff, &[0xff, 1]);
304    }
305
306    #[test]
307    fn unpack_coils_from_a_byte_array() {
308        assert!(unpack_coils(&[], 0, &mut []).is_ok());
309        assert!(unpack_coils(&[], 0, &mut [false, false]).is_ok());
310        assert!(unpack_coils(&[1, 2, 3], 0, &mut []).is_ok());
311        assert_eq!(
312            unpack_coils(&[], 1, &mut []).err().unwrap(),
313            Error::BufferSize
314        );
315
316        let buff = &mut [false];
317        assert!(unpack_coils(&[0b1], 1, buff).is_ok());
318        assert_eq!(&[true], buff);
319
320        let buff = &mut [false; 2];
321        assert!(unpack_coils(&[0b01], 2, buff).is_ok());
322        assert_eq!(&[true, false], buff);
323
324        let buff = &mut [false; 2];
325        assert!(unpack_coils(&[0b10], 2, buff).is_ok());
326        assert_eq!(&[false, true], buff);
327
328        let buff = &mut [false; 3];
329        assert!(unpack_coils(&[0b101], 3, buff).is_ok());
330        assert_eq!(&[true, false, true], buff);
331
332        let buff = &mut [false; 10];
333        assert!(unpack_coils(&[0xff, 0b11], 10, buff).is_ok());
334        assert_eq!(&[true; 10], buff);
335    }
336}