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}