1pub mod error;
2pub mod model;
3pub mod models;
4pub mod point;
5pub mod types;
6pub mod utils;
7
8use error::Error;
9use model::Model;
10use point::{Point, PointType};
11use std::{collections::HashMap, net::SocketAddr};
12use tokio_modbus::{client::Context, prelude::*};
13use tokio_serial::SerialStream;
14use types::Address;
15
16pub struct Client {
17 pub slave_id: u8,
19
20 pub start_address: Address,
22
23 pub models: HashMap<u16, Address>,
25
26 modbus_client: Context,
28}
29
30#[cfg(feature = "tcp")]
31pub async fn connect_tcp(
32 socket_addr: SocketAddr,
33 slave_id: u8,
34 start_address: Address,
35) -> Result<Client, Error> {
36 let modbus_client = tcp::connect_slave(socket_addr, Slave(slave_id))
37 .await
38 .map_err(Error::Io)?;
39
40 return connect(modbus_client, slave_id, start_address).await;
41}
42
43#[cfg(feature = "rtu")]
44pub async fn connect_rtu(
45 device_path: &str,
46 baud_rate: u32,
47 slave_id: u8,
48 start_address: Address,
49) -> Result<Client, Error> {
50 let builder = tokio_serial::new(device_path, baud_rate);
51 let serial = SerialStream::open(&builder).unwrap();
52 let modbus_client = rtu::connect_slave(serial, Slave(slave_id))
53 .await
54 .map_err(Error::Io)?;
55
56 return connect(modbus_client, slave_id, start_address).await;
57}
58
59pub async fn connect(
60 client: Context,
61 slave_id: u8,
62 start_address: Address,
63) -> Result<Client, Error> {
64 let mut client = Client {
65 slave_id,
66 start_address,
67 models: HashMap::new(),
68 modbus_client: client,
69 };
70
71 client.model_discovery().await?;
72 return Ok(client);
73}
74
75impl Client {
76 async fn model_discovery(&mut self) -> Result<(), Error> {
78 let mut base_addr = self.start_address;
79
80 let res = self
82 .read_holding_registers(base_addr, 2)
83 .await
84 .expect("SunS identifier");
85
86 if res != vec![0x5375, 0x6e53] {
87 return Err(Error::Client());
88 }
89 base_addr += 2;
90
91 loop {
93 let res = self.read_holding_registers(base_addr, 2).await?;
94 let model_id = res[0];
95 let model_length = res[1];
96
97 if model_id == 0xFFFF || model_length == 0xFFFF {
98 return Ok(()); }
100 self.models.insert(model_id, base_addr + 2);
101
102 base_addr += 2; base_addr += model_length; }
105 }
106
107 async fn read_holding_registers(&mut self, addr: Address, cnt: u16) -> Result<Vec<u16>, Error> {
109 return self
110 .modbus_client
111 .read_holding_registers(addr, cnt)
112 .await
113 .map_err(Error::Io);
114 }
115
116 async fn write_holding_registers(
118 &mut self,
119 addr: Address,
120 data: Vec<u16>,
121 ) -> Result<(), Error> {
122 return self
123 .modbus_client
124 .write_multiple_registers(addr, &data)
125 .await
126 .map_err(Error::Io);
127 }
128}
129
130impl Client {
131 pub async fn read_point<T: Model, K: PointType<K>>(
133 &mut self,
134 point: Point<T, K>,
135 ) -> Result<K, Error> {
136 if let Some(model_addr) = self.models.get(&T::ID) {
137 let address = *model_addr + point.offset;
138 return self
139 .read_holding_registers(address, point.length)
140 .await
141 .and_then(|res| Ok(K::decode(res)));
142 }
143
144 return Err(Error::UnsupportedModel(T::ID));
145 }
146
147 pub async fn write_point<T: Model, K: PointType<K>>(
149 &mut self,
150 point: Point<T, K>,
151 data: K,
152 ) -> Result<(), Error> {
153 if !point.write_access {
154 return Err(Error::WriteNotSupported());
155 }
156
157 if let Some(model_addr) = self.models.get(&T::ID) {
158 let address = *model_addr + point.offset;
159 let buff = K::encode(data);
160 return self.write_holding_registers(address, buff).await;
161 }
162
163 return Err(Error::UnsupportedModel(T::ID));
164 }
165}