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