Skip to main content

meterbus_wired_datalink/frame/field/
control.rs

1//! Wired M-Bus data-link control fields and communication types.
2//!
3//! The one-byte control field, commonly called the C-field, identifies the
4//! data-link communication carried by a structured frame. It also carries two
5//! direction-dependent flags. In messages from a master, those flags are the
6//! frame count bit (FCB) and frame count valid bit (FCV). In messages from a
7//! slave, the same bit positions are access demand (ACD) and data flow control
8//! (DFC).
9//!
10//! [`Control`] preserves any `u8` received from the wire. Its methods explain
11//! known values and expose their flags. Unknown values remain available for
12//! error reporting. [`CommunicationType`] names the recognized message type, and
13//! [`ControlError`] reports a control value used with the wrong frame format.
14//!
15//! # Bit layout
16//!
17//! The field is interpreted as follows for the communications represented by
18//! this crate:
19//!
20//! ```text
21//! bit:   7   6   5   4   3   2   1   0
22//!      +---+---+---+---+---+---+---+---+
23//!      | 0 | D | X | Y | function code |
24//!      +---+---+---+---+---+---+---+---+
25//! ```
26//!
27//! `D` distinguishes the message direction:
28//!
29//! | `D` | Direction | `X` (bit 5) | `Y` (bit 4) |
30//! | --- | --- | --- | --- |
31//! | `1` | Master to slave | FCB | FCV |
32//! | `0` | Slave to master | ACD | DFC |
33//!
34//! [`Control::direction`] identifies `D`. Direction-specific flag
35//! accessors return [`Some`] only when their interpretation applies and
36//! [`None`] for the opposite direction. This avoids reporting, for example,
37//! an ACD value from a master request merely because bit 5 is set.
38//!
39//! Bit 7 is clear for every communication supported here. The low four bits
40//! carry the function code. Classification uses the complete byte rather than
41//! only the function code because direction and required flag combinations
42//! are part of the supported control values.
43//!
44//! # Supported control values
45//!
46//! | C-field | [`CommunicationType`] | Direction | Format | Flag state |
47//! | --- | --- | --- | --- | --- |
48//! | `0x40` | [`CommunicationType::SndNke`] | Master to slave | Short | FCB = 0, FCV = 0 |
49//! | `0x5a`, `0x7a` | [`CommunicationType::ReqUd1`] | Master to slave | Short | FCV = 1; FCB = 0 or 1 |
50//! | `0x5b`, `0x7b` | [`CommunicationType::ReqUd2`] | Master to slave | Short | FCV = 1; FCB = 0 or 1 |
51//! | `0x53`, `0x73` | [`CommunicationType::SndUd`] | Master to slave | Variable | FCV = 1; bit 5 = 0 or 1 |
52//! | `0x43` | [`CommunicationType::SndUd2`] | Master to slave | Variable | FCB = 0, FCV = 0 |
53//! | `0x08`, `0x18`, `0x28`, `0x38` | [`CommunicationType::RspUd`] | Slave to master | Variable | ACD and DFC independently clear or set |
54//!
55//! Every other byte maps to [`CommunicationType::Unsupported`], while [`Control`]
56//! still preserves the original value.
57//!
58//! # Frame compatibility
59//!
60//! Short frames support SND-NKE, REQ-UD1, and REQ-UD2. Variable frames support
61//! SND-UD, SND-UD2, and RSP-UD. Frame constructors reject other combinations
62//! with a [`ControlError`].
63//!
64//! [`ShortFrame::new`]: crate::ShortFrame::new
65//! [`ControlFrame::new`]: crate::ControlFrame::new
66//! [`LongFrame::new`]: crate::LongFrame::new
67//!
68//! For master messages, bit 5 is FCB and bit 4 is FCV. For slave messages, the
69//! same bits are ACD and DFC. The accessors return [`None`] when a flag does not
70//! apply to that direction.
71//!
72//! # Examples
73//!
74//! Inspect a REQ-UD2 control field sent by a master:
75//!
76//! ```
77//! use meterbus_wired_datalink::{CommunicationType, Control, Direction};
78//!
79//! let control = Control::req_ud2(true);
80//! assert_eq!(control.value(), 0x7b);
81//! assert_eq!(control.communication_type(), CommunicationType::ReqUd2);
82//! assert_eq!(control.direction(), Some(Direction::MasterToSlave));
83//! assert_eq!(control.frame_count_bit(), Some(true));
84//! assert_eq!(control.frame_count_valid(), Some(true));
85//! assert_eq!(control.access_demand(), None);
86//! ```
87//!
88//! Callers still maintain FCB state, act on ACD and DFC, and decide which reply
89//! is valid for a request.
90
91use core::fmt;
92
93/// A data-link control field.
94#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
95pub struct Control(u8);
96
97impl Control {
98    /// Creates a control field from its wire value.
99    #[must_use]
100    pub const fn new(value: u8) -> Self {
101        Self(value)
102    }
103
104    /// Creates an SND-NKE control field.
105    #[must_use]
106    pub const fn snd_nke() -> Self {
107        Self(0x40)
108    }
109
110    /// Creates a REQ-UD1 control field with the requested frame-count bit.
111    #[must_use]
112    pub const fn req_ud1(fcb: bool) -> Self {
113        Self(0x5a | ((fcb as u8) << 5))
114    }
115
116    /// Creates a REQ-UD2 control field with the requested frame-count bit.
117    #[must_use]
118    pub const fn req_ud2(fcb: bool) -> Self {
119        Self(0x5b | ((fcb as u8) << 5))
120    }
121
122    /// Creates an SND-UD control field with bit 5 clear or set.
123    #[must_use]
124    pub const fn snd_ud(bit5: bool) -> Self {
125        Self(0x53 | ((bit5 as u8) << 5))
126    }
127
128    /// Creates an SND-UD2 control field.
129    #[must_use]
130    pub const fn snd_ud2() -> Self {
131        Self(0x43)
132    }
133
134    /// Creates an RSP-UD control field with access-demand and data-flow-control flags.
135    #[must_use]
136    pub const fn rsp_ud(acd: bool, dfc: bool) -> Self {
137        Self(0x08 | ((acd as u8) << 5) | ((dfc as u8) << 4))
138    }
139
140    /// Returns the wire value.
141    #[must_use]
142    pub const fn value(self) -> u8 {
143        self.0
144    }
145
146    /// Returns the supported communication type represented by this field.
147    #[must_use]
148    pub const fn communication_type(self) -> CommunicationType {
149        match self.0 {
150            0x40 => CommunicationType::SndNke,
151            0x43 => CommunicationType::SndUd2,
152            0x53 | 0x73 => CommunicationType::SndUd,
153            0x5a | 0x7a => CommunicationType::ReqUd1,
154            0x5b | 0x7b => CommunicationType::ReqUd2,
155            0x08 | 0x18 | 0x28 | 0x38 => CommunicationType::RspUd,
156            _ => CommunicationType::Unsupported,
157        }
158    }
159
160    /// Returns the direction of a supported communication.
161    #[must_use]
162    pub const fn direction(self) -> Option<Direction> {
163        match self.communication_type() {
164            CommunicationType::SndNke
165            | CommunicationType::ReqUd1
166            | CommunicationType::ReqUd2
167            | CommunicationType::SndUd
168            | CommunicationType::SndUd2 => Some(Direction::MasterToSlave),
169            CommunicationType::RspUd => Some(Direction::SlaveToMaster),
170            CommunicationType::Unsupported => None,
171        }
172    }
173
174    /// Returns the frame-count bit for a message sent by a master.
175    #[must_use]
176    pub const fn frame_count_bit(self) -> Option<bool> {
177        if matches!(self.direction(), Some(Direction::MasterToSlave)) {
178            Some(self.0 & 0x20 != 0)
179        } else {
180            None
181        }
182    }
183
184    /// Returns the frame-count-valid bit for a message sent by a master.
185    #[must_use]
186    pub const fn frame_count_valid(self) -> Option<bool> {
187        if matches!(self.direction(), Some(Direction::MasterToSlave)) {
188            Some(self.0 & 0x10 != 0)
189        } else {
190            None
191        }
192    }
193
194    /// Returns the access-demand bit for a message sent by a slave.
195    #[must_use]
196    pub const fn access_demand(self) -> Option<bool> {
197        if matches!(self.direction(), Some(Direction::SlaveToMaster)) {
198            Some(self.0 & 0x20 != 0)
199        } else {
200            None
201        }
202    }
203
204    /// Returns the data-flow-control bit for a message sent by a slave.
205    #[must_use]
206    pub const fn data_flow_control(self) -> Option<bool> {
207        if matches!(self.direction(), Some(Direction::SlaveToMaster)) {
208            Some(self.0 & 0x10 != 0)
209        } else {
210            None
211        }
212    }
213
214    pub(crate) const fn validate_short_frame(self) -> Result<(), ControlError> {
215        if matches!(
216            self.communication_type(),
217            CommunicationType::SndNke | CommunicationType::ReqUd1 | CommunicationType::ReqUd2
218        ) {
219            Ok(())
220        } else {
221            Err(ControlError::InvalidForShortFrame { value: self.0 })
222        }
223    }
224
225    pub(crate) const fn validate_variable_frame(self) -> Result<(), ControlError> {
226        if matches!(
227            self.communication_type(),
228            CommunicationType::SndUd | CommunicationType::SndUd2 | CommunicationType::RspUd
229        ) {
230            Ok(())
231        } else {
232            Err(ControlError::InvalidForVariableFrame { value: self.0 })
233        }
234    }
235}
236
237/// Direction of a supported data-link communication.
238#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
239#[non_exhaustive]
240pub enum Direction {
241    /// A request or command sent by a master.
242    MasterToSlave,
243    /// A response sent by a slave.
244    SlaveToMaster,
245}
246
247impl From<u8> for Control {
248    fn from(value: u8) -> Self {
249        Self::new(value)
250    }
251}
252
253/// A supported wired M-Bus communication type.
254#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
255pub enum CommunicationType {
256    /// Normalize the link layer.
257    SndNke,
258    /// Request time-critical data.
259    ReqUd1,
260    /// Request standard data.
261    ReqUd2,
262    /// Send user data.
263    SndUd,
264    /// Send user data and request a response.
265    SndUd2,
266    /// Respond with user data.
267    RspUd,
268    /// A control value unsupported by wired M-Bus.
269    Unsupported,
270}
271
272/// Error produced when a control field is incompatible with a frame.
273#[derive(Clone, Copy, Debug, Eq, PartialEq)]
274pub enum ControlError {
275    /// The control value cannot be used in a short frame.
276    InvalidForShortFrame {
277        /// The rejected control-field value.
278        value: u8,
279    },
280    /// The control value cannot be used in a variable-format frame.
281    InvalidForVariableFrame {
282        /// The rejected control-field value.
283        value: u8,
284    },
285}
286
287impl fmt::Display for ControlError {
288    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            Self::InvalidForShortFrame { value } => write!(
291                formatter,
292                "control value 0x{value:02x} is invalid for a short frame"
293            ),
294            Self::InvalidForVariableFrame { value } => write!(
295                formatter,
296                "control value 0x{value:02x} is invalid for a variable-format frame"
297            ),
298        }
299    }
300}
301
302impl core::error::Error for ControlError {}
303
304#[cfg(test)]
305#[cfg_attr(coverage_nightly, coverage(off))]
306mod tests {
307    use super::*;
308    #[cfg(feature = "alloc")]
309    use alloc::string::ToString;
310
311    #[test]
312    fn classifies_supported_communication_types() {
313        let cases = [
314            (0x40, CommunicationType::SndNke),
315            (0x43, CommunicationType::SndUd2),
316            (0x53, CommunicationType::SndUd),
317            (0x73, CommunicationType::SndUd),
318            (0x5a, CommunicationType::ReqUd1),
319            (0x7a, CommunicationType::ReqUd1),
320            (0x5b, CommunicationType::ReqUd2),
321            (0x7b, CommunicationType::ReqUd2),
322            (0x08, CommunicationType::RspUd),
323            (0x18, CommunicationType::RspUd),
324            (0x28, CommunicationType::RspUd),
325            (0x38, CommunicationType::RspUd),
326            (0xff, CommunicationType::Unsupported),
327        ];
328
329        for (value, communication_type) in cases {
330            let control = Control::from(value);
331            assert_eq!(control.value(), value);
332            assert_eq!(control.communication_type(), communication_type);
333        }
334    }
335
336    #[test]
337    fn exposes_master_and_slave_flags() {
338        let master = Control::new(0x7b);
339        assert_eq!(master.direction(), Some(Direction::MasterToSlave));
340        assert_eq!(master.frame_count_bit(), Some(true));
341        assert_eq!(master.frame_count_valid(), Some(true));
342        assert_eq!(master.access_demand(), None);
343        assert_eq!(master.data_flow_control(), None);
344
345        let slave = Control::new(0x38);
346        assert_eq!(slave.direction(), Some(Direction::SlaveToMaster));
347        assert_eq!(slave.frame_count_bit(), None);
348        assert_eq!(slave.frame_count_valid(), None);
349        assert_eq!(slave.access_demand(), Some(true));
350        assert_eq!(slave.data_flow_control(), Some(true));
351    }
352
353    #[test]
354    fn constructors_produce_named_control_values() {
355        assert_eq!(Control::snd_nke().value(), 0x40);
356        assert_eq!(Control::req_ud1(false).value(), 0x5a);
357        assert_eq!(Control::req_ud1(true).value(), 0x7a);
358        assert_eq!(Control::req_ud2(false).value(), 0x5b);
359        assert_eq!(Control::req_ud2(true).value(), 0x7b);
360        assert_eq!(Control::snd_ud(false).value(), 0x53);
361        assert_eq!(Control::snd_ud(true).value(), 0x73);
362        assert_eq!(Control::snd_ud2().value(), 0x43);
363        assert_eq!(Control::rsp_ud(false, false).value(), 0x08);
364        assert_eq!(Control::rsp_ud(false, true).value(), 0x18);
365        assert_eq!(Control::rsp_ud(true, false).value(), 0x28);
366        assert_eq!(Control::rsp_ud(true, true).value(), 0x38);
367    }
368
369    #[test]
370    fn all_control_bytes_expose_flags_only_for_supported_direction() {
371        for value in u8::MIN..=u8::MAX {
372            let control = Control::new(value);
373            match control.direction() {
374                Some(Direction::MasterToSlave) => {
375                    assert!(control.frame_count_bit().is_some());
376                    assert!(control.frame_count_valid().is_some());
377                    assert_eq!(control.access_demand(), None);
378                    assert_eq!(control.data_flow_control(), None);
379                }
380                Some(Direction::SlaveToMaster) => {
381                    assert_eq!(control.frame_count_bit(), None);
382                    assert_eq!(control.frame_count_valid(), None);
383                    assert!(control.access_demand().is_some());
384                    assert!(control.data_flow_control().is_some());
385                }
386                None => {
387                    assert_eq!(control.frame_count_bit(), None);
388                    assert_eq!(control.frame_count_valid(), None);
389                    assert_eq!(control.access_demand(), None);
390                    assert_eq!(control.data_flow_control(), None);
391                }
392            }
393        }
394    }
395
396    #[test]
397    fn validates_frame_compatibility() {
398        assert_eq!(Control::new(0x40).validate_short_frame(), Ok(()));
399        assert_eq!(Control::new(0x53).validate_variable_frame(), Ok(()));
400
401        let error = Control::new(0x53).validate_short_frame().unwrap_err();
402        assert_eq!(error, ControlError::InvalidForShortFrame { value: 0x53 });
403        #[cfg(feature = "alloc")]
404        assert_eq!(
405            error.to_string(),
406            "control value 0x53 is invalid for a short frame"
407        );
408        assert_eq!(
409            Control::new(0x40).validate_variable_frame(),
410            Err(ControlError::InvalidForVariableFrame { value: 0x40 })
411        );
412    }
413}