File Coverage

blib/lib/USB/TMC.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             USB::TMC - Perl interface to USB Test & Measurement (USBTMC) backend.
4              
5             =head1 SYNOPSIS
6              
7             use USB::TMC;
8              
9             # Open usb connection to Agilent 34410A digital multimeter
10             my $usbtmc = USB::TMC->new(
11             vid => 0x0957,
12             pid => 0x0607,
13             serial => 'MY47000419', # only needed if vid/pid is ambiguous
14             );
15            
16             $usbtmc->write(data => "*CLS\n");
17             $usbtmc->write(data => "VOLT:NPLC 10\n");
18              
19             print $usbtmc->query(data => ":read?\n", length => 100);
20            
21             my $capabilities = $usbtmc->get_capabilities();
22             my $support_term_char = $capabilities->{support_term_char};
23              
24             =head1 DESCRIPTION
25              
26             This module provides a user-space L<USBTMC|http://www.usb.org/developers/docs/devclass_docs/USBTMC_1_006a.zip> driver.
27              
28             Internally this module is based on L<USB::LibUSB>.
29              
30             Does not yet support the additional features of USBTMC-USB488. But those could
31             easily be added if needed.
32              
33             =head1 METHODS
34              
35             Errors with USB transfers will result in a croak.
36              
37             Use L<default timeout|/new> if C<timeout> arg is not given.
38              
39             =cut
40              
41 1     1   43481 use strict;
  1         1  
  1         25  
42 1     1   5 use warnings;
  1         1  
  1         39  
43              
44             package USB::TMC;
45             $USB::TMC::VERSION = '0.003';
46 1     1   97 use USB::LibUSB;
  0            
  0            
47             use Moose;
48             use MooseX::Params::Validate 'validated_list';
49             use Carp;
50             use Data::Dumper 'Dumper';
51              
52             use constant {
53             MSGID_DEV_DEP_MSG_OUT => 1,
54             MSGID_REQUEST_DEV_DEP_MSG_IN => 2,
55             MSGID_DEV_DEP_MSG_IN => 2,
56             MSGID_VENDOR_SPECIFIC_OUT => 126,
57             MSGID_REQUEST_VENDOR_SPECIFIC_IN => 127,
58             MSGID_VENDOR_SPECIFIC_IN => 127,
59              
60             MESSAGE_FINALIZES_TRANSFER => "\x{01}",
61             MESSAGE_DOES_NOT_FINALIZE_TRANSFER => "\x{00}",
62              
63             FEATURE_SELECTOR_ENDPOINT_HALT => 0,
64              
65             BULK_HEADER_LENGTH => 12,
66             };
67              
68             my $null_byte = "\x{00}";
69              
70             has 'vid' => (
71             is => 'ro',
72             isa => 'Int',
73             required => 1
74             );
75              
76             has 'pid' => (
77             is => 'ro',
78             isa => 'Int',
79             required => 1
80             );
81              
82             has 'serial' => (
83             is => 'ro',
84             isa => 'Str',
85             );
86              
87             has 'ctx' => (
88             is => 'ro',
89             isa => 'USB::LibUSB',
90             init_arg => undef,
91             writer => '_ctx',
92             );
93              
94             has 'device' => (
95             is => 'ro',
96             isa => 'USB::LibUSB::Device',
97             init_arg => undef,
98             writer => '_device',
99             );
100              
101             has 'handle' => (
102             is => 'ro',
103             isa => 'USB::LibUSB::Device::Handle',
104             init_arg => undef,
105             writer => '_handle',
106             );
107              
108             # Bulk endpoint addresses.
109             has 'bulk_out_endpoint' => (
110             is => 'ro',
111             isa => 'Int',
112             init_arg => undef,
113             writer => '_bulk_out_endpoint',
114             );
115              
116             has 'bulk_in_endpoint' => (
117             is => 'ro',
118             isa => 'Int',
119             init_arg => undef,
120             writer => '_bulk_in_endpoint',
121             );
122              
123             has 'btag' => (
124             is => 'ro',
125             isa => 'Int',
126             init_arg => undef,
127             writer => '_btag',
128             default => 0,
129             );
130              
131             has 'reset_device' => (
132             is => 'ro',
133             isa => 'Bool',
134             default => 1,
135             );
136              
137             has 'debug_mode' => (
138             is => 'ro',
139             isa => 'Bool',
140             default => 0
141             );
142              
143             has 'libusb_log_level' => (
144             is => 'ro',
145             isa => 'Int',
146             default => LIBUSB_LOG_LEVEL_WARNING,
147             );
148              
149             has 'term_char' => (
150             is => 'ro',
151             isa => 'Maybe[Str]',
152             default => undef,
153             );
154              
155             has 'interface_number' => (
156             is => 'ro',
157             isa => 'Int',
158             writer => '_interface_number',
159             init_arg => undef,
160            
161             );
162              
163             has 'timeout' => (
164             is => 'rw',
165             isa => 'Num',
166             default => 5,
167             );
168              
169             sub _get_timeout_arg {
170             my $self = shift;
171             my $timeout = shift;
172             if (not defined $timeout) {
173             $timeout = $self->timeout();
174             }
175              
176             $timeout = sprintf("%.0f", $timeout * 1000);
177             return $timeout;
178             }
179              
180             sub _debug {
181             my $self = shift;
182             if ($self->debug_mode()) {
183             carp @_;
184             }
185             }
186              
187             =head2 new
188              
189             my $usbtmc = USB::TMC->new(
190             vid => $vid,
191             pid => $pid,
192             serial => $serial, # optional
193             reset_device => 0, # default: do device reset
194             debug_mode => 1, # print lots of debug messages
195             libusb_log_level => LIBUSB_LOG_LEVEL_DEBUG, # Import LIBUSB_LOG_LEVEL_* constant from USB::LibUSB
196             term_char => "\n", # Stop a read request if the term_char occurs in the
197             # byte stream. Default: do not use term char
198             timeout => 10, # timeout in seconds. default: 5
199             );
200              
201             =cut
202            
203            
204            
205             sub BUILD {
206             my $self = shift;
207              
208             # TermChar valid?
209             my $term_char = $self->term_char();
210             if (defined $term_char) {
211             if (length $term_char != 1 || $term_char =~ /[^[:ascii:]]/) {
212             croak "invalid TermChar";
213             }
214             $self->_debug("Using TermChar ", Dumper $term_char);
215             }
216             else {
217             $self->_debug("Not using TermChar");
218             }
219              
220             my $ctx = USB::LibUSB->init();
221             $ctx->set_debug($self->libusb_log_level());
222              
223             my $handle;
224             if ($self->serial()) {
225             $handle = $ctx->open_device_with_vid_pid_serial(
226             $self->vid(), $self->pid(), $self->serial());
227             }
228             else {
229             # Croak if we have multiple devices with the same vid:pid.
230             $handle = $ctx->open_device_with_vid_pid_unique(
231             $self->vid(), $self->pid());
232             }
233            
234             if ($self->reset_device()) {
235             # Clean up.
236             $self->_debug("Doing device reset.");
237             $handle->reset_device();
238             }
239            
240             my $device = $handle->get_device();
241            
242             eval {
243             # This will throw on windows and darwin. Catch exception with eval.
244             $self->_debug("enable auto detach of kernel driver.");
245             $handle->set_auto_detach_kernel_driver(1);
246             };
247            
248            
249              
250            
251             $self->_ctx($ctx);
252             $self->_device($device);
253             $self->_handle($handle);
254              
255             my $usbtmc_interface_number = $self->_find_usbtmc_interface();
256             $self->_interface_number($usbtmc_interface_number);
257            
258             $self->_debug("Claiming interface no. $usbtmc_interface_number");
259             $handle->claim_interface($usbtmc_interface_number);
260            
261             $self->_get_endpoint_addresses();
262              
263             $self->_debug(
264             "Request clear_feature endpoint_halt for both bulk endpoints."
265             );
266              
267             $self->clear_halt_out();
268             $self->clear_halt_in();
269             $self->clear_feature_endpoint_out();
270             $self->clear_feature_endpoint_in();
271             }
272              
273             sub _find_usbtmc_interface {
274             # Relevant if device has additional non-TMC interfaces.
275             my $self = shift;
276             my $config = $self->device()->get_active_config_descriptor();
277             my @interfaces = @{$config->{interface}};
278             for my $interface (@interfaces) {
279             if ($interface->{bInterfaceClass} == 0xFE
280             && $interface->{bInterfaceSubClass} == 3) {
281             my $number = $interface->{bInterfaceNumber};
282             $self->_debug("Found USBTMC interface at number $number");
283             return $number;
284             }
285             }
286             croak "Did not find a USBTMC interface. Interfaces: ", Dumper \@interfaces;
287             }
288              
289             sub _get_endpoint_addresses {
290             my $self = shift;
291             my $interface_number = $self->interface_number();
292            
293             my $config = $self->device()->get_active_config_descriptor();
294             my $interface = $config->{interface}[$interface_number];
295             my @endpoints = @{$interface->{endpoint}};
296              
297             if (@endpoints != 2 && @endpoints != 3) {
298             croak "USBTMC interface needs either 2 or 3 endpoints.";
299             }
300              
301             my ($bulk_out_address, $bulk_in_address);
302             for my $endpoint (@endpoints) {
303             my $address = $endpoint->{bEndpointAddress};
304             my $direction = $address & LIBUSB_ENDPOINT_DIR_MASK;
305             my $type = $endpoint->{bmAttributes} & LIBUSB_TRANSFER_TYPE_MASK;
306             if ($type == LIBUSB_TRANSFER_TYPE_BULK) {
307             if ($direction == LIBUSB_ENDPOINT_OUT) {
308             $self->_debug("Found bulk-out endpoint with address ".
309             sprintf("0x%x", $address));
310             $bulk_out_address = $address;
311             }
312             elsif ($direction == LIBUSB_ENDPOINT_IN) {
313             $self->_debug("Found bulk-in endpoint with address ".
314             sprintf("0x%x", $address));
315             $bulk_in_address = $address;
316             }
317             }
318             }
319            
320             if (!$bulk_out_address || !$bulk_in_address) {
321             croak "Did not find all required endpoints.";
322             }
323            
324             $self->_bulk_out_endpoint($bulk_out_address);
325             $self->_bulk_in_endpoint($bulk_in_address);
326             }
327              
328             =head2 write
329              
330             $usbtmc->write(data => $data, timeout => $timeout);
331              
332             Do DEV_DEP_MSG_OUT transfer.
333              
334              
335             =cut
336              
337             sub write {
338             my $self = shift;
339             $self->dev_dep_msg_out(@_);
340             }
341              
342             =head2 read
343              
344             my $data = $usbtmc->read(length => $read_length, timeout => $timeout);
345              
346             Do REQUEST_DEV_DEP_MSG_IN and DEV_DEP_MSG_IN transfers.
347              
348             Typically you will not need this method and only use L</query>.
349              
350             =cut
351              
352              
353             sub read {
354             my $self = shift;
355             my ($length, $timeout) = validated_list(
356             \@_,
357             length => {isa => 'Int'},
358             timeout => {isa => 'Maybe[Num]', optional => 1}
359             );
360              
361             $self->request_dev_dep_msg_in(length => $length, timeout => $timeout);
362             return $self->dev_dep_msg_in(length => $length, timeout => $timeout);
363             }
364              
365             =head2 query
366              
367             my $data = $usbtmc->query(data => $data, length => $read_length, timeout => $timeout);
368              
369             Send a query command and read the result.
370              
371             =cut
372              
373             sub query {
374             my $self = shift;
375             my ($data, $length, $timeout) = validated_list(
376             \@_,
377             data => {isa => 'Str'},
378             length => {isa => 'Int'},
379             timeout => {isa => 'Maybe[Num]', optional => 1},
380             );
381              
382             $self->write(data => $data, timeout => $timeout);
383             return $self->read(length => $length, timeout => $timeout);
384             }
385              
386              
387             sub dev_dep_msg_out {
388             my $self = shift;
389             my ($data, $timeout) = validated_list(
390             \@_,
391             data => {isa => 'Str'},
392             timeout => {isa => 'Maybe[Num]', optional => 1},
393             );
394              
395             $timeout = $self->_get_timeout_arg($timeout);
396            
397             $self->_debug("Doing dev_dep_msg_out with data $data");
398            
399             my $header = $self->_dev_dep_msg_out_header(length => length $data);
400             my $endpoint = $self->bulk_out_endpoint();
401              
402             # Ensure that total number of bytes is multiple of 4.
403             $data .= $null_byte x ((4 - (length $data) % 4) % 4);
404             $self->handle()->bulk_transfer_write($endpoint, $header . $data, $timeout);
405             }
406              
407             sub dev_dep_msg_in {
408             my $self = shift;
409             my ($length, $timeout) = validated_list(
410             \@_,
411             length => {isa => 'Int'},
412             timeout => {isa => 'Maybe[Num]', optional => 1}
413             );
414              
415             $timeout = $self->_get_timeout_arg($timeout);
416            
417             $self->_debug("Doing dev_dep_msg_in with length $length");
418            
419             my $endpoint = $self->bulk_in_endpoint();
420             my $data = $self->handle()->bulk_transfer_read(
421             $endpoint, $length + BULK_HEADER_LENGTH
422             , $timeout
423             );
424            
425             if (length $data < BULK_HEADER_LENGTH) {
426             croak "dev_dep_msg_in does not contain header";
427             }
428            
429             my $header = substr($data, 0, BULK_HEADER_LENGTH);
430              
431             my $transfer_size = unpack('V', substr($header, 4, 4));
432            
433             # Data may contain trailing alignment bytes!
434             # strip them by returning only $transfer_size bytes.
435             $data = substr($data, BULK_HEADER_LENGTH, $transfer_size);
436             return $data;
437             }
438              
439             sub request_dev_dep_msg_in {
440             my $self = shift;
441             my ($length, $timeout) = validated_list(
442             \@_,
443             length => {isa => 'Int', default => 1000},
444             timeout => {isa => 'Maybe[Num]', optional => 1},
445             );
446              
447             $timeout = $self->_get_timeout_arg($timeout);
448            
449             $self->_debug("Doing request_dev_dep_msg_in with length $length");
450             my $header = $self->_request_dev_dep_msg_in_header(length => $length);
451             my $endpoint = $self->bulk_out_endpoint();
452              
453             # Length of $header is already multiple of 4.
454             $self->handle()->bulk_transfer_write($endpoint, $header, $timeout);
455             }
456              
457             sub _dev_dep_msg_out_header {
458             my $self = shift;
459             my ($length) = validated_list(\@_, length => {isa => 'Int'});
460            
461             my $header = $self->_bulk_out_header(MSGID => MSGID_DEV_DEP_MSG_OUT);
462             $header .= pack('V', $length);
463             $header .= MESSAGE_FINALIZES_TRANSFER;
464             $header .= $null_byte x 3; # Reserved bytes.
465             return $header;
466             }
467              
468             sub _request_dev_dep_msg_in_header {
469             my $self = shift;
470             my ($length) = validated_list(\@_, length => {isa => 'Int'});
471             my $header = $self->_bulk_out_header(MSGID => MSGID_REQUEST_DEV_DEP_MSG_IN);
472             # Transfer length
473             $header .= pack('V', $length);
474            
475             my $term_char = $self->term_char();
476             if (defined $term_char) {
477             $header .= pack('C', 2);
478             $header .= $term_char;
479             }
480             else {
481             $header .= pack('C', 0);
482             $header .= $null_byte;
483             }
484             $header .= $null_byte x 2; # Reserved. Must be 0x00.
485            
486             return $header;
487             }
488              
489              
490             sub _bulk_out_header {
491             my $self = shift;
492             my ($MSGID) = validated_list(\@_, MSGID => {isa => 'Int'});
493             my $bulk_out_header = pack('C', $MSGID);
494             my ($btag, $btag_inverse) = $self->_btags();
495             $bulk_out_header .= $btag . $btag_inverse;
496              
497             $bulk_out_header .= $null_byte; # Reserved. Must be 0x00;
498              
499             return $bulk_out_header;
500             }
501              
502             sub _btags {
503             my $self = shift;
504             my $btag = $self->btag();
505             $btag++;
506             if ($btag == 256) {
507             $btag = 1;
508             }
509             $self->_btag($btag);
510             my $btag_inverse = ($btag ^ 0xff);
511             return (pack('C', $btag), pack('C', $btag_inverse));
512             }
513              
514             sub clear {
515             my $self = shift;
516             my ($timeout) = validated_list(
517             \@_, timeout => {isa => 'Maybe[Num]', optional => 1});
518              
519             $timeout = $self->_get_timeout_arg($timeout);
520            
521             my $bmRequestType = 0xa1; # See USBTMC 4.2.1.6 INITIATE_CLEAR
522             my $bRequest = 5;
523             my $wValue = 0;
524             my $wIndex = $self->interface_number();
525             my $wLength = 1;
526             return $self->handle()->control_transfer_read($bmRequestType, $bRequest, $wValue, $wIndex, $wLength, $timeout);
527             # FIXME: check clear status in loop.
528            
529             }
530              
531             sub clear_feature_endpoint_out {
532             my $self = shift;
533             my ($timeout) = validated_list(
534             \@_, timeout => {isa => 'Maybe[Num]', optional => 1});
535              
536             $timeout = $self->_get_timeout_arg($timeout);
537            
538             my $endpoint = $self->bulk_out_endpoint();
539             my $bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD
540             | LIBUSB_RECIPIENT_ENDPOINT;
541             my $bRequest = LIBUSB_REQUEST_CLEAR_FEATURE;
542             my $wValue = FEATURE_SELECTOR_ENDPOINT_HALT;
543             my $wIndex = $endpoint;
544             $self->handle()->control_transfer_write(
545             $bmRequestType, $bRequest, $wValue, $wIndex, "", $timeout);
546             }
547              
548             sub clear_feature_endpoint_in {
549             my $self = shift;
550             my ($timeout) = validated_list(
551             \@_, timeout => {isa => 'Maybe[Num]', optional => 1});
552              
553             $timeout = $self->_get_timeout_arg($timeout);
554            
555             my $endpoint = $self->bulk_in_endpoint();
556             my $bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD
557             | LIBUSB_RECIPIENT_ENDPOINT;
558             my $bRequest = LIBUSB_REQUEST_CLEAR_FEATURE;
559             my $wValue = FEATURE_SELECTOR_ENDPOINT_HALT;
560             my $wIndex = $endpoint;
561             $self->handle()->control_transfer_write(
562             $bmRequestType, $bRequest, $wValue, $wIndex, "", $timeout);
563             }
564              
565             sub clear_halt_out {
566             my $self = shift;
567             my $endpoint = $self->bulk_out_endpoint();
568             $self->handle()->clear_halt($endpoint);
569             }
570              
571             sub clear_halt_in {
572             my $self = shift;
573             my $endpoint = $self->bulk_in_endpoint();
574             $self->handle()->clear_halt($endpoint);
575             }
576              
577             =head2 get_capabilities
578              
579             my $capabilites = $usbtmc->get_capabilities(timeout => $timeout);
580              
581             Do GET_CAPABILITIES request.
582              
583             The C<$capabilities> hash contains the following keys:
584              
585             =over
586              
587             =item bcdUSBTMC
588              
589             =item listen_only
590              
591             =item talk_only
592              
593             =item accept_indicator_pulse
594              
595             =item support_term_char
596              
597             =back
598              
599             =cut
600              
601              
602             sub get_capabilities {
603             my $self = shift;
604             my ($timeout) = validated_list(
605             \@_, timeout => {isa => 'Maybe[Num]', optional => 1});
606              
607             $timeout = $self->_get_timeout_arg($timeout);
608            
609             my $bmRequestType = 0xa1;
610             my $bRequest = 7;
611             my $wValue = 0;
612             my $wIndex = $self->interface_number();
613             my $wLength = 0x18;
614              
615             my $handle = $self->handle();
616             my $caps = $handle->control_transfer_read($bmRequestType, $bRequest, $wValue, $wIndex, $wLength, $timeout);
617             if (length $caps != $wLength) {
618             croak "Incomplete response in get_capabilities.";
619             }
620            
621             my $status = unpack('C', substr($caps, 0, 1));
622            
623             if ($status != 1) {
624             croak "GET_CAPABILITIES not successfull. status = $status";
625             }
626            
627             my $bcdUSBTMC = unpack('v', substr($caps, 2, 2));
628             my $interface_capabilities = unpack('C', substr($caps, 4, 1));
629             my $device_capabilites = unpack('C', substr($caps, 5, 1));
630            
631             return {
632             bcdUSBTMC => $bcdUSBTMC,
633             listen_only => $interface_capabilities & 1,
634             talk_only => ($interface_capabilities >> 1) & 1,
635             accept_indicator_pulse => ($interface_capabilities >> 2) & 1,
636             support_term_char => $device_capabilites & 1,
637             };
638             }
639              
640             __PACKAGE__->meta->make_immutable();
641              
642             1;
643              
644