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/// Represents the state of a block of registers read from a Modbus server.
25///
26/// This structure maintains the starting address and the quantity of registers,
27/// providing safe accessors to individual values within the block.
28///
29/// # Type Parameters
30/// * `N` - The internal storage capacity, defaults to [`MAX_REGISTERS_PER_PDU`].
31#[derive(Debug, PartialEq, Eq, Clone)]
32pub struct Registers<const N: usize = MAX_REGISTERS_PER_PDU> {
33 /// The starting address of the first register in this block.
34 from_address: u16,
35 /// The number of registers in this block.
36 quantity: u16,
37 /// The register values.
38 values: [u16; N],
39}
40
41impl<const N: usize> Registers<N> {
42 /// Creates a new `Registers` instance.
43 ///
44 /// # Arguments
45 /// * `from_address` - The starting Modbus address.
46 /// * `quantity` - The number of registers to be managed in this block.
47 ///
48 pub fn new(from_address: u16, quantity: u16) -> Result<Self, MbusError> {
49 if quantity as usize > N {
50 return Err(MbusError::InvalidQuantity);
51 }
52 Ok(Self {
53 from_address,
54 quantity,
55 values: [0; N],
56 })
57 }
58
59 /// Loads register values into the model and validates the length against capacity.
60 ///
61 /// # Arguments
62 /// * `values` - A slice of 16-bit values to copy into the internal buffer.
63 /// * `length` - The number of registers being loaded.
64 pub fn with_values(mut self, values: &[u16], length: u16) -> Result<Self, MbusError> {
65 if length > N as u16 {
66 return Err(MbusError::InvalidQuantity);
67 }
68 if length > self.quantity {
69 return Err(MbusError::InvalidQuantity);
70 }
71 self.values[..length as usize].copy_from_slice(values);
72
73 Ok(self)
74 }
75
76 /// Returns the starting Modbus address of the first register.
77 pub fn from_address(&self) -> u16 {
78 self.from_address
79 }
80
81 /// Returns the number of registers currently held in this block.
82 pub fn quantity(&self) -> u16 {
83 self.quantity
84 }
85
86 /// Returns the register values.
87 pub fn values(&self) -> &[u16; N] {
88 &self.values
89 }
90
91 /// Updates the value of a specific register within the block.
92 ///
93 /// # Arguments
94 /// * `address` - The Modbus address of the register to update.
95 /// * `value` - The new 16-bit unsigned integer value.
96 ///
97 /// # Errors
98 /// Returns `MbusError::InvalidAddress` if the address is outside the range
99 /// defined by `from_address` and `quantity`.
100 pub fn set_value(&mut self, address: u16, value: u16) -> Result<(), MbusError> {
101 // Check if the address is within the bounds of this register block
102 if address < self.from_address || address >= self.from_address + self.quantity {
103 return Err(MbusError::InvalidAddress);
104 }
105
106 // Calculate the local index and update the value
107 let index = (address - self.from_address) as usize;
108 self.values[index] = value;
109
110 Ok(())
111 }
112
113 /// Retrieves the value of a specific register by its address.
114 ///
115 /// # Arguments
116 /// * `address` - The Modbus address to query.
117 ///
118 /// # Errors
119 /// Returns `MbusError::InvalidAddress` if the address is outside the block range.
120 pub fn value(&self, address: u16) -> Result<u16, MbusError> {
121 if address < self.from_address || address >= self.from_address + self.quantity {
122 return Err(MbusError::InvalidAddress);
123 }
124 let index = (address - self.from_address) as usize;
125 self.values
126 .get(index)
127 .copied()
128 .ok_or(MbusError::InvalidAddress)
129 }
130}