1#![no_std]
2pub trait RegisterRead {
5 fn get(&self, addr: u16) -> u16;
6 fn is_valid(&self, addr: u16) -> bool;
7}
8
9pub trait RegisterWrite {
11 fn set_reg(&mut self, addr: u16, val: u16);
12}
13
14pub trait BitRead {
16 fn get(&self, addr: u16) -> bool;
17 fn is_valid(&self, addr: u16) -> bool;
18}
19
20pub trait BitWrite {
22 fn set_bit(&mut self, addr: u16, val: bool);
23}
24
25pub struct Coil<const N: usize> {
27 regs: [bool; N],
28}
29impl<const N: usize> Coil<N> {
30 pub const fn new() -> Self {
31 Self { regs: [false; N] }
32 }
33 pub fn set_bit(&mut self, addr: u16, val: bool) {
34 let i = addr as usize;
35 if i < N {
36 self.regs[i] = val;
37 }
38 }
39 pub fn as_bits(&self) -> &[bool] {
40 &self.regs
41 }
42}
43
44impl<const N: usize> BitRead for Coil<N> {
45 fn get(&self, addr: u16) -> bool {
46 let i = addr as usize;
47 if i < N {
48 self.regs[i]
49 } else {
50 false
51 }
52 }
53 fn is_valid(&self, addr: u16) -> bool {
54 (addr as usize) < N
55 }
56}
57
58impl<const N: usize> BitWrite for Coil<N> {
59 fn set_bit(&mut self, addr: u16, val: bool) {
60 Coil::set_bit(self, addr, val);
61 }
62}
63
64pub struct Ists<const N: usize> {
66 regs: [bool; N],
67}
68impl<const N: usize> Ists<N> {
69 pub const fn new() -> Self {
70 Self { regs: [false; N] }
71 }
72 pub fn set_bit(&mut self, addr: u16, val: bool) {
73 let i = addr as usize;
74 if i < N {
75 self.regs[i] = val;
76 }
77 }
78}
79impl<const N: usize> BitRead for Ists<N> {
80 fn get(&self, addr: u16) -> bool {
81 let i = addr as usize;
82 if i < N {
83 self.regs[i]
84 } else {
85 false
86 }
87 }
88 fn is_valid(&self, addr: u16) -> bool {
89 (addr as usize) < N
90 }
91}
92
93pub struct Ireg<const N: usize> {
95 regs: [u16; N],
96}
97impl<const N: usize> Ireg<N> {
98 pub const fn new() -> Self {
99 Self { regs: [0; N] }
100 }
101 pub fn set(&mut self, addr: u16, val: u16) {
102 let i = addr as usize;
103 if i < N {
104 self.regs[i] = val;
105 }
106 }
107}
108impl<const N: usize> RegisterRead for Ireg<N> {
109 fn get(&self, addr: u16) -> u16 {
110 let i = addr as usize;
111 if i < N {
112 self.regs[i]
113 } else {
114 0
115 }
116 }
117 fn is_valid(&self, addr: u16) -> bool {
118 (addr as usize) < N
119 }
120}
121
122pub struct Hreg<const N: usize> {
124 regs: [u16; N],
125}
126
127impl<const N: usize> Hreg<N> {
128 pub const fn new() -> Self {
129 Self { regs: [0; N] }
130 }
131
132 pub fn set(&mut self, addr: u16, val: u16) {
133 let i = addr as usize;
134 if i < N {
135 self.regs[i] = val;
136 }
137 }
138
139 pub fn as_slice(&self) -> &[u16] {
140 &self.regs
141 }
142}
143
144impl<const N: usize> RegisterRead for Hreg<N> {
146 fn get(&self, addr: u16) -> u16 {
147 let i = addr as usize;
148 if i < N {
149 self.regs[i]
150 } else {
151 0
152 }
153 }
154 fn is_valid(&self, addr: u16) -> bool {
155 (addr as usize) < N
156 }
157}
158
159impl<const N: usize> RegisterWrite for Hreg<N> {
160 fn set_reg(&mut self, addr: u16, val: u16) {
161 Hreg::set(self, addr, val);
162 }
163}
164
165static mut SEED: u32 = 0x1234_5678;
171
172#[inline]
173fn xorshift32_next() -> u32 {
174 unsafe {
176 let mut x = SEED;
177 x ^= x << 13;
178 x ^= x >> 17;
179 x ^= x << 5;
180 SEED = x;
181 x
182 }
183}
184
185pub fn random(start_val: u16, end_val: u16) -> u16 {
187 let (lo, hi) = if start_val <= end_val {
188 (start_val, end_val)
189 } else {
190 (end_val, start_val)
191 };
192
193 let span = (hi as u32).wrapping_sub(lo as u32).wrapping_add(1);
194 let r = xorshift32_next() % span;
195 (lo as u32 + r) as u16
196}
197
198#[derive(Clone, Copy, Debug)]
199pub struct Req03 {
200 pub unit_id: u8,
201 pub start_addr: u16,
202 pub quantity: u16,
203}
204
205pub mod exc {
206 pub const ILLEGAL_FUNCTION: u8 = 0x01;
207 pub const ILLEGAL_DATA_ADDRESS: u8 = 0x02;
208 pub const ILLEGAL_DATA_VALUE: u8 = 0x03;
209}
210
211pub fn crc16_modbus(data: &[u8]) -> u16 {
213 let mut crc: u16 = 0xFFFF;
214 for &b in data {
215 crc ^= b as u16;
216 for _ in 0..8 {
217 if (crc & 0x0001) != 0 {
218 crc = (crc >> 1) ^ 0xA001;
219 } else {
220 crc >>= 1;
221 }
222 }
223 }
224 crc
225}
226
227pub fn build_resp_bit_reads<const MAX_QTY: usize, B: BitRead>(
234 out: &mut [u8],
235 unit_id: u8,
236 func: u8, start_addr: u16,
238 quantity: u16,
239 bits: &B,
240) -> usize {
241 let qty = quantity as usize;
242 let byte_cnt = (qty + 7) / 8;
243
244 out[0] = unit_id;
245 out[1] = func;
246 out[2] = byte_cnt as u8;
247
248 for i in 0..byte_cnt {
250 out[3 + i] = 0;
251 }
252
253 for j in 0..qty {
254 let addr = start_addr.wrapping_add(j as u16);
255 let bit = bits.get(addr);
256 if bit {
257 let byte_i = j / 8;
258 let bit_i = j % 8;
259 out[3 + byte_i] |= 1u8 << bit_i; }
261 }
262
263 let body_len = 3 + byte_cnt;
264 let crc = crc16_modbus(&out[..body_len]);
265 out[body_len] = (crc & 0xFF) as u8;
266 out[body_len + 1] = (crc >> 8) as u8;
267 body_len + 2
268}
269
270pub fn build_exception_resp<const BUF: usize>(
272 out: &mut [u8; BUF],
273 unit_id: u8,
274 function_exception: u8, exception_code: u8,
276) -> usize {
277 out[0] = unit_id;
278 out[1] = function_exception;
279 out[2] = exception_code;
280
281 let crc = crc16_modbus(&out[..3]);
282 out[3] = (crc & 0xFF) as u8;
283 out[4] = (crc >> 8) as u8;
284 5
285}
286
287pub struct ModbusCtx<'a, H, I, C, D> {
289 pub holdings: &'a mut H, pub inputs: &'a mut I, pub coils: &'a mut C, pub ists: &'a mut D, }
294
295impl<'a, H, I, C, D> ModbusCtx<'a, H, I, C, D>
296where
297 H: RegisterRead + RegisterWrite,
298 I: RegisterRead,
299 C: BitRead + BitWrite,
300 D: BitRead,
301{
302 pub fn pharse_pdu<const MAX_QTY: usize>(
307 &mut self,
308 req8: &[u8; 8],
309 out_tx: &mut [u8],
310 out_exc: &mut [u8; 5],
311 ) -> usize {
312 let unit_id = req8[0];
313 let func = req8[1];
314
315 let expected_crc = u16::from_le_bytes([req8[6], req8[7]]);
317 let calc_crc = crc16_modbus(&req8[..6]);
318 if expected_crc != calc_crc {
319 return build_exception_resp_fixed::<5>(
321 out_exc,
322 unit_id,
323 func | 0x80,
324 exc::ILLEGAL_DATA_VALUE,
325 );
326 }
327
328 let start_addr = u16::from_be_bytes([req8[2], req8[3]]);
330 let quantity = u16::from_be_bytes([req8[4], req8[5]]);
331
332 match func {
333 0x01 => {
334 if quantity == 0 || (quantity as usize) > MAX_QTY {
336 return build_exception_resp_fixed::<5>(
337 out_exc,
338 unit_id,
339 func | 0x80,
340 exc::ILLEGAL_DATA_VALUE,
341 );
342 }
343 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
345 if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end) {
346 return build_exception_resp_fixed::<5>(
347 out_exc,
348 unit_id,
349 func | 0x80,
350 exc::ILLEGAL_DATA_ADDRESS,
351 );
352 }
353 build_resp_bit_reads::<MAX_QTY, _>(
354 out_tx, unit_id, 0x01, start_addr, quantity, self.coils,
355 )
356 }
357
358 0x02 => {
359 if quantity == 0 || (quantity as usize) > MAX_QTY {
361 return build_exception_resp_fixed::<5>(
362 out_exc,
363 unit_id,
364 func | 0x80,
365 exc::ILLEGAL_DATA_VALUE,
366 );
367 }
368 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
370 if !self.ists.is_valid(start_addr) || !self.ists.is_valid(end) {
371 return build_exception_resp_fixed::<5>(
372 out_exc,
373 unit_id,
374 func | 0x80,
375 exc::ILLEGAL_DATA_ADDRESS,
376 );
377 }
378 build_resp_bit_reads::<MAX_QTY, _>(
379 out_tx, unit_id, 0x02, start_addr, quantity, self.ists,
380 )
381 }
382
383 0x03 => {
384 if quantity == 0 || (quantity as usize) > MAX_QTY {
386 return build_exception_resp_fixed::<5>(
387 out_exc,
388 unit_id,
389 func | 0x80,
390 exc::ILLEGAL_DATA_VALUE,
391 );
392 }
393 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
395 if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end) {
396 return build_exception_resp_fixed::<5>(
397 out_exc,
398 unit_id,
399 func | 0x80,
400 exc::ILLEGAL_DATA_ADDRESS,
401 );
402 }
403 build_resp_regs::<MAX_QTY, _>(
404 out_tx,
405 unit_id,
406 0x03,
407 start_addr,
408 quantity,
409 self.holdings,
410 )
411 }
412
413 0x04 => {
414 if quantity == 0 || (quantity as usize) > MAX_QTY {
416 return build_exception_resp_fixed::<5>(
417 out_exc,
418 unit_id,
419 func | 0x80,
420 exc::ILLEGAL_DATA_VALUE,
421 );
422 }
423 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
425 if !self.inputs.is_valid(start_addr) || !self.inputs.is_valid(end) {
426 return build_exception_resp_fixed::<5>(
427 out_exc,
428 unit_id,
429 func | 0x80,
430 exc::ILLEGAL_DATA_ADDRESS,
431 );
432 }
433 build_resp_regs::<MAX_QTY, _>(
434 out_tx,
435 unit_id,
436 0x04,
437 start_addr,
438 quantity,
439 self.inputs,
440 )
441 }
442
443 0x05 => {
444 let coil_value = quantity;
446
447 let bit = match coil_value {
448 0xFF00 => true,
449 0x0000 => false,
450 _ => {
451 return build_exception_resp_fixed::<5>(
452 out_exc,
453 unit_id,
454 0x05 | 0x80,
455 exc::ILLEGAL_DATA_VALUE,
456 );
457 }
458 };
459
460 if !self.coils.is_valid(start_addr) {
461 return build_exception_resp_fixed::<5>(
462 out_exc,
463 unit_id,
464 0x05 | 0x80,
465 exc::ILLEGAL_DATA_ADDRESS,
466 );
467 }
468
469 self.coils.set_bit(start_addr, bit);
470
471 out_tx[0] = unit_id;
473 out_tx[1] = 0x05;
474 out_tx[2] = (start_addr >> 8) as u8;
475 out_tx[3] = (start_addr & 0xFF) as u8;
476 out_tx[4] = (coil_value >> 8) as u8;
477 out_tx[5] = (coil_value & 0xFF) as u8;
478
479 let crc = crc16_modbus(&out_tx[..6]);
480 out_tx[6] = (crc & 0xFF) as u8;
481 out_tx[7] = (crc >> 8) as u8;
482 8
483 }
484
485 0x06 => {
486 let reg_value = quantity;
488
489 if !self.holdings.is_valid(start_addr) {
490 return build_exception_resp_fixed::<5>(
491 out_exc,
492 unit_id,
493 0x06 | 0x80,
494 exc::ILLEGAL_DATA_ADDRESS,
495 );
496 }
497
498 self.holdings.set_reg(start_addr, reg_value);
499
500 out_tx[0] = unit_id;
502 out_tx[1] = 0x06;
503 out_tx[2] = (start_addr >> 8) as u8;
504 out_tx[3] = (start_addr & 0xFF) as u8;
505 out_tx[4] = (reg_value >> 8) as u8;
506 out_tx[5] = (reg_value & 0xFF) as u8;
507
508 let crc = crc16_modbus(&out_tx[..6]);
509 out_tx[6] = (crc & 0xFF) as u8;
510 out_tx[7] = (crc >> 8) as u8;
511 8
512 }
513
514 _ => {
515 build_exception_resp_fixed::<5>(
517 out_exc,
518 unit_id,
519 func | 0x80,
520 exc::ILLEGAL_FUNCTION,
521 )
522 }
523 }
524 }
525}
526
527fn build_exception_resp_fixed<const BUF: usize>(
529 out_exc: &mut [u8; 5],
530 unit_id: u8,
531 function_exception: u8,
532 exception_code: u8,
533) -> usize {
534 out_exc[0] = unit_id;
535 out_exc[1] = function_exception;
536 out_exc[2] = exception_code;
537
538 let crc = crc16_modbus(&out_exc[..3]);
539 out_exc[3] = (crc & 0xFF) as u8;
540 out_exc[4] = (crc >> 8) as u8;
541 5
542}
543fn build_resp_regs<const MAX_QTY: usize, R: RegisterRead>(
546 out_tx: &mut [u8],
547 unit_id: u8,
548 func: u8,
549 start_addr: u16,
550 quantity: u16,
551 regs: &R,
552) -> usize {
553 let qty = quantity as usize;
554
555 out_tx[0] = unit_id;
556 out_tx[1] = func;
557 out_tx[2] = (qty as u8) * 2;
558
559 for i in 0..qty {
560 let addr = start_addr.wrapping_add(i as u16);
561 let v = regs.get(addr);
562 let base = 3 + i * 2;
563 out_tx[base] = (v >> 8) as u8;
564 out_tx[base + 1] = (v & 0xFF) as u8;
565 }
566
567 let body_len = 3 + qty * 2;
568 let crc = crc16_modbus(&out_tx[..body_len]);
569 out_tx[body_len] = (crc & 0xFF) as u8;
570 out_tx[body_len + 1] = (crc >> 8) as u8;
571
572 body_len + 2
573}