Skip to main content

mbus_core/models/register/
model.rs

1//! # Modbus Register Models
2//!
3//! This module defines the data structures for handling **Holding Registers** (FC 0x03, 0x06, 0x10)
4//! and **Input Registers** (FC 0x04).
5//!
6//! In Modbus, registers are 16-bit unsigned integers.
7//! - **Holding Registers**: Read-write registers used for configuration, setpoints, and control.
8//! - **Input Registers**: Read-only registers typically used for sensor data or status.
9//!
10//! ## Key Components
11//! - [`Registers`]: A container for a block of 16-bit register values.
12//! - [`MAX_REGISTERS_PER_PDU`]: The protocol limit for registers in a single request/response.
13//!
14//! ## Protocol Limits
15//! According to the Modbus specification, a single PDU can carry up to 125 registers (250 bytes).
16//! This implementation uses a generic constant `N` to allow for smaller, memory-optimized
17//! allocations in `no_std` environments while defaulting to the protocol maximum.
18
19use crate::errors::MbusError;
20
21/// Maximum number of registers that can be read/written in a single Modbus PDU (125 registers).
22pub const MAX_REGISTERS_PER_PDU: usize = 125;
23
24/// Marker type representing Holding Registers mode.
25#[derive(Debug, PartialEq, Eq, Clone, Copy)]
26pub struct Holding;
27
28/// Marker type representing Input Registers mode.
29#[derive(Debug, PartialEq, Eq, Clone, Copy)]
30pub struct Input;
31
32/// A block of Modbus holding registers (FC 0x03, 0x06, 0x10).
33#[allow(deprecated)]
34pub type HoldingRegisters<const N: usize = MAX_REGISTERS_PER_PDU> = Registers<N, Holding>;
35
36/// A block of Modbus input registers (FC 0x04).
37#[allow(deprecated)]
38pub type InputRegisters<const N: usize = MAX_REGISTERS_PER_PDU> = Registers<N, Input>;
39
40/// Represents the state of a block of registers read from a Modbus server.
41///
42/// This structure maintains the starting address and the quantity of registers,
43/// providing safe accessors to individual values within the block.
44///
45/// # Type Parameters
46/// * `N` - The internal storage capacity, defaults to [`MAX_REGISTERS_PER_PDU`].
47/// * `Mode` - The access mode constraint (defaults to `Holding` for backwards compatibility).
48#[derive(Debug, PartialEq, Eq, Clone)]
49#[deprecated(
50    since = "0.11.0",
51    note = "Do not use `Registers` directly. Please use the typed registers `HoldingRegisters` or `InputRegisters` instead. In the future, this struct will not be exported to the user space."
52)]
53pub struct Registers<const N: usize = MAX_REGISTERS_PER_PDU, Mode = Holding> {
54    /// The starting address of the first register in this block.
55    from_address: u16,
56    /// The number of registers in this block.
57    quantity: u16,
58    /// The register values.
59    values: [u16; N],
60    /// Compile-time mode marker.
61    _mode: core::marker::PhantomData<Mode>,
62}
63
64#[allow(deprecated)]
65impl<const N: usize, Mode> Registers<N, Mode> {
66    /// Creates a new `Registers` instance.
67    ///
68    /// # Arguments
69    /// * `from_address` - The starting Modbus address.
70    /// * `quantity` - The number of registers to be managed in this block.
71    ///
72    pub fn new(from_address: u16, quantity: u16) -> Result<Self, MbusError> {
73        if quantity as usize > N {
74            return Err(MbusError::InvalidQuantity);
75        }
76        Ok(Self {
77            from_address,
78            quantity,
79            values: [0; N],
80            _mode: core::marker::PhantomData,
81        })
82    }
83
84    /// Loads register values into the model and validates the length against capacity.
85    ///
86    /// # Arguments
87    /// * `values` - A slice of 16-bit values to copy into the internal buffer.
88    /// * `length` - The number of registers being loaded.
89    pub fn with_values(mut self, values: &[u16], length: u16) -> Result<Self, MbusError> {
90        if length > N as u16 {
91            return Err(MbusError::InvalidQuantity);
92        }
93        if length > self.quantity {
94            return Err(MbusError::InvalidQuantity);
95        }
96        self.values[..length as usize].copy_from_slice(values);
97
98        Ok(self)
99    }
100
101    /// Creates a new block of registers and initializes it from big-endian bytes.
102    ///
103    /// # Arguments
104    /// * `from_address` - The starting Modbus address.
105    /// * `quantity` - The number of registers to be managed.
106    /// * `bytes` - Slice of big-endian bytes to decode into registers.
107    pub fn new_from_be_bytes(
108        from_address: u16,
109        quantity: u16,
110        bytes: &[u8],
111    ) -> Result<Self, MbusError> {
112        let mut reg = Self::new(from_address, quantity)?;
113        for (i, chunk) in bytes.chunks_exact(2).take(quantity as usize).enumerate() {
114            reg.values[i] = u16::from_be_bytes([chunk[0], chunk[1]]);
115        }
116        Ok(reg)
117    }
118
119    /// Returns the starting Modbus address of the first register.
120    pub fn from_address(&self) -> u16 {
121        self.from_address
122    }
123
124    /// Returns the number of registers currently held in this block.
125    pub fn quantity(&self) -> u16 {
126        self.quantity
127    }
128
129    /// Returns the register values.
130    pub fn values(&self) -> &[u16; N] {
131        &self.values
132    }
133
134    /// Retrieves the value of a specific register by its address.
135    ///
136    /// # Arguments
137    /// * `address` - The Modbus address to query.
138    ///
139    /// # Errors
140    /// Returns `MbusError::InvalidAddress` if the address is outside the block range.
141    pub fn value(&self, address: u16) -> Result<u16, MbusError> {
142        if address < self.from_address || address >= self.from_address + self.quantity {
143            return Err(MbusError::InvalidAddress);
144        }
145        let index = (address - self.from_address) as usize;
146        self.values
147            .get(index)
148            .copied()
149            .ok_or(MbusError::InvalidAddress)
150    }
151}
152
153#[allow(deprecated)]
154impl<const N: usize> Registers<N, Holding> {
155    /// Updates the value of a specific register within the block.
156    ///
157    /// # Arguments
158    /// * `address` - The Modbus address of the register to update.
159    /// * `value` - The new 16-bit unsigned integer value.
160    ///
161    /// # Errors
162    /// Returns `MbusError::InvalidAddress` if the address is outside the range
163    /// defined by `from_address` and `quantity`.
164    pub fn set_value(&mut self, address: u16, value: u16) -> Result<(), MbusError> {
165        // Check if the address is within the bounds of this register block
166        if address < self.from_address || address >= self.from_address + self.quantity {
167            return Err(MbusError::InvalidAddress);
168        }
169
170        // Calculate the local index and update the value
171        let index = (address - self.from_address) as usize;
172        self.values[index] = value;
173
174        Ok(())
175    }
176}