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/// 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}