1use core::fmt;
2
3mod coils;
4mod data;
5pub(crate) mod rtu;
6pub(crate) mod tcp;
7
8pub use self::{coils::*, data::*};
9use byteorder::{BigEndian, ByteOrder};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum FunctionCode {
16 ReadCoils,
18
19 ReadDiscreteInputs,
21
22 WriteSingleCoil,
24
25 WriteSingleRegister,
27
28 ReadHoldingRegisters,
30
31 ReadInputRegisters,
33
34 WriteMultipleCoils,
36
37 WriteMultipleRegisters,
39
40 MaskWriteRegister,
42
43 ReadWriteMultipleRegisters,
45
46 #[cfg(feature = "rtu")]
47 ReadExceptionStatus,
48
49 #[cfg(feature = "rtu")]
50 Diagnostics,
51
52 #[cfg(feature = "rtu")]
53 GetCommEventCounter,
54
55 #[cfg(feature = "rtu")]
56 GetCommEventLog,
57
58 #[cfg(feature = "rtu")]
59 ReportServerId,
60
61 Custom(u8),
71}
72
73impl FunctionCode {
74 #[must_use]
76 pub const fn new(value: u8) -> Self {
77 match value {
78 0x01 => Self::ReadCoils,
79 0x02 => Self::ReadDiscreteInputs,
80 0x05 => Self::WriteSingleCoil,
81 0x06 => Self::WriteSingleRegister,
82 0x03 => Self::ReadHoldingRegisters,
83 0x04 => Self::ReadInputRegisters,
84 0x0F => Self::WriteMultipleCoils,
85 0x10 => Self::WriteMultipleRegisters,
86 0x16 => Self::MaskWriteRegister,
87 0x17 => Self::ReadWriteMultipleRegisters,
88 #[cfg(feature = "rtu")]
89 0x07 => Self::ReadExceptionStatus,
90 #[cfg(feature = "rtu")]
91 0x08 => Self::Diagnostics,
92 #[cfg(feature = "rtu")]
93 0x0B => Self::GetCommEventCounter,
94 #[cfg(feature = "rtu")]
95 0x0C => Self::GetCommEventLog,
96 #[cfg(feature = "rtu")]
97 0x11 => Self::ReportServerId,
98 code => FunctionCode::Custom(code),
99 }
100 }
101
102 #[must_use]
104 pub const fn value(self) -> u8 {
105 match self {
106 Self::ReadCoils => 0x01,
107 Self::ReadDiscreteInputs => 0x02,
108 Self::WriteSingleCoil => 0x05,
109 Self::WriteSingleRegister => 0x06,
110 Self::ReadHoldingRegisters => 0x03,
111 Self::ReadInputRegisters => 0x04,
112 Self::WriteMultipleCoils => 0x0F,
113 Self::WriteMultipleRegisters => 0x10,
114 Self::MaskWriteRegister => 0x16,
115 Self::ReadWriteMultipleRegisters => 0x17,
116 #[cfg(feature = "rtu")]
117 Self::ReadExceptionStatus => 0x07,
118 #[cfg(feature = "rtu")]
119 Self::Diagnostics => 0x08,
120 #[cfg(feature = "rtu")]
121 Self::GetCommEventCounter => 0x0B,
122 #[cfg(feature = "rtu")]
123 Self::GetCommEventLog => 0x0C,
124 #[cfg(feature = "rtu")]
125 Self::ReportServerId => 0x11,
126 Self::Custom(code) => code,
127 }
128 }
129}
130
131impl fmt::Display for FunctionCode {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 self.value().fmt(f)
134 }
135}
136
137#[cfg(feature = "rtu")]
139pub(crate) type SubFunctionCode = u16;
140
141pub(crate) type Address = u16;
143
144pub(crate) type Coil = bool;
149
150pub(crate) type Word = u16;
152
153pub(crate) type Quantity = u16;
155
156type RawData<'r> = &'r [u8];
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum Request<'r> {
162 ReadCoils(Address, Quantity),
163 ReadDiscreteInputs(Address, Quantity),
164 WriteSingleCoil(Address, Coil),
165 WriteMultipleCoils(Address, Coils<'r>),
166 ReadInputRegisters(Address, Quantity),
167 ReadHoldingRegisters(Address, Quantity),
168 WriteSingleRegister(Address, Word),
169 WriteMultipleRegisters(Address, Data<'r>),
170 ReadWriteMultipleRegisters(Address, Quantity, Address, Data<'r>),
171 #[cfg(feature = "rtu")]
172 ReadExceptionStatus,
173 #[cfg(feature = "rtu")]
174 Diagnostics(SubFunctionCode, Data<'r>),
175 #[cfg(feature = "rtu")]
176 GetCommEventCounter,
177 #[cfg(feature = "rtu")]
178 GetCommEventLog,
179 #[cfg(feature = "rtu")]
180 ReportServerId,
181 Custom(FunctionCode, &'r [u8]),
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct ExceptionResponse {
196 pub function: FunctionCode,
197 pub exception: Exception,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct RequestPdu<'r>(pub Request<'r>);
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub struct ResponsePdu<'r>(pub Result<Response<'r>, ExceptionResponse>);
207
208#[cfg(feature = "rtu")]
209type Status = u16;
210#[cfg(feature = "rtu")]
211type EventCount = u16;
212#[cfg(feature = "rtu")]
213type MessageCount = u16;
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum Response<'r> {
218 ReadCoils(Coils<'r>),
219 ReadDiscreteInputs(Coils<'r>),
220 WriteSingleCoil(Address),
221 WriteMultipleCoils(Address, Quantity),
222 ReadInputRegisters(Data<'r>),
223 ReadHoldingRegisters(Data<'r>),
224 WriteSingleRegister(Address, Word),
225 WriteMultipleRegisters(Address, Quantity),
226 ReadWriteMultipleRegisters(Data<'r>),
227 #[cfg(feature = "rtu")]
228 ReadExceptionStatus(u8),
229 #[cfg(feature = "rtu")]
230 Diagnostics(Data<'r>),
231 #[cfg(feature = "rtu")]
232 GetCommEventCounter(Status, EventCount),
233 #[cfg(feature = "rtu")]
234 GetCommEventLog(Status, EventCount, MessageCount, &'r [u8]),
235 #[cfg(feature = "rtu")]
236 ReportServerId(&'r [u8], bool),
237 Custom(FunctionCode, &'r [u8]),
247}
248
249impl<'r> From<Request<'r>> for FunctionCode {
250 fn from(r: Request<'r>) -> Self {
251 use Request as R;
252
253 match r {
254 R::ReadCoils(_, _) => Self::ReadCoils,
255 R::ReadDiscreteInputs(_, _) => Self::ReadDiscreteInputs,
256 R::WriteSingleCoil(_, _) => Self::WriteSingleCoil,
257 R::WriteMultipleCoils(_, _) => Self::WriteMultipleCoils,
258 R::ReadInputRegisters(_, _) => Self::ReadInputRegisters,
259 R::ReadHoldingRegisters(_, _) => Self::ReadHoldingRegisters,
260 R::WriteSingleRegister(_, _) => Self::WriteSingleRegister,
261 R::WriteMultipleRegisters(_, _) => Self::WriteMultipleRegisters,
262 R::ReadWriteMultipleRegisters(_, _, _, _) => Self::ReadWriteMultipleRegisters,
263 #[cfg(feature = "rtu")]
264 R::ReadExceptionStatus => Self::ReadExceptionStatus,
265 #[cfg(feature = "rtu")]
266 R::Diagnostics(_, _) => Self::Diagnostics,
267 #[cfg(feature = "rtu")]
268 R::GetCommEventCounter => Self::GetCommEventCounter,
269 #[cfg(feature = "rtu")]
270 R::GetCommEventLog => Self::GetCommEventLog,
271 #[cfg(feature = "rtu")]
272 R::ReportServerId => Self::ReportServerId,
273 R::Custom(code, _) => code,
274 }
275 }
276}
277
278impl<'r> From<Response<'r>> for FunctionCode {
279 fn from(r: Response<'r>) -> Self {
280 use Response as R;
281
282 match r {
283 R::ReadCoils(_) => Self::ReadCoils,
284 R::ReadDiscreteInputs(_) => Self::ReadDiscreteInputs,
285 R::WriteSingleCoil(_) => Self::WriteSingleCoil,
286 R::WriteMultipleCoils(_, _) => Self::WriteMultipleCoils,
287 R::ReadInputRegisters(_) => Self::ReadInputRegisters,
288 R::ReadHoldingRegisters(_) => Self::ReadHoldingRegisters,
289 R::WriteSingleRegister(_, _) => Self::WriteSingleRegister,
290 R::WriteMultipleRegisters(_, _) => Self::WriteMultipleRegisters,
291 R::ReadWriteMultipleRegisters(_) => Self::ReadWriteMultipleRegisters,
292 #[cfg(feature = "rtu")]
293 R::ReadExceptionStatus(_) => Self::ReadExceptionStatus,
294 #[cfg(feature = "rtu")]
295 R::Diagnostics(_) => Self::Diagnostics,
296 #[cfg(feature = "rtu")]
297 R::GetCommEventCounter(_, _) => Self::GetCommEventCounter,
298 #[cfg(feature = "rtu")]
299 R::GetCommEventLog(_, _, _, _) => Self::GetCommEventLog,
300 #[cfg(feature = "rtu")]
301 R::ReportServerId(_, _) => Self::ReportServerId,
302 R::Custom(code, _) => code,
303 }
304 }
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum Exception {
310 IllegalFunction = 0x01,
311 IllegalDataAddress = 0x02,
312 IllegalDataValue = 0x03,
313 ServerDeviceFailure = 0x04,
314 Acknowledge = 0x05,
315 ServerDeviceBusy = 0x06,
316 MemoryParityError = 0x08,
317 GatewayPathUnavailable = 0x0A,
318 GatewayTargetDevice = 0x0B,
319}
320
321impl fmt::Display for Exception {
322 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323 let desc = match *self {
324 Self::IllegalFunction => "Illegal function",
325 Self::IllegalDataAddress => "Illegal data address",
326 Self::IllegalDataValue => "Illegal data value",
327 Self::ServerDeviceFailure => "Server device failure",
328 Self::Acknowledge => "Acknowledge",
329 Self::ServerDeviceBusy => "Server device busy",
330 Self::MemoryParityError => "Memory parity error",
331 Self::GatewayPathUnavailable => "Gateway path unavailable",
332 Self::GatewayTargetDevice => "Gateway target device failed to respond",
333 };
334 write!(f, "{desc}")
335 }
336}
337
338impl<'r> Request<'r> {
339 #[must_use]
341 pub fn pdu_len(&self) -> usize {
342 match *self {
343 Self::ReadCoils(_, _)
344 | Self::ReadDiscreteInputs(_, _)
345 | Self::ReadInputRegisters(_, _)
346 | Self::ReadHoldingRegisters(_, _)
347 | Self::WriteSingleRegister(_, _)
348 | Self::WriteSingleCoil(_, _) => 5,
349 Self::WriteMultipleCoils(_, coils) => 6 + coils.packed_len(),
350 Self::WriteMultipleRegisters(_, words) => 6 + words.data.len(),
351 Self::ReadWriteMultipleRegisters(_, _, _, words) => 10 + words.data.len(),
352 Self::Custom(_, data) => 1 + data.len(),
353 #[cfg(feature = "rtu")]
354 _ => todo!(), }
356 }
357}
358
359impl<'r> Response<'r> {
360 #[must_use]
362 pub fn pdu_len(&self) -> usize {
363 match *self {
364 Self::ReadCoils(coils) | Self::ReadDiscreteInputs(coils) => 2 + coils.packed_len(),
365 Self::WriteSingleCoil(_) => 3,
366 Self::WriteMultipleCoils(_, _)
367 | Self::WriteMultipleRegisters(_, _)
368 | Self::WriteSingleRegister(_, _) => 5,
369 Self::ReadInputRegisters(words)
370 | Self::ReadHoldingRegisters(words)
371 | Self::ReadWriteMultipleRegisters(words) => 2 + words.len() * 2,
372 Self::Custom(_, data) => 1 + data.len(),
373 Self::ReadExceptionStatus(_) => 2,
374 #[cfg(feature = "rtu")]
375 _ => unimplemented!(), }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382
383 use super::*;
384
385 #[test]
386 fn function_code_into_u8() {
387 let x: u8 = FunctionCode::WriteMultipleCoils.value();
388 assert_eq!(x, 15);
389 let x: u8 = FunctionCode::Custom(0xBB).value();
390 assert_eq!(x, 0xBB);
391 }
392
393 #[test]
394 fn function_code_from_u8() {
395 assert_eq!(FunctionCode::new(15), FunctionCode::WriteMultipleCoils);
396 assert_eq!(FunctionCode::new(0xBB), FunctionCode::Custom(0xBB));
397 }
398
399 #[test]
400 fn function_code_from_request() {
401 use Request::*;
402 let requests = &[
403 (ReadCoils(0, 0), 1),
404 (ReadDiscreteInputs(0, 0), 2),
405 (WriteSingleCoil(0, true), 5),
406 (
407 WriteMultipleCoils(
408 0,
409 Coils {
410 quantity: 0,
411 data: &[],
412 },
413 ),
414 0x0F,
415 ),
416 (ReadInputRegisters(0, 0), 0x04),
417 (ReadHoldingRegisters(0, 0), 0x03),
418 (WriteSingleRegister(0, 0), 0x06),
419 (
420 WriteMultipleRegisters(
421 0,
422 Data {
423 quantity: 0,
424 data: &[],
425 },
426 ),
427 0x10,
428 ),
429 (
430 ReadWriteMultipleRegisters(
431 0,
432 0,
433 0,
434 Data {
435 quantity: 0,
436 data: &[],
437 },
438 ),
439 0x17,
440 ),
441 (Custom(FunctionCode::Custom(88), &[]), 88),
442 ];
443 for (req, expected) in requests {
444 let code: u8 = FunctionCode::from(*req).value();
445 assert_eq!(*expected, code);
446 }
447 }
448
449 #[test]
450 fn function_code_from_response() {
451 use Response::*;
452 let responses = &[
453 (
454 ReadCoils(Coils {
455 quantity: 0,
456 data: &[],
457 }),
458 1,
459 ),
460 (
461 ReadDiscreteInputs(Coils {
462 quantity: 0,
463 data: &[],
464 }),
465 2,
466 ),
467 (WriteSingleCoil(0x0), 5),
468 (WriteMultipleCoils(0x0, 0x0), 0x0F),
469 (
470 ReadInputRegisters(Data {
471 quantity: 0,
472 data: &[],
473 }),
474 0x04,
475 ),
476 (
477 ReadHoldingRegisters(Data {
478 quantity: 0,
479 data: &[],
480 }),
481 0x03,
482 ),
483 (WriteSingleRegister(0, 0), 0x06),
484 (WriteMultipleRegisters(0, 0), 0x10),
485 (
486 ReadWriteMultipleRegisters(Data {
487 quantity: 0,
488 data: &[],
489 }),
490 0x17,
491 ),
492 (Custom(FunctionCode::Custom(99), &[]), 99),
493 ];
494 for (req, expected) in responses {
495 let code: u8 = FunctionCode::from(*req).value();
496 assert_eq!(*expected, code);
497 }
498 }
499
500 #[test]
501 fn test_request_pdu_len() {
502 assert_eq!(Request::ReadCoils(0x12, 5).pdu_len(), 5);
503 assert_eq!(Request::WriteSingleRegister(0x12, 0x33).pdu_len(), 5);
504 let buf = &mut [0, 0];
505 assert_eq!(
506 Request::WriteMultipleCoils(0, Coils::from_bools(&[true, false], buf).unwrap())
507 .pdu_len(),
508 7
509 );
510 }
512
513 #[test]
514 fn test_response_pdu_len() {
515 let buf = &mut [0, 0];
516 assert_eq!(
517 Response::ReadCoils(Coils::from_bools(&[true], buf).unwrap()).pdu_len(),
518 3
519 );
520 }
522}