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