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}