sdm72_lib/
tokio_async_safe_client.rs

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