r413d08_lib/tokio_sync_safe_client.rs
1//! Provides a thread-safe, synchronous Modbus client for the R413D08 relay module.
2//!
3//! This module defines the [`SafeClient`] struct, which acts as a high-level,
4//! stateful, and thread-safe interface for interacting with the R413D08 device.
5//! It wraps a `tokio-modbus` `sync::Context` within a `std::sync::Mutex` to ensure
6//! that all operations are serialized, making it safe to share across multiple
7//! threads (e.g., using an `Arc<SafeClient>`).
8
9use crate::{protocol as proto, tokio_common::Result, tokio_sync::R413D08};
10use std::sync::{Arc, Mutex};
11use tokio_modbus::{client::sync::Context, prelude::SlaveContext, Slave};
12
13/// A thread-safe, synchronous client for an R413D08 relay module.
14///
15/// This client encapsulates a [`tokio_modbus::client::sync::Context`] within an
16/// `Arc<Mutex<...>>`, allowing it to be safely shared and cloned across multiple
17/// threads. All device operations are internally serialized,
18/// preventing concurrent access issues.
19///
20/// It also provides a safer `set_address` method that automatically updates
21/// the client's internal slave ID after successfully changing the device's
22/// Modbus address, preventing desynchronization errors.
23///
24/// # Example
25///
26/// ```no_run
27/// use r413d08_lib::{
28/// protocol::Port,
29/// tokio_sync_safe_client::SafeClient,
30/// };
31/// use std::thread;
32/// use tokio_modbus::client::sync::tcp;
33///
34/// fn main() -> Result<(), Box<dyn std::error::Error>> {
35/// let socket_addr = "127.0.0.1:502".parse()?;
36/// let ctx = tcp::connect(socket_addr)?;
37/// let client = SafeClient::new(ctx);
38///
39/// // Clone the client to share it between threads
40/// let client_clone = client.clone();
41/// thread::spawn(move || {
42/// // Use the client in another thread
43/// client_clone.set_port_open(Port::try_from(1).unwrap()).unwrap();
44/// });
45///
46/// // Use the client in the main thread
47/// let status = client.read_ports()?;
48/// println!("Port status: {}", status);
49///
50/// Ok(())
51/// }
52/// ```
53#[derive(Clone)]
54pub struct SafeClient {
55 ctx: Arc<Mutex<Context>>,
56}
57
58impl SafeClient {
59 /// Creates a new `SafeClient` instance.
60 ///
61 /// # Arguments
62 ///
63 /// * `ctx`: A synchronous Modbus client context, already connected.
64 pub fn new(ctx: Context) -> Self {
65 Self {
66 ctx: Arc::new(Mutex::new(ctx)),
67 }
68 }
69
70 /// Creates a new `SafeClient` from an existing `Arc<Mutex<Context>>`.
71 ///
72 /// This allows multiple `SafeClient` instances to share the exact same
73 /// underlying connection context.
74 pub fn from_shared(ctx: Arc<Mutex<Context>>) -> Self {
75 Self { ctx }
76 }
77
78 /// Clones and returns the underlying `Arc<Mutex<Context>>`.
79 ///
80 /// This allows the shared context to be used by other parts of an
81 /// application that may need direct access to the Modbus context.
82 pub fn clone_shared(&self) -> Arc<Mutex<Context>> {
83 self.ctx.clone()
84 }
85
86 /// Reads the current status (Open/Close) of all ports.
87 pub fn read_ports(&self) -> Result<proto::PortStates> {
88 let mut guard = self.ctx.lock().unwrap();
89 R413D08::read_ports(&mut guard)
90 }
91
92 /// Sets the specified port to the **Open** state.
93 pub fn set_port_open(&self, port: proto::Port) -> Result<()> {
94 let mut guard = self.ctx.lock().unwrap();
95 R413D08::set_port_open(&mut guard, port)
96 }
97
98 /// Sets **all** ports to the **Open** state.
99 pub fn set_all_open(&self) -> Result<()> {
100 let mut guard = self.ctx.lock().unwrap();
101 R413D08::set_all_open(&mut guard)
102 }
103
104 /// Sets the specified port to the **Close** state.
105 pub fn set_port_close(&self, port: proto::Port) -> Result<()> {
106 let mut guard = self.ctx.lock().unwrap();
107 R413D08::set_port_close(&mut guard, port)
108 }
109
110 /// Sets **all** ports to the **Close** state.
111 pub fn set_all_close(&self) -> Result<()> {
112 let mut guard = self.ctx.lock().unwrap();
113 R413D08::set_all_close(&mut guard)
114 }
115
116 /// Toggles the current state of the specified port.
117 pub fn set_port_toggle(&self, port: proto::Port) -> Result<()> {
118 let mut guard = self.ctx.lock().unwrap();
119 R413D08::set_port_toggle(&mut guard, port)
120 }
121
122 /// Latches the specified port (opens it and closes all others).
123 pub fn set_port_latch(&self, port: proto::Port) -> Result<()> {
124 let mut guard = self.ctx.lock().unwrap();
125 R413D08::set_port_latch(&mut guard, port)
126 }
127
128 /// Activates the specified port momentarily.
129 pub fn set_port_momentary(&self, port: proto::Port) -> Result<()> {
130 let mut guard = self.ctx.lock().unwrap();
131 R413D08::set_port_momentary(&mut guard, port)
132 }
133
134 /// Activates the specified port with a delayed close.
135 pub fn set_port_delay(&self, port: proto::Port, delay: u8) -> Result<()> {
136 let mut guard = self.ctx.lock().unwrap();
137 R413D08::set_port_delay(&mut guard, port, delay)
138 }
139
140 /// Reads the configured Modbus device address.
141 ///
142 /// It's recommended to use the broadcast address for this operation,
143 /// ensuring only one device is on the bus.
144 pub fn read_address(&self) -> Result<proto::Address> {
145 let mut guard = self.ctx.lock().unwrap();
146 R413D08::read_address(&mut guard)
147 }
148
149 /// Sets a new Modbus device address.
150 ///
151 /// **This method is safer than the stateless equivalent.** Upon successfully
152 /// changing the device's address, it automatically updates the client's
153 /// internal slave ID to match. This keeps the client synchronized with the
154 /// device state, preventing subsequent communication errors.
155 pub fn set_address(&self, address: proto::Address) -> Result<()> {
156 let mut guard = self.ctx.lock().unwrap();
157 R413D08::set_address(&mut guard, address)?;
158 guard.set_slave(Slave(*address));
159 Ok(())
160 }
161}