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}