sdm72_lib/
tokio_sync_safe_client.rs

1//! This module provides a thread-safe synchronous client for the SDM72 energy meter.
2//!
3//! The [`SafeClient`] struct wraps the core synchronous API and manages the Modbus
4//! context within an `Arc<Mutex>`, allowing it to be shared across threads safely.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use sdm72_lib::{
10//!     protocol::Address,
11//!     tokio_sync_safe_client::SafeClient,
12//! };
13//! use tokio_modbus::client::sync::tcp;
14//! use tokio_modbus::Slave;
15//! use std::time::Duration;
16//!
17//! fn main() -> Result<(), Box<dyn std::error::Error>> {
18//!     let socket_addr = "192.168.1.100:502".parse()?;
19//!     let ctx = tcp::connect_slave(socket_addr, Slave(*Address::default()))?;
20//!     let mut client = SafeClient::new(ctx);
21//!
22//!     let values = client.read_all(&Duration::from_millis(100))?;
23//!
24//!     println!("Successfully read values: {:#?}", values);
25//!
26//!     Ok(())
27//! }
28//! ```
29
30use crate::{
31    protocol as proto,
32    tokio_common::{AllSettings, AllValues, Result},
33    tokio_sync::SDM72,
34};
35use std::sync::{Arc, Mutex};
36use tokio_modbus::{client::sync::Context, prelude::SlaveContext};
37
38/// A thread-safe synchronous client for the SDM72 energy meter.
39#[derive(Clone)]
40pub struct SafeClient {
41    ctx: Arc<Mutex<Context>>,
42}
43
44macro_rules! read_holding {
45    ($func_name:ident, $ty:ident) => {
46        paste::item! {
47            #[doc = "Reads the [`proto::" $ty "`] value from the Modbus holding register."]
48            pub fn $func_name(&mut self) -> Result<proto::$ty> {
49                let mut ctx = self.ctx.lock().unwrap();
50                SDM72::$func_name(&mut ctx)
51            }
52        }
53    };
54}
55
56macro_rules! write_holding {
57    ($func_name:ident, $ty:ident) => {
58        paste::item! {
59            #[doc = "Writes the [`proto::" $ty "`] value to the Modbus holding register."]
60            pub fn [< set_ $func_name >](&mut self, value: proto::$ty) -> Result<()> {
61                let mut ctx = self.ctx.lock().unwrap();
62                SDM72::[< set_ $func_name >](&mut ctx, value)
63            }
64        }
65    };
66}
67
68impl SafeClient {
69    /// Creates a new `SafeClient` instance.
70    ///
71    /// # Arguments
72    ///
73    /// * `ctx`: A synchronous Modbus client context, already connected.
74    pub fn new(ctx: Context) -> Self {
75        Self {
76            ctx: Arc::new(Mutex::new(ctx)),
77        }
78    }
79
80    /// Creates a new `SafeClient` from an existing `Arc<Mutex<Context>>`.
81    ///
82    /// This allows multiple `SafeClient` instances to share the exact same
83    /// underlying connection context.
84    pub fn from_shared(ctx: Arc<Mutex<Context>>) -> Self {
85        Self { ctx }
86    }
87
88    /// Clones and returns the underlying `Arc<Mutex<Context>>`.
89    ///
90    /// This allows the shared context to be used by other parts of an
91    /// application that may need direct access to the Modbus context.
92    pub fn clone_shared(&self) -> Arc<Mutex<Context>> {
93        self.ctx.clone()
94    }
95
96    read_holding!(system_type, SystemType);
97    write_holding!(system_type, SystemType);
98    read_holding!(pulse_width, PulseWidth);
99    write_holding!(pulse_width, PulseWidth);
100    read_holding!(kppa, KPPA);
101
102    /// Sets the Key Parameter Programming Authorization (KPPA).
103    ///
104    /// This is required to change settings on the meter.
105    pub fn set_kppa(&mut self, password: proto::Password) -> Result<()> {
106        let mut ctx = self.ctx.lock().unwrap();
107        SDM72::set_kppa(&mut ctx, password)
108    }
109
110    read_holding!(parity_and_stop_bit, ParityAndStopBit);
111    write_holding!(parity_and_stop_bit, ParityAndStopBit);
112    read_holding!(address, Address);
113
114    pub fn set_address(&mut self, value: proto::Address) -> Result<()> {
115        let mut ctx = self.ctx.lock().unwrap();
116        SDM72::set_address(&mut ctx, value)?;
117        ctx.set_slave(tokio_modbus::Slave(*value));
118        Ok(())
119    }
120
121    read_holding!(pulse_constant, PulseConstant);
122    write_holding!(pulse_constant, PulseConstant);
123    read_holding!(password, Password);
124    write_holding!(password, Password);
125    read_holding!(baud_rate, BaudRate);
126    write_holding!(baud_rate, BaudRate);
127    read_holding!(auto_scroll_time, AutoScrollTime);
128    write_holding!(auto_scroll_time, AutoScrollTime);
129    read_holding!(backlight_time, BacklightTime);
130    write_holding!(backlight_time, BacklightTime);
131    read_holding!(pulse_energy_type, PulseEnergyType);
132    write_holding!(pulse_energy_type, PulseEnergyType);
133
134    /// Resets the historical data on the meter.
135    ///
136    /// This requires KPPA authorization.
137    pub fn reset_historical_data(&mut self) -> Result<()> {
138        let mut ctx = self.ctx.lock().unwrap();
139        SDM72::reset_historical_data(&mut ctx)
140    }
141
142    read_holding!(serial_number, SerialNumber);
143    read_holding!(meter_code, MeterCode);
144    read_holding!(software_version, SoftwareVersion);
145
146    /// Reads all settings from the meter in a single batch operation.
147    pub fn read_all_settings(&mut self, delay: &std::time::Duration) -> Result<AllSettings> {
148        let mut ctx = self.ctx.lock().unwrap();
149        SDM72::read_all_settings(&mut ctx, delay)
150    }
151
152    /// Reads all measurement values from the meter in a single batch operation.
153    pub fn read_all(&mut self, delay: &std::time::Duration) -> Result<AllValues> {
154        let mut ctx = self.ctx.lock().unwrap();
155        SDM72::read_all(&mut ctx, delay)
156    }
157}