1use crate::error::{ModbusError, ModbusResult};
15
16pub const MAX_READ_COILS: u16 = 2000;
19pub const MAX_READ_DISCRETE_INPUTS: u16 = 2000;
21pub const MAX_WRITE_COILS: u16 = 1968;
23pub const MAX_WRITE_REGISTERS: u16 = 123;
25
26pub fn pack_bits(bits: &[bool]) -> Vec<u8> {
30 let byte_count = (bits.len() + 7) / 8;
31 let mut bytes = vec![0u8; byte_count];
32 for (i, &bit) in bits.iter().enumerate() {
33 if bit {
34 bytes[i / 8] |= 1 << (i % 8);
35 }
36 }
37 bytes
38}
39
40pub fn unpack_bits(bytes: &[u8], count: usize) -> Vec<bool> {
42 let mut bits = Vec::with_capacity(count);
43 for i in 0..count {
44 let byte_idx = i / 8;
45 let bit_idx = i % 8;
46 let value = if byte_idx < bytes.len() {
47 (bytes[byte_idx] >> bit_idx) & 1 == 1
48 } else {
49 false
50 };
51 bits.push(value);
52 }
53 bits
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct ReadCoilsRequest {
61 pub start_address: u16,
63 pub quantity: u16,
65}
66
67impl ReadCoilsRequest {
68 pub fn new(start_address: u16, quantity: u16) -> ModbusResult<Self> {
70 if quantity == 0 || quantity > MAX_READ_COILS {
71 return Err(ModbusError::InvalidCount(quantity));
72 }
73 Ok(Self {
74 start_address,
75 quantity,
76 })
77 }
78
79 pub fn encode(&self) -> Vec<u8> {
81 let mut buf = Vec::with_capacity(5);
82 buf.push(0x01);
83 buf.extend_from_slice(&self.start_address.to_be_bytes());
84 buf.extend_from_slice(&self.quantity.to_be_bytes());
85 buf
86 }
87
88 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
90 if data.len() < 4 {
91 return Err(ModbusError::Io(std::io::Error::new(
92 std::io::ErrorKind::UnexpectedEof,
93 "ReadCoilsRequest: expected 4 data bytes",
94 )));
95 }
96 let start_address = u16::from_be_bytes([data[0], data[1]]);
97 let quantity = u16::from_be_bytes([data[2], data[3]]);
98 Self::new(start_address, quantity)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct ReadCoilsResponse {
105 pub coil_status: Vec<bool>,
107}
108
109impl ReadCoilsResponse {
110 pub fn new(coil_status: Vec<bool>) -> Self {
112 Self { coil_status }
113 }
114
115 pub fn encode(&self) -> Vec<u8> {
117 let packed = pack_bits(&self.coil_status);
118 let mut buf = Vec::with_capacity(2 + packed.len());
119 buf.push(0x01);
120 buf.push(packed.len() as u8);
121 buf.extend_from_slice(&packed);
122 buf
123 }
124
125 pub fn decode(data: &[u8], quantity: usize) -> ModbusResult<Self> {
127 if data.is_empty() {
128 return Err(ModbusError::Io(std::io::Error::new(
129 std::io::ErrorKind::UnexpectedEof,
130 "ReadCoilsResponse: missing byte count",
131 )));
132 }
133 let byte_count = data[0] as usize;
134 if data.len() < 1 + byte_count {
135 return Err(ModbusError::Io(std::io::Error::new(
136 std::io::ErrorKind::UnexpectedEof,
137 "ReadCoilsResponse: incomplete packed bytes",
138 )));
139 }
140 let packed = &data[1..1 + byte_count];
141 let coil_status = unpack_bits(packed, quantity);
142 Ok(Self { coil_status })
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct ReadDiscreteInputsRequest {
151 pub start_address: u16,
153 pub quantity: u16,
155}
156
157impl ReadDiscreteInputsRequest {
158 pub fn new(start_address: u16, quantity: u16) -> ModbusResult<Self> {
160 if quantity == 0 || quantity > MAX_READ_DISCRETE_INPUTS {
161 return Err(ModbusError::InvalidCount(quantity));
162 }
163 Ok(Self {
164 start_address,
165 quantity,
166 })
167 }
168
169 pub fn encode(&self) -> Vec<u8> {
171 let mut buf = Vec::with_capacity(5);
172 buf.push(0x02);
173 buf.extend_from_slice(&self.start_address.to_be_bytes());
174 buf.extend_from_slice(&self.quantity.to_be_bytes());
175 buf
176 }
177
178 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
180 if data.len() < 4 {
181 return Err(ModbusError::Io(std::io::Error::new(
182 std::io::ErrorKind::UnexpectedEof,
183 "ReadDiscreteInputsRequest: expected 4 data bytes",
184 )));
185 }
186 let start_address = u16::from_be_bytes([data[0], data[1]]);
187 let quantity = u16::from_be_bytes([data[2], data[3]]);
188 Self::new(start_address, quantity)
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct ReadDiscreteInputsResponse {
195 pub input_status: Vec<bool>,
197}
198
199impl ReadDiscreteInputsResponse {
200 pub fn new(input_status: Vec<bool>) -> Self {
202 Self { input_status }
203 }
204
205 pub fn encode(&self) -> Vec<u8> {
207 let packed = pack_bits(&self.input_status);
208 let mut buf = Vec::with_capacity(2 + packed.len());
209 buf.push(0x02);
210 buf.push(packed.len() as u8);
211 buf.extend_from_slice(&packed);
212 buf
213 }
214
215 pub fn decode(data: &[u8], quantity: usize) -> ModbusResult<Self> {
217 if data.is_empty() {
218 return Err(ModbusError::Io(std::io::Error::new(
219 std::io::ErrorKind::UnexpectedEof,
220 "ReadDiscreteInputsResponse: missing byte count",
221 )));
222 }
223 let byte_count = data[0] as usize;
224 if data.len() < 1 + byte_count {
225 return Err(ModbusError::Io(std::io::Error::new(
226 std::io::ErrorKind::UnexpectedEof,
227 "ReadDiscreteInputsResponse: incomplete packed bytes",
228 )));
229 }
230 let packed = &data[1..1 + byte_count];
231 let input_status = unpack_bits(packed, quantity);
232 Ok(Self { input_status })
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct WriteMultipleCoilsRequest {
241 pub start_address: u16,
243 pub outputs: Vec<bool>,
245}
246
247impl WriteMultipleCoilsRequest {
248 pub fn new(start_address: u16, outputs: Vec<bool>) -> ModbusResult<Self> {
250 let qty = outputs.len() as u16;
251 if qty == 0 || qty > MAX_WRITE_COILS {
252 return Err(ModbusError::InvalidCount(qty));
253 }
254 Ok(Self {
255 start_address,
256 outputs,
257 })
258 }
259
260 pub fn quantity(&self) -> u16 {
262 self.outputs.len() as u16
263 }
264
265 pub fn encode(&self) -> Vec<u8> {
267 let packed = pack_bits(&self.outputs);
268 let qty = self.outputs.len() as u16;
269 let mut buf = Vec::with_capacity(6 + packed.len());
270 buf.push(0x0F);
271 buf.extend_from_slice(&self.start_address.to_be_bytes());
272 buf.extend_from_slice(&qty.to_be_bytes());
273 buf.push(packed.len() as u8);
274 buf.extend_from_slice(&packed);
275 buf
276 }
277
278 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
280 if data.len() < 5 {
281 return Err(ModbusError::Io(std::io::Error::new(
282 std::io::ErrorKind::UnexpectedEof,
283 "WriteMultipleCoilsRequest: too short",
284 )));
285 }
286 let start_address = u16::from_be_bytes([data[0], data[1]]);
287 let quantity = u16::from_be_bytes([data[2], data[3]]);
288 let byte_count = data[4] as usize;
289 if data.len() < 5 + byte_count {
290 return Err(ModbusError::Io(std::io::Error::new(
291 std::io::ErrorKind::UnexpectedEof,
292 "WriteMultipleCoilsRequest: incomplete packed bytes",
293 )));
294 }
295 let packed = &data[5..5 + byte_count];
296 let outputs = unpack_bits(packed, quantity as usize);
297 Self::new(start_address, outputs)
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct WriteMultipleCoilsResponse {
304 pub start_address: u16,
306 pub quantity: u16,
308}
309
310impl WriteMultipleCoilsResponse {
311 pub fn new(start_address: u16, quantity: u16) -> Self {
313 Self {
314 start_address,
315 quantity,
316 }
317 }
318
319 pub fn encode(&self) -> Vec<u8> {
321 let mut buf = Vec::with_capacity(5);
322 buf.push(0x0F);
323 buf.extend_from_slice(&self.start_address.to_be_bytes());
324 buf.extend_from_slice(&self.quantity.to_be_bytes());
325 buf
326 }
327
328 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
330 if data.len() < 4 {
331 return Err(ModbusError::Io(std::io::Error::new(
332 std::io::ErrorKind::UnexpectedEof,
333 "WriteMultipleCoilsResponse: expected 4 data bytes",
334 )));
335 }
336 let start_address = u16::from_be_bytes([data[0], data[1]]);
337 let quantity = u16::from_be_bytes([data[2], data[3]]);
338 Ok(Self {
339 start_address,
340 quantity,
341 })
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct WriteMultipleRegistersRequest {
350 pub start_address: u16,
352 pub values: Vec<u16>,
354}
355
356impl WriteMultipleRegistersRequest {
357 pub fn new(start_address: u16, values: Vec<u16>) -> ModbusResult<Self> {
359 let qty = values.len() as u16;
360 if qty == 0 || qty > MAX_WRITE_REGISTERS {
361 return Err(ModbusError::InvalidCount(qty));
362 }
363 Ok(Self {
364 start_address,
365 values,
366 })
367 }
368
369 pub fn quantity(&self) -> u16 {
371 self.values.len() as u16
372 }
373
374 pub fn encode(&self) -> Vec<u8> {
376 let qty = self.values.len() as u16;
377 let byte_count = (qty * 2) as u8;
378 let mut buf = Vec::with_capacity(6 + self.values.len() * 2);
379 buf.push(0x10);
380 buf.extend_from_slice(&self.start_address.to_be_bytes());
381 buf.extend_from_slice(&qty.to_be_bytes());
382 buf.push(byte_count);
383 for &v in &self.values {
384 buf.extend_from_slice(&v.to_be_bytes());
385 }
386 buf
387 }
388
389 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
391 if data.len() < 5 {
392 return Err(ModbusError::Io(std::io::Error::new(
393 std::io::ErrorKind::UnexpectedEof,
394 "WriteMultipleRegistersRequest: too short",
395 )));
396 }
397 let start_address = u16::from_be_bytes([data[0], data[1]]);
398 let quantity = u16::from_be_bytes([data[2], data[3]]) as usize;
399 let byte_count = data[4] as usize;
400 if data.len() < 5 + byte_count {
401 return Err(ModbusError::Io(std::io::Error::new(
402 std::io::ErrorKind::UnexpectedEof,
403 "WriteMultipleRegistersRequest: incomplete register data",
404 )));
405 }
406 let mut values = Vec::with_capacity(quantity);
407 for i in 0..quantity {
408 let offset = 5 + i * 2;
409 if offset + 1 >= data.len() {
410 break;
411 }
412 values.push(u16::from_be_bytes([data[offset], data[offset + 1]]));
413 }
414 Self::new(start_address, values)
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, Eq)]
420pub struct WriteMultipleRegistersResponse {
421 pub start_address: u16,
423 pub quantity: u16,
425}
426
427impl WriteMultipleRegistersResponse {
428 pub fn new(start_address: u16, quantity: u16) -> Self {
430 Self {
431 start_address,
432 quantity,
433 }
434 }
435
436 pub fn encode(&self) -> Vec<u8> {
438 let mut buf = Vec::with_capacity(5);
439 buf.push(0x10);
440 buf.extend_from_slice(&self.start_address.to_be_bytes());
441 buf.extend_from_slice(&self.quantity.to_be_bytes());
442 buf
443 }
444
445 pub fn decode(data: &[u8]) -> ModbusResult<Self> {
447 if data.len() < 4 {
448 return Err(ModbusError::Io(std::io::Error::new(
449 std::io::ErrorKind::UnexpectedEof,
450 "WriteMultipleRegistersResponse: expected 4 data bytes",
451 )));
452 }
453 let start_address = u16::from_be_bytes([data[0], data[1]]);
454 let quantity = u16::from_be_bytes([data[2], data[3]]);
455 Ok(Self {
456 start_address,
457 quantity,
458 })
459 }
460}
461
462#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
470 fn test_pack_bits_empty() {
471 assert!(pack_bits(&[]).is_empty());
472 }
473
474 #[test]
475 fn test_pack_bits_single_byte() {
476 let bits = [true, false, true, false, true, false, true, false];
478 assert_eq!(pack_bits(&bits), vec![0x55]);
479 }
480
481 #[test]
482 fn test_pack_bits_partial_byte() {
483 let bits = [true, true, false];
485 assert_eq!(pack_bits(&bits), vec![0x03]);
486 }
487
488 #[test]
489 fn test_pack_unpack_roundtrip() {
490 let original: Vec<bool> = (0..13).map(|i| i % 3 == 0).collect();
491 let packed = pack_bits(&original);
492 let unpacked = unpack_bits(&packed, original.len());
493 assert_eq!(unpacked, original);
494 }
495
496 #[test]
499 fn test_read_coils_request_new_valid() {
500 let req = ReadCoilsRequest::new(100, 10).expect("valid");
501 assert_eq!(req.start_address, 100);
502 assert_eq!(req.quantity, 10);
503 }
504
505 #[test]
506 fn test_read_coils_request_zero_quantity_rejected() {
507 assert!(ReadCoilsRequest::new(0, 0).is_err());
508 }
509
510 #[test]
511 fn test_read_coils_request_max_quantity() {
512 assert!(ReadCoilsRequest::new(0, 2000).is_ok());
513 }
514
515 #[test]
516 fn test_read_coils_request_over_max_rejected() {
517 assert!(ReadCoilsRequest::new(0, 2001).is_err());
518 }
519
520 #[test]
521 fn test_read_coils_request_encode_decode() {
522 let req = ReadCoilsRequest::new(200, 16).expect("valid");
523 let encoded = req.encode();
524 assert_eq!(encoded.len(), 5);
526 assert_eq!(encoded[0], 0x01);
527 let decoded = ReadCoilsRequest::decode(&encoded[1..]).expect("decoded");
528 assert_eq!(decoded, req);
529 }
530
531 #[test]
532 fn test_read_coils_response_encode_decode() {
533 let status: Vec<bool> = vec![true, false, true, true, false, false, true, false, true];
534 let resp = ReadCoilsResponse::new(status.clone());
535 let encoded = resp.encode();
536 assert_eq!(encoded[0], 0x01);
538 let decoded = ReadCoilsResponse::decode(&encoded[1..], status.len()).expect("decoded");
539 assert_eq!(decoded.coil_status, status);
540 }
541
542 #[test]
543 fn test_read_coils_response_aligned_bits() {
544 let status = vec![true; 8];
546 let resp = ReadCoilsResponse::new(status.clone());
547 let encoded = resp.encode();
548 assert_eq!(encoded.len(), 3);
550 let decoded = ReadCoilsResponse::decode(&encoded[1..], 8).expect("ok");
551 assert_eq!(decoded.coil_status, status);
552 }
553
554 #[test]
557 fn test_read_discrete_inputs_request_valid() {
558 let req = ReadDiscreteInputsRequest::new(0, 1).expect("valid");
559 assert_eq!(req.quantity, 1);
560 }
561
562 #[test]
563 fn test_read_discrete_inputs_request_zero_rejected() {
564 assert!(ReadDiscreteInputsRequest::new(0, 0).is_err());
565 }
566
567 #[test]
568 fn test_read_discrete_inputs_request_max() {
569 assert!(ReadDiscreteInputsRequest::new(0, 2000).is_ok());
570 }
571
572 #[test]
573 fn test_read_discrete_inputs_request_over_max() {
574 assert!(ReadDiscreteInputsRequest::new(0, 2001).is_err());
575 }
576
577 #[test]
578 fn test_read_discrete_inputs_encode_decode() {
579 let req = ReadDiscreteInputsRequest::new(50, 5).expect("valid");
580 let encoded = req.encode();
581 assert_eq!(encoded[0], 0x02);
582 let decoded = ReadDiscreteInputsRequest::decode(&encoded[1..]).expect("decoded");
583 assert_eq!(decoded, req);
584 }
585
586 #[test]
587 fn test_read_discrete_inputs_response_encode_decode() {
588 let status = vec![false, true, false, true, true];
589 let resp = ReadDiscreteInputsResponse::new(status.clone());
590 let encoded = resp.encode();
591 assert_eq!(encoded[0], 0x02);
592 let decoded = ReadDiscreteInputsResponse::decode(&encoded[1..], status.len()).expect("ok");
593 assert_eq!(decoded.input_status, status);
594 }
595
596 #[test]
599 fn test_write_multiple_coils_request_valid() {
600 let outputs = vec![true, false, true];
601 let req = WriteMultipleCoilsRequest::new(10, outputs.clone()).expect("valid");
602 assert_eq!(req.start_address, 10);
603 assert_eq!(req.quantity(), 3);
604 }
605
606 #[test]
607 fn test_write_multiple_coils_request_zero_rejected() {
608 assert!(WriteMultipleCoilsRequest::new(0, vec![]).is_err());
609 }
610
611 #[test]
612 fn test_write_multiple_coils_request_over_max_rejected() {
613 let too_many = vec![false; 1969];
614 assert!(WriteMultipleCoilsRequest::new(0, too_many).is_err());
615 }
616
617 #[test]
618 fn test_write_multiple_coils_encode_decode() {
619 let outputs: Vec<bool> = (0..10).map(|i| i % 2 == 0).collect();
620 let req = WriteMultipleCoilsRequest::new(0, outputs.clone()).expect("valid");
621 let encoded = req.encode();
622 assert_eq!(encoded[0], 0x0F);
623 let decoded = WriteMultipleCoilsRequest::decode(&encoded[1..]).expect("decoded");
624 assert_eq!(decoded.outputs, outputs);
625 }
626
627 #[test]
628 fn test_write_multiple_coils_response_encode_decode() {
629 let resp = WriteMultipleCoilsResponse::new(20, 15);
630 let encoded = resp.encode();
631 assert_eq!(encoded[0], 0x0F);
632 let decoded = WriteMultipleCoilsResponse::decode(&encoded[1..]).expect("decoded");
633 assert_eq!(decoded, resp);
634 }
635
636 #[test]
637 fn test_write_multiple_coils_bit_packing() {
638 let outputs = vec![true; 8];
640 let req = WriteMultipleCoilsRequest::new(0, outputs).expect("valid");
641 let encoded = req.encode();
642 assert_eq!(encoded.len(), 7);
644 assert_eq!(encoded[6], 0xFF);
646 }
647
648 #[test]
651 fn test_write_multiple_registers_request_valid() {
652 let values = vec![100u16, 200, 300];
653 let req = WriteMultipleRegistersRequest::new(40001, values.clone()).expect("valid");
654 assert_eq!(req.start_address, 40001);
655 assert_eq!(req.quantity(), 3);
656 }
657
658 #[test]
659 fn test_write_multiple_registers_request_zero_rejected() {
660 assert!(WriteMultipleRegistersRequest::new(0, vec![]).is_err());
661 }
662
663 #[test]
664 fn test_write_multiple_registers_request_over_max_rejected() {
665 let too_many = vec![0u16; 124];
666 assert!(WriteMultipleRegistersRequest::new(0, too_many).is_err());
667 }
668
669 #[test]
670 fn test_write_multiple_registers_encode_decode() {
671 let values: Vec<u16> = (1..=5).collect();
672 let req = WriteMultipleRegistersRequest::new(100, values.clone()).expect("valid");
673 let encoded = req.encode();
674 assert_eq!(encoded[0], 0x10);
675 let decoded = WriteMultipleRegistersRequest::decode(&encoded[1..]).expect("decoded");
676 assert_eq!(decoded.values, values);
677 }
678
679 #[test]
680 fn test_write_multiple_registers_response_encode_decode() {
681 let resp = WriteMultipleRegistersResponse::new(100, 5);
682 let encoded = resp.encode();
683 assert_eq!(encoded[0], 0x10);
684 let decoded = WriteMultipleRegistersResponse::decode(&encoded[1..]).expect("decoded");
685 assert_eq!(decoded, resp);
686 }
687
688 #[test]
689 fn test_write_multiple_registers_encoding_length() {
690 let req = WriteMultipleRegistersRequest::new(0, vec![1, 2, 3]).expect("valid");
692 assert_eq!(req.encode().len(), 12);
693 }
694
695 #[test]
696 fn test_write_multiple_registers_max_count() {
697 let values = vec![0xFFFFu16; 123];
698 assert!(WriteMultipleRegistersRequest::new(0, values).is_ok());
699 }
700}