modbus/scoped.rs
1//! A set of objects which automatically change their register or coil value when they go out of scope
2//!
3//! # Examples
4//!
5//! When the `auto` object goes out of scope and is dropped, the value of coil `10` is switched `On`:
6//!
7//! ```
8//! # extern crate modbus;
9//! # extern crate test_server;
10//! # use test_server::start_dummy_server;
11//! # fn main() {
12//! use modbus::{Client, Coil};
13//! use modbus::tcp;
14//! use modbus::scoped::{ScopedCoil, CoilDropFunction};
15//! # if cfg!(feature = "modbus-server-tests") {
16//! # let (_s, port) = start_dummy_server(Some(22222));
17//!
18//! let mut cfg = tcp::Config::default();
19//! # cfg.tcp_port = port;
20//! let mut client = tcp::Transport::new_with_cfg("127.0.0.1", cfg).unwrap();
21//! {
22//! let mut auto = ScopedCoil::new(&mut client, 10, CoilDropFunction::On).unwrap();
23//! assert_eq!(auto.mut_transport().read_coils(10, 1).unwrap(), vec![Coil::Off]);
24//! }
25//! assert_eq!(client.read_coils(10, 1).unwrap(), vec![Coil::On]);
26//! # }
27//! # }
28//! ```
29//!
30//! When the `auto` object goes out of scope and is dropped, the value of register `10` is modified by
31//! function `fun`:
32//!
33//! ```
34//! # extern crate modbus;
35//! # extern crate test_server;
36//! # use test_server::start_dummy_server;
37//! # fn main() {
38//! use modbus::{Client, Coil};
39//! use modbus::tcp;
40//! use modbus::scoped::{ScopedRegister, RegisterDropFunction};
41//! # if cfg!(feature = "modbus-server-tests") {
42//! # let (_s, port) = start_dummy_server(Some(22223));
43//!
44//! let mut cfg = tcp::Config::default();
45//! # cfg.tcp_port = port;
46//! let mut client = tcp::Transport::new_with_cfg("127.0.0.1", cfg).unwrap();
47//! client.write_single_register(10, 1);
48//! {
49//! let fun = |v| v + 5;
50//! let mut auto = ScopedRegister::new(&mut client, 10, RegisterDropFunction::Fun(&fun)).unwrap();
51//! assert_eq!(auto.mut_transport().read_holding_registers(10, 1).unwrap(), vec![1]);
52//! }
53//! assert_eq!(client.read_holding_registers(10, 1).unwrap(), vec![6]);
54//! # }
55//! # }
56//! ```
57
58use crate::{Client, Coil, Result, Transport};
59
60/// Action to perform when the `ScopedCoil` is dropped.
61pub enum CoilDropFunction {
62 /// Set the coil to `Coil::On`
63 On,
64 /// Set the coil to `Coil::Off`
65 Off,
66 /// Toggle the current value.
67 Toggle,
68}
69
70/// Action to perform when the `ScopedRegister` is dropped.
71pub enum RegisterDropFunction<'a> {
72 /// Set the register to zero value
73 Zero,
74 /// Increment the current register value by 1
75 Increment,
76 /// Decrement the current register value by 1
77 Decrement,
78 /// Set the register value to the given value.
79 Value(u16),
80 /// Execute the given function on the current value, setting the register with the result value.
81 Fun(&'a dyn Fn(u16) -> u16),
82}
83
84/// Auto object which modifies it's coil value depending on a given modification function if it
85/// goes out of scope.
86pub struct ScopedCoil<'a> {
87 address: u16,
88 fun: CoilDropFunction,
89 transport: &'a mut Transport,
90}
91
92impl<'a> Drop for ScopedCoil<'a> {
93 fn drop(&mut self) {
94 let _ = self.transport.read_coils(self.address, 1).map(|value| {
95 if value.len() == 1 {
96 let drop_value = match self.fun {
97 CoilDropFunction::On => Coil::On,
98 CoilDropFunction::Off => Coil::Off,
99 CoilDropFunction::Toggle => match value[0] {
100 Coil::On => Coil::Off,
101 Coil::Off => Coil::On,
102 },
103 };
104 let _ = self.transport.write_single_coil(self.address, drop_value);
105 }
106 });
107 }
108}
109
110impl<'a> ScopedCoil<'a> {
111 /// Create a new `ScopedCoil` object with `address` and drop function when the object goes
112 /// out of scope.
113 pub fn new(
114 transport: &mut Transport,
115 address: u16,
116 fun: CoilDropFunction,
117 ) -> Result<ScopedCoil> {
118 Ok(ScopedCoil {
119 address,
120 fun,
121 transport,
122 })
123 }
124
125 pub fn mut_transport(&mut self) -> &mut Transport {
126 self.transport
127 }
128}
129
130/// Auto object which modifies it's register value depending on a given modification function if it
131/// goes out of scope.
132pub struct ScopedRegister<'a> {
133 address: u16,
134 fun: RegisterDropFunction<'a>,
135 transport: &'a mut Transport,
136}
137
138impl<'a> Drop for ScopedRegister<'a> {
139 fn drop(&mut self) {
140 let _ = self
141 .transport
142 .read_holding_registers(self.address, 1)
143 .map(|value| {
144 if value.len() == 1 {
145 let drop_value = match self.fun {
146 RegisterDropFunction::Zero => 0u16,
147 RegisterDropFunction::Increment => value[0] + 1,
148 RegisterDropFunction::Decrement => value[0] - 1,
149 RegisterDropFunction::Value(v) => v,
150 RegisterDropFunction::Fun(f) => f(value[0]),
151 };
152 let _ = self
153 .transport
154 .write_single_register(self.address, drop_value);
155 }
156 });
157 }
158}
159
160impl<'a> ScopedRegister<'a> {
161 /// Create a new `ScopedRegister` object with `address` and drop function when the object goes
162 /// out of scope.
163 pub fn new<'b>(
164 transport: &'b mut Transport,
165 address: u16,
166 fun: RegisterDropFunction<'b>,
167 ) -> Result<ScopedRegister<'b>> {
168 Ok(ScopedRegister {
169 address,
170 fun,
171 transport,
172 })
173 }
174
175 pub fn mut_transport(&mut self) -> &mut Transport {
176 self.transport
177 }
178}