File Coverage

blib/lib/Protocol/Modbus.pm
Criterion Covered Total %
statement 161 189 85.1
branch 10 22 45.4
condition 4 9 44.4
subroutine 49 54 90.7
pod 5 14 35.7
total 229 288 79.5


line stmt bran cond sub pod time code
1             package Protocol::Modbus;
2              
3 5     5   42088 use 5.006001;
  5         18  
  5         198  
4 5     5   29 use strict;
  5         10  
  5         190  
5 5     5   25 use warnings;
  5         11  
  5         151  
6 5     5   3266 use Protocol::Modbus::Request;
  5         15  
  5         125  
7 5     5   3020 use Protocol::Modbus::Response;
  5         12  
  5         149  
8 5     5   3429 use Protocol::Modbus::Transaction;
  5         13  
  5         135  
9 5     5   3123 use Protocol::Modbus::Transport;
  5         14  
  5         133  
10 5     5   30 use Protocol::Modbus::Exception;
  5         7  
  5         99  
11 5     5   24 use Carp;
  5         9  
  5         411  
12              
13             #------------------------------------------------
14             # Modbus module version
15             #------------------------------------------------
16             our $VERSION = '0.10';
17              
18             #------------------------------------------------
19             # Modbus related CONSTANTS
20             #------------------------------------------------
21              
22             # Function codes
23 5     5   25 use constant FUNC_READ_COILS => 0x01;
  5         9  
  5         331  
24 5     5   24 use constant FUNC_READ_INPUTS => 0x02;
  5         9  
  5         277  
25 5     5   26 use constant FUNC_READ_HOLD_REGISTERS => 0x03;
  5         22  
  5         295  
26 5     5   27 use constant FUNC_READ_INPUT_REGISTERS => 0x04;
  5         8  
  5         342  
27 5     5   25 use constant FUNC_WRITE_COIL => 0x05;
  5         9  
  5         231  
28 5     5   32 use constant FUNC_WRITE_REGISTER => 0x06;
  5         8  
  5         308  
29 5     5   24 use constant FUNC_READ_EXCEPTION_STATUS => 0x07;
  5         21  
  5         222  
30 5     5   25 use constant FUNC_DIAGNOSTICS => 0x08;
  5         10  
  5         197  
31 5     5   20 use constant FUNC_GET_COMM_EVENT_COUNTER => 0x0B;
  5         19  
  5         203  
32 5     5   24 use constant FUNC_GET_COMM_EVENT_LOG => 0x0C;
  5         7  
  5         204  
33 5     5   23 use constant FUNC_WRITE_COILS => 0x0F;
  5         9  
  5         198  
34 5     5   24 use constant FUNC_WRITE_REGISTERS => 0x10;
  5         7  
  5         190  
35 5     5   22 use constant FUNC_REPORT_SLAVE_ID => 0x11;
  5         8  
  5         203  
36 5     5   22 use constant FUNC_READ_FILE_RECORD => 0x1406;
  5         7  
  5         209  
37 5     5   22 use constant FUNC_WRITE_FILE_RECORD => 0x1506;
  5         7  
  5         210  
38 5     5   23 use constant FUNC_MASK_WRITE_REGISTER => 0x16;
  5         6  
  5         190  
39 5     5   23 use constant FUNC_RW_MULTIPLE_REGISTERS => 0x17;
  5         10  
  5         208  
40 5     5   21 use constant FUNC_READ_FIFO_QUEUE => 0x18;
  5         10  
  5         198  
41 5     5   22 use constant FUNC_CANOPEN_GEN_REFERENCE => 0x2B0D;
  5         8  
  5         176  
42 5     5   22 use constant FUNC_READ_DEVICE_ID => 0x2B0E;
  5         13  
  5         250  
43              
44             # Parameter types
45 5     5   22 use constant PARAM_ADDRESS => 1;
  5         7  
  5         208  
46 5     5   21 use constant PARAM_QUANTITY => 2;
  5         8  
  5         235  
47 5     5   32 use constant PARAM_VALUE => 3;
  5         7  
  5         238  
48 5     5   172 use constant PARAM_COUNT => 4;
  5         15  
  5         223  
49 5     5   48 use constant PARAM_OUTPUTS => 5;
  5         10  
  5         333  
50 5     5   24 use constant PARAM_MASK => 6;
  5         9  
  5         233  
51 5     5   25 use constant PARAM_IS_LIST => 8;
  5         7  
  5         214  
52 5     5   24 use constant PARAM_OUTPUT_LIST => 8;
  5         6  
  5         179  
53 5     5   31 use constant PARAM_REGISTER_LIST => 9;
  5         7  
  5         456  
54              
55             # How parameters are managed
56             #
57             # `n' => Big-endian word (16 bit)
58             # 'C' => Unsigned char (8 bit)
59             #
60 5         5006 use constant PARAM_SPEC => [
61             undef, # 0
62             ['address', 2, 'n'], # 1
63             ['quantity', 2, 'n'], # 2
64             ['value', 2, 'n'], # 3
65             ['count', 1, 'C'], # 4
66             ['outputs', 0, 'n*'], # 5
67             ['mask', 2, 'n'], # 6
68             undef, # 7
69             ['outputs', 0, 'n*'], # 8
70             ['registers', 0, 'n*'], # 9
71 5     5   23 ];
  5         14  
72              
73             #
74             # Class constructor
75             #
76             sub new {
77 5     5 0 4246 my ($obj, %args) = @_;
78 5   33     50 my $class = ref($obj) || $obj;
79 5         24 my $self = {_options => {%args},};
80              
81             # If driver property specified, load "additional" modbus class (TCP / RTU)
82 5 100 66     58 if (exists $args{driver} && $args{driver} ne '') {
83 1         4 $class = "Protocol::Modbus::$args{driver}";
84 1     1   87 eval "use $class";
  1         9  
  1         1  
  1         16  
85 1 50       4 if ($@) {
86 0         0 croak("Protocol::Modbus driver `$args{driver}' failed to load: $@");
87 0         0 return (undef);
88             }
89             }
90              
91 5         26 bless $self, $class;
92             }
93              
94             # Build a read coils request
95             sub readCoilsRequest {
96 2     2 1 730 my ($self, %args) = @_;
97 2         83 $args{function} = &Protocol::Modbus::FUNC_READ_COILS;
98 2         13 return $self->request(%args);
99             }
100              
101             sub readInputsRequest {
102 0     0 1 0 my ($self, %args) = @_;
103 0         0 $args{function} = &Protocol::Modbus::FUNC_READ_INPUTS;
104 0         0 return $self->request(%args);
105             }
106              
107             sub readHoldRegistersRequest {
108 2     2 1 12 my ($self, %args) = @_;
109 2         8 $args{function} = &Protocol::Modbus::FUNC_READ_HOLD_REGISTERS;
110 2         20 return $self->request(%args);
111             }
112              
113             sub writeCoilRequest {
114 3     3 1 459 my ($self, %args) = @_;
115 3         17 $args{function} = &Protocol::Modbus::FUNC_WRITE_COIL;
116              
117             # The only allowed values are 0x0000 and 0xFF00
118 3 100       28 if (!exists $args{value}) {
    50          
119 1         9 return throw Protocol::Modbus::Exception(
120             function => $args{function},
121             code => &Protocol::Modbus::Exception::ILLEGAL_DATA_VALUE
122             );
123             }
124             elsif ($args{value} != 0) {
125              
126             # Don't throw exception, auto-convert value (it's more perlish)
127             #
128             # return throw Protocol::Modbus::Exception(
129             # function => $args{function},
130             # code => &Protocol::Modbus::Exception::ILLEGAL_DATA_VALUE
131             # );
132             #
133 2         5 $args{value} = 0xFF00;
134             }
135              
136 2         15 return $self->request(%args);
137             }
138              
139             sub writeRegisterRequest {
140 1     1 1 4 my ($self, %args) = @_;
141 1         5 $args{function} = &Protocol::Modbus::FUNC_WRITE_REGISTER;
142              
143 1 50       6 if (!exists $args{value}) {
144 0         0 return throw Protocol::Modbus::Exception(
145             function => $args{function},
146             code => &Protocol::Modbus::Exception::ILLEGAL_DATA_VALUE
147             );
148             }
149 1         10 return $self->request(%args);
150             }
151              
152             sub close {
153 0     0 0 0 my $self = $_[0];
154 0         0 my $transport = $self->transport;
155 0         0 my $ok = 1;
156 0 0       0 if ($self->transport->connected()) {
157 0         0 $ok = $self->transport->disconnect();
158             }
159 0         0 return ($ok);
160             }
161              
162             # "Pure" Modbus protocol doesn't need to add anything to requests
163             sub processBeforeSend {
164 6     6 0 11 my ($self, $req) = @_;
165              
166             # noop
167 6         9 return ($req);
168             }
169              
170             sub processAfterReceive {
171 0     0 0 0 my ($self, $res) = @_;
172 0         0 return ($res);
173             }
174              
175             # Build a generic request
176             sub request {
177 12     12 0 41 my ($self, %req_params) = @_;
178 12 50       42 if (!exists $req_params{function}) {
179 0         0 croak('Invalid request() called without \'function\' parameter');
180 0         0 return (undef);
181             }
182              
183 12         70 my $req = Protocol::Modbus::Request->new(%req_params);
184              
185             # Add header and trailer (for TCP/RTU protocol flavours)
186 12         49 $self->processBeforeSend($req);
187              
188 12         47 return ($req);
189             }
190              
191             sub parseResponse {
192 0     0 0 0 my ($self, $res) = @_;
193              
194             # Response at this stage is only initialized with raw frame
195             # that came from transport layer
196              
197             # Let protocol layer "modify" raw data
198             # For example, Modbus/TCP should strip out the MBAP header...
199 0         0 $res = $self->processAfterReceive($res);
200              
201             # Invalid response!
202 0 0       0 if (!$res) {
203 0         0 warn('Received invalid response. Protocol layer refused data.');
204 0         0 return (undef);
205             }
206              
207             # Invalid PDU?
208 0 0       0 if (!$res->pdu()) {
209 0         0 warn('Invalid Modbus PDU!');
210 0         0 return (undef);
211             }
212              
213             # Ok, valid PDU. Process the response.
214 0         0 return ($res->process());
215             }
216              
217             sub transaction {
218 6     6 0 10 my ($self, $trs, $req) = @_;
219 6         7 my $oXact;
220              
221 6 50 33     25 if (!exists $self->{_transaction} || !$self->{_transaction}) {
222              
223             # Set transport object
224 6         23 $self->transport($trs);
225              
226             # Create a new transaction
227             #$self->{_transaction} =
228 6         40 $oXact = Protocol::Modbus::Transaction->new(
229             protocol => $self,
230             transport => $trs,
231             request => $req,
232             );
233              
234             #warn('Create new transaction (id=', $oXact->id(), ')');
235             #$self->{_transaction} = $oXact;
236             }
237             else {
238              
239             # Return the last generated transaction
240 0         0 $oXact = $self->{_transaction};
241             }
242              
243 6         17 return ($oXact);
244             }
245              
246             sub transport {
247 6     6 0 7 my $self = shift;
248 6 50       14 if (@_) {
249 6         15 $self->{_options}->{transport} = $_[0];
250             }
251 6         14 return ($self->{_options}->{transport});
252             }
253              
254             sub options {
255 0     0 0 0 my $self = $_[0];
256 0         0 return $self->{_options};
257             }
258              
259             1;
260              
261             __END__