File Coverage

blib/lib/Protocol/Modbus/TCP.pm
Criterion Covered Total %
statement 32 55 58.1
branch 2 8 25.0
condition 2 3 66.6
subroutine 8 10 80.0
pod 0 5 0.0
total 44 81 54.3


line stmt bran cond sub pod time code
1             package Protocol::Modbus::TCP;
2              
3 2     2   4447 use strict;
  2         6  
  2         110  
4 2     2   13 use warnings;
  2         5  
  2         67  
5 2     2   13 use Protocol::Modbus;
  2         11  
  2         40  
6 2     2   11 use Protocol::Modbus::Request;
  2         4  
  2         48  
7 2     2   11 use Carp;
  2         3  
  2         1279  
8              
9             # Derive from Protocol::Modbus
10             @Protocol::Modbus::TCP::ISA = 'Protocol::Modbus';
11              
12             sub request {
13 6     6 0 26 my ($self, %args) = @_;
14              
15             # Unit is useless in TCP/IP messages
16 6 100 66     31 if (!exists $args{unit} || !$args{unit}) {
17 2         6 $args{unit} = 0xFF;
18             }
19              
20             # Pass control to super class
21 6         42 return $self->SUPER::request(%args);
22             }
23              
24             # Needed to encapsulate modbus request with the MBAP header
25             # to be transmitted via TCP/IP
26             sub requestHeader {
27 6     6 0 9 my ($self, $req) = @_;
28              
29             # If not one, open a transaction
30 6         23 my $trans = $self->transaction();
31              
32 6         21 my $req_pdu = $req->pdu();
33              
34             # Assemble MBAP Header as follows
35 6         23 my $trans_id = $trans->id();
36 6         8 my $proto_id = 0x0000; # Modbus = 0x0000
37 6         13 my $length = 1 + length $req_pdu; # 1 Byte Unit + N bytes request
38 6         17 my $unit = $req->options->{unit};
39              
40             # Pack the MBAP header
41 6         22 my $mbap = pack('nnnC', $trans_id, $proto_id, $length, $unit);
42              
43             #warn('Computed MBAP [' . unpack('H*', $mbap) . ']');
44 6         32 return ($mbap);
45             }
46              
47             sub extractPdu {
48 0     0 0 0 my ($self, $transaction, $raw_data) = @_;
49 0         0 my ($mbap, $pdu);
50              
51             # Match transaction ids
52 0         0 my $prev_tid = $transaction->id();
53 0         0 my $this_tid = ord substr($raw_data, 0, 1);
54              
55             # If transaction id does not match, we must ignore this message
56             #if( $prev_tid != $this_tid )
57             #{
58             # # XXX Raise exception?
59             # warn('Transaction IDs don\'t match (prev=', $prev_tid, ', this=', $this_tid, ')');
60             # return();
61             #}
62              
63             # Now unpack the raw MBAP data into fields
64 0         0 my ($tid, $protocol, $count, $unit) = unpack('nnnC', $raw_data);
65              
66             #warn('tid=', $tid);
67             #warn('protocol=', $protocol);
68             #warn('count=', $count);
69             #warn('unit=', $unit);
70              
71             # Protocol id must be modbus (0x0000)
72 0 0       0 if ($protocol != 0) {
73              
74             # XXX Raise exception?
75 0         0 warn('Protocol isn\'t 0x0000 (Modbus)');
76 0         0 return ();
77             }
78              
79             # Shouldn't be?
80 0 0       0 if ($unit != 0xFF) {
81              
82             # XXX So what?
83             }
84              
85             # Split MBAP and PDU
86 0         0 $mbap = substr($raw_data, 0, 7);
87 0         0 $pdu = substr($raw_data, 7, $count);
88              
89 0         0 return ($mbap, $pdu);
90             }
91              
92             # Process binary data after receiving
93             # Protocol should be responsible for processing binary
94             # packets to obtain a single Modbus PDU frame
95             #
96             # Modbus/TCP packets are composed of [MBAP + PDU]
97             #
98             sub processAfterReceive {
99 0     0 0 0 my ($self, $res) = @_;
100 0         0 my $raw_data = $res->frame();
101 0         0 my ($mbap, $pdu);
102              
103             # Check that MBAP header corresponds to current transaction
104 0         0 my $trn = $self->transaction();
105              
106             #warn('Extracting PDU from [', uc unpack('H*', $raw_data), ']');
107              
108 0         0 eval { ($mbap, $pdu) = $self->extractPdu($trn, $raw_data); };
  0         0  
109 0 0       0 if ($@) {
110 0         0 warn('Exception generated! (', $@, ')');
111 0         0 return ($@);
112             }
113              
114             #warn('MBAP=[', unpack('H*', $mbap), ']');
115             #warn('PDU =[', unpack('H*', $pdu ), ']');
116              
117             # Set response PDU field
118 0         0 $res->pdu($pdu);
119              
120 0         0 return ($res);
121             }
122              
123             # Process a request before sending on the wirea
124             # Add MBAP header
125             sub processBeforeSend {
126 6     6 0 10 my ($self, $req) = @_;
127 6         17 my $mbap = $self->requestHeader($req);
128 6         17 $req->header($mbap);
129 6         12 return ($req);
130             }
131              
132             1;
133