Skip to main content

meterbus_wired_datalink/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
4//! Wired M-Bus data-link fields and frame codecs.
5//!
6//! This crate represents and encodes the wired M-Bus frames exchanged by
7//! masters and slaves. Its wire formats and message types follow the
8//! link-layer portion of EN 13757-2:2018+A1:2023, which uses the FT1.2 format
9//! class defined by the EN 60870-5 series.
10//!
11//! The API is suitable for M-Bus masters, meters, bridges, and diagnostic
12//! tools. It is [`no_std`](https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute)
13//! and supports encoding without dynamic allocation.
14//!
15//! # Scope
16//!
17//! The crate provides:
18//!
19//! - typed control and address fields;
20//! - ACK and NACK acknowledgement frames;
21//! - fixed-length short frames;
22//! - variable-format control frames without user data; and
23//! - variable-format long frames containing user data.
24//!
25//! Constructors and decoders validate each frame's structure, checksum, and
26//! supported control-field use. Address values remain representable even when
27//! they are reserved or inappropriate for a particular exchange; callers can
28//! inspect [`Address::kind`] and [`Address::expects_response`] when applying
29//! protocol rules.
30//!
31//! This crate does **not** implement:
32//!
33//! - the wired electrical interface or UART configuration;
34//! - byte timing, collision handling, retries, or response timeouts;
35//! - master or slave state machines;
36//! - application-layer interpretation of the control-information byte or user
37//!   data.
38//!
39//! Those concerns belong to the surrounding physical, transport, and
40//! application layers. In particular, application data carried by a
41//! [`LongFrame`] is specified separately by EN 13757-3.
42//!
43//! # Choosing a frame
44//!
45//! | Type | Wire form | Typical role |
46//! | --- | --- | --- |
47//! | [`AckFrame`] | One byte, `0xe5` | Positively acknowledge an accepted request |
48//! | [`NackFrame`] | One byte, `0xa2` | Reject an unsupported SND-UD2 request |
49//! | [`ShortFrame`] | Five-byte fixed format | Send SND-NKE, REQ-UD1, or REQ-UD2 |
50//! | [`ControlFrame`] | Nine-byte variable format with no user data | Carry control and control-information fields without an application payload |
51//! | [`LongFrame`] | Variable format with 1 to 252 user-data bytes | Send or respond with application data |
52//!
53//! A frame constructor rejects a [`Control`] value that is incompatible with
54//! that frame format. [`CommunicationType`] identifies the supported
55//! communication type represented by a raw control field.
56//!
57//! # Encoding and decoding
58//!
59//! Every `decode` function consumes a slice containing exactly one complete
60//! frame. It rejects truncated input, trailing bytes, invalid delimiters,
61//! invalid lengths, checksum mismatches, and incompatible control fields as
62//! applicable to that frame type. [`decoder::stream`] identifies boundaries
63//! across arbitrary chunks and can recover from noise without allocation.
64//!
65//! Every frame provides `encode_into`, which writes into a caller-owned buffer
66//! and returns only the initialized portion. When the `alloc` feature is
67//! enabled, frames also provide `encode`, which returns an allocated
68//! `Vec`.
69//!
70//! # Example
71//!
72//! The following constructs the standard five-byte shape of a REQ-UD2 request
73//! for slave address 1, encodes it without allocation, and decodes it again:
74//!
75//! ```
76//! use meterbus_wired_datalink::{Address, Control, ShortFrame};
77//!
78//! # fn main() -> Result<(), meterbus_wired_datalink::ShortFrameError> {
79//! let request = ShortFrame::new(Control::req_ud2(false), Address::new(1))?;
80//! let mut storage = [0_u8; ShortFrame::LEN];
81//! let encoded = request.encode_into(&mut storage)?;
82//!
83//! assert_eq!(encoded, [0x10, 0x5b, 0x01, 0x5c, 0x16]);
84//! assert_eq!(ShortFrame::decode(encoded)?, request);
85//! # Ok(())
86//! # }
87//! ```
88//!
89//! # Allocation and crate features
90//!
91//! The crate itself always uses `#![no_std]`.
92//!
93//! - the default feature set is allocation-free;
94//! - `alloc` adds allocating `encode` methods and the collecting
95//!   `decoder::stream::StreamDecoder::push` convenience method.
96//!
97//! Without `alloc`, encode with `encode_into` and stream with `push_into`.
98//!
99//! A valid frame is not necessarily the right frame for the current exchange.
100//! Callers must still manage message order, timing, retries, and application
101//! data according to EN 13757-2 and EN 13757-3.
102
103#[cfg(feature = "alloc")]
104extern crate alloc;
105
106pub mod decoder;
107mod frame;
108
109pub use frame::{AckFrame, AckFrameError};
110pub use frame::{Address, AddressKind, CommunicationType, Control, ControlError, Direction};
111pub use frame::{ControlFrame, ControlFrameError};
112pub use frame::{Frame, FrameEncodeError, FrameKind};
113pub use frame::{LongFrame, LongFrameError};
114pub use frame::{NackFrame, NackFrameError};
115pub use frame::{ShortFrame, ShortFrameError};