modbus_rtu/slave/data_model/structure.rs
1/// Represents a statically defined set of Modbus RTU data addresses.
2///
3/// The addresses must be:
4/// - Known at compile time
5/// - Strictly increasing (ordered)
6/// - Unique
7///
8/// This structure is intended to be defined as a global constant/static value,
9/// and should not be instantiated dynamically.
10///
11/// ---
12/// # Examples
13/// ```
14/// use modbus_rtu::slave::DataStructure;
15///
16/// // Define data structure as const.
17/// const DATA_STRUCTURE: DataStructure<4> = DataStructure::new([
18/// 0x0000,
19/// 0x0001,
20/// 0x1234,
21/// 0x5678,
22/// ]);
23/// ```
24///
25#[derive(Debug)]
26pub struct DataStructure<const L: usize>([u16; L]);
27
28
29impl<const L: usize> DataStructure<L> {
30 /// Creates a new `DataStructure` after validating the input addresses.
31 ///
32 /// This function checks that the addresses are strictly ordered and unique at compile time.
33 /// It will panic if the addresses are not ordered or contain duplicates.
34 ///
35 /// ---
36 /// # Arguments
37 /// - `addresses`: The list of Modbus RTU register addresses to be stored in the structure.
38 ///
39 /// ---
40 /// # Returns
41 /// A new `DataStructure` instance if the validation passes.
42 ///
43 /// ---
44 /// # Examples
45 /// ```
46 /// use modbus_rtu::slave::DataStructure;
47 ///
48 /// const STRUCTURE: DataStructure<5> = DataStructure::new([
49 /// 0x0000,
50 /// 0x0001,
51 /// 0x0002,
52 /// 0x1234,
53 /// 0x5678,
54 /// ]);
55 /// ```
56 ///
57 pub const fn new(addresses: [u16; L]) -> DataStructure<L> {
58 let _ = Self::validate(&addresses);
59 Self(addresses)
60 }
61
62 /// Get the index of the specified address using a manual binary search approach.
63 ///
64 /// This function performs a binary search on the statically defined address list.
65 /// It will panic if the address is not found in the list.
66 ///
67 /// ---
68 /// # Arguments
69 /// - `address`: The address to look for in the data structure.
70 ///
71 /// ---
72 /// # Returns
73 /// The index of the address in the list if it exists, or panics if not found.
74 ///
75 /// ---
76 /// # Examples
77 /// ```
78 /// use modbus_rtu::slave::DataStructure;
79 ///
80 /// const STRUCTURE: DataStructure<5> = DataStructure::new([
81 /// 0x0000,
82 /// 0x0001,
83 /// 0x0002,
84 /// 0x1234,
85 /// 0x5678,
86 /// ]);
87 ///
88 /// assert_eq!(STRUCTURE.get(0x0001), 1);
89 /// ```
90 ///
91 /// The code below will panic at complie time.
92 /// ```should_panic
93 /// # use modbus_rtu::slave::DataStructure;
94 /// # const STRUCTURE: DataStructure<5> = DataStructure::new([
95 /// # 0x0000,
96 /// # 0x0001,
97 /// # 0x0002,
98 /// # 0x1234,
99 /// # 0x5678,
100 /// # ]);
101 /// // Will panic!!
102 /// let index = STRUCTURE.get(0x0003);
103 /// ```
104 ///
105 /// ---
106 /// # Panics
107 /// This function will panic if the specified address is not found in the address list.
108 /// The panic message will indicate that the address is not registered and should be checked for validity.
109 ///
110 #[inline(always)]
111 pub const fn get(&self, address: u16) -> usize {
112 let mut left = 0;
113 let mut right = self.0.len() - 1;
114
115 while left <= right {
116 let mid = (left + right) / 2;
117 if self.0[mid] == address {
118 return mid;
119 } else if self.0[mid] < address {
120 left = mid + 1;
121 } else {
122 right = mid - 1;
123 }
124 }
125
126 panic!("Address not found in the address list. Ensure the address is valid and registered.");
127 }
128
129 /// Finds the index of the specified address using the binary search algorithm.
130 ///
131 /// This function performs a binary search on the statically defined address list.
132 /// It returns the index of the address if found.
133 ///
134 /// ---
135 /// # Arguments
136 /// - `address`: The address to search for in the data structure.
137 ///
138 /// ---
139 /// # Returns
140 /// `Some(index)` if the address exists in the list, or `None` if not found.
141 ///
142 /// ---
143 /// # Examples
144 /// ```
145 /// use modbus_rtu::slave::DataStructure;
146 ///
147 /// const STRUCTURE: DataStructure<5> = DataStructure::new([
148 /// 0x0000,
149 /// 0x0001,
150 /// 0x0002,
151 /// 0x1234,
152 /// 0x5678,
153 /// ]);
154 ///
155 /// assert_eq!(STRUCTURE.find(0x0001), Some(1));
156 /// assert_eq!(STRUCTURE.find(0x0003), None);
157 /// ```
158 ///
159 pub fn find(&self, address: u16) -> Option<usize> {
160 self.0.binary_search(&address).ok()
161 }
162
163 /// Retrieves the address at the specified index.
164 ///
165 /// This function accesses the address stored at the given index in the data structure's array.
166 /// It is a simple getter function that does not perform any validation.
167 ///
168 /// ---
169 /// # Arguments
170 /// - `index`: The index of the address to retrieve. The number must be derived from the logical relationships defined within the data structure.
171 ///
172 /// ---
173 /// # Returns
174 /// The `u16` address located at the given index.
175 ///
176 /// ---
177 /// # Examples
178 /// ```
179 /// use modbus_rtu::slave::DataStructure;
180 ///
181 /// const STRUCTURE: DataStructure<5> = DataStructure::new([
182 /// 0x0000,
183 /// 0x0001,
184 /// 0x0002,
185 /// 0x1234,
186 /// 0x5678,
187 /// ]);
188 ///
189 /// assert_eq!(STRUCTURE.get_address_by_index(3), 0x1234);
190 /// ```
191 ///
192 /// The code below will panic at compile time.
193 /// ```should_panic
194 /// # use modbus_rtu::slave::DataStructure;
195 /// # const STRUCTURE: DataStructure<5> = DataStructure::new([
196 /// # 0x0000,
197 /// # 0x0001,
198 /// # 0x0002,
199 /// # 0x1234,
200 /// # 0x5678,
201 /// # ]);
202 /// // Will panic!!
203 /// let address: u16 = STRUCTURE.get_address_by_index(10);
204 /// ```
205 ///
206 /// ---
207 /// # Panics
208 /// This function will panic if the `index` is out of bounds for the address list.
209 /// The number must be derived from the logical relationships defined within the data structure.
210 ///
211 pub fn get_address_by_index(&self, index: usize) -> u16 {
212 self.0[index]
213 }
214
215 /// Returns the number of addresses stored in this data structure.
216 ///
217 /// This is equivalent to the compile-time constant length `L`.
218 ///
219 /// ---
220 /// # Returns
221 /// Total count of valid(defined) addresses
222 ///
223 /// ---
224 /// # Examples
225 /// ```
226 /// use modbus_rtu::slave::DataStructure;
227 ///
228 /// const ADDRESSES: [u16; 4] = [
229 /// 0x0000,
230 /// 0x0001,
231 /// 0x1234,
232 /// 0x5678,
233 /// ];
234 ///
235 /// const DATA_STRUCTURE: DataStructure<4> = DataStructure::new(ADDRESSES);
236 ///
237 /// assert_eq!(ADDRESSES.len(), DATA_STRUCTURE.len());
238 /// ```
239 ///
240 pub fn len(&self) -> usize {
241 L
242 }
243
244 /// Validates that the address list is strictly increasing and unique.
245 ///
246 /// ---
247 /// # Arguments
248 /// - `addresses`: The list of addresses to validate.
249 ///
250 /// ---
251 /// # Returns
252 /// `true` if the addresses are valid (ordered and unique), otherwise panics.
253 ///
254 /// ---
255 /// # Panics
256 /// This function will panic if the addresses are not strictly increasing or contain duplicates.
257 ///
258 const fn validate(addresses: &[u16; L]) -> bool {
259 if addresses.len() < 2 {
260 return true;
261 }
262
263 let mut i = 0;
264 while i < addresses.len() - 1 {
265 if addresses[i] >= addresses[i + 1] {
266 panic!("Addresses for data structure must be ordered and unique. Found invalid order at index.");
267 }
268 i += 1;
269 }
270 true
271 }
272}
273
274
275impl DataStructure<0> {
276 /// An empty data structure.
277 ///
278 /// This is used to define a data model that does not contain any data.
279 ///
280 pub const EMPTY: DataStructure<0> = DataStructure::new([]);
281}