r4dcb08_lib/
tokio_async.rs

1//! Asynchronous `tokio-modbus` client for the R4DCB08 temperature module.
2//!
3//! This module provides a high-level API (`R4DCB08` struct) to interact with
4//! the R4DCB08 8-channel temperature module using Modbus RTU or TCP. It handles
5//! the conversion between Rust types defined in the `crate::protocol` module and
6//! the raw Modbus register values.
7//!
8//! All client methods are `async` and must be `.await`ed.
9//!
10//! # Examples
11//!
12//! ## TCP Client Example
13//!
14//! ```no_run
15//! use r4dcb08_lib::tokio_async::R4DCB08;
16//! use std::net::SocketAddr;
17//! use std::time::Duration;
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//!     let socket_addr: SocketAddr = "127.0.0.1:502".parse()?;
22//!
23//!     // Connect to the Modbus TCP device
24//!     let mut modbus_ctx = tokio_modbus::client::tcp::connect(socket_addr).await?;
25//!
26//!     // Read temperatures from all 8 channels with a timeout
27//!     let result = tokio::time::timeout(
28//!         Duration::from_secs(1),
29//!         R4DCB08::read_temperatures(&mut modbus_ctx),
30//!     )
31//!     .await;
32//!
33//!     match result {
34//!         Ok(Ok(temperatures)) => println!("Temperatures: {}", temperatures),
35//!         Ok(Err(e)) => eprintln!("Modbus error: {}", e),
36//!         Err(e) => eprintln!("Timeout error: {}", e),
37//!     }
38//!
39//!     Ok(())
40//! }
41//! ```
42//!
43//! ## RTU Client Example
44//!
45//! ```no_run
46//! use r4dcb08_lib::tokio_async::R4DCB08;
47//! use r4dcb08_lib::protocol::{Address, BaudRate};
48//! use std::time::Duration;
49//!
50//! #[tokio::main]
51//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
52//!     let builder = r4dcb08_lib::tokio_common::serial_port_builder(
53//!         "/dev/ttyUSB0", // Or "COM3" on Windows, etc.
54//!         &BaudRate::B9600,
55//!     );
56//!     let port = tokio_serial::SerialStream::open(&builder)?;
57//!     let slave = tokio_modbus::Slave(1);
58//!     let mut modbus_ctx = tokio_modbus::client::rtu::attach_slave(port, slave);
59//!
60//!     // Read the device's configured baud rate with a timeout
61//!     let result = tokio::time::timeout(
62//!         Duration::from_secs(1),
63//!         R4DCB08::read_baud_rate(&mut modbus_ctx),
64//!     )
65//!     .await;
66//!
67//!     match result {
68//!         Ok(Ok(remote_baud_rate)) => println!("Device baud rate: {}", remote_baud_rate),
69//!         Ok(Err(e)) => eprintln!("Modbus error: {}", e),
70//!         Err(e) => eprintln!("Timeout error: {}", e),
71//!     }
72//!
73//!     Ok(())
74//! }
75//! ```
76
77use crate::{protocol as proto, tokio_common::Result};
78use tokio_modbus::prelude::{Reader, Writer};
79
80/// Asynchronous client for interacting with the R4DCB08 temperature module over Modbus.
81///
82/// This struct provides methods to read sensor data and configure the module's
83/// operational parameters by wrapping `tokio-modbus` asynchronous operations.
84///
85/// All methods that interact with the Modbus device are `async` and return `Future`s.
86#[derive(Debug)]
87pub struct R4DCB08;
88
89impl R4DCB08 {
90    /// Helper function to map tokio result to our result.
91    fn map_tokio_result<T>(result: tokio_modbus::Result<T>) -> Result<T> {
92        match result {
93            Ok(Ok(result)) => Ok(result),
94            Ok(Err(err)) => Err(err.into()), // Modbus exception
95            Err(err) => Err(err.into()),     // IO error
96        }
97    }
98
99    /// Helper function to read holding registers and decode them into a specific type.
100    async fn read_and_decode<T, F>(
101        ctx: &mut tokio_modbus::client::Context,
102        address: u16,
103        quantity: u16,
104        decoder: F,
105    ) -> Result<T>
106    where
107        F: FnOnce(&[u16]) -> std::result::Result<T, proto::Error>,
108    {
109        Ok(decoder(&Self::map_tokio_result(
110            ctx.read_holding_registers(address, quantity).await,
111        )?)?)
112    }
113
114    /// Reads the current temperatures from all 8 available channels in degrees Celsius (°C).
115    ///
116    /// If a channel's sensor is not connected or reports an error, the corresponding
117    /// `proto::Temperature` value will be `proto::Temperature::NAN`.
118    ///
119    /// # Returns
120    ///
121    /// A `Result<proto::Temperatures>` containing the temperatures for all channels,
122    /// or a Modbus error.
123    ///
124    /// # Errors
125    ///
126    /// * `tokio_modbus::Error` if a Modbus communication error occurs (e.g., IO error, timeout handled by wrapper, Modbus exception).
127    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidData` if the device returns
128    ///   an unexpected number of registers.
129    ///
130    /// # Examples
131    ///
132    /// ```no_run
133    /// # use r4dcb08_lib::tokio_async::R4DCB08;
134    /// # use std::time::Duration;
135    /// # #[tokio::main]
136    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
137    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
138    /// let temperatures = tokio::time::timeout(Duration::from_secs(2), R4DCB08::read_temperatures(&mut modbus_ctx)).await??;
139    /// println!("Temperatures read successfully:");
140    /// for (i, temp) in temperatures.iter().enumerate() {
141    ///     println!("  Channel {}: {}", i, temp); // `temp` uses Display impl from protocol
142    /// }
143    /// # Ok(())
144    /// # }
145    /// ```
146    pub async fn read_temperatures(
147        ctx: &mut tokio_modbus::client::Context,
148    ) -> Result<proto::Temperatures> {
149        Self::read_and_decode(
150            ctx,
151            proto::Temperatures::ADDRESS,
152            proto::Temperatures::QUANTITY,
153            proto::Temperatures::decode_from_holding_registers,
154        )
155        .await
156    }
157
158    /// Reads the configured temperature correction values (°C) for all 8 channels.
159    ///
160    /// A `proto::Temperature` value of `0.0` typically means no correction is applied,
161    /// while `proto::Temperature::NAN` might indicate an uninitialized or error state for a correction value if read.
162    ///
163    /// # Returns
164    ///
165    /// A `Result<proto::TemperatureCorrection>` containing correction values for each channel,
166    /// or a Modbus error.
167    ///
168    /// # Errors
169    ///
170    /// * `tokio_modbus::Error` for Modbus communication errors.
171    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidData` if the device returns
172    ///   an unexpected number of registers.
173    ///
174    /// # Examples
175    ///
176    /// ```no_run
177    /// # use r4dcb08_lib::tokio_async::R4DCB08;
178    /// # use std::time::Duration;
179    /// # #[tokio::main]
180    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
181    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
182    /// let corrections = tokio::time::timeout(Duration::from_secs(2), R4DCB08::read_temperature_correction(&mut modbus_ctx)).await??;
183    /// println!("Temperature correction values: {}", corrections);
184    /// # Ok(())
185    /// # }
186    /// ```
187    pub async fn read_temperature_correction(
188        ctx: &mut tokio_modbus::client::Context,
189    ) -> Result<proto::TemperatureCorrection> {
190        Self::read_and_decode(
191            ctx,
192            proto::TemperatureCorrection::ADDRESS,
193            proto::TemperatureCorrection::QUANTITY,
194            proto::TemperatureCorrection::decode_from_holding_registers,
195        )
196        .await
197    }
198
199    /// Sets a temperature correction value for a specific channel.
200    ///
201    /// The `correction` value will be added to the raw temperature reading by the module.
202    /// Setting a correction value of `0.0` effectively disables it for that channel.
203    ///
204    /// # Arguments
205    ///
206    /// * `channel` - The `proto::Channel` to configure.
207    /// * `correction` - The `proto::Temperature` correction value to apply (in °C).
208    ///   This type ensures the temperature value is within the representable range.
209    ///
210    /// # Returns
211    ///
212    /// A `Result<()>` indicating success or failure of the write operation.
213    ///
214    /// # Errors
215    ///
216    /// * `tokio_modbus::Error` for Modbus communication errors.
217    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidInput` if the
218    ///   `correction` value is `NAN`.
219    ///
220    /// # Examples
221    ///
222    /// ```no_run
223    /// # use r4dcb08_lib::tokio_async::R4DCB08;
224    /// use r4dcb08_lib::protocol::{Channel, Temperature, Error};
225    /// use std::time::Duration;
226    ///
227    /// # #[tokio::main]
228    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
229    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
230    /// // Set the temperature correction for channel 3 to +1.3°C.
231    /// let channel = Channel::try_from(3)?;
232    /// let correction_value = Temperature::try_from(1.3)?;
233    ///
234    /// tokio::time::timeout(Duration::from_secs(2), R4DCB08::set_temperature_correction(&mut modbus_ctx, channel, correction_value)).await??;
235    /// println!("Correction for channel {} set to {}.", channel, correction_value);
236    /// # Ok(())
237    /// # }
238    /// ```
239    pub async fn set_temperature_correction(
240        ctx: &mut tokio_modbus::client::Context,
241        channel: proto::Channel,
242        correction: proto::Temperature,
243    ) -> Result<()> {
244        Self::map_tokio_result(
245            ctx.write_single_register(
246                proto::TemperatureCorrection::channel_address(channel),
247                proto::TemperatureCorrection::encode_for_write_register(correction)?,
248            )
249            .await,
250        )
251    }
252
253    /// Reads the automatic temperature reporting interval.
254    ///
255    /// An interval of `0` seconds ([`proto::AutomaticReport::DISABLED`]) means automatic reporting is off.
256    ///
257    /// # Returns
258    ///
259    /// A ` Result<proto::AutomaticReport>` indicating the configured reporting interval,
260    /// or a Modbus error.
261    ///
262    /// # Errors
263    ///
264    /// * `tokio_modbus::Error` for Modbus communication errors.
265    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidData` if the device returns
266    ///   malformed data.
267    ///
268    /// # Examples
269    ///
270    /// ```no_run
271    /// # use r4dcb08_lib::tokio_async::R4DCB08;
272    /// # use std::time::Duration;
273    /// # #[tokio::main]
274    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
275    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
276    /// let report = tokio::time::timeout(Duration::from_secs(2), R4DCB08::read_automatic_report(&mut modbus_ctx)).await??;
277    /// if report.is_disabled() {
278    ///     println!("Automatic reporting is disabled.");
279    /// } else {
280    ///     println!("Automatic report interval: {} seconds.", report.as_secs());
281    /// }
282    /// # Ok(())
283    /// # }
284    /// ```
285    pub async fn read_automatic_report(
286        ctx: &mut tokio_modbus::client::Context,
287    ) -> Result<proto::AutomaticReport> {
288        Self::read_and_decode(
289            ctx,
290            proto::AutomaticReport::ADDRESS,
291            proto::AutomaticReport::QUANTITY,
292            proto::AutomaticReport::decode_from_holding_registers,
293        )
294        .await
295    }
296
297    /// Sets the automatic temperature reporting interval.
298    ///
299    /// When enabled (interval > 0), the module may periodically send temperature data
300    /// unsolicitedly over the RS485 bus (behavior depends on module firmware).
301    ///
302    /// # Arguments
303    ///
304    /// * `report` - The `proto::AutomaticReport` interval (0 = disabled, 1-255 seconds).
305    ///   The `proto::AutomaticReport` type ensures the value is within the valid hardware range.
306    ///
307    /// # Returns
308    ///
309    /// A `Result<()>` indicating success or failure of the write operation.
310    ///
311    /// # Errors
312    ///
313    /// * `tokio_modbus::Error` for Modbus communication errors.
314    ///
315    /// # Examples
316    ///
317    /// ```no_run
318    /// # use r4dcb08_lib::tokio_async::R4DCB08;
319    /// use r4dcb08_lib::protocol::{AutomaticReport, Error};
320    /// use std::time::Duration;
321    ///
322    /// # #[tokio::main]
323    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
324    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
325    /// let report_interval = AutomaticReport::try_from(Duration::from_secs(10))?;
326    ///
327    /// tokio::time::timeout(Duration::from_secs(2), R4DCB08::set_automatic_report(&mut modbus_ctx, report_interval)).await??;
328    /// println!("Automatic report interval set to 10 seconds.");
329    /// # Ok(())
330    /// # }
331    /// ```
332    pub async fn set_automatic_report(
333        ctx: &mut tokio_modbus::client::Context,
334        report: proto::AutomaticReport,
335    ) -> Result<()> {
336        Self::map_tokio_result(
337            ctx.write_single_register(
338                proto::AutomaticReport::ADDRESS,
339                report.encode_for_write_register(),
340            )
341            .await,
342        )
343    }
344
345    /// Reads the current Modbus communication baud rate setting from the device.
346    ///
347    /// # Returns
348    ///
349    /// A `Result<proto::BaudRate>` containing the configured baud rate,
350    /// or a Modbus error.
351    ///
352    /// # Errors
353    ///
354    /// * `tokio_modbus::Error` for Modbus communication errors.
355    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidData` if the device returns
356    ///   an invalid baud rate code.
357    ///
358    /// # Examples
359    ///
360    /// ```no_run
361    /// # use r4dcb08_lib::tokio_async::R4DCB08;
362    /// # use std::time::Duration;
363    /// # #[tokio::main]
364    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
365    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
366    /// let baud_rate = tokio::time::timeout(Duration::from_secs(2), R4DCB08::read_baud_rate(&mut modbus_ctx)).await??;
367    /// println!("Current baud rate: {}", baud_rate);
368    /// # Ok(())
369    /// # }
370    /// ```
371    pub async fn read_baud_rate(
372        ctx: &mut tokio_modbus::client::Context,
373    ) -> Result<proto::BaudRate> {
374        Self::read_and_decode(
375            ctx,
376            proto::BaudRate::ADDRESS,
377            proto::BaudRate::QUANTITY,
378            proto::BaudRate::decode_from_holding_registers,
379        )
380        .await
381    }
382
383    /// Sets the Modbus communication baud rate for the device.
384    ///
385    /// **Important:** The new baud rate setting will only take effect after the
386    /// R4DCB08 module is **power cycled** (turned off and then on again).
387    ///
388    /// # Arguments
389    ///
390    /// * `baud_rate` - The desired `proto::BaudRate` to set.
391    ///
392    /// # Returns
393    ///
394    /// A `Result<()>` indicating success or failure of the write operation.
395    ///
396    /// # Errors
397    ///
398    /// * `tokio_modbus::Error` for Modbus communication errors.
399    ///
400    /// # Examples
401    ///
402    /// ```no_run
403    /// # use r4dcb08_lib::tokio_async::R4DCB08;
404    /// use r4dcb08_lib::protocol::{BaudRate, Error};
405    /// use std::time::Duration;
406    ///
407    /// # #[tokio::main]
408    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
409    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
410    /// // Set the baud rate to 19200.
411    /// let new_baud_rate = BaudRate::B19200; // Direct enum variant
412    /// // Or from u16:
413    /// // let new_baud_rate = BaudRate::try_from(19200)?;
414    ///
415    /// tokio::time::timeout(Duration::from_secs(2), R4DCB08::set_baud_rate(&mut modbus_ctx, new_baud_rate)).await??;
416    /// println!("Baud rate set to {}. Power cycle the device for changes to take effect.", new_baud_rate);
417    /// # Ok(())
418    /// # }
419    /// ```
420    pub async fn set_baud_rate(
421        ctx: &mut tokio_modbus::client::Context,
422        baud_rate: proto::BaudRate,
423    ) -> Result<()> {
424        Self::map_tokio_result(
425            ctx.write_single_register(
426                proto::BaudRate::ADDRESS,
427                baud_rate.encode_for_write_register(),
428            )
429            .await,
430        )
431    }
432
433    /// Resets the R4DCB08 module to its factory default settings.
434    ///
435    /// This resets all configurable parameters like Modbus Address, Baud Rate,
436    /// Temperature Corrections, etc., to their original defaults.
437    ///
438    /// **Important:**
439    /// * After this command is successfully sent, the module may become unresponsive
440    ///   on the Modbus bus until it is power cycled.
441    /// * A **power cycle** (turning the device off and then on again) is **required**
442    ///   to complete the factory reset process and for the default settings to be applied.
443    ///
444    /// # Returns
445    ///
446    /// A `Result<()>` indicating if the reset command was sent successfully.
447    /// It does not confirm the reset is complete, only that the Modbus write was acknowledged.
448    ///
449    /// # Errors
450    ///
451    /// * `tokio_modbus::Error` for Modbus communication errors. A timeout error after this
452    ///   command might be expected as the device resets and may not send a response.
453    ///
454    /// # Examples
455    ///
456    /// ```no_run
457    /// # use r4dcb08_lib::tokio_async::R4DCB08;
458    /// # use std::time::Duration;
459    /// # #[tokio::main]
460    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
461    /// # let mut modbus_ctx = tokio_modbus::client::tcp::connect("127.0.0.1:502".parse()?).await?;
462    /// println!("Attempting to send factory reset command...");
463    /// match tokio::time::timeout(Duration::from_secs(2), R4DCB08::factory_reset(&mut modbus_ctx)).await {
464    ///     Ok(Ok(())) => println!("Factory reset command sent. Power cycle the device to complete."),
465    ///     Ok(Err(e)) => eprintln!("Modbus error during factory reset: {}", e),
466    ///     Err(e) => {
467    ///         // After the a successful factory reset we get no response :-(
468    ///         println!("Factory reset command sent. Device timed out as expected. Power cycle to complete.");
469    ///     }
470    /// }
471    /// # Ok(())
472    /// # }
473    /// ```
474    pub async fn factory_reset(ctx: &mut tokio_modbus::client::Context) -> Result<()> {
475        Self::map_tokio_result(
476            ctx.write_single_register(
477                proto::FactoryReset::ADDRESS,
478                proto::FactoryReset::encode_for_write_register(),
479            )
480            .await,
481        )
482    }
483
484    /// Reads the current Modbus device address (Slave ID) from the module.
485    ///
486    /// **Important Usage Notes:**
487    /// * This command is typically used when the device's
488    ///   current address is unknown. To do this, the Modbus request **must be sent to
489    ///   the broadcast address ([`proto::Address::BROADCAST`])**.
490    /// * **Single Device Only:** Only **one** R4DCB08 module should be connected to the
491    ///   Modbus bus when executing this command with the broadcast address. If multiple
492    ///   devices are present, they might all respond, leading to data collisions and errors.
493    ///
494    /// # Returns
495    ///
496    /// A `Result<proto::Address>` containing the device's configured Modbus address,
497    /// or a Modbus error.
498    ///
499    /// # Errors
500    ///
501    /// * `tokio_modbus::Error` for Modbus communication errors.
502    /// * `tokio_modbus::Error::Transport` with `std::io::ErrorKind::InvalidData` if the device returns
503    ///   a malformed or out-of-range address.
504    ///
505    /// # Examples
506    ///
507    /// ```no_run
508    /// use r4dcb08_lib::tokio_async::R4DCB08;
509    /// use r4dcb08_lib::protocol;
510    /// use std::time::Duration;
511    ///
512    /// # #[tokio::main]
513    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
514    /// // Requires serial port features enabled in tokio-modbus
515    /// let builder = tokio_serial::new("/dev/ttyUSB0", 9600) // Baud rate 9600
516    ///    .parity(tokio_serial::Parity::None)
517    ///    .stop_bits(tokio_serial::StopBits::One)
518    ///    .data_bits(tokio_serial::DataBits::Eight)
519    ///    .flow_control(tokio_serial::FlowControl::None);
520    ///
521    /// let port = tokio_serial::SerialStream::open(&builder)?;
522    /// // Assume only one device connected, use broadcast address for reading
523    /// let mut modbus_ctx = tokio_modbus::client::rtu::attach_slave(port, tokio_modbus::Slave(*protocol::Address::BROADCAST));
524    ///
525    /// println!("Attempting to read device address using broadcast...");
526    /// let device_address = tokio::time::timeout(Duration::from_secs(2), R4DCB08::read_address(&mut modbus_ctx)).await??;
527    /// println!("Successfully read device address: {}", device_address);
528    /// # Ok(())
529    /// # }
530    /// ```
531    pub async fn read_address(ctx: &mut tokio_modbus::client::Context) -> Result<proto::Address> {
532        Self::read_and_decode(
533            ctx,
534            proto::Address::ADDRESS,
535            proto::Address::QUANTITY,
536            proto::Address::decode_from_holding_registers,
537        )
538        .await
539    }
540
541    /// Sets a new Modbus device address.
542    ///
543    /// **Warning:**
544    /// * This permanently changes the device's Modbus address.
545    /// * This command must be sent while addressing the device using its **current** Modbus address.
546    /// * After successfully changing the address, subsequent communication with the
547    ///   device **must** use the new address.
548    ///
549    /// # Arguments
550    ///
551    /// * `new_address` - The new `proto::Address` to assign to the device.
552    ///
553    /// # Returns
554    ///
555    /// A `Result<()>` indicating success or failure of the write operation.
556    ///
557    /// # Errors
558    ///
559    /// * `tokio_modbus::Error` for Modbus communication errors.
560    ///
561    /// # Examples
562    ///
563    /// ```no_run
564    /// use r4dcb08_lib::tokio_async::R4DCB08;
565    /// use r4dcb08_lib::protocol::{Address, Error};
566    /// use std::time::Duration;
567    ///
568    /// # #[tokio::main]
569    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
570    /// // Requires serial port features enabled in tokio-modbus
571    /// let builder = tokio_serial::new("/dev/ttyUSB0", 9600) // Baud rate 9600
572    ///    .parity(tokio_serial::Parity::None)
573    ///    .stop_bits(tokio_serial::StopBits::One)
574    ///    .data_bits(tokio_serial::DataBits::Eight)
575    ///    .flow_control(tokio_serial::FlowControl::None);
576    ///
577    /// // --- Assume device is currently at address 1 ---
578    /// let current_device_address = Address::try_from(1)?;
579    ///
580    /// let port = tokio_serial::SerialStream::open(&builder)?;
581    /// let mut modbus_ctx = tokio_modbus::client::rtu::attach_slave(port, tokio_modbus::Slave(*current_device_address));
582    ///
583    /// // --- New address we want to set ---
584    /// let new_device_address = Address::try_from(10)?;
585    ///
586    /// println!("Attempting to change device address from {} to {}...", current_device_address, new_device_address);
587    /// tokio::time::timeout(Duration::from_secs(2), R4DCB08::set_address(&mut modbus_ctx, new_device_address)).await??;
588    /// println!("Device address successfully changed to {}.", new_device_address);
589    /// println!("You will need to reconnect using the new address for further communication.");
590    /// # Ok(())
591    /// # }
592    /// ```
593    pub async fn set_address(
594        ctx: &mut tokio_modbus::client::Context,
595        new_address: proto::Address,
596    ) -> Result<()> {
597        Self::map_tokio_result(
598            ctx.write_single_register(
599                proto::Address::ADDRESS,
600                new_address.encode_for_write_register(),
601            )
602            .await,
603        )
604    }
605}