easy_modbus/frame/mod.rs
1use std::collections::HashMap;
2use std::io::ErrorKind;
3use std::sync::Mutex;
4
5use crate::frame::request::*;
6use crate::frame::response::*;
7
8pub mod request;
9pub mod response;
10
11/// Modbus Frame
12#[derive(Debug)]
13pub struct Frame {
14 /// Modbus protocol version (RTU or TCP)
15 version: Version,
16
17 /// Tid Buffer
18 tid_map: Mutex<HashMap<u8, u16>>,
19}
20
21impl Frame {
22 /// Create a TCP frame
23 ///
24 /// A Modbus variant used for communications over TCP/IP networks.
25 ///
26 /// # Examples
27 ///
28 /// ```
29 /// use easy_modbus::Frame;
30 /// let tcp = Frame::tcp();
31 /// ```
32 pub fn tcp() -> Frame {
33 Frame {
34 version: Version::Tcp,
35 tid_map: Mutex::new(HashMap::new()),
36 }
37 }
38
39 /// Create a RTU frame
40 ///
41 /// Used in serial communication, and is the most common implementation available for Modbus.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use easy_modbus::Frame;
47 /// let rut = Frame::rtu();
48 /// ```
49 pub fn rtu() -> Frame {
50 Frame {
51 version: Version::Rtu,
52 tid_map: Mutex::new(HashMap::new()),
53 }
54 }
55
56 /// Create a read coils request (Function Code: 0x01)
57 ///
58 /// * `unit_id` - Server address
59 /// * `first_address` - Address of first coil to read
60 /// * `number` - Number of coils to read
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use easy_modbus::Frame;
66 /// let request = Frame::tcp().read_coils_request(0x01, 0x02, 0x08);
67 /// ```
68 pub fn read_coils_request(&self, unit_id: u8, first_address: u16, number: u16) -> Request {
69 let function = Function::ReadCoils;
70 let request_body = ReadCoilsRequest::new(first_address, number);
71 let head = self.head(unit_id, function, request_body.len(), false);
72 Request::ReadCoils(head, request_body)
73 }
74
75 /// Create a read discrete Request (Function Code: 0x02)
76 ///
77 /// * `unit_id` - Server address
78 /// * `first_address` - Address of first discrete input to read
79 /// * `number` - Number of discrete input to read
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use easy_modbus::Frame;
85 /// let request = Frame::tcp().read_discrete_request(0x0B, 0x007A, 0x001C);
86 /// ```
87 pub fn read_discrete_request(&self, unit_id: u8, first_address: u16, number: u16) -> Request {
88 let function = Function::ReadDiscreteInputs;
89 let request_body = ReadDiscreteInputsRequest::new(first_address, number);
90 let head = self.head(unit_id, function, request_body.len(), false);
91 Request::ReadDiscreteInputs(head, request_body)
92 }
93
94 /// Create a read multiple holding registers request (Function Code: 0x03)
95 ///
96 /// * `unit_id` - Server address
97 /// * `first_address` - Address of first register to read
98 /// * `number` - Number of discrete input to read
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use easy_modbus::Frame;
104 /// let request = Frame::tcp().read_multiple_holding_registers_request(0x0B, 0x006F, 0x0003);
105 /// ```
106 pub fn read_multiple_holding_registers_request(
107 &self,
108 unit_id: u8,
109 first_address: u16,
110 number: u16,
111 ) -> Request {
112 let function = Function::ReadMultipleHoldingRegisters;
113 let request_body = ReadMultipleHoldingRegistersRequest::new(first_address, number);
114 let head = self.head(unit_id, function, request_body.len(), false);
115 Request::ReadMultipleHoldingRegisters(head, request_body)
116 }
117
118 /// Create a read input registers request (Function Code: 0x04)
119 ///
120 /// * `unit_id` - Server address
121 /// * `first_address` - Address of first register to read
122 /// * `number` - Number of registers to read
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// use easy_modbus::Frame;
128 /// let request = Frame::tcp().read_input_registers_request(0x0B, 0x000A, 0x0001);
129 /// ```
130 pub fn read_input_registers_request(
131 &self,
132 unit_id: u8,
133 first_address: u16,
134 number: u16,
135 ) -> Request {
136 let function = Function::ReadInputRegisters;
137 let request_body = ReadInputRegistersRequest::new(first_address, number);
138 let head = self.head(unit_id, function, request_body.len(), false);
139 Request::ReadInputRegisters(head, request_body)
140 }
141
142 /// Create a write single coil request (Function Code: 0x05)
143 ///
144 /// * `unit_id` - Server address
145 /// * `address` - Address of coil to write
146 /// * `value` - Value to write. 0 (0x0000) for off, 65,280 (0xFF00) for on.
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// use easy_modbus::Frame;
152 /// let request = Frame::tcp().write_single_coil_request(0x0B, 0x00BF, 0x0000);
153 /// ```
154 pub fn write_single_coil_request(&self, unit_id: u8, address: u16, value: u16) -> Request {
155 let function = Function::WriteSingleCoil;
156 let request_body = WriteSingleCoilRequest::new(address, value);
157 let head = self.head(unit_id, function, request_body.len(), false);
158 Request::WriteSingleCoil(head, request_body)
159 }
160
161 /// Create a write single holding register request (Function Code: 0x06)
162 ///
163 /// * `unit_id` - Server address
164 /// * `address` - Address of Holding Register to write
165 /// * `value` - Value to write
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use easy_modbus::Frame;
171 /// let request = Frame::tcp().write_single_holding_register_request(0x0B, 0x0004, 0xABCD);
172 /// ```
173 pub fn write_single_holding_register_request(
174 &self,
175 unit_id: u8,
176 address: u16,
177 value: u16,
178 ) -> Request {
179 let function = Function::WriteSingleHoldingRegister;
180 let request_body = WriteSingleHoldingRegisterRequest::new(address, value);
181 let head = self.head(unit_id, function, request_body.len(), false);
182 Request::WriteSingleHoldingRegister(head, request_body)
183 }
184
185 /// Create a write multiple coils request (Function Code: 0x0F)
186 ///
187 /// * `unit_id` - Server address
188 /// * `address` - Address of Holding Register to write
189 /// * `coils_number` - Number of coils to write
190 /// * `values` - Coil values. Value of each coil is binary (0 for off, 1 for on).
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// use easy_modbus::Frame;
196 /// let request = Frame::tcp().write_multiple_coils_request(
197 /// 0x0B,
198 /// 0x001B,
199 /// 0x0009,
200 /// vec![0x4D, 0x01]
201 /// );
202 /// ```
203 pub fn write_multiple_coils_request(
204 &self,
205 unit_id: u8,
206 address: u16,
207 coils_number: u16,
208 values: Vec<u8>,
209 ) -> Request {
210 let function = Function::WriteMultipleCoils;
211 let request_body = WriteMultipleCoilsRequest::new(address, coils_number, values);
212 let head = self.head(unit_id, function, request_body.len(), false);
213 Request::WriteMultipleCoils(head, request_body)
214 }
215
216 /// Create a write multiple coils request (Function Code: 0x10)
217 ///
218 /// * `unit_id` - Server address
219 /// * `address` - Address of first holding registers to write
220 /// * `values` - New values of holding registers
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use easy_modbus::Frame;
226 /// let request = Frame::tcp().write_multiple_holding_registers_request(
227 /// 0x0B,
228 /// 0x0012,
229 /// vec![0x0B, 0x0A, 0xC1, 0x02],
230 /// );
231 /// ```
232 pub fn write_multiple_holding_registers_request(
233 &self,
234 unit_id: u8,
235 address: u16,
236 values: Vec<u8>,
237 ) -> Request {
238 let function = Function::WriteMultipleHoldingRegisters;
239 let request_body = WriteMultipleHoldingRegistersRequest::new(address, values);
240 let head = self.head(unit_id, function, request_body.len(), false);
241 Request::WriteMultipleHoldingRegisters(head, request_body)
242 }
243
244 /// Create a read coils response (Function Code: 0x01)
245 ///
246 /// * `unit_id` - Server address
247 /// * `values` - Coil input values, Values of each coil input is binary (0 for off, 1 for on).
248 /// First requested coil input is as least significant bit of first byte in reply. If number of
249 /// coils inputs is not a multiple of 8, most significant bits of last byte will be stuffed zeros.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// use easy_modbus::Frame;
255 /// let response = Frame::tcp().read_coils_response(0x0B, vec![0xCD, 0x6B, 0xB2, 0x7F]);
256 /// ```
257 pub fn read_coils_response(&self, unit_id: u8, values: Vec<u8>) -> Response {
258 let function = Function::ReadCoils;
259 let response_body = ReadCoilsResponse::new(values);
260 let head = self.head(unit_id, function, response_body.len(), false);
261 Response::ReadCoils(head, response_body)
262 }
263
264 /// Create a read discrete response (Function Code: 0x02)
265 ///
266 /// * `unit_id` - Server address
267 /// * `values` - Discrete input values
268 ///
269 /// # Examples
270 ///
271 /// ```
272 /// use easy_modbus::Frame;
273 /// let response = Frame::tcp().read_discrete_response(0x0B, vec![0xAC, 0xDB, 0xFB, 0x0D]);
274 /// ```
275 pub fn read_discrete_response(&self, unit_id: u8, values: Vec<u8>) -> Response {
276 let function = Function::ReadDiscreteInputs;
277 let response_body = ReadDiscreteInputsResponse::new(values);
278 let head = self.head(unit_id, function, response_body.len(), false);
279 Response::ReadDiscreteInputs(head, response_body)
280 }
281
282 /// Create a read holding register response (Function Code: 0x03)
283 ///
284 /// * `unit_id` - Server address
285 /// * `values` - Discrete input values
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// use easy_modbus::Frame;
291 /// let response = Frame::tcp().read_discrete_response(0x0B, vec![0xAC, 0xDB, 0xFB, 0x0D]);
292 /// ```
293 pub fn read_holding_register_response(&self, unit_id: u8, values: Vec<u8>) -> Response {
294 let function = Function::ReadMultipleHoldingRegisters;
295 let response_body = ReadMultipleHoldingRegistersResponse::new(values);
296 let head = self.head(unit_id, function, response_body.len(), false);
297 Response::ReadMultipleHoldingRegisters(head, response_body)
298 }
299
300 /// Create a read input register response (Function Code: 0x04)
301 ///
302 /// * `unit_id` - Server address
303 /// * `values` - Register values
304 ///
305 /// # Examples
306 ///
307 /// ```
308 /// use easy_modbus::Frame;
309 /// let response = Frame::tcp().read_input_register_response(0x01, vec![0x10, 0x2F]);
310 /// ```
311 pub fn read_input_register_response(&self, unit_id: u8, values: Vec<u8>) -> Response {
312 let function = Function::ReadInputRegisters;
313 let response_body = ReadInputRegistersResponse::new(values);
314 let head = self.head(unit_id, function, response_body.len(), false);
315 Response::ReadInputRegisters(head, response_body)
316 }
317
318 /// Create a write single coil response (Function Code: 0x05)
319 ///
320 /// * `unit_id` - Server address
321 /// * `values` - Register values
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// use easy_modbus::Frame;
327 /// let response = Frame::tcp().write_single_coil_response(0x01, 0x00BF, 0x0000);
328 /// ```
329 pub fn write_single_coil_response(&self, unit_id: u8, address: u16, value: u16) -> Response {
330 let function = Function::WriteSingleCoil;
331 let response_body = WriteSingleCoilResponse::new(address, value);
332 let head = self.head(unit_id, function, response_body.len(), false);
333 Response::WriteSingleCoil(head, response_body)
334 }
335
336 /// Create a write single coil response (Function Code: 0x06)
337 ///
338 /// * `unit_id` - Server address
339 /// * `address` - Address of coil
340 /// * `value` - Value to write. 0 (0x0000) for off, 65,280 (0xFF00) for on.
341 ///
342 /// # Examples
343 ///
344 /// ```
345 /// use easy_modbus::Frame;
346 /// let response = Frame::tcp().write_single_holding_register_response(0x01, 0x0004, 0xFF00);
347 /// ```
348 pub fn write_single_holding_register_response(
349 &self,
350 unit_id: u8,
351 address: u16,
352 value: u16,
353 ) -> Response {
354 let function = Function::WriteSingleHoldingRegister;
355 let response_body = WriteSingleHoldingRegisterResponse::new(address, value);
356 let head = self.head(unit_id, function, response_body.len(), false);
357 Response::WriteSingleHoldingRegister(head, response_body)
358 }
359
360 /// Create a write multiple coils response (Function Code: 0x0F)
361 ///
362 /// * `unit_id` - Server address
363 /// * `address` - Address of first written holding register
364 /// * `coils_number` - Number of written holding registers
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use easy_modbus::Frame;
370 /// let response = Frame::tcp().write_multiple_coils_response(0x01, 0x001B, 0x0009);
371 /// ```
372 pub fn write_multiple_coils_response(
373 &self,
374 unit_id: u8,
375 address: u16,
376 number: u16,
377 ) -> Response {
378 let function = Function::WriteMultipleCoils;
379 let response_body = WriteMultipleCoilsResponse::new(address, number);
380 let head = self.head(unit_id, function, response_body.len(), false);
381 Response::WriteMultipleCoils(head, response_body)
382 }
383
384 /// Create a write multiple holding registers response (Function Code: 0x10)
385 ///
386 /// * `unit_id` - Server address
387 /// * `address` - Address of first written holding register
388 /// * `coils_number` - Number of written holding registers
389 ///
390 /// # Examples
391 ///
392 /// ```
393 /// use easy_modbus::Frame;
394 /// let response = Frame::tcp().write_multiple_holding_registers_response(0x01, 0x0012, 0x0002);
395 /// ```
396 pub fn write_multiple_holding_registers_response(
397 &self,
398 unit_id: u8,
399 address: u16,
400 number: u16,
401 ) -> Response {
402 let function = Function::WriteMultipleHoldingRegisters;
403 let response_body = WriteMultipleHoldingRegistersResponse::new(address, number);
404 let head = self.head(unit_id, function, response_body.len(), false);
405 Response::WriteMultipleHoldingRegisters(head, response_body)
406 }
407
408 /// Create a exception response
409 ///
410 /// * `unit_id` - Server address
411 /// * `function` - Modbus Function enum
412 /// * `exception` - Modbus Exception enum
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// use easy_modbus::{Exception, Frame, Function};
418 /// let response = Frame::tcp().exception_response(
419 /// 0x0A,
420 /// Function::ReadCoils,
421 /// Exception::IllegalDataAddress,
422 /// );
423 /// ```
424 pub fn exception_response(
425 &self,
426 unit_id: u8,
427 function: Function,
428 exception: Exception,
429 ) -> Response {
430 let response_body = ExceptionResponse::new(exception);
431 let head = self.head(unit_id, function, response_body.len(), true);
432 Response::Exception(head, response_body)
433 }
434
435
436 /// Build modbus message head
437 fn head(&self, uid: u8, function: Function, body_length: u16, is_exception: bool) -> Head {
438 Head::new(
439 self.get_tid(uid),
440 uid,
441 function,
442 body_length,
443 self.version,
444 is_exception,
445 )
446 }
447
448 /// Get tid by uid from tid_map
449 fn get_tid(&self, unit_id: u8) -> u16 {
450 if self.version == Version::Rtu {
451 return 0;
452 }
453
454 let mut map = self.tid_map.lock().unwrap();
455 let value = match map.get(&unit_id) {
456 None => 1,
457 Some(v) => {
458 if v < &0xFFFF {
459 v + 1
460 } else {
461 1
462 }
463 }
464 };
465 map.insert(unit_id, value);
466 value
467 }
468}
469
470/// Protocol versions
471///
472/// Versions of the Modbus protocol exist for serial ports, and for Ethernet and other protocols
473/// that support the Internet protocol suite. BUT NOW JUST SUPPORT **TCP** AND **RTU**.
474#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
475pub enum Version {
476 Tcp,
477 Rtu,
478}
479
480#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
481pub struct Head {
482 /// Transaction Identifier
483 pub(crate) tid: u16,
484
485 /// Protocol Identifier
486 pub(crate) pid: u16,
487
488 /// Pack length
489 pub(crate) length: u16,
490
491 /// Server address(Tcp) or Slave address(Rtu)
492 pub(crate) uid: u8,
493
494 /// Modbus Function
495 pub(crate) function: Function,
496
497 /// Frame version
498 pub(crate) version: Version,
499
500 /// Check is exception
501 pub(crate) is_exception: bool,
502}
503
504/// Exception types
505#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
506pub enum Exception {
507 /// Code 1
508 ///
509 /// Function code received in the query is not recognized or allowed by server
510 IllegalFunction,
511
512 /// Code 2
513 ///
514 /// Data address of some or all the required entities are not allowed or do not exist in server
515 IllegalDataAddress,
516
517 /// Code 3
518 ///
519 /// Value is not accepted by server
520 IllegalDataValue,
521
522 /// Code 5
523 ///
524 /// Unrecoverable error occurred while server was attempting to perform requested action
525 SlaveDeviceFailure,
526
527 /// Code 6
528 ///
529 /// Server has accepted request and is processing it, but a long duration of time is required.
530 /// This response is returned to prevent a timeout error from occurring in the client. client
531 /// can next issue a Poll Program Complete message to determine whether processing is completed
532 Acknowledge,
533}
534
535impl Exception {
536 pub(crate) fn to_code(&self) -> u8 {
537 use Exception::*;
538 match self {
539 IllegalFunction => 0x01,
540 IllegalDataAddress => 0x02,
541 IllegalDataValue => 0x03,
542 SlaveDeviceFailure => 0x04,
543 Acknowledge => 0x05,
544 }
545 }
546 pub(crate) fn from_code(code: u8) -> Option<Exception> {
547 use Exception::*;
548 let exception = match code {
549 0x01 => IllegalDataValue,
550 0x02 => IllegalDataAddress,
551 0x03 => IllegalDataValue,
552 0x04 => SlaveDeviceFailure,
553 0x05 => Acknowledge,
554 _ => {
555 return None;
556 }
557 };
558 Some(exception)
559 }
560 pub(crate) fn as_error_kind(&self) -> ErrorKind {
561 use Exception::*;
562 match self {
563 IllegalFunction => ErrorKind::Unsupported,
564 IllegalDataAddress => ErrorKind::AddrNotAvailable,
565 IllegalDataValue => ErrorKind::InvalidData,
566 SlaveDeviceFailure => ErrorKind::Interrupted,
567 Acknowledge => ErrorKind::WouldBlock,
568 }
569 }
570}
571
572/// Modbus functions
573#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
574pub enum Function {
575 ReadCoils,
576 ReadDiscreteInputs,
577 ReadMultipleHoldingRegisters,
578 ReadInputRegisters,
579 WriteSingleCoil,
580 WriteSingleHoldingRegister,
581 WriteMultipleCoils,
582 WriteMultipleHoldingRegisters,
583}
584
585trait Length {
586 fn len(&self) -> u16;
587}
588
589impl Function {
590 pub(crate) fn to_code(&self) -> u8 {
591 use Function::*;
592 match self {
593 ReadCoils => 0x01,
594 ReadDiscreteInputs => 0x02,
595 ReadMultipleHoldingRegisters => 0x03,
596 ReadInputRegisters => 0x04,
597 WriteSingleCoil => 0x05,
598 WriteSingleHoldingRegister => 0x06,
599 WriteMultipleCoils => 0x0F,
600 WriteMultipleHoldingRegisters => 0x10,
601 }
602 }
603}
604
605impl Head {
606 pub fn new(
607 tid: u16,
608 uid: u8,
609 function: Function,
610 body_length: u16,
611 version: Version,
612 is_exception: bool,
613 ) -> Head {
614 Head {
615 tid,
616 pid: 0x00,
617 length: body_length + 2,
618 uid,
619 function,
620 version,
621 is_exception,
622 }
623 }
624
625 pub fn body_length(&mut self, body_length: u16) {
626 self.length = body_length + 2;
627 }
628}
629
630#[test]
631fn test_head() {
632 let head_l = Head::new(0x01, 0x02, Function::ReadCoils, 4, Version::Tcp, false);
633 let head_r = Head {
634 tid: 0x01,
635 pid: 0x00,
636 length: 6,
637 function: Function::ReadCoils,
638 uid: 0x02,
639 version: Version::Tcp,
640 is_exception: false,
641 };
642 assert_eq!(head_l, head_r);
643}