1use std::{borrow::Cow, io};
7
8use async_trait::async_trait;
9
10use crate::{frame::*, slave::*, Result};
11
12#[cfg(feature = "rtu")]
13pub mod rtu;
14
15#[cfg(feature = "tcp")]
16pub mod tcp;
17
18#[cfg(feature = "sync")]
19pub mod sync;
20
21#[async_trait]
23pub trait Client: SlaveContext + Send {
24 async fn call(&mut self, request: Request<'_>) -> Result<Response>;
26
27 async fn disconnect(&mut self) -> io::Result<()>;
37}
38
39#[async_trait]
41pub trait Reader: Client {
42 async fn read_coils(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>>;
44
45 async fn read_discrete_inputs(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>>;
47
48 async fn read_holding_registers(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Word>>;
50
51 async fn read_input_registers(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Word>>;
53
54 async fn read_write_multiple_registers(
59 &mut self,
60 read_addr: Address,
61 read_count: Quantity,
62 write_addr: Address,
63 write_data: &[Word],
64 ) -> Result<Vec<Word>>;
65}
66
67#[async_trait]
69pub trait Writer: Client {
70 async fn write_single_coil(&mut self, addr: Address, coil: Coil) -> Result<()>;
72
73 async fn write_single_register(&mut self, addr: Address, word: Word) -> Result<()>;
75
76 async fn write_multiple_coils(&mut self, addr: Address, coils: &'_ [Coil]) -> Result<()>;
78
79 async fn write_multiple_registers(&mut self, addr: Address, words: &[Word]) -> Result<()>;
81
82 async fn masked_write_register(
84 &mut self,
85 addr: Address,
86 and_mask: Word,
87 or_mask: Word,
88 ) -> Result<()>;
89}
90
91#[allow(missing_debug_implementations)]
93pub struct Context {
94 client: Box<dyn Client>,
95}
96
97impl From<Box<dyn Client>> for Context {
98 fn from(client: Box<dyn Client>) -> Self {
99 Self { client }
100 }
101}
102
103impl From<Context> for Box<dyn Client> {
104 fn from(val: Context) -> Self {
105 val.client
106 }
107}
108
109#[async_trait]
110impl Client for Context {
111 async fn call(&mut self, request: Request<'_>) -> Result<Response> {
112 self.client.call(request).await
113 }
114
115 async fn disconnect(&mut self) -> io::Result<()> {
116 self.client.disconnect().await
117 }
118}
119
120impl SlaveContext for Context {
121 fn set_slave(&mut self, slave: Slave) {
122 self.client.set_slave(slave);
123 }
124}
125
126#[async_trait]
127impl Reader for Context {
128 async fn read_coils<'a>(&'a mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>> {
129 self.client
130 .call(Request::ReadCoils(addr, cnt))
131 .await
132 .map(|result| {
133 result.map(|response| match response {
134 Response::ReadCoils(mut coils) => {
135 debug_assert!(coils.len() >= cnt.into());
136 coils.truncate(cnt.into());
137 coils
138 }
139 _ => unreachable!("call() should reject mismatching responses"),
140 })
141 })
142 }
143
144 async fn read_discrete_inputs<'a>(
145 &'a mut self,
146 addr: Address,
147 cnt: Quantity,
148 ) -> Result<Vec<Coil>> {
149 self.client
150 .call(Request::ReadDiscreteInputs(addr, cnt))
151 .await
152 .map(|result| {
153 result.map(|response| match response {
154 Response::ReadDiscreteInputs(mut coils) => {
155 debug_assert!(coils.len() >= cnt.into());
156 coils.truncate(cnt.into());
157 coils
158 }
159 _ => unreachable!("call() should reject mismatching responses"),
160 })
161 })
162 }
163
164 async fn read_input_registers<'a>(
165 &'a mut self,
166 addr: Address,
167 cnt: Quantity,
168 ) -> Result<Vec<Word>> {
169 self.client
170 .call(Request::ReadInputRegisters(addr, cnt))
171 .await
172 .map(|result| {
173 result.map(|response| match response {
174 Response::ReadInputRegisters(words) => {
175 debug_assert_eq!(words.len(), cnt.into());
176 words
177 }
178 _ => unreachable!("call() should reject mismatching responses"),
179 })
180 })
181 }
182
183 async fn read_holding_registers<'a>(
184 &'a mut self,
185 addr: Address,
186 cnt: Quantity,
187 ) -> Result<Vec<Word>> {
188 self.client
189 .call(Request::ReadHoldingRegisters(addr, cnt))
190 .await
191 .map(|result| {
192 result.map(|response| match response {
193 Response::ReadHoldingRegisters(words) => {
194 debug_assert_eq!(words.len(), cnt.into());
195 words
196 }
197 _ => unreachable!("call() should reject mismatching responses"),
198 })
199 })
200 }
201
202 async fn read_write_multiple_registers<'a>(
203 &'a mut self,
204 read_addr: Address,
205 read_count: Quantity,
206 write_addr: Address,
207 write_data: &[Word],
208 ) -> Result<Vec<Word>> {
209 self.client
210 .call(Request::ReadWriteMultipleRegisters(
211 read_addr,
212 read_count,
213 write_addr,
214 Cow::Borrowed(write_data),
215 ))
216 .await
217 .map(|result| {
218 result.map(|response| match response {
219 Response::ReadWriteMultipleRegisters(words) => {
220 debug_assert_eq!(words.len(), read_count.into());
221 words
222 }
223 _ => unreachable!("call() should reject mismatching responses"),
224 })
225 })
226 }
227}
228
229#[async_trait]
230impl Writer for Context {
231 async fn write_single_coil<'a>(&'a mut self, addr: Address, coil: Coil) -> Result<()> {
232 self.client
233 .call(Request::WriteSingleCoil(addr, coil))
234 .await
235 .map(|result| {
236 result.map(|response| match response {
237 Response::WriteSingleCoil(rsp_addr, rsp_coil) => {
238 debug_assert_eq!(addr, rsp_addr);
239 debug_assert_eq!(coil, rsp_coil);
240 }
241 _ => unreachable!("call() should reject mismatching responses"),
242 })
243 })
244 }
245
246 async fn write_multiple_coils<'a>(&'a mut self, addr: Address, coils: &[Coil]) -> Result<()> {
247 let cnt = coils.len();
248 self.client
249 .call(Request::WriteMultipleCoils(addr, Cow::Borrowed(coils)))
250 .await
251 .map(|result| {
252 result.map(|response| match response {
253 Response::WriteMultipleCoils(rsp_addr, rsp_cnt) => {
254 debug_assert_eq!(addr, rsp_addr);
255 debug_assert_eq!(cnt, rsp_cnt.into());
256 }
257 _ => unreachable!("call() should reject mismatching responses"),
258 })
259 })
260 }
261
262 async fn write_single_register<'a>(&'a mut self, addr: Address, word: Word) -> Result<()> {
263 self.client
264 .call(Request::WriteSingleRegister(addr, word))
265 .await
266 .map(|result| {
267 result.map(|response| match response {
268 Response::WriteSingleRegister(rsp_addr, rsp_word) => {
269 debug_assert_eq!(addr, rsp_addr);
270 debug_assert_eq!(word, rsp_word);
271 }
272 _ => unreachable!("call() should reject mismatching responses"),
273 })
274 })
275 }
276
277 async fn write_multiple_registers<'a>(
278 &'a mut self,
279 addr: Address,
280 data: &[Word],
281 ) -> Result<()> {
282 let cnt = data.len();
283 self.client
284 .call(Request::WriteMultipleRegisters(addr, Cow::Borrowed(data)))
285 .await
286 .map(|result| {
287 result.map(|response| match response {
288 Response::WriteMultipleRegisters(rsp_addr, rsp_cnt) => {
289 debug_assert_eq!(addr, rsp_addr);
290 debug_assert_eq!(cnt, rsp_cnt.into());
291 }
292 _ => unreachable!("call() should reject mismatching responses"),
293 })
294 })
295 }
296
297 async fn masked_write_register<'a>(
298 &'a mut self,
299 addr: Address,
300 and_mask: Word,
301 or_mask: Word,
302 ) -> Result<()> {
303 self.client
304 .call(Request::MaskWriteRegister(addr, and_mask, or_mask))
305 .await
306 .map(|result| {
307 result.map(|response| match response {
308 Response::MaskWriteRegister(rsp_addr, rsp_and_mask, rsp_or_mask) => {
309 debug_assert_eq!(addr, rsp_addr);
310 debug_assert_eq!(and_mask, rsp_and_mask);
311 debug_assert_eq!(or_mask, rsp_or_mask);
312 }
313 _ => unreachable!("call() should reject mismatching responses"),
314 })
315 })
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use crate::{Error, Result};
322
323 use super::*;
324 use std::{io, sync::Mutex};
325
326 #[derive(Default, Debug)]
327 pub(crate) struct ClientMock {
328 slave: Option<Slave>,
329 last_request: Mutex<Option<Request<'static>>>,
330 next_response: Option<Result<Response>>,
331 }
332
333 #[allow(dead_code)]
334 impl ClientMock {
335 pub(crate) fn slave(&self) -> Option<Slave> {
336 self.slave
337 }
338
339 pub(crate) fn last_request(&self) -> &Mutex<Option<Request<'static>>> {
340 &self.last_request
341 }
342
343 pub(crate) fn set_next_response(&mut self, next_response: Result<Response>) {
344 self.next_response = Some(next_response);
345 }
346 }
347
348 #[async_trait]
349 impl Client for ClientMock {
350 async fn call(&mut self, request: Request<'_>) -> Result<Response> {
351 *self.last_request.lock().unwrap() = Some(request.into_owned());
352 match self.next_response.take().unwrap() {
353 Ok(response) => Ok(response),
354 Err(Error::Transport(err)) => {
355 Err(io::Error::new(err.kind(), format!("{err}")).into())
356 }
357 Err(err) => Err(err),
358 }
359 }
360
361 async fn disconnect(&mut self) -> io::Result<()> {
362 Ok(())
363 }
364 }
365
366 impl SlaveContext for ClientMock {
367 fn set_slave(&mut self, slave: Slave) {
368 self.slave = Some(slave);
369 }
370 }
371
372 #[test]
373 fn read_some_coils() {
374 let response_coils = [true, false, false, true, false, true, false, true];
377 for num_coils in 1..8 {
378 let mut client = Box::<ClientMock>::default();
379 client.set_next_response(Ok(Ok(Response::ReadCoils(response_coils.to_vec()))));
380 let mut context = Context { client };
381 context.set_slave(Slave(1));
382 let coils = futures::executor::block_on(context.read_coils(1, num_coils))
383 .unwrap()
384 .unwrap();
385 assert_eq!(&response_coils[0..num_coils as usize], &coils[..]);
386 }
387 }
388
389 #[test]
390 fn read_some_discrete_inputs() {
391 let response_inputs = [true, false, false, true, false, true, false, true];
394 for num_inputs in 1..8 {
395 let mut client = Box::<ClientMock>::default();
396 client.set_next_response(Ok(Ok(Response::ReadDiscreteInputs(
397 response_inputs.to_vec(),
398 ))));
399 let mut context = Context { client };
400 context.set_slave(Slave(1));
401 let inputs = futures::executor::block_on(context.read_discrete_inputs(1, num_inputs))
402 .unwrap()
403 .unwrap();
404 assert_eq!(&response_inputs[0..num_inputs as usize], &inputs[..]);
405 }
406 }
407}