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}