File Coverage

blib/lib/Device/ProXR.pm
Criterion Covered Total %
statement 15 89 16.8
branch 1 40 2.5
condition 0 11 0.0
subroutine 5 10 50.0
pod 3 3 100.0
total 24 153 15.6


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.06
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 => qq{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   569 use Moo;
  1         2  
  1         5  
40             ## Moo enables strictures
41             ## no critic (TestingAndDebugging::RequireUseStrict)
42             ## no critic (TestingAndDebugging::RequireUseWarnings)
43 1     1   234 use Readonly;
  1         1  
  1         42  
44 1     1   5 use Time::HiRes qw(usleep);
  1         1  
  1         7  
45 1     1   125 use Carp qw(confess cluck);
  1         2  
  1         187  
46            
47             ## Version string
48             our $VERSION = qq{0.06};
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         1098 require Device::SerialPort;
88 1         30393 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             ## Write all settings to the serial port
261 0           $obj->write_settings;
262            
263             ## Set the port object
264 0           $self->_port_obj($obj);
265            
266             ## Return the port object
267 0           return($self->_port_obj);
268             }
269            
270            
271            
272             ##****************************************************************************
273             ##****************************************************************************
274            
275             =head2 last_error()
276            
277             =over 2
278            
279             =item B
280            
281             Returns the last error message
282            
283             =item B
284            
285             NONE
286            
287             =item B
288            
289             String containing the last error, or an empty string if no error has been
290             encountered
291            
292             =back
293            
294             =cut
295            
296             ##----------------------------------------------------------------------------
297             sub last_error
298             {
299 0     0 1   my $self = shift;
300            
301 0           return($self->_error_message);
302             }
303            
304             ##****************************************************************************
305             ##****************************************************************************
306            
307             =head2 send_command(cmd, param)
308            
309             =over 2
310            
311             =item B
312            
313             Sends the given command wand optional parameter.
314             NOTE: This method adds the required 0xFE before the command, and
315             encapsulation of the packet in API mode
316            
317             =item B
318            
319             cmd - Command to send
320             param - Optional parameter
321            
322             =item B
323            
324             UNDEF on error (last_error set), or number of bytes sent
325            
326             =back
327            
328             =cut
329            
330             ##----------------------------------------------------------------------------
331             sub send_command
332             {
333 0     0 1   my $self = shift;
334 0           my $cmd = shift;
335 0           my $param = shift;
336            
337             ## See if we received a command
338 0 0         unless (defined($cmd))
339             {
340             ## No command, so set the error message
341 0           $self->_error_message(qq{Missing command parameter!});
342             ## Return UNDEF indicating an error
343 0           return;
344             }
345            
346             ## Assemble the string to send
347 0           my $tx_buff = chr($PROXR_CMD) . chr($cmd);
348             ## Add the parameter if provided
349 0 0         $tx_buff .= chr($param) if (defined($param));
350            
351             ## See if we are in API mode
352 0 0         if ($self->API_mode)
353             {
354             ## API Mode sends (and receives) using the format
355             ## 0xAA COUNT BYTES CHECKSUM
356             ## Where COUNT is the number of BYTES
357             ## CHECKSUM is the 8-bit rolling checksum of the entire buffer
358 0 0         my $count = (defined($param) ? 3 : 2);
359 0           my $chksum = $PROXR_API_START + $count + $PROXR_CMD + $cmd;
360 0 0         $chksum += $param if (defined($param));
361 0           $chksum = $chksum % 256;
362 0           $tx_buff = chr($PROXR_API_START) . chr($count) . $tx_buff . chr($chksum);
363             }
364            
365             ## Print debug output
366 0 0         if ($self->debug_level)
367             {
368 0           print(qq{send_command(): });
369 0           _display_buffer($tx_buff);
370             }
371            
372 0 0         return unless ($self->_get_port_object);
373            
374            
375             ## Send the buffer
376 0           my $tx_len = $self->_port_obj->write($tx_buff);
377            
378             ## Flush all RX and TX buffers
379 0           $self->_port_obj->purge_all;
380            
381             ## Return the number of bytes transmitted
382 0           return $tx_len;
383            
384             }
385            
386             ##****************************************************************************
387             ##****************************************************************************
388            
389             =head2 get_response(count, ms_timeout)
390            
391             =over 2
392            
393             =item B
394            
395             Return a buffer containing the response received from the controller
396             NOTE: In API mode, the checksum is verified and the header byte (0xAA)
397             count and checksum are removed from the buffer
398            
399             =item B
400            
401             count - Number of bytes expected
402             DEFAULT: 1
403             ms_timeout - Optional timeout in milliseconds
404             DEFAULT: 400
405            
406            
407             =item B
408            
409             UNDEF on error (last_error set), or SCALAR containing the data received
410            
411             =back
412            
413             =cut
414            
415             ##----------------------------------------------------------------------------
416             sub get_response
417             {
418 0     0 1   my $self = shift;
419 0   0       my $count = shift // 1;
420 0   0       my $ms_timeout = shift // $GET_RESPONSE_DEFAULT_MS_TIMEOUT;
421            
422             ## Number of bytes expected
423 0           my $expected = $count;
424             ## API mode, responses contain 3 extra bytes
425 0 0         $expected += 3 if ($self->API_mode);
426            
427 0 0         return unless ($self->_has_port_obj);
428            
429 0           my $rx_buff = qq{};
430            
431             ## Set the timeout
432 0           $self->_port_obj->read_const_time($ms_timeout);
433            
434 0           my $timeout = 0;
435 0   0       while ((!$timeout) and ($expected != length($rx_buff)))
436             {
437             ## Read the bytes
438 0           my ($rx_count, $rx_raw) = $self->_port_obj->read(16);
439 0 0         if ($rx_count)
440             {
441 0           $rx_buff .= $rx_raw ;
442             }
443             else
444             {
445 0           $timeout = 1;
446             }
447             }
448            
449             ## Print debug output
450 0 0         if ($self->debug_level)
451             {
452 0           printf(
453             qq{get_response(): Expected %d, received %d\n},
454             $expected,
455             length($rx_buff),
456             );
457 0           _display_buffer($rx_buff);
458             }
459            
460             ## See if we received what we expected
461 0 0         if ($expected == length($rx_buff))
462             {
463 0 0         if ($self->API_mode)
464             {
465             ## Trim off the 0xAA and COUNT from the beginning, and
466             ## checksum from the end
467 0           $rx_buff = substr($rx_buff, 2, -1);
468             }
469             }
470            
471 0           return($rx_buff);
472            
473             }
474            
475             ##----------------------------------------------------------------------------
476             ## @fn _display_buffer($buff)
477             ## @brief Display the given buffer as hexadecimal bytes
478             ## @param $buff - Buffer to be displayed
479             ## @return NONE
480             ## @note
481             ##----------------------------------------------------------------------------
482             sub _display_buffer
483             {
484 0     0     my $buff = shift;
485            
486             ## Iterate through the buffer
487 0           foreach my $idx (0 .. (length($buff) - 1))
488             {
489 0           printf(qq{0x%02X }, ord(substr($buff, $idx, 1)));
490             ## Send newline after 16 bytes
491 0 0 0       print(qq{\n}) if ($idx && (0 == ($idx % 0x10)));
492             }
493            
494             ## Send newline
495 0 0         print(qq{\n}) if (length($buff) % 0x10);
496            
497 0           return;
498             }
499            
500            
501            
502             ##****************************************************************************
503             ## Additional POD documentation
504             ##****************************************************************************
505            
506             =head1 AUTHOR
507            
508             Paul Durden Ealabamapaul AT gmail.comE
509            
510             =head1 COPYRIGHT & LICENSE
511            
512             Copyright (C) 2015 by Paul Durden.
513            
514             This program is free software; you can redistribute it and/or modify it
515             under the same terms as Perl itself.
516            
517             =cut
518            
519             1; ## End of module
520             __END__