File Coverage

blib/lib/Device/ProXR.pm
Criterion Covered Total %
statement 15 88 17.0
branch 1 40 2.5
condition 0 11 0.0
subroutine 5 10 50.0
pod 3 3 100.0
total 24 152 15.7


line stmt bran cond sub pod time code
1             package Device::ProXR;
2             ##----------------------------------------------------------------------------
3             ## :mode=perl:indentSize=2:tabSize=2:noTabs=true:
4             ##****************************************************************************
5             ## NOTES:
6             ## * Before comitting this file to the repository, ensure Perl Critic can be
7             ## invoked at the HARSH [3] level with no errors
8             ##****************************************************************************
9            
10             =head1 NAME
11            
12             Device::ProXR - A Moo based object oriented interface for creating
13             controlling devices using the National Control Devices ProXR command set
14            
15             =head1 VERSION
16            
17             Version 0.05
18            
19             =head1 SYNOPSIS
20            
21             ## Device::ProXR is a base class that is typicall extended
22             ## see Device::ProXR::RelayControl
23             use Device::ProXR;
24            
25             my $board = Device::ProXR->new(port => 'COM1');
26            
27             =head1 SEE ALSO
28            
29             L
30            
31             See the L for the devices with
32             the ProXR series controller.
33            
34            
35             =cut
36            
37             ##****************************************************************************
38             ##****************************************************************************
39 1     1   805 use Moo;
  1         2  
  1         6  
40             ## Moo enables strictures
41             ## no critic (TestingAndDebugging::RequireUseStrict)
42             ## no critic (TestingAndDebugging::RequireUseWarnings)
43 1     1   287 use Readonly;
  1         2  
  1         53  
44 1     1   5 use Time::HiRes qw(usleep);
  1         2  
  1         9  
45 1     1   156 use Carp qw(confess cluck);
  1         3  
  1         201  
46            
47             ## Version string
48             our $VERSION = qq{0.05};
49            
50            
51             ##--------------------------------------------------------
52             ## Time conversion contants
53             ##--------------------------------------------------------
54             ## uSeconds per millisecond
55             Readonly::Scalar my $USECS_PER_MS => 1000;
56             ## milliseconds per second
57             Readonly::Scalar my $MS_PER_SEC => 1000;
58             ## uSeconds per second
59             Readonly::Scalar my $USECS_PER_SEC => $USECS_PER_MS * $MS_PER_SEC;
60            
61             ##--------------------------------------------------------
62             ## Various timeouts
63             ##--------------------------------------------------------
64             Readonly::Scalar my $GET_RESPONSE_DEFAULT_MS_TIMEOUT => 400;
65             ## millisecond timeout when reading from the FPC serial port
66             Readonly::Scalar my $READ_POLL_TIMEOUT_MS => 1000;
67            
68             ##--------------------------------------------------------
69             ## Symbolic constants
70             ##--------------------------------------------------------
71            
72             Readonly::Scalar my $PROXR_API_START => 0xAA;
73             Readonly::Scalar my $PROXR_CMD => 0xFE;
74            
75             ##--------------------------------------------------------
76             ## Conditionally load the needed serial module
77             ##--------------------------------------------------------
78             BEGIN
79             {
80 1 50   1   5 if ($^O eq 'MSWin32')
81             {
82 0         0 require Win32::SerialPort;
83 0         0 Win32::SerialPort->import;
84             }
85             else
86             {
87 1         1121 require Device::SerialPort;
88 1         37252 Device::SerialPort->import;
89             }
90             }
91             ##****************************************************************************
92             ## Object attribute
93             ##****************************************************************************
94            
95             =head1 ATTRIBUTES
96            
97             =cut
98            
99             ##****************************************************************************
100             ##****************************************************************************
101            
102             =over 2
103            
104             =item B
105            
106             Port used to communicate with the device
107            
108             =back
109            
110             =cut
111            
112             ##----------------------------------------------------------------------------
113             has port => (
114             is => qq{rw},
115             default => qq{},
116             );
117            
118             ##****************************************************************************
119             ##****************************************************************************
120            
121             =over 2
122            
123             =item B
124            
125             Baud rate for port used to communicate with the device.
126             NOTE: This only applies to serial port communications
127             DEFAULT: 115200
128            
129             =back
130            
131             =cut
132            
133             ##----------------------------------------------------------------------------
134             has baud => (
135             is => qq{rw},
136             default => qq{115200},
137             );
138            
139             ##****************************************************************************
140             ##****************************************************************************
141            
142             =over 2
143            
144             =item B
145            
146             Enable the API mode of communications. This mode adds byte counts and
147             checksums to all commands and responses.
148             DEFAULT: 1
149            
150             =back
151            
152             =cut
153            
154             ##----------------------------------------------------------------------------
155             has API_mode => (
156             is => qq{rw},
157             default => qq{1},
158             );
159            
160             ##****************************************************************************
161             ##****************************************************************************
162            
163             =over 2
164            
165             =item B
166            
167             Debug level controls amount of debugging information displayed
168             DEFAULT: 0
169            
170             =back
171            
172             =cut
173            
174             ##----------------------------------------------------------------------------
175             has debug_level => (
176             is => qq{rw},
177             default => 0,
178             );
179            
180             ##****************************************************************************
181             ## "Private" atributes
182             ##***************************************************************************
183            
184             ## Holds the port object
185             has _port_obj => (
186             is => qq{rw},
187             predicate => 1,
188            
189             );
190            
191             ## Error message
192             has _error_message => (
193             is => qq{rw},
194             default => qq{},
195             );
196            
197            
198             ##****************************************************************************
199             ## Object Methods
200             ##****************************************************************************
201            
202             =head1 METHODS
203            
204             =cut
205            
206             ##----------------------------------------------------------------------------
207             ## @fn _get_port_object()
208             ## @brief Returns the port object, opening it if needed. Returns UNDEF
209             ## on error and sets last_error
210             ## @param
211             ## @return Port object, or UNDEF on error
212             ## @note
213             ##----------------------------------------------------------------------------
214             sub _get_port_object ## no critic (ProhibitUnusedPrivateSubroutines)
215             {
216 0     0     my $self = shift;
217            
218             ## Returh the object if it already exists
219 0 0         return($self->_port_obj) if ($self->_has_port_obj);
220            
221             ## See if a port was specified
222 0 0         unless ($self->port)
223             {
224 0           $self->_error_message(qq{Missing port attribute!});
225 0           return;
226             }
227            
228             ## Create the object
229 0           my $obj;
230            
231             ## See if we running Windows
232 0 0         if ($^O eq q{MSWin32})
233             {
234             ## Running Windows, use Win32::SerialPort
235 0           $obj = Win32::SerialPort->new($self->port, 1);
236             }
237             else
238             {
239             ## Not running Windows, use Device::SerialPort
240 0           $obj = Device::SerialPort->new($self->port, 1);
241             }
242            
243             ## See if opened the port
244 0 0         unless ($obj)
245             {
246             ## There was an error opening the port
247 0           $self->_error_message(qq{Could not open port "} . $self->port . qq{"});
248 0           return;
249             }
250            
251             ## Configure the port
252 0           $obj->baudrate($self->baud);
253 0           $obj->parity(qq{none});
254 0           $obj->databits(8);
255 0           $obj->stopbits(1);
256 0           $obj->handshake(qq{none});
257 0           $obj->read_const_time($READ_POLL_TIMEOUT_MS);
258 0           $obj->purge_all;
259            
260             ## Set the port object
261 0           $self->_port_obj($obj);
262            
263             ## Return the port object
264 0           return($self->_port_obj);
265             }
266            
267            
268            
269             ##****************************************************************************
270             ##****************************************************************************
271            
272             =head2 last_error()
273            
274             =over 2
275            
276             =item B
277            
278             Returns the last error message
279            
280             =item B
281            
282             NONE
283            
284             =item B
285            
286             String containing the last error, or an empty string if no error has been
287             encountered
288            
289             =back
290            
291             =cut
292            
293             ##----------------------------------------------------------------------------
294             sub last_error
295             {
296 0     0 1   my $self = shift;
297            
298 0           return($self->_error_message);
299             }
300            
301             ##****************************************************************************
302             ##****************************************************************************
303            
304             =head2 send_command(cmd, param)
305            
306             =over 2
307            
308             =item B
309            
310             Sends the given command wand optional parameter.
311             NOTE: This method adds the required 0xFE before the command, and
312             encapsulation of the packet in API mode
313            
314             =item B
315            
316             cmd - Command to send
317             param - Optional parameter
318            
319             =item B
320            
321             UNDEF on error (last_error set), or number of bytes sent
322            
323             =back
324            
325             =cut
326            
327             ##----------------------------------------------------------------------------
328             sub send_command
329             {
330 0     0 1   my $self = shift;
331 0           my $cmd = shift;
332 0           my $param = shift;
333            
334             ## See if we received a command
335 0 0         unless (defined($cmd))
336             {
337             ## No command, so set the error message
338 0           $self->_error_message(qq{Missing command parameter!});
339             ## Return UNDEF indicating an error
340 0           return;
341             }
342            
343             ## Assemble the string to send
344 0           my $tx_buff = chr($PROXR_CMD) . chr($cmd);
345             ## Add the parameter if provided
346 0 0         $tx_buff .= chr($param) if (defined($param));
347            
348             ## See if we are in API mode
349 0 0         if ($self->API_mode)
350             {
351             ## API Mode sends (and receives) using the format
352             ## 0xAA COUNT BYTES CHECKSUM
353             ## Where COUNT is the number of BYTES
354             ## CHECKSUM is the 8-bit rolling checksum of the entire buffer
355 0 0         my $count = (defined($param) ? 3 : 2);
356 0           my $chksum = $PROXR_API_START + $count + $PROXR_CMD + $cmd;
357 0 0         $chksum += $param if (defined($param));
358 0           $chksum = $chksum % 256;
359 0           $tx_buff = chr($PROXR_API_START) . chr($count) . $tx_buff . chr($chksum);
360             }
361            
362             ## Print debug output
363 0 0         if ($self->debug_level)
364             {
365 0           print(qq{send_command(): });
366 0           _display_buffer($tx_buff);
367             }
368            
369 0 0         return unless ($self->_get_port_object);
370            
371            
372             ## Send the buffer
373 0           my $tx_len = $self->_port_obj->write($tx_buff);
374            
375             ## Flush all RX and TX buffers
376 0           $self->_port_obj->purge_all;
377            
378             ## Return the number of bytes transmitted
379 0           return $tx_len;
380            
381             }
382            
383             ##****************************************************************************
384             ##****************************************************************************
385            
386             =head2 get_response(count, ms_timeout)
387            
388             =over 2
389            
390             =item B
391            
392             Return a buffer containing the response received from the controller
393             NOTE: In API mode, the checksum is verified and the header byte (0xAA)
394             count and checksum are removed from the buffer
395            
396             =item B
397            
398             count - Number of bytes expected
399             DEFAULT: 1
400             ms_timeout - Optional timeout in milliseconds
401             DEFAULT: 400
402            
403            
404             =item B
405            
406             UNDEF on error (last_error set), or SCALAR containing the data received
407            
408             =back
409            
410             =cut
411            
412             ##----------------------------------------------------------------------------
413             sub get_response
414             {
415 0     0 1   my $self = shift;
416 0   0       my $count = shift // 1;
417 0   0       my $ms_timeout = shift // $GET_RESPONSE_DEFAULT_MS_TIMEOUT;
418            
419             ## Number of bytes expected
420 0           my $expected = $count;
421             ## API mode, responses contain 3 extra bytes
422 0 0         $expected += 3 if ($self->API_mode);
423            
424 0 0         return unless ($self->_has_port_obj);
425            
426 0           my $rx_buff = qq{};
427            
428             ## Set the timeout
429 0           $self->_port_obj->read_const_time($ms_timeout);
430            
431 0           my $timeout = 0;
432 0   0       while ((!$timeout) and ($expected != length($rx_buff)))
433             {
434             ## Read the bytes
435 0           my ($rx_count, $rx_raw) = $self->_port_obj->read(16);
436 0 0         if ($rx_count)
437             {
438 0           $rx_buff .= $rx_raw ;
439             }
440             else
441             {
442 0           $timeout = 1;
443             }
444             }
445            
446             ## Print debug output
447 0 0         if ($self->debug_level)
448             {
449 0           printf(
450             qq{get_response(): Expected %d, received %d\n},
451             $expected,
452             length($rx_buff),
453             );
454 0           _display_buffer($rx_buff);
455             }
456            
457             ## See if we received what we expected
458 0 0         if ($expected == length($rx_buff))
459             {
460 0 0         if ($self->API_mode)
461             {
462             ## Trim off the 0xAA and COUNT from the beginning, and
463             ## checksum from the end
464 0           $rx_buff = substr($rx_buff, 2, -1);
465             }
466             }
467            
468 0           return($rx_buff);
469            
470             }
471            
472             ##----------------------------------------------------------------------------
473             ## @fn _display_buffer($buff)
474             ## @brief Display the given buffer as hexadecimal bytes
475             ## @param $buff - Buffer to be displayed
476             ## @return NONE
477             ## @note
478             ##----------------------------------------------------------------------------
479             sub _display_buffer
480             {
481 0     0     my $buff = shift;
482            
483             ## Iterate through the buffer
484 0           foreach my $idx (0 .. (length($buff) - 1))
485             {
486 0           printf(qq{0x%02X }, ord(substr($buff, $idx, 1)));
487             ## Send newline after 16 bytes
488 0 0 0       print(qq{\n}) if ($idx && (0 == ($idx % 0x10)));
489             }
490            
491             ## Send newline
492 0 0         print(qq{\n}) if (length($buff) % 0x10);
493            
494 0           return;
495             }
496            
497            
498            
499             ##****************************************************************************
500             ## Additional POD documentation
501             ##****************************************************************************
502            
503             =head1 AUTHOR
504            
505             Paul Durden Ealabamapaul AT gmail.comE
506            
507             =head1 COPYRIGHT & LICENSE
508            
509             Copyright (C) 2015 by Paul Durden.
510            
511             This program is free software; you can redistribute it and/or modify it
512             under the same terms as Perl itself.
513            
514             =cut
515            
516             1; ## End of module
517             __END__