Skip to main content

modbus_impl/
lib.rs

1#![no_std]
2#![allow(non_snake_case)]
3/// RegisterRead:支持 is_valid(addr) 用于越界检查
4/// 支持03/04寄存器的读取(Hreg/Ireg)
5pub trait RegisterRead {
6    fn get(&self, addr: u16) -> u16;
7    fn is_valid(&self, addr: u16) -> bool;
8}
9
10/// 用于 FC06 写单保持寄存器
11pub trait RegisterWrite {
12    fn set_reg(&mut self, addr: u16, val: u16);
13    fn set_qty(&mut self, qty: u16);
14    fn get_qty(&mut self) -> usize;
15}
16
17/// 支持 01/02 的位读取(Coil/ISTS)
18pub trait BitRead {
19    fn get(&self, addr: u16) -> bool;
20    fn is_valid(&self, addr: u16) -> bool;
21}
22
23/// 用于 FC05 写单线圈
24pub trait BitWrite {
25    fn set_bit(&mut self, addr: u16, val: bool);
26    fn set_qty(&mut self, qty: u16);
27    fn get_qty(&mut self) -> usize;
28}
29
30/// Coil 结构体(FC01)
31pub struct Coil<const N: usize> {
32    regs: [bool; N],
33    qty: usize,
34}
35impl<const N: usize> Coil<N> {
36    pub const fn new() -> Self {
37        Self {
38            regs: [false; N],
39            qty: 0,
40        }
41    }
42
43    pub fn set_bit(&mut self, addr: u16, val: bool) {
44        let i = addr as usize;
45        if i < N {
46            self.regs[i] = val;
47        }
48    }
49
50    pub fn set_qty(&mut self, qty: u16) {
51        let i = qty as usize;
52        if i < N {
53            self.qty = i;
54        }
55        //if i > N, qty = N-1
56        self.qty = N - 1;
57    }
58
59    pub fn get_qty(&mut self) -> usize {
60        self.qty
61    }
62
63    pub fn as_bits(&self) -> &[bool] {
64        &self.regs
65    }
66}
67
68impl<const N: usize> BitRead for Coil<N> {
69    fn get(&self, addr: u16) -> bool {
70        let i = addr as usize;
71        if i < N {
72            self.regs[i]
73        } else {
74            false
75        }
76    }
77    fn is_valid(&self, addr: u16) -> bool {
78        (addr as usize) < N
79    }
80}
81
82impl<const N: usize> BitWrite for Coil<N> {
83    fn set_bit(&mut self, addr: u16, val: bool) {
84        Coil::set_bit(self, addr, val);
85    }
86
87    fn set_qty(&mut self, qty: u16) {
88        Coil::set_qty(self, qty);
89    }
90
91    fn get_qty(&mut self) -> usize {
92        Coil::get_qty(self)
93    }
94}
95
96/// ISTS(离散输入)结构体(FC02)
97pub struct Ists<const N: usize> {
98    regs: [bool; N],
99}
100impl<const N: usize> Ists<N> {
101    pub const fn new() -> Self {
102        Self { regs: [false; N] }
103    }
104    pub fn set_bit(&mut self, addr: u16, val: bool) {
105        let i = addr as usize;
106        if i < N {
107            self.regs[i] = val;
108        }
109    }
110}
111impl<const N: usize> BitRead for Ists<N> {
112    fn get(&self, addr: u16) -> bool {
113        let i = addr as usize;
114        if i < N {
115            self.regs[i]
116        } else {
117            false
118        }
119    }
120    fn is_valid(&self, addr: u16) -> bool {
121        (addr as usize) < N
122    }
123}
124
125/// IREG(输入寄存器)结构体(FC04)
126pub struct Ireg<const N: usize> {
127    regs: [u16; N],
128}
129impl<const N: usize> Ireg<N> {
130    pub const fn new() -> Self {
131        Self { regs: [0; N] }
132    }
133    pub fn set(&mut self, addr: u16, val: u16) {
134        let i = addr as usize;
135        if i < N {
136            self.regs[i] = val;
137        }
138    }
139}
140impl<const N: usize> RegisterRead for Ireg<N> {
141    fn get(&self, addr: u16) -> u16 {
142        let i = addr as usize;
143        if i < N {
144            self.regs[i]
145        } else {
146            0
147        }
148    }
149    fn is_valid(&self, addr: u16) -> bool {
150        (addr as usize) < N
151    }
152}
153
154///HREG(保持寄存器)结构体(FC03)
155pub struct Hreg<const N: usize> {
156    regs: [u16; N],
157    qty: usize,
158}
159
160impl<const N: usize> Hreg<N> {
161    pub const fn new() -> Self {
162        Self {
163            regs: [0; N],
164            qty: 0,
165        }
166    }
167
168    pub fn set(&mut self, addr: u16, val: u16) {
169        let i = addr as usize;
170        if i < N {
171            self.regs[i] = val;
172        }
173    }
174
175    pub fn set_qty(&mut self, qty: u16) {
176        let i = qty as usize;
177        if i < N {
178            self.qty = i;
179        }
180        //if i > N, qty = N-1
181        self.qty = N - 1;
182    }
183
184    pub fn get_qty(&mut self) -> usize {
185        self.qty
186    }
187
188    pub fn as_slice(&self) -> &[u16] {
189        &self.regs
190    }
191}
192
193// RegisterRead 实现:提供 get + is_valid
194impl<const N: usize> RegisterRead for Hreg<N> {
195    fn get(&self, addr: u16) -> u16 {
196        let i = addr as usize;
197        if i < N {
198            self.regs[i]
199        } else {
200            0
201        }
202    }
203    fn is_valid(&self, addr: u16) -> bool {
204        (addr as usize) < N
205    }
206}
207
208impl<const N: usize> RegisterWrite for Hreg<N> {
209    fn set_reg(&mut self, addr: u16, val: u16) {
210        Hreg::set(self, addr, val);
211    }
212    fn set_qty(&mut self, qty: u16) {
213        Hreg::set_qty(self, qty);
214    }
215    fn get_qty(&mut self) -> usize {
216        Hreg::get_qty(self)
217    }
218}
219
220///////////////////////////////////////////////////////////////////////////////
221// 2) random(start_val, end_val)  —— no_std 伪随机
222///////////////////////////////////////////////////////////////////////////////
223
224// xorshift32 seed:单线程场景足够用
225static mut SEED: u32 = 0x1234_5678;
226
227#[inline]
228fn xorshift32_next() -> u32 {
229    // SAFETY: 单线程典型嵌入式用法;如需多核/中断并发请告诉我再改为更安全的方案
230    unsafe {
231        let mut x = SEED;
232        x ^= x << 13;
233        x ^= x >> 17;
234        x ^= x << 5;
235        SEED = x;
236        x
237    }
238}
239
240/// 生成闭区间 [min(start_val,end_val), max(...)] 内的随机 u16
241pub fn Random(start_val: u16, end_val: u16) -> u16 {
242    let (lo, hi) = if start_val <= end_val {
243        (start_val, end_val)
244    } else {
245        (end_val, start_val)
246    };
247
248    let span = (hi as u32).wrapping_sub(lo as u32).wrapping_add(1);
249    let r = xorshift32_next() % span;
250    (lo as u32 + r) as u16
251}
252
253pub mod exc {
254    pub const ILLEGAL_FUNCTION: u8 = 0x01;
255    pub const ILLEGAL_DATA_ADDRESS: u8 = 0x02;
256    pub const ILLEGAL_DATA_VALUE: u8 = 0x03;
257}
258
259/// Modbus RTU CRC16,用于生成CRC16验证码
260pub fn crc16_modbus(data: &[u8]) -> u16 {
261    let mut crc: u16 = 0xFFFF;
262    for &b in data {
263        crc ^= b as u16;
264        for _ in 0..8 {
265            if (crc & 0x0001) != 0 {
266                crc = (crc >> 1) ^ 0xA001;
267            } else {
268                crc >>= 1;
269            }
270        }
271    }
272    crc
273}
274
275/// 组装 FC01 / FC02 响应:
276/*
277  Unit(1) + Func(1) + ByteCount(1) + PackedBits(N) + CRC(2)
278
279  packed bits:Modbus 规定每字节 bit0 是第一个位(LSB-first)
280*/
281pub fn build_resp_bit_reads<const MAX_QTY: usize, B: BitRead>(
282    out: &mut [u8],
283    unit_id: u8,
284    func: u8, // 0x01 or 0x02
285    start_addr: u16,
286    quantity: u16,
287    bits: &B,
288) -> usize {
289    let qty = quantity as usize;
290    let byte_cnt = (qty + 7) / 8;
291
292    out[0] = unit_id;
293    out[1] = func;
294    out[2] = byte_cnt as u8;
295
296    // 清零位区
297    for i in 0..byte_cnt {
298        out[3 + i] = 0;
299    }
300
301    for j in 0..qty {
302        let addr = start_addr.wrapping_add(j as u16);
303        let bit = bits.get(addr);
304        if bit {
305            let byte_i = j / 8;
306            let bit_i = j % 8;
307            out[3 + byte_i] |= 1u8 << bit_i; // LSB-first
308        }
309    }
310
311    let body_len = 3 + byte_cnt;
312    let crc = crc16_modbus(&out[..body_len]);
313    out[body_len] = (crc & 0xFF) as u8;
314    out[body_len + 1] = (crc >> 8) as u8;
315    body_len + 2
316}
317
318/// 组装异常响应:Function=03|0x80 + ExceptionCode(1) + CRC(2)
319pub fn build_exception_resp<const BUF: usize>(
320    out: &mut [u8; BUF],
321    unit_id: u8,
322    function_exception: u8, // 例如 0x83
323    exception_code: u8,
324) -> usize {
325    out[0] = unit_id;
326    out[1] = function_exception;
327    out[2] = exception_code;
328
329    let crc = crc16_modbus(&out[..3]);
330    out[3] = (crc & 0xFF) as u8;
331    out[4] = (crc >> 8) as u8;
332    5
333}
334
335///ModbusCtx,用于沟通上下文
336pub struct ModbusCtx<'a, H, I, C, D> {
337    pub holdings: &'a mut H, // FC03
338    pub inputs: &'a mut I,   // FC04
339    pub coils: &'a mut C,    // FC01
340    pub ists: &'a mut D,     // FC02
341}
342
343impl<'a, H, I, C, D> ModbusCtx<'a, H, I, C, D>
344where
345    H: RegisterRead + RegisterWrite,
346    I: RegisterRead,
347    C: BitRead + BitWrite,
348    D: BitRead,
349{
350    /// pharse_pdu: 解析固定 8 字节 Modbus RTU 请求,并组装响应/异常
351    /// 返回值:
352    /// - 正常响应:返回 out_tx 中的长度
353    /// - 异常响应:写入 out_exc,并返回 5
354    pub fn pharse_pdu<const MAX_QTY: usize>(
355        &mut self,
356        req8: &[u8; 8],
357        out_tx: &mut [u8],
358        out_exc: &mut [u8; 5],
359    ) -> usize {
360        let unit_id = req8[0];
361        let func = req8[1];
362
363        // -------- CRC check --------
364        let expected_crc = u16::from_le_bytes([req8[6], req8[7]]);
365        let calc_crc = crc16_modbus(&req8[..6]);
366        if expected_crc != calc_crc {
367            // CRC 错:返回 ILLEGAL_DATA_VALUE,功能码用请求 func|0x80
368            return build_exception_resp_fixed::<5>(
369                out_exc,
370                unit_id,
371                func | 0x80,
372                exc::ILLEGAL_DATA_VALUE,
373            );
374        }
375
376        // -------- common fields --------
377        let start_addr = u16::from_be_bytes([req8[2], req8[3]]);
378        let quantity = u16::from_be_bytes([req8[4], req8[5]]);
379
380        match func {
381            0x01 => {
382                // quantity 校验
383                if quantity == 0 || (quantity as usize) > MAX_QTY {
384                    return build_exception_resp_fixed::<5>(
385                        out_exc,
386                        unit_id,
387                        func | 0x80,
388                        exc::ILLEGAL_DATA_VALUE,
389                    );
390                }
391                // FC01 Read Coils
392                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
393                if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end) {
394                    return build_exception_resp_fixed::<5>(
395                        out_exc,
396                        unit_id,
397                        func | 0x80,
398                        exc::ILLEGAL_DATA_ADDRESS,
399                    );
400                }
401                build_resp_bit_reads::<MAX_QTY, _>(
402                    out_tx, unit_id, 0x01, start_addr, quantity, self.coils,
403                )
404            }
405
406            0x02 => {
407                // quantity 校验
408                if quantity == 0 || (quantity as usize) > MAX_QTY {
409                    return build_exception_resp_fixed::<5>(
410                        out_exc,
411                        unit_id,
412                        func | 0x80,
413                        exc::ILLEGAL_DATA_VALUE,
414                    );
415                }
416                // FC02 Read Discrete Inputs
417                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
418                if !self.ists.is_valid(start_addr) || !self.ists.is_valid(end) {
419                    return build_exception_resp_fixed::<5>(
420                        out_exc,
421                        unit_id,
422                        func | 0x80,
423                        exc::ILLEGAL_DATA_ADDRESS,
424                    );
425                }
426                build_resp_bit_reads::<MAX_QTY, _>(
427                    out_tx, unit_id, 0x02, start_addr, quantity, self.ists,
428                )
429            }
430
431            0x03 => {
432                // quantity 校验
433                if quantity == 0 || (quantity as usize) > MAX_QTY {
434                    return build_exception_resp_fixed::<5>(
435                        out_exc,
436                        unit_id,
437                        func | 0x80,
438                        exc::ILLEGAL_DATA_VALUE,
439                    );
440                }
441                // FC03 Read Holding Registers
442                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
443                if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end) {
444                    return build_exception_resp_fixed::<5>(
445                        out_exc,
446                        unit_id,
447                        func | 0x80,
448                        exc::ILLEGAL_DATA_ADDRESS,
449                    );
450                }
451                build_resp_regs::<MAX_QTY, _>(
452                    out_tx,
453                    unit_id,
454                    0x03,
455                    start_addr,
456                    quantity,
457                    self.holdings,
458                )
459            }
460
461            0x04 => {
462                // quantity 校验
463                if quantity == 0 || (quantity as usize) > MAX_QTY {
464                    return build_exception_resp_fixed::<5>(
465                        out_exc,
466                        unit_id,
467                        func | 0x80,
468                        exc::ILLEGAL_DATA_VALUE,
469                    );
470                }
471                // FC04 Read Input Registers
472                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
473                if !self.inputs.is_valid(start_addr) || !self.inputs.is_valid(end) {
474                    return build_exception_resp_fixed::<5>(
475                        out_exc,
476                        unit_id,
477                        func | 0x80,
478                        exc::ILLEGAL_DATA_ADDRESS,
479                    );
480                }
481                build_resp_regs::<MAX_QTY, _>(
482                    out_tx,
483                    unit_id,
484                    0x04,
485                    start_addr,
486                    quantity,
487                    self.inputs,
488                )
489            }
490
491            0x05 => {
492                // FC05: addr=start_addr, value=quantity(0xFF00/0x0000)
493                let coil_value = quantity;
494
495                let bit = match coil_value {
496                    0xFF00 => true,
497                    0x0000 => false,
498                    _ => {
499                        return build_exception_resp_fixed::<5>(
500                            out_exc,
501                            unit_id,
502                            0x05 | 0x80,
503                            exc::ILLEGAL_DATA_VALUE,
504                        );
505                    }
506                };
507
508                if !self.coils.is_valid(start_addr) {
509                    return build_exception_resp_fixed::<5>(
510                        out_exc,
511                        unit_id,
512                        0x05 | 0x80,
513                        exc::ILLEGAL_DATA_ADDRESS,
514                    );
515                }
516
517                self.coils.set_bit(start_addr, bit);
518
519                // 正常响应:回显请求前6字节 + CRC
520                out_tx[0] = unit_id;
521                out_tx[1] = 0x05;
522                out_tx[2] = (start_addr >> 8) as u8;
523                out_tx[3] = (start_addr & 0xFF) as u8;
524                out_tx[4] = (coil_value >> 8) as u8;
525                out_tx[5] = (coil_value & 0xFF) as u8;
526
527                let crc = crc16_modbus(&out_tx[..6]);
528                out_tx[6] = (crc & 0xFF) as u8;
529                out_tx[7] = (crc >> 8) as u8;
530                8
531            }
532
533            0x06 => {
534                // FC06: 写单保持寄存器:addr=start_addr, value=quantity(任意 u16)
535                let reg_value = quantity;
536
537                if !self.holdings.is_valid(start_addr) {
538                    return build_exception_resp_fixed::<5>(
539                        out_exc,
540                        unit_id,
541                        0x06 | 0x80,
542                        exc::ILLEGAL_DATA_ADDRESS,
543                    );
544                }
545
546                self.holdings.set_reg(start_addr, reg_value);
547
548                // 正常响应:回显请求前6字节 + CRC
549                out_tx[0] = unit_id;
550                out_tx[1] = 0x06;
551                out_tx[2] = (start_addr >> 8) as u8;
552                out_tx[3] = (start_addr & 0xFF) as u8;
553                out_tx[4] = (reg_value >> 8) as u8;
554                out_tx[5] = (reg_value & 0xFF) as u8;
555
556                let crc = crc16_modbus(&out_tx[..6]);
557                out_tx[6] = (crc & 0xFF) as u8;
558                out_tx[7] = (crc >> 8) as u8;
559                8
560            }
561
562            _ => {
563                // 不支持功能码
564                build_exception_resp_fixed::<5>(
565                    out_exc,
566                    unit_id,
567                    func | 0x80,
568                    exc::ILLEGAL_FUNCTION,
569                )
570            }
571        }
572    }
573
574    /// pharse_frame:处理可变长度 Modbus RTU 请求帧(01/02/03/04/05/06 固定8,15/16 可变)
575    /// out_tx:成功响应写入缓冲
576    /// out_exc:异常响应固定 5 字节缓冲
577    pub fn pharse_frame<const MAX_QTY: usize>(
578        &mut self,
579        req: &[u8],
580        out_tx: &mut [u8],
581        out_exc: &mut [u8; 5],
582    ) -> usize {
583        // 最小长度至少要有 unit(1)+func(1)+CRC(2)=4,但这里按 Modbus RTU 解析从 6 开始也行
584        if req.len() < 8 {
585            return build_exception_resp_fixed::<5>(
586                out_exc,
587                req.get(0).copied().unwrap_or(0),
588                0x80, // func未知时用 0x80占位(也可改成0)
589                exc::ILLEGAL_DATA_VALUE,
590            );
591        }
592
593        let unit_id = req[0];
594        let func = req[1];
595
596        // CRC check:最后两个字节为 CRC
597        let expected_crc = u16::from_le_bytes([req[req.len() - 2], req[req.len() - 1]]);
598        let calc_crc = crc16_modbus(&req[..req.len() - 2]);
599        if expected_crc != calc_crc {
600            return build_exception_resp_fixed::<5>(
601                out_exc,
602                unit_id,
603                func | 0x80,
604                exc::ILLEGAL_DATA_VALUE,
605            );
606        }
607
608        // -------- 共用字段起点:start_addr 在偏移2..4,quantity 在偏移4..6 --------
609        let start_addr = u16::from_be_bytes([req[2], req[3]]);
610        let quantity = u16::from_be_bytes([req[4], req[5]]);
611
612        // -------- 处理各功能码 --------
613        match func {
614            0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 => {
615                if req.len() != 8 {
616                    return build_exception_resp_fixed::<5>(
617                        out_exc,
618                        unit_id,
619                        func | 0x80,
620                        exc::ILLEGAL_DATA_VALUE,
621                    );
622                }
623                let req8: [u8; 8] = req[..8].try_into().unwrap();
624                return self.pharse_pdu::<MAX_QTY>(&req8, out_tx, out_exc);
625            }
626
627            0x0F => {
628                // FC15:Write Multiple Coils
629                // 请求结构:Unit(1) Func(1) Start(2) Quantity(2) ByteCount(1) CoilsBytes(N) CRC(2)
630                // 长度:9 + ByteCount
631                if req.len() < 9 {
632                    return build_exception_resp_fixed::<5>(
633                        out_exc,
634                        unit_id,
635                        func | 0x80,
636                        exc::ILLEGAL_DATA_VALUE,
637                    );
638                }
639
640                let byte_count = req[6] as usize;
641                let expected_len = 9 + byte_count;
642                if req.len() != expected_len {
643                    return build_exception_resp_fixed::<5>(
644                        out_exc,
645                        unit_id,
646                        func | 0x80,
647                        exc::ILLEGAL_DATA_VALUE,
648                    );
649                }
650
651                // quantity 校验(01/02 的 MAX_QTY 你用同一个常量也可以)
652                if quantity == 0 || (quantity as usize) > MAX_QTY {
653                    return build_exception_resp_fixed::<5>(
654                        out_exc,
655                        unit_id,
656                        func | 0x80,
657                        exc::ILLEGAL_DATA_VALUE,
658                    );
659                }
660
661                // 地址范围校验:start..start+quantity-1
662                let end_addr = start_addr.wrapping_add(quantity.saturating_sub(1));
663                if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end_addr) {
664                    return build_exception_resp_fixed::<5>(
665                        out_exc,
666                        unit_id,
667                        func | 0x80,
668                        exc::ILLEGAL_DATA_ADDRESS,
669                    );
670                }
671
672                // ByteCount 必须等于 ceil(quantity/8)
673                let min_byte_cnt = (quantity as usize + 7) / 8;
674                if byte_count != min_byte_cnt {
675                    return build_exception_resp_fixed::<5>(
676                        out_exc,
677                        unit_id,
678                        func | 0x80,
679                        exc::ILLEGAL_DATA_VALUE,
680                    );
681                }
682
683                //set the qty for the coils
684                self.coils.set_qty(quantity);
685
686                let coils_bytes = &req[7..7 + byte_count];
687
688                // 写入线圈(LSB-first packing)
689                for j in 0..quantity as usize {
690                    let addr = start_addr.wrapping_add(j as u16);
691                    let byte_i = j / 8;
692                    let bit_i = j % 8;
693                    let bit = ((coils_bytes[byte_i] >> bit_i) & 0x01) != 0;
694                    self.coils.set_bit(addr, bit);
695                }
696
697                // 响应:echo Unit Func StartAddr Quantity CRC(固定8字节)
698                // out_tx 需要至少 8 字节
699                out_tx[0] = unit_id;
700                out_tx[1] = 0x0F;
701                out_tx[2] = (start_addr >> 8) as u8;
702                out_tx[3] = (start_addr & 0xFF) as u8;
703                out_tx[4] = (quantity >> 8) as u8;
704                out_tx[5] = (quantity & 0xFF) as u8;
705
706                let crc = crc16_modbus(&out_tx[..6]);
707                out_tx[6] = (crc & 0xFF) as u8;
708                out_tx[7] = (crc >> 8) as u8;
709                8
710            }
711
712            0x10 => {
713                // FC16:Write Multiple Registers
714                // 请求结构:Unit Func Start(2) Quantity(2) ByteCount(1) RegBytes(2*Quantity) CRC(2)
715                if req.len() < 9 {
716                    return build_exception_resp_fixed::<5>(
717                        out_exc,
718                        unit_id,
719                        func | 0x80,
720                        exc::ILLEGAL_DATA_VALUE,
721                    );
722                }
723
724                let byte_count = req[6] as usize;
725                let expected_len = 9 + byte_count;
726                if req.len() != expected_len {
727                    return build_exception_resp_fixed::<5>(
728                        out_exc,
729                        unit_id,
730                        func | 0x80,
731                        exc::ILLEGAL_DATA_VALUE,
732                    );
733                }
734
735                if quantity == 0 || (quantity as usize) > MAX_QTY {
736                    return build_exception_resp_fixed::<5>(
737                        out_exc,
738                        unit_id,
739                        func | 0x80,
740                        exc::ILLEGAL_DATA_VALUE,
741                    );
742                }
743
744                let end_addr = start_addr.wrapping_add(quantity.saturating_sub(1));
745                if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end_addr) {
746                    return build_exception_resp_fixed::<5>(
747                        out_exc,
748                        unit_id,
749                        func | 0x80,
750                        exc::ILLEGAL_DATA_ADDRESS,
751                    );
752                }
753
754                // ByteCount 必须等于 quantity*2
755                if byte_count != (quantity as usize) * 2 {
756                    return build_exception_resp_fixed::<5>(
757                        out_exc,
758                        unit_id,
759                        func | 0x80,
760                        exc::ILLEGAL_DATA_VALUE,
761                    );
762                }
763
764                //写入qty数据,方便后面调用
765                self.holdings.set_qty(quantity);
766
767                let reg_bytes = &req[7..7 + byte_count];
768
769                //写入保存寄存器
770                for j in 0..quantity as usize {
771                    let addr = start_addr.wrapping_add(j as u16);
772                    let base = j * 2;
773                    let v = u16::from_be_bytes([reg_bytes[base], reg_bytes[base + 1]]);
774                    self.holdings.set_reg(addr, v);
775                }
776
777                // 响应:echo Unit Func StartAddr Quantity CRC(固定8字节)
778                out_tx[0] = unit_id;
779                out_tx[1] = 0x10;
780                out_tx[2] = (start_addr >> 8) as u8;
781                out_tx[3] = (start_addr & 0xFF) as u8;
782                out_tx[4] = (quantity >> 8) as u8;
783                out_tx[5] = (quantity & 0xFF) as u8;
784
785                let crc = crc16_modbus(&out_tx[..6]);
786                out_tx[6] = (crc & 0xFF) as u8;
787                out_tx[7] = (crc >> 8) as u8;
788                8
789            }
790
791            _ => build_exception_resp_fixed::<5>(
792                out_exc,
793                unit_id,
794                func | 0x80,
795                exc::ILLEGAL_FUNCTION,
796            ),
797        }
798    }
799}
800
801// ---------------- helper:异常帧(固定 5 字节) ----------------
802fn build_exception_resp_fixed<const BUF: usize>(
803    out_exc: &mut [u8; 5],
804    unit_id: u8,
805    function_exception: u8,
806    exception_code: u8,
807) -> usize {
808    out_exc[0] = unit_id;
809    out_exc[1] = function_exception;
810    out_exc[2] = exception_code;
811
812    let crc = crc16_modbus(&out_exc[..3]);
813    out_exc[3] = (crc & 0xFF) as u8;
814    out_exc[4] = (crc >> 8) as u8;
815    5
816}
817// ---------------- helper:reg 读取响应(FC03/FC04) ----------------
818// out_tx 需要至少 >= 3 + MAX_QTY*2 + 2
819fn build_resp_regs<const MAX_QTY: usize, R: RegisterRead>(
820    out_tx: &mut [u8],
821    unit_id: u8,
822    func: u8,
823    start_addr: u16,
824    quantity: u16,
825    regs: &R,
826) -> usize {
827    let qty = quantity as usize;
828
829    out_tx[0] = unit_id;
830    out_tx[1] = func;
831    out_tx[2] = (qty as u8) * 2;
832
833    for i in 0..qty {
834        let addr = start_addr.wrapping_add(i as u16);
835        let v = regs.get(addr);
836        let base = 3 + i * 2;
837        out_tx[base] = (v >> 8) as u8;
838        out_tx[base + 1] = (v & 0xFF) as u8;
839    }
840
841    let body_len = 3 + qty * 2;
842    let crc = crc16_modbus(&out_tx[..body_len]);
843    out_tx[body_len] = (crc & 0xFF) as u8;
844    out_tx[body_len + 1] = (crc >> 8) as u8;
845
846    body_len + 2
847}
848
849/// FrameLen4Func,获取func需要用到的数据长度
850/// get the len for func_code
851#[inline]
852pub fn FrameLen4Func(func: u8, buf: &[u8], cur_len: usize) -> Option<usize> {
853    // 固定 8 字节功能码
854    match func {
855        0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 => return Some(8),
856
857        // FC15/FC16:长度 = 9 + ByteCount
858        0x0F | 0x10 => {
859            if cur_len < 7 {
860                return None; // 还没拿到 ByteCount
861            }
862            let byte_count = buf[6] as usize;
863            return Some(9 + byte_count);
864        }
865        _ => return None,
866    }
867}