File Coverage

Device/SNP.pm
Criterion Covered Total %
statement 164 200 82.0
branch 52 96 54.1
condition 8 21 38.1
subroutine 22 25 88.0
pod n/a
total 246 342 71.9


line stmt bran cond sub pod time code
1             # SNP.pm
2             #
3             # Implement GE Fanuc SNP protocol on serial line
4             # Protocol is described at http://globalcare.gefanuc.com in
5             # these documents:
6             # GFK-0582D SNP-X
7             # GFK-0529C SNP
8             # ONly slave is supported so far.
9             # You can subclass and override functions to implement
10             # specific device features, or implemnt your own data storage and access functions.
11             # By default it reads and writes data from the data segments
12             # in %Device::SNP::segment
13             #
14             # Author: Mike McCauley (mikem@airspayce.com)
15             # Copyright (C) 2006 Mike McCauley
16             # $Id: SNP.pm,v 1.1 2006/05/31 23:30:53 mikem Exp mikem $
17 1     1   8861 use Device::SerialPort;
  1         43222  
  1         72  
18 1     1   12 use strict;
  1         2  
  1         4661  
19              
20             package Device::SNP;
21             our $VERSION = '1.3';
22              
23             $Device::SNP::StartOfMessage = 0x1b;
24             $Device::SNP::EndOfBlock = 0x17;
25              
26             $Device::SNP::BroadcastSNPID = "\xff\xff\xff\xff\xff\xff\xff\xff";
27             $Device::SNP::NullSNPID = "\x00\x00\x00\x00\x00\x00\x00\x00";
28              
29             # SNP Message types (not used here, except for XMessage and Text)
30             $Device::SNP::MtypeAttach = 0x41;
31             $Device::SNP::MtypeAttachResponse = 0x52;
32             $Device::SNP::MtypeMailbox = 0x4d;
33             $Device::SNP::MtypeText = 0x54;
34             $Device::SNP::MtypeBlockTransfer = 0x42;
35             $Device::SNP::MtypeConnection = 0x43;
36             $Device::SNP::MtypeUpdate = 0x55;
37             $Device::SNP::MtypeInquiry = 0x49;
38             $Device::SNP::MtypeXMessage = 0x58;
39              
40             # SNP-X Message types
41             $Device::SNP::XtypeAttach = 0x00;
42             $Device::SNP::XtypeAttachResponse = 0x80;
43             $Device::SNP::XtypeRead = 0x01;
44             $Device::SNP::XtypeReadResponse = 0x81;
45             $Device::SNP::XtypeWrite = 0x02;
46             $Device::SNP::XtypeWriteResponse = 0x82;
47              
48             # Major error codes
49             $Device::SNP::ErrorMajorNone = 0x00;
50             $Device::SNP::ErrorMajorServiceRequestError = 0x05;
51              
52             # Minor error codes
53             $Device::SNP::ErrorMinorNone = 0x00;
54             $Device::SNP::ErrorMinorInvalidInputParameter = 0xf4;
55              
56             # These variables hold the PLC data in raw format
57             # They will auto-vivify to be as big as needed
58             %Device::SNP::segment =
59             (
60             'R' => [],
61             'AI' => [],
62             'AQ' => [],
63             'I' => [],
64             'Q' => [],
65             'T' => [],
66             'M' => [],
67             'SA' => [],
68             'SB' => [],
69             'SC' => [],
70             'S' => [],
71             'G' => [],
72             );
73              
74             # Description of the data segments we can handle.
75             # The index is the PLC memory type code from table 6-1
76             # The addressing type is word, byte or bit
77             %Device::SNP::segments =
78             (
79             0x08 => ['R', 'word'], # Registers %R, word
80             0x0a => ['AI', 'word'], # Analog Inputs %AI, word
81             0x0c => ['AQ', 'word'], # Analog Outputs %AQ, word
82              
83             0x46 => ['I', 'bit'], # Discrete Inputs %I, bit
84             0x10 => ['I', 'byte'], # Discrete Inputs %I, byte
85              
86             0x48 => ['Q', 'bit'], # Discrete Outputs %Q, bit
87             0x12 => ['Q', 'byte'], # Discrete Outputs %Q, byte
88              
89             0x4a => ['T', 'bit'], # Discrete Temporaries %T, bit
90             0x14 => ['T', 'byte'], # Discrete Temporaries %T, byte
91              
92             0x4c => ['M', 'bit'], # Discrete Internals %M, bit
93             0x16 => ['M', 'byte'], # Discrete Internals %M, byte
94              
95             0x4e => ['SA', 'bit'], # Discretes %SA, bit
96             0x18 => ['SA', 'byte'], # Discretes %SA, byte
97              
98             0x50 => ['SB', 'bit'], # Discretes %SB, bit
99             0x1a => ['SB', 'byte'], # Discretes %SB, byte
100              
101             0x52 => ['SC', 'bit'], # Discretes %SC, bit
102             0x1c => ['SC', 'byte'], # Discretes %SC, byte
103              
104             0x54 => ['S', 'bit'], # Discretes %S read only, bit
105             0x1e => ['S', 'byte'], # Discretes %S, byte
106              
107             0x56 => ['G', 'bit'], # Genius Global Data %G, bit
108             0x38 => ['G', 'byte'], # Genius Global Data %G, byte
109             );
110              
111             # Raw data formats:
112             # decimal raw reversed
113             # bit access in a word segment:
114             # 0 0000
115             # 1 0100
116             # word is 2 bytes. LSB first, MSB second
117             # 0 0000
118             # 1 0100
119             # 256 0001
120             # 258 0201
121             # dword is 4 bytes. LSB first, MSB last
122             # 0 00000000
123             # 1 01000000
124             # 256 00010000
125             # 65536 00000100
126             # 16777216 00000001
127             # 6730598g5 01020304
128             # Floating point is 4 bytes:
129             # -8 000000c1
130             # -4 000080c0
131             # -2 000000c0
132             # -1 000080bf
133             # 0 00000000
134             # 1 0000803f
135             # 2 00000040
136             # 4 00008040
137             # 8 00000041
138             # 10 00002041
139             # 16 00008041
140              
141             # Tests:
142             # broadcast xattach:
143             #&handle_raw_message(pack('H*', '1b58ffffffffffffffff0000000000000000170000000079'));
144             # point-to-point xattach:
145             #&handle_raw_message(pack('H*', '1b5800000000000000000000000000000000170000000079'));
146             # read:
147             #&handle_raw_message(pack('H*', '1b5800000000000000000146010001000000170000000037'));
148             #&handle_raw_message(pack('H*', '1B584142434445460000010800000400000017000000001A'));
149             #exit;
150              
151             package Device::SNP::Slave;
152              
153             #####################################################################
154             sub new
155             {
156 1     1   121 my ($class, %args) = @_;
157              
158 1         4 my $self = {};
159 1         3 bless $self, $class;
160              
161             # Initialize some values
162 1         7 $self->{Portname} = '/dev/ttyS1';
163 1         2 $self->{SNPID} = '';
164 1         2 $self->{Debug} = 0;
165              
166             # Override with args
167 1         5 map {$self->{$_} = $args{$_}} (keys %args);
  2         9  
168              
169 1         3 $self->{attached} = 0;
170 1         3 $self->{plcstatusword} = 0;
171             # Expected values for next packet, in case of deferred write
172 1         3 $self->{expecttype} = 0;
173 1         2 $self->{expectlength} = 24;
174 1         4 $self->{expectSelector} = 0;
175 1         2 $self->{expectOffset} = 0;
176 1         2 $self->{expectLength} = 0;
177              
178 1         5 return $self;
179             }
180              
181             #####################################################################
182             sub run
183             {
184 0     0   0 my ($self) = @_;
185              
186             # Open the port
187              
188 0         0 my $port = new Device::SerialPort($self->{Portname});
189 0 0       0 die "Could not open serial port $self->{Portname}: $!" unless $port;
190              
191             # Set up the port for standard SNP
192 0         0 $port->baudrate(19200);
193 0         0 $port->databits(8);
194 0         0 $port->parity('odd');
195 0         0 $port->stopbits(1);
196 0         0 $port->handshake('none');
197 0         0 $port->write_settings();
198 0         0 $port->read_char_time(0);
199 0         0 $port->read_const_time(1000);
200 0         0 $port->stty_icanon(0);
201             # $port->save('/tmp/xx');
202 0         0 $self->{port} = $port;
203 0         0 $self->main_loop();
204             }
205              
206             #####################################################################
207             sub main_loop
208             {
209 1     1   85 my ($self) = @_;
210              
211 1         3 while (1)
212             {
213 11         16 my ($count, $in, $buf);
214            
215             # Wait for Start-Of-Message
216 11         14 while (1)
217             {
218 11         3294 ($count, $in) = $self->{port}->read(1);
219 11 50 33     145 last if ($count == 1) && ($in eq "\x1b");
220             }
221 11         18 $buf = $in;
222 11         42 ($count, $in) = $self->{port}->read($self->{expectlength} - 1);
223 11 50       172 next unless $count == ($self->{expectlength} - 1);
224 11         20 $buf .= $in;
225            
226 11         31 $self->handle_raw_message($buf);
227             }
228             }
229              
230             #####################################################################
231             sub handle_raw_message
232             {
233 11     11   16 my ($self, $msg) = @_;
234              
235 11         43 my $hex = unpack('H*', $msg);
236 11 50       30 print "receive: $hex\n" if $self->{Debug};
237              
238             # Calculate the correct BCC, using everything except
239             # the last byte which is the received BCC
240 11         34 my $mybcc = &compute_bcc(substr($msg, 0, -1));
241              
242             # Now have a complete 24 byte SNP command in $buf, including the start char
243             # Unpack into header data and trailer
244 11         28 my $header = substr($msg, 0, 2);
245 11         18 my $cmddata = substr($msg, 2, -6);
246 11         23 my $trailer = substr($msg, -6);
247              
248             # Decode header and trailer
249 11         31 my ($som, $mtype) = unpack('C C', $header);
250 11         34 my ($eob, $nexttype, $nextlength, $unused, $bcc) = unpack('C C v C C', $trailer);
251              
252             # Message contents checks
253 11 50 66     80 if ($bcc != $mybcc)
    50          
    50          
    50          
254             {
255 0         0 warn "Bad BCC, should be $mybcc, received $bcc";
256             }
257             elsif ($som != $Device::SNP::StartOfMessage)
258             {
259 0         0 warn "Incorrect Start-Of-Message";
260             }
261             elsif ($eob != $Device::SNP::EndOfBlock)
262             {
263 0         0 warn "Incorrect EndOf-Block";
264             }
265             elsif ($self->{expecttype} && $self->{expecttype} != $mtype)
266             {
267 0         0 warn "Expected next type of $nexttype, but received $mtype";
268             }
269             else
270             {
271             # OK
272             # Check whether there is info about the next expected type
273 11         17 $self->{expecttype} = $nexttype;
274 11         18 $self->{expectlength} = 24;
275 11 100       22 $self->{expectlength} = $nextlength
276             if $nexttype;
277             # Dispatch the message
278 11         34 $self->handle_message($mtype, $cmddata);
279             }
280             }
281              
282             #####################################################################
283             sub handle_message
284             {
285 11     11   17 my ($self, $mtype, $cmddata) = @_;
286              
287 11 50       30 print "handle_message $mtype\n" if $self->{Debug};
288 11 100       90 if ($mtype == $Device::SNP::MtypeXMessage)
    50          
289             {
290 10         39 my ($snpid, $reqcode, $data) = unpack('a8 C a*', $cmddata);
291 10         33 $self->handle_x_message($snpid, $reqcode, $data);
292             }
293             elsif ($mtype == $Device::SNP::MtypeText)
294             {
295 1         12 $self->handle_t_message($cmddata);
296             }
297             }
298              
299             #####################################################################
300             sub handle_t_message
301             {
302 1     1   3 my ($self, $data) = @_;
303              
304 1         4 my $x = unpack('H*', $data);
305 1 50       4 print "got a T: $x\n" if $self->{Debug};
306 1 50       4 if ($self->handle_write($self->{expectSelector},
307             $self->{expectOffset},
308             $self->{expectLength}, $data))
309             {
310             # Reply
311 1         6 $self->send_message($Device::SNP::MtypeText,
312             pack('C v C C v',
313             $Device::SNP::XtypeWriteResponse,
314             $self->{plcstatusword},
315             $Device::SNP::ErrorMajorNone,
316             $Device::SNP::ErrorMinorNone, 0));
317             }
318             else
319             {
320             # Error
321 0         0 $self->send_message($Device::SNP::MtypeText,
322             pack('C v C C v',
323             $Device::SNP::XtypeWriteResponse,
324             $self->{plcstatusword},
325             $Device::SNP::ErrorMajorServiceRequestError,
326             $Device::SNP::ErrorMinorInvalidInputParameter, 0));
327             }
328             }
329              
330             #####################################################################
331             sub handle_x_message
332             {
333 10     10   15 my ($self, $snpid, $reqcode, $cmddata) = @_;
334              
335 10 50       26 print "handle_x_message $reqcode\n" if $self->{Debug};
336 10 100       54 if ($reqcode == $Device::SNP::XtypeAttach)
    100          
    50          
337             {
338 1         4 $self->handle_x_attach($snpid);
339             }
340             elsif ($reqcode == $Device::SNP::XtypeRead)
341             {
342 7         23 $self->handle_x_read($snpid, $cmddata);
343             }
344             elsif ($reqcode == $Device::SNP::XtypeWrite)
345             {
346             # REVISIT: handle broadcast writes
347 2         8 $self->handle_x_write($snpid, $cmddata);
348             }
349             }
350              
351             #####################################################################
352             sub handle_x_attach
353             {
354 1     1   3 my ($self, $snpid) = @_;
355              
356 1 50       3 print "handle_x_attach\n" if $self->{Debug};
357 1 0 33     4 return unless ($snpid eq $Device::SNP::BroadcastSNPID
      33        
358             || $snpid eq $Device::SNP::NullSNPID
359             || $snpid eq $self->{SNPID});
360             # According to the docs, No reply required
361             # but Datapanel 160 does not work correctly unless
362             # we do reply to the broadcast attach :-(
363 1         4 $self->send_x_attach_response();
364 1         12 $self->{attached}++;
365             }
366              
367             #####################################################################
368             sub handle_x_read
369             {
370 7     7   10 my ($self, $snpid, $cmddata) = @_;
371              
372 7 50       37 print "handle_x_read $snpid\n" if $self->{Debug};
373 7 50 33     23 return unless ( $snpid eq $Device::SNP::NullSNPID
374             || $snpid eq $self->{SNPID});
375              
376 7         18 my ($selector, $offset, $length, $unused) = unpack('C v v v', $cmddata);
377 7         23 $self->handle_read($selector, $offset, $length);
378             }
379              
380             #####################################################################
381             sub handle_read
382             {
383 7     7   10 my ($self, $selector, $offset, $length) = @_;
384              
385 7 50       20 print "handle_read $selector, $offset, $length\n" if $self->{Debug};
386              
387 7         10 my ($segmentname, $type) = @{$Device::SNP::segments{$selector}};
  7         36  
388 7         12 my $data;
389 7 100       28 if ($type eq 'word')
    50          
    50          
390             {
391 5         16 $data = $self->read_words($segmentname, $offset, $length);
392             }
393             elsif ($type eq 'byte')
394             {
395 0         0 $data = $self->read_bytes($segmentname, $offset, $length);
396             }
397             elsif ($type eq 'bit')
398             {
399 2         7 $data = $self->read_bits($segmentname, $offset, $length);
400             }
401              
402 7 50       18 if (defined $data)
403             {
404 7         44 $self->send_x_message(pack('C v C C v/a*',
405             $Device::SNP::XtypeReadResponse,
406             $self->{plcstatusword},
407             $Device::SNP::ErrorMajorNone,
408             $Device::SNP::ErrorMinorNone, $data));
409             }
410             else
411             {
412             # Error
413 0         0 $self->send_x_message(pack('C v C C v',
414             $Device::SNP::XtypeReadResponse,
415             $self->{plcstatusword},
416             $Device::SNP::ErrorMajorServiceRequestError,
417             $Device::SNP::ErrorMinorInvalidInputParameter, 0));
418             }
419             }
420              
421             #####################################################################
422             sub read_words
423             {
424 5     5   9 my ($self, $segmentname, $offset, $length) = @_;
425              
426 5         13 my $segment = $Device::SNP::segment{$segmentname};
427 5 50       14 return unless defined $segment;
428              
429 5         9 my $boffset = $offset * 2;
430 5         9 my $blength = $length * 2;
431 5         13 return pack('C*', @{$segment}[$boffset .. ($boffset + $blength)]);
  5         38  
432             }
433              
434             #####################################################################
435             sub read_bytes
436             {
437 0     0   0 my ($self, $segmentname, $offset, $length) = @_;
438              
439 0         0 my $segment = $Device::SNP::segment{$segmentname};
440 0 0       0 return unless defined $segment;
441              
442 0         0 return pack('C*', @{$segment}[$offset .. ($offset + $length)]);
  0         0  
443             }
444              
445             #####################################################################
446             sub read_bits
447             {
448 2     2   3 my ($self, $segmentname, $offset, $length) = @_;
449              
450 2         4 my $segment = $Device::SNP::segment{$segmentname};
451 2 50       12 return unless defined $segment;
452              
453 2         8 my $boffset = int($offset / 8);
454 2         6 my $blength = int(($length + 7) / 8);
455 2         4 return pack('C*', @{$segment}[$boffset .. ($boffset + $blength)]);
  2         12  
456             }
457              
458             #####################################################################
459             sub handle_x_write
460             {
461 2     2   4 my ($self, $snpid, $cmddata) = @_;
462              
463 2 50 33     16 return unless ($snpid eq $Device::SNP::BroadcastSNPID
      33        
464             || $snpid eq $Device::SNP::NullSNPID
465             || $snpid eq $self->{SNPID});
466              
467 2         7 my ($selector, $offset, $length, $data) = unpack('C v v a*', $cmddata);
468 2 50       8 print "handle_x_write $selector, $offset, $length\n" if $self->{Debug};
469              
470 2 100       9 if ($self->{expecttype} == $Device::SNP::MtypeText)
    50          
471             {
472             # Sigh, the data will be in the next request,
473             # remember the data from this message until later
474 1         3 $self->{expectSelector} = $selector;
475 1         3 $self->{expectOffset} = $offset;
476 1         2 $self->{expectLength} = $length;
477 1         6 $self->send_x_message(pack('C v C C v',
478             $Device::SNP::XtypeWriteResponse,
479             $self->{plcstatusword},
480             $Device::SNP::ErrorMajorNone,
481             $Device::SNP::ErrorMinorNone, 0));
482             }
483             elsif ($self->handle_write($selector, $offset, $length, $data))
484             {
485 1         7 $self->send_x_message(pack('C v C C v',
486             $Device::SNP::XtypeWriteResponse,
487             $self->{plcstatusword},
488             $Device::SNP::ErrorMajorNone,
489             $Device::SNP::ErrorMinorNone, 0));
490             }
491             else
492             {
493             # Error
494 0         0 $self->send_x_message(pack('C v C C v',
495             $Device::SNP::XtypeWriteResponse,
496             $self->{plcstatusword},
497             $Device::SNP::ErrorMajorServiceRequestError,
498             $Device::SNP::ErrorMinorInvalidInputParameter, 0));
499             }
500              
501             # Intermediate response is the same is write response
502             }
503              
504             #####################################################################
505             # Write data to the sement
506             sub handle_write
507             {
508 2     2   5 my ($self, $selector, $offset, $length, $data) = @_;
509              
510 2         6 my $x = unpack('H*', $data);
511 2 50       6 print "handle_write $selector, $offset, $length, $x\n" if $self->{Debug};
512              
513 2         3 my ($segmentname, $type) = @{$Device::SNP::segments{$selector}};
  2         8  
514 2 100       33 if ($type eq 'word')
    50          
    50          
515             {
516 1         6 return $self->write_words($segmentname, $offset, $length, $data);
517             }
518             elsif ($type eq 'byte')
519             {
520 0         0 return $self->write_bytes($segmentname, $offset, $length, $data);
521             }
522             elsif ($type eq 'bit')
523             {
524 1         5 return $self->write_bits($segmentname, $offset, $length, $data);
525             }
526             }
527              
528             #####################################################################
529             sub write_words
530             {
531 1     1   3 my ($self, $segmentname, $offset, $length, $data) = @_;
532              
533 1         3 my $segment = $Device::SNP::segment{$segmentname};
534 1 50       4 return unless defined $segment;
535              
536 1         3 my $boffset = $offset * 2;
537 1         3 my $blength = $length * 2;
538 1         4 for (my $i = 0; $i < $blength; $i++)
539             {
540 4         9 @{$segment}[$boffset++] = ord(substr($data, $i, 1));
  4         11  
541             }
542 1         6 return 1;
543             }
544              
545             #####################################################################
546             sub write_bytes
547             {
548 0     0   0 my ($self, $segmentname, $offset, $length, $data) = @_;
549              
550 0         0 my $segment = $Device::SNP::segment{$segmentname};
551 0 0       0 return unless defined $segment;
552              
553 0         0 for (my $i = 0; $i < $length; $i++)
554             {
555 0         0 @{$segment}[$offset++] = ord(substr($data, $i, 1));
  0         0  
556             }
557 0         0 return 1;
558             }
559              
560             #####################################################################
561             sub write_bits
562             {
563 1     1   3 my ($self, $segmentname, $offset, $length, $data) = @_;
564              
565 1         9 my $segment = $Device::SNP::segment{$segmentname};
566 1 50       6 return unless defined $segment;
567 1         3 my @data = unpack('C*', $data);
568 1         5 for (my $i = 0; $i < $length; $i++)
569             {
570 1         4 my $destindex = int(($offset + $i) / 8);
571 1         4 my $srcindex = int((($offset % 8) + $i) / 8);
572 1         2 my $bit = ($offset + $i) % 8;
573 1         2 my $mask = 1 << $bit;
574 1 50       4 if ($data[$srcindex] & $mask)
575             {
576             # Set
577 1         5 $$segment[$destindex] |= $mask;
578             }
579             else
580             {
581             # Clear
582 0         0 $$segment[$destindex] &= ~$mask;
583             }
584             }
585 1         8 return 1;
586             }
587              
588             #####################################################################
589             sub send_x_attach_response
590             {
591 1     1   2 my ($self) = @_;
592            
593 1 50       3 print "send_x_attach_response\n" if $self->{Debug};
594 1         9 $self->send_x_message(pack('a8 C a7',
595             $self->{SNPID},
596             $Device::SNP::XtypeAttachResponse,
597             ''));
598             }
599              
600             #####################################################################
601             sub send_x_message
602             {
603 10     10   16 my ($self, $cmddata) = @_;
604              
605 10         23 $self->send_message($Device::SNP::MtypeXMessage, $cmddata);
606             }
607              
608             #####################################################################
609             sub send_message
610             {
611 11     11   20 my ($self, $mtype, $cmddata) = @_;
612 11         36 my $msg = pack('C C a* C C n C',
613             $Device::SNP::StartOfMessage,
614             $mtype,
615             $cmddata,
616             $Device::SNP::EndOfBlock,
617             0, 0, 0);
618             # Append the BCC byte
619 11         22 $msg .= chr(compute_bcc($msg));
620              
621             # Send it
622 11         9074 $self->send_raw_message($msg);
623             }
624              
625             #####################################################################
626             sub send_raw_message
627             {
628 11     11   33 my ($self, $msg) = @_;
629              
630             # Print it out
631 11         50 my $hex = unpack('H*', $msg);
632 11 50       36 print "send: $hex\n" if $self->{Debug};
633              
634 11 50       31 return unless $self->{port}; # Testing
635 11         46 $self->{port}->dtr_active('T');
636 11         48 my $count = $self->{port}->write($msg);
637 10         1068 $self->{port}->write_drain();
638 10         37 $self->{port}->dtr_active('F');
639 10 50       33 warn "write failed\n" unless ($count);
640 10 50       75 warn "write incomplete\n" unless $count == length($msg);
641             }
642              
643             #####################################################################
644             sub compute_bcc
645             {
646 22     22   166 my ($s) = @_;
647              
648 22         27 my $bcc = 0;
649 22         128 for (split(//, $s))
650             {
651 433         439 $bcc ^= ord($_);
652             # 8 bit rotate (msb -> lsb)
653 433         770 $bcc <<= 1;
654 433 100       980 $bcc |= 1 if $bcc & 0x100;
655 433         558 $bcc &= 0xff;
656             }
657              
658 22         136 return $bcc;
659             }
660              
661             1;
662             __END__