1use std::fmt;
4use std::time::{Duration, Instant};
5use std::vec::Vec;
6
7use pamoja_hal::port::{PortError, PortKind, SerialPort, Settings};
8
9use crate::adu::Adu;
10use crate::error::ModbusError;
11use crate::function::Exception;
12use crate::pdu::Pdu;
13use crate::request::Request;
14use crate::server::BROADCAST;
15
16pub const RESPONSE_TIMEOUT: Duration = Duration::from_secs(1);
19
20pub const TURNAROUND: Duration = Duration::from_millis(100);
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub enum ClientError {
28 Request(ModbusError),
30 BroadcastRead,
32 Port(PortError),
34 Timeout {
36 unit: u8,
38 received: usize,
40 },
41 Frame(ModbusError),
43 WrongUnit {
45 expected: u8,
47 found: u8,
49 },
50 WrongFunction {
52 expected: u8,
54 found: u8,
56 },
57 Mismatch {
60 unit: u8,
62 },
63 Exception {
65 unit: u8,
67 function: u8,
69 exception: Exception,
71 },
72}
73
74impl fmt::Display for ClientError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 ClientError::Request(error) => write!(f, "the request cannot be sent: {error}"),
78 ClientError::BroadcastRead => {
79 f.write_str("a read cannot be broadcast, since no device answers a broadcast")
80 }
81 ClientError::Port(error) => write!(f, "the serial port failed: {error}"),
82 ClientError::Timeout { unit, received: 0 } => {
83 write!(f, "unit {unit} did not answer within the response timeout")
84 }
85 ClientError::Timeout { unit, received } => write!(
86 f,
87 "unit {unit} sent {received} bytes of a reply and then went quiet"
88 ),
89 ClientError::Frame(error) => write!(f, "the reply is not a valid frame: {error}"),
90 ClientError::WrongUnit { expected, found } => {
91 write!(f, "unit {found} answered a request for unit {expected}")
92 }
93 ClientError::WrongFunction { expected, found } => write!(
94 f,
95 "the reply names function {found:#04x}, not the {expected:#04x} asked for"
96 ),
97 ClientError::Mismatch { unit } => {
98 write!(
99 f,
100 "unit {unit} answered with values the request did not ask for"
101 )
102 }
103 ClientError::Exception {
104 unit,
105 function,
106 exception,
107 } => write!(
108 f,
109 "unit {unit} refused function {function:#04x} with exception {:#04x}, {exception}",
110 exception.code()
111 ),
112 }
113 }
114}
115
116impl std::error::Error for ClientError {
117 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118 match self {
119 ClientError::Request(error) | ClientError::Frame(error) => Some(error),
120 ClientError::Port(error) => Some(error),
121 _ => None,
122 }
123 }
124}
125
126#[derive(Debug)]
164pub struct Client {
165 port: SerialPort,
166 response_timeout: Duration,
167 turnaround: Duration,
168}
169
170enum Expect {
171 Bits { quantity: u16 },
172 Registers { quantity: u16 },
173 Echo,
174}
175
176impl Client {
177 pub fn new(port: SerialPort) -> Client {
187 Client {
188 port,
189 response_timeout: RESPONSE_TIMEOUT,
190 turnaround: TURNAROUND,
191 }
192 }
193
194 #[must_use]
204 pub fn with_response_timeout(mut self, timeout: Duration) -> Client {
205 self.response_timeout = timeout;
206 self
207 }
208
209 #[must_use]
220 pub fn with_turnaround(mut self, delay: Duration) -> Client {
221 self.turnaround = delay;
222 self
223 }
224
225 pub fn set_response_timeout(&mut self, timeout: Duration) {
232 self.response_timeout = timeout;
233 }
234
235 pub fn set_turnaround(&mut self, delay: Duration) {
242 self.turnaround = delay;
243 }
244
245 pub fn port(&self) -> &SerialPort {
247 &self.port
248 }
249
250 pub fn response_timeout(&self) -> Duration {
252 self.response_timeout
253 }
254
255 pub fn turnaround(&self) -> Duration {
257 self.turnaround
258 }
259
260 pub fn frame_gap(settings: Settings) -> Duration {
271 if settings.baud > 19_200 {
272 Duration::from_micros(1_750)
273 } else {
274 Duration::from_nanos((settings.character_nanos() * 7).div_ceil(2))
275 }
276 }
277
278 pub fn read_coils(
295 &mut self,
296 unit: u8,
297 start: u16,
298 quantity: u16,
299 ) -> Result<Vec<bool>, ClientError> {
300 self.read_bits(unit, Pdu::read_coils(start, quantity), quantity)
301 }
302
303 pub fn read_discrete_inputs(
319 &mut self,
320 unit: u8,
321 start: u16,
322 quantity: u16,
323 ) -> Result<Vec<bool>, ClientError> {
324 self.read_bits(unit, Pdu::read_discrete_inputs(start, quantity), quantity)
325 }
326
327 pub fn read_holding_registers(
343 &mut self,
344 unit: u8,
345 start: u16,
346 quantity: u16,
347 ) -> Result<Vec<u16>, ClientError> {
348 let pdu = Pdu::read_holding_registers(start, quantity);
349 self.read_words(unit, pdu, quantity)
350 }
351
352 pub fn read_input_registers(
368 &mut self,
369 unit: u8,
370 start: u16,
371 quantity: u16,
372 ) -> Result<Vec<u16>, ClientError> {
373 let pdu = Pdu::read_input_registers(start, quantity);
374 self.read_words(unit, pdu, quantity)
375 }
376
377 pub fn write_single_coil(
389 &mut self,
390 unit: u8,
391 address: u16,
392 on: bool,
393 ) -> Result<(), ClientError> {
394 let pdu = Pdu::write_single_coil(address, on);
395 self.write(unit, pdu, pdu)
396 }
397
398 pub fn write_single_register(
410 &mut self,
411 unit: u8,
412 address: u16,
413 value: u16,
414 ) -> Result<(), ClientError> {
415 let pdu = Pdu::write_single_register(address, value);
416 self.write(unit, pdu, pdu)
417 }
418
419 pub fn write_multiple_coils(
431 &mut self,
432 unit: u8,
433 start: u16,
434 values: &[bool],
435 ) -> Result<(), ClientError> {
436 let pdu = Pdu::write_multiple_coils(start, values).map_err(ClientError::Request)?;
437 let echo = Pdu::write_multiple_coils_reply(start, values.len() as u16);
438 self.write(unit, pdu, echo)
439 }
440
441 pub fn write_multiple_registers(
453 &mut self,
454 unit: u8,
455 start: u16,
456 values: &[u16],
457 ) -> Result<(), ClientError> {
458 let pdu = Pdu::write_multiple_registers(start, values).map_err(ClientError::Request)?;
459 let echo = Pdu::write_multiple_registers_reply(start, values.len() as u16);
460 self.write(unit, pdu, echo)
461 }
462
463 fn read_bits(&mut self, unit: u8, pdu: Pdu, quantity: u16) -> Result<Vec<bool>, ClientError> {
464 let reply = self
465 .transact(unit, pdu, Expect::Bits { quantity })?
466 .ok_or(ClientError::BroadcastRead)?;
467 let bits = reply
468 .response()
469 .coils(quantity)
470 .map_err(|_| ClientError::Mismatch { unit })?;
471 Ok(bits.collect())
472 }
473
474 fn read_words(&mut self, unit: u8, pdu: Pdu, quantity: u16) -> Result<Vec<u16>, ClientError> {
475 let reply = self
476 .transact(unit, pdu, Expect::Registers { quantity })?
477 .ok_or(ClientError::BroadcastRead)?;
478 let words = reply
479 .response()
480 .registers()
481 .map_err(|_| ClientError::Frame(ModbusError::MalformedResponse))?;
482 if words.len() != usize::from(quantity) {
483 return Err(ClientError::Mismatch { unit });
484 }
485 Ok(words.collect())
486 }
487
488 fn write(&mut self, unit: u8, pdu: Pdu, echo: Pdu) -> Result<(), ClientError> {
489 match self.transact(unit, pdu, Expect::Echo)? {
490 Some(reply) if reply.pdu() != echo.as_bytes() => Err(ClientError::Mismatch { unit }),
491 _ => Ok(()),
492 }
493 }
494
495 fn transact(&mut self, unit: u8, pdu: Pdu, expect: Expect) -> Result<Option<Adu>, ClientError> {
498 let request = Request::parse(pdu.as_bytes())
499 .map_err(|_| ClientError::Request(ModbusError::InvalidValueCount))?;
500 if unit == BROADCAST && request.reads() {
501 return Err(ClientError::BroadcastRead);
502 }
503 let frame = pdu.to_adu(unit);
504 self.port.wait(Client::frame_gap(self.port.settings()));
505 self.port.discard_input().map_err(ClientError::Port)?;
506 self.port
507 .write(frame.as_bytes())
508 .map_err(ClientError::Port)?;
509 if unit == BROADCAST {
510 self.port.wait(self.turnaround);
511 return Ok(None);
512 }
513 let reply = self.receive(unit, &expect)?;
514 let adu = Adu::parse(&reply).map_err(ClientError::Frame)?;
515 if adu.address() != unit {
516 return Err(ClientError::WrongUnit {
517 expected: unit,
518 found: adu.address(),
519 });
520 }
521 let function = pdu.function_code();
522 let found = adu.function_code();
523 if found & 0x7F != function {
524 return Err(ClientError::WrongFunction {
525 expected: function,
526 found,
527 });
528 }
529 if found & 0x80 != 0 {
530 let exception = adu
531 .exception()
532 .ok_or(ClientError::Frame(ModbusError::MalformedResponse))?;
533 return Err(ClientError::Exception {
534 unit,
535 function,
536 exception,
537 });
538 }
539 Ok(Some(adu))
540 }
541
542 fn receive(&mut self, unit: u8, expect: &Expect) -> Result<Vec<u8>, ClientError> {
545 let clock = self.port.kind() == PortKind::Device;
546 let started = Instant::now();
547 let mut frame = [0u8; Adu::MAX_LEN];
548 let mut want = match expect {
549 Expect::Bits { quantity, .. } => 5 + usize::from(*quantity).div_ceil(8),
550 Expect::Registers { quantity } => 5 + 2 * usize::from(*quantity),
551 Expect::Echo => 8,
552 };
553 let mut got = 0;
554 while got < want {
555 let timeout = if clock {
556 self.response_timeout.saturating_sub(started.elapsed())
557 } else {
558 self.response_timeout
559 };
560 let count = self
561 .port
562 .read(&mut frame[got..want], timeout)
563 .map_err(ClientError::Port)?;
564 if count == 0 {
565 return Err(ClientError::Timeout {
566 unit,
567 received: got,
568 });
569 }
570 got += count;
571 if got >= 2 && frame[1] & 0x80 != 0 {
572 want = 5;
573 } else if got >= 3 && !matches!(expect, Expect::Echo) {
574 want = (5 + usize::from(frame[2])).min(Adu::MAX_LEN);
575 }
576 }
577 Ok(frame[..want].to_vec())
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use std::sync::{Arc, Mutex};
585
586 use pamoja_hal::port::{Parity, PortStep, StopBits};
587
588 use crate::server::{Line, Server};
589
590 fn line() -> Settings {
591 Settings::new(19_200).with_parity(Parity::Even)
592 }
593
594 fn meter() -> Server {
595 Server::new(17)
596 .unwrap()
597 .with_holding_registers(107, &[2301, 418, 0])
598 .with_coils(0, &[false; 4])
599 }
600
601 #[test]
602 fn the_frame_gap_is_three_and_a_half_characters_up_to_19200_and_fixed_above() {
603 let at = |baud| Client::frame_gap(Settings::new(baud).with_parity(Parity::Even));
604 assert_eq!(at(9_600), Duration::from_nanos(4_010_419));
606 assert_eq!(at(19_200), Duration::from_nanos(2_005_210));
607 for baud in [38_400, 57_600, 115_200] {
608 assert_eq!(at(baud), Duration::from_micros(1_750));
609 }
610 let two = Settings::new(9_600).with_stop_bits(StopBits::Two);
612 assert_eq!(Client::frame_gap(two), at(9_600));
613 }
614
615 #[test]
616 fn a_read_puts_the_specification_frame_on_the_wire_and_decodes_the_reply() {
617 let reply = Pdu::read_holding_registers_reply(&[0x022B, 0x0000, 0x0064])
618 .unwrap()
619 .to_adu(0x11);
620 let port = SerialPort::scripted(
621 line(),
622 [
623 PortStep::write([0x11, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x76, 0x87]),
624 PortStep::read(reply.as_bytes()),
625 ],
626 );
627 let mut client = Client::new(port.clone());
628 let values = client.read_holding_registers(0x11, 0x006B, 3).unwrap();
629 assert_eq!(values, [0x022B, 0x0000, 0x0064]);
630 assert_eq!(port.remaining(), Some(0));
631 assert_eq!(port.waited_micros(), 2_005, "only the frame gap was waited");
632 }
633
634 #[test]
635 fn every_function_round_trips_against_a_server() {
636 let meter = Arc::new(Mutex::new(
637 meter().with_discrete_inputs(0x00C4, &[true, false, true]),
638 ));
639 let mut client = Client::new(SerialPort::simulated(line(), Arc::clone(&meter)));
640 assert_eq!(
641 client.read_holding_registers(17, 107, 3).unwrap(),
642 [2301, 418, 0]
643 );
644 client.write_single_register(17, 109, 7).unwrap();
645 client
646 .write_multiple_registers(17, 107, &[2300, 420])
647 .unwrap();
648 assert_eq!(
649 client.read_holding_registers(17, 107, 3).unwrap(),
650 [2300, 420, 7]
651 );
652 client.write_single_coil(17, 1, true).unwrap();
653 client.write_multiple_coils(17, 2, &[true, true]).unwrap();
654 assert_eq!(
655 client.read_coils(17, 0, 4).unwrap(),
656 [false, true, true, true]
657 );
658 assert_eq!(
659 client.read_discrete_inputs(17, 0x00C4, 3).unwrap(),
660 [true, false, true]
661 );
662 let device = meter.lock().unwrap();
663 assert_eq!(device.holding_register(109), Some(7));
664 assert_eq!(device.served(), 8);
665 }
666
667 #[test]
668 fn a_refusal_comes_back_as_the_devices_exception() {
669 let mut client = Client::new(SerialPort::simulated(line(), meter()));
670 assert_eq!(
671 client.read_holding_registers(17, 108, 3),
672 Err(ClientError::Exception {
673 unit: 17,
674 function: 0x03,
675 exception: Exception::IllegalDataAddress
676 })
677 );
678 assert_eq!(
679 client.write_single_coil(17, 9, true),
680 Err(ClientError::Exception {
681 unit: 17,
682 function: 0x05,
683 exception: Exception::IllegalDataAddress
684 })
685 );
686 }
687
688 #[test]
689 fn a_unit_that_never_answers_times_out_after_the_response_timeout() {
690 let port = SerialPort::simulated(line(), meter());
691 let mut client =
692 Client::new(port.clone()).with_response_timeout(Duration::from_millis(250));
693 assert_eq!(
694 client.read_holding_registers(18, 107, 1),
695 Err(ClientError::Timeout {
696 unit: 18,
697 received: 0
698 })
699 );
700 assert_eq!(port.waited_micros(), 2_005 + 250_000);
701 }
702
703 #[test]
704 fn a_broadcast_write_waits_out_the_turnaround_and_every_device_carries_it_out() {
705 let mut line_of = Line::new();
706 let first = line_of.attach(meter());
707 let second = line_of.attach(Server::new(18).unwrap().with_holding_registers(109, &[0]));
708 let port = SerialPort::simulated(line(), line_of);
709 let mut client = Client::new(port.clone());
710 client.write_single_register(BROADCAST, 109, 1).unwrap();
711 client
712 .write_multiple_registers(BROADCAST, 109, &[1])
713 .unwrap();
714 assert_eq!(first.lock().unwrap().holding_register(109), Some(1));
715 assert_eq!(second.lock().unwrap().holding_register(109), Some(1));
716 assert_eq!(port.received(), 0, "nobody answers a broadcast");
717 assert_eq!(port.waited_micros(), 2 * (2_005 + 100_000));
718
719 assert_eq!(
720 client.read_holding_registers(BROADCAST, 109, 1),
721 Err(ClientError::BroadcastRead)
722 );
723 assert_eq!(port.written(), 8 + 11, "the broadcast read never went out");
724 }
725
726 #[test]
727 fn a_reply_that_fails_its_checks_is_refused_before_a_value_is_read() {
728 let request = Pdu::read_holding_registers(107, 1).to_adu(17);
729 let good = Pdu::read_holding_registers_reply(&[2301]).unwrap();
730
731 let mut corrupt = good.to_adu(17).as_bytes().to_vec();
732 corrupt[3] ^= 0x01;
733 let scripted = |reply: Vec<u8>| {
734 SerialPort::scripted(
735 line(),
736 [PortStep::write(request.as_bytes()), PortStep::read(reply)],
737 )
738 };
739 let mut client = Client::new(scripted(corrupt));
740 assert!(matches!(
741 client.read_holding_registers(17, 107, 1),
742 Err(ClientError::Frame(ModbusError::CrcMismatch { .. }))
743 ));
744
745 let mut client = Client::new(scripted(good.to_adu(18).as_bytes().to_vec()));
746 assert_eq!(
747 client.read_holding_registers(17, 107, 1),
748 Err(ClientError::WrongUnit {
749 expected: 17,
750 found: 18
751 })
752 );
753
754 let other = Pdu::read_input_registers_reply(&[2301]).unwrap().to_adu(17);
755 let mut client = Client::new(scripted(other.as_bytes().to_vec()));
756 assert_eq!(
757 client.read_holding_registers(17, 107, 1),
758 Err(ClientError::WrongFunction {
759 expected: 0x03,
760 found: 0x04
761 })
762 );
763
764 let two = Pdu::read_holding_registers_reply(&[2301, 418])
765 .unwrap()
766 .to_adu(17);
767 let mut client = Client::new(scripted(two.as_bytes().to_vec()));
768 assert_eq!(
769 client.read_holding_registers(17, 107, 1),
770 Err(ClientError::Mismatch { unit: 17 })
771 );
772
773 let half = good.to_adu(17).as_bytes()[..4].to_vec();
774 let mut client = Client::new(scripted(half));
775 assert_eq!(
776 client.read_holding_registers(17, 107, 1),
777 Err(ClientError::Timeout {
778 unit: 17,
779 received: 4
780 })
781 );
782 }
783
784 #[test]
785 fn a_request_the_protocol_cannot_carry_never_reaches_the_line() {
786 let port = SerialPort::simulated(line(), meter());
787 let mut client = Client::new(port.clone());
788 assert_eq!(
789 client.read_holding_registers(17, 0, 126),
790 Err(ClientError::Request(ModbusError::InvalidValueCount))
791 );
792 assert_eq!(
793 client.write_multiple_registers(17, 0, &[0; 124]),
794 Err(ClientError::Request(ModbusError::InvalidValueCount))
795 );
796 assert_eq!(port.written(), 0);
797 }
798
799 #[test]
800 fn a_reserved_unit_is_still_asked_since_some_devices_answer_on_one() {
801 let request = Pdu::read_input_registers(0, 1).to_adu(248);
802 let reply = Pdu::read_input_registers_reply(&[2301])
803 .unwrap()
804 .to_adu(248);
805 let port = SerialPort::scripted(
806 line(),
807 [
808 PortStep::write(request.as_bytes()),
809 PortStep::read(reply.as_bytes()),
810 ],
811 );
812 let mut client = Client::new(port);
813 assert_eq!(client.read_input_registers(248, 0, 1), Ok(vec![2301]));
814 }
815
816 #[test]
817 fn a_stale_reply_is_dropped_before_the_next_request() {
818 let (gateway, device) = SerialPort::pair(line());
819 device
820 .write(
821 Pdu::read_holding_registers_reply(&[1])
822 .unwrap()
823 .to_adu(17)
824 .as_bytes(),
825 )
826 .unwrap();
827 let mut client = Client::new(gateway).with_response_timeout(Duration::from_millis(5));
828 assert_eq!(
829 client.read_holding_registers(17, 107, 1),
830 Err(ClientError::Timeout {
831 unit: 17,
832 received: 0
833 })
834 );
835 }
836}