File Coverage

blib/lib/Robotics.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Robotics;
2              
3 6     6   8204155 use warnings;
  6         15  
  6         227  
4 6     6   60 use strict;
  6         12  
  6         223  
5              
6 6     6   37 use Carp;
  6         14  
  6         479  
7 6     6   2691 use Moose;
  0            
  0            
8             use MooseX::StrictConstructor;
9              
10             #use Module::Pluggable::Object; # maybe in future
11             use IO::Socket;
12             use YAML::XS;
13              
14             our @Devices = (
15             "Robotics::Tecan",
16             "Robotics::Fialab"
17             );
18              
19             has 'alias' => ( is => 'rw' );
20              
21             has 'device' => ( is => 'rw' );
22              
23             has 'devices' => (
24             traits => ['Hash'],
25             is => 'rw',
26             isa => 'HashRef[Str]',
27             default => sub { {} },
28             );
29              
30             =head1 NAME
31              
32             Robotics - Robotics hardware control and abstraction
33              
34             =head1 VERSION
35              
36             Version 0.23
37              
38             =cut
39              
40             our $VERSION = '0.23';
41              
42              
43             =head1 SYNOPSIS
44              
45             Provides local communication to robotics hardware devices, related
46             peripherals, or network communication to these devices. Also
47             provides a high-level, object oriented software interface to
48             abstract the low level robotics commands or low level robotics
49             hardware. Environmental configuration is provided with a
50             configuration file in YAML format. Allows other hardware device
51             drivers to be plugged into this module.
52              
53             Simple examples are provided in the examples/ directory of the
54             distribution.
55              
56              
57              
58             Nominclature note: The name "Robotics" is used in full, rather than
59             "Robot", to distinguish mechanical robots from the many
60             internet-spidering software modules or software user agents commonly
61             (and erroneously) referred to as "robots". Robotics has motors;
62             both the internet & software do not!
63              
64             =cut
65              
66             # Application should always perform device probing as first thing,
67             # so this is done as 'new'
68             sub BUILD {
69             my ($self, $params) = @_;
70            
71             if ($self->device()) {
72             print STDOUT "Setting up ". $self->device(). "\n";
73             }
74             else {
75             $self->probe();
76             }
77             }
78              
79             sub probe {
80             my ($self, $params) = @_;
81             print STDOUT "Searching for locally connected robotics devices\n";
82            
83             # Find Tecan Gemini, EVO, Genesis, ...
84              
85             my $this = shift;
86             my %device_tree;
87             $self->devices( \%device_tree );
88             for my $class ( @Robotics::Devices ) {
89             warn "Loading $class\n";
90             if ( _try_load($class) ) {
91             my $result = $class->probe();
92             if (defined($result)) {
93             $self->devices->{$class} = $result;
94             #$list{$class} = $result;
95             }
96             }
97             else {
98             die "should not get here; could not load ".
99             "Robotics::Device subclass $class\n\n\n$@";
100             }
101             }
102              
103             # Add other robotics systems here
104            
105             # TODO Perhaps scan serial ports using Hardware::PortScanner
106             }
107              
108             sub printDevices {
109             my ($self, $params) = @_;
110             my $yamlstring;
111             if ($self->devices() ) {
112             $yamlstring = "\n".YAML::XS::Dump( $self->devices() );
113             }
114             return $yamlstring;
115             }
116              
117             sub findDevice {
118             my ($self, %params) = @_;
119             my $root;
120             my $want = $params{"product"} || return "";
121             $root = $params{root};
122             if (!$root) {
123             $root = $self->devices();
124             }
125             for my $key (keys %{$root}) {
126             if ($key =~ /$want/) {
127             return $root->{$key};
128             }
129             else {
130             my $val;
131             eval {
132             if (keys %{$root->{$key}}) {
133             $val = $self->findDevice(
134             root => $root->{$key},
135             %params);
136             if (defined($val)) {
137             return $val;
138             }
139             }
140             };
141             if ($val) {
142             return $val;
143             }
144             }
145             }
146             return undef;
147             }
148              
149             =secret
150             # see example from File::ChangeNotify
151             my $finder =
152             Module::Pluggable::Object->new( search_path => 'Robotics::Device' );
153              
154             =cut
155              
156             sub _try_load
157             {
158             my $class = shift;
159              
160             eval { Class::MOP::load_class($class) };
161              
162             my $e = $@;
163             die $e if $e && $e !~ /Can\'t locate/;
164              
165             return $e ? 0 : 1;
166             }
167              
168             sub configure {
169             my $self = shift;
170             my $infile = shift || croak "cant open configuration file";
171            
172             open(IN, $infile) || return 1;
173             my $s = do { local $/ = };
174             $self->{CONFIG} = YAML::XS::Load($s) || return 2;
175            
176             warn "Configuring from $infile\n";
177             my $root;
178             my $model;
179             for $root (keys %{$self->{CONFIG}}) {
180             if ($root =~ m/tecan/i) {
181             warn "Configuring $root\n";
182             for $model (keys %{$self->{CONFIG}->{$root}}) {
183             warn "Configuring $model\n";
184             if ($model =~ m/genesis/i) {
185             Robotics::Tecan::Genesis::configure(
186             $self, $self->{CONFIG}->{$root}->{$model});
187             }
188             }
189             }
190             elsif ($root =~ m/objects/i) {
191             die "Configuring $root\n";
192             #Robotics::Objects::configure($self, $self->{CONFIG}->{$root});
193             }
194             }
195             return 0;
196             }
197              
198              
199             # Convert well string to well number
200             # Returns:
201             # >0 well number if success
202             # 0 if error
203             #
204             sub convertWellStringToNumber {
205             my $s = $_[0]; # string
206             my $size = $_[1] || 96; # size of plate
207             my $orient = $_[2] || "L"; # orientation of plate
208              
209             my $row = substr($s, 0, 1);
210             my $col = substr($s, 1);
211             $row = ord($row) - 64;
212             if ($row < 0 || $row > 16) {
213             warn "not a well string, '$s'";
214             return $s;
215             }
216             if ($col > 12 && $size == 96) {
217             warn "bad well string $s";
218             return 0;
219             }
220             if ($col > 24 && $size == 384) {
221             warn "bad well string $s";
222             return 0;
223             }
224             if ($size == 96) {
225             if ($orient eq "L") {
226             return ($col - 1) * 8 + $row;
227             }
228             elsif ($orient eq "P") {
229             return ($row - 1) * 12 + $col;
230             }
231             else {
232             warn "bad well string $s\n";
233             return 0;
234             }
235             }
236             if ($size == 384) {
237             if ($orient eq "L") {
238             return ($col - 1) * 16 + $row;
239             }
240             elsif ($orient eq "P") {
241             return ($row - 1) * 24 + $col;
242             }
243             else {
244             warn "bad well string $s\n";
245             return 0;
246             }
247             }
248             }
249              
250             # Convert well number to well (x,y) number
251             # Returns:
252             # well array (x,y) if success
253             # 0 if error
254             #
255             sub convertWellNumberToXY {
256             return convertWellStringToXY(
257             convertWellNumberToString(@_));
258            
259             }
260              
261             # Convert well string to well (x,y) number
262             # Returns:
263             # well array (x,y) if success
264             # 0 if error
265             #
266             sub convertWellStringToXY {
267             my $s = $_[0]; # string
268             my $size = $_[1] || 96; # size of plate
269             my $orient = $_[2] || "L"; # orientation of plate
270              
271             my $row = substr($s, 0, 1);
272             my $col = substr($s, 1);
273             $row = ord($row) - 64;
274             if ($row < 0 || $row > 16) {
275             warn "not a well string, '$s'";
276             return $s;
277             }
278             if ($col > 12 && $size == 96) {
279             warn "bad well string $s";
280             return 0;
281             }
282             if ($col > 24 && $size == 384) {
283             warn "bad well string $s";
284             return 0;
285             }
286             if ($size == 96) {
287             if ($orient eq "L") {
288             return ($col, $row);
289             }
290             elsif ($orient eq "P") {
291             return ($row, $col);
292             }
293             else {
294             warn "bad well string $s\n";
295             return 0;
296             }
297             }
298             if ($size == 384) {
299             if ($orient eq "L") {
300             return ($col, $row);
301             }
302             elsif ($orient eq "P") {
303             return ($row, $col);
304             }
305             else {
306             warn "bad well string $s\n";
307             return 0;
308             }
309             }
310             }
311              
312              
313             # Convert well number to well string
314             # Returns:
315             # string if success
316             # "" if error
317             #
318             sub convertWellNumberToString {
319             my $n = $_[0]; # number
320             my $size = $_[1] || 96; # size of plate
321             my $orient = $_[2] || "L"; # Landscape or Portrait orientation
322              
323             my $col;
324             my $row;
325             my $s;
326             if ($n < 1) {
327             warn "not a well number '$n'";
328             return $n;
329             }
330             elsif ($n <= 96 && $size == 96) {
331             if ($orient eq "P") {
332             $row = int(($n - 1) / 12) + 1;
333             $col = ($n - (($col - 1) * 12));
334             }
335             elsif ($orient eq "L") {
336             $col = int(($n-1) / 8) + 1;
337             $row = ($n - (($col - 1) * 8));
338             }
339             if ($row == 0) { $row = 8; }
340             $s = chr(64+$row); # I bet no one has EBCDIC anymore
341             }
342             elsif ($n <= 384 && $size == 384) {
343             if ($orient eq "P") {
344             $row = int(($n-1) / 24) + 1;
345             $col = ($n - (($col - 1) * 24));
346             }
347             elsif ($orient eq "L") {
348             $col = int(($n-1) / 16) + 1;
349             $row = ($n - (($col - 1) * 16));
350             }
351             if ($row == 0) { $row = 16; }
352             $s = chr(64+$row);
353             }
354             else {
355             warn "bad well number '$n'\n";
356             }
357              
358             $s .= $col;
359              
360             return $s;
361             }
362              
363             =head1 EXPORT
364              
365             No exported functions
366              
367             =head1 FUNCTIONS
368              
369             =head2 new
370              
371             Probes the local machine for connected hardware and returns the
372             device tree.
373              
374              
375             =head2 configure
376              
377             Loads configuration data into memory.
378              
379             =item pathname of configuration file in YAML format
380              
381             Returns:
382             0 if success,
383             1 if file error,
384             2 if configuration error.
385              
386              
387             =head2 convertWellStringToNumber
388              
389             Helper function.
390              
391             Converts a microtiter plate well string (such as "B7")
392             to a well number (such as 39), depending on
393             plate size and plate orientation. Well #1 is defined
394             as "A1".
395              
396             Arguments:
397              
398             =item Well String. Should be in the range: "A1" .. [total size of plate]
399              
400             =item Size of plate (number of wells). Example: 96 or 384.
401             Default is 96.
402              
403             =item Orientation of plate, either "L" for landscape
404             or "P" for portrait (default "L"). Landscape means, when
405             looking at the plate on a table, the coordinates are defined
406             for the long side running left-to-right, and the beginning
407             row is the furthest away.
408              
409             Returns:
410              
411             =item Number > 0 (such as 43), if success.
412              
413             =item 0, if error.
414              
415            
416             =head2 convertWellNumberToString
417              
418             Helper function.
419              
420             Converts a microtiter plate well number (such as 54)
421             to a co-ordinate string (such as "D5"), depending on
422             plate size and plate orientation. Well #1 is defined
423             as "A1".
424              
425             Arguments:
426              
427             =item Well number. Should be in the range: 1 .. [total size]
428              
429             =item Size of plate (number of wells). Example: 96 or 384.
430             Default is 96.
431              
432             =item Orientation of plate, either "L" for landscape
433             or "P" for portrait (default "L"). Landscape means, when
434             looking at the plate on a table, the coordinates are defined
435             for the long side running left-to-right.
436              
437             Returns:
438              
439             =item String (such as "A1"), if success.
440              
441             =item Null string, if error.
442              
443            
444             =head2 convertWellStringToXY
445              
446             Converts a microtiter plate well string (such as "E8") to an
447             (x,y) coordinate array (such as (5,6)).
448              
449             Arguments:
450              
451             =item Well coordinate string. The top left well is
452             defined as A1.
453              
454             =item Size of plate (number of wells). Example: 96 or 384.
455             Default is 96.
456              
457             =item Orientation of plate, either "L" for landscape
458             or "P" for portrait (default "L"). Landscape means, when
459             looking at the plate on a table, the coordinates are defined
460             for the long side running left-to-right, and the beginning
461             row is the furthest away.
462              
463             Returns:
464              
465             =item Array (such as (8,8)), if success.
466              
467             =item 0, if error.
468              
469             =head2 convertWellNumberToXY
470              
471             Uses the other convertWell functions to convert a
472             well number (1 .. (total size)) into (x,y) coordinates.
473             See previous functions for args and return values.
474              
475              
476             =head1 AUTHOR
477              
478             Jonathan Cline, C<< >>
479              
480             =head1 BUGS
481              
482             Please report any bugs or feature requests to C, or through
483             the web interface at L. I will be notified, and then you'll
484             automatically be notified of progress on your bug as I make changes.
485              
486              
487             =head1 SUPPORT
488              
489             You can find documentation for this module with the perldoc command.
490              
491             perldoc Robotics
492              
493              
494             You can also look for information at:
495              
496             =over 4
497              
498             =item * RT: CPAN's request tracker
499              
500             L
501              
502             =item * AnnoCPAN: Annotated CPAN documentation
503              
504             L
505              
506             =item * CPAN Ratings
507              
508             L
509              
510             =item * Search CPAN
511              
512             L
513              
514             =back
515              
516              
517             =head1 ACKNOWLEDGEMENTS
518              
519              
520             =head1 COPYRIGHT & LICENSE
521              
522             Copyright 2009 Jonathan Cline.
523              
524             This program is free software; you can redistribute it and/or modify it
525             under the terms of either: the GNU General Public License as published
526             by the Free Software Foundation; or the Artistic License.
527              
528             See http://dev.perl.org/licenses/ for more information.
529              
530              
531             =cut
532              
533             no Moose;
534              
535             __PACKAGE__->meta->make_immutable;
536              
537             1; # End of Robotics
538              
539             __END__