File Coverage

blib/lib/Robotics/Tecan.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             package Robotics::Tecan;
2              
3 4     4   6060683 use warnings;
  4         11  
  4         130  
4 4     4   21 use strict;
  4         8  
  4         111  
5 4     4   1427 use Moose;
  0            
  0            
6             use Carp;
7              
8             has 'connection' => ( is => 'rw' );
9             has 'serveraddr' => ( is => 'rw' );
10             has 'password' => ( is => 'rw' );
11             has 'port' => ( is => 'rw', isa => 'Int' );
12             has 'token' => ( is => 'rw');
13             has 'VERSION' => ( is => 'rw' );
14             has 'STATUS' => ( is => 'rw' );
15             has 'HWTYPE' => ( is => 'rw' );
16             has 'HWALIAS' => ( is => 'rw' );
17             has 'HWNAME' => ( is => 'rw' );
18             has 'HWSPEC' => ( is => 'rw' );
19             has 'TIP_MAX' => ( is => 'rw' );
20             has 'HWDEVICES' => ( is => 'rw' );
21             has 'DATAPATH' => ( is => 'rw', isa => 'Maybe[Robotics::Tecan]' );
22             has 'COMPILER' => ( is => 'rw' );
23             has 'compile_package' => (is => 'rw', isa => 'Str' );
24              
25             has 'CONFIG' => ( is => 'rw', isa => 'Maybe[HashRef]' );
26             has 'POINTS' => ( is => 'rw', isa => 'Maybe[HashRef]' );
27             has 'OBJECTS' => ( is => 'rw', isa => 'Maybe[HashRef]' );
28             has 'WORLD' => ( is => 'rw', isa => 'Maybe[HashRef]' );
29              
30             use Robotics::Tecan::Gemini; # Software<->Software interface
31             use Robotics::Tecan::Genesis; # Software<->Hardware interface
32             use Robotics::Tecan::Client;
33             with 'Robotics::Tecan::Server';
34              
35             # note for gemini device driver:
36             # to write a "dying gasp" to the filehandle prior to closure from die,
37             # implement DEMOLISH, which would be called if BUILD dies
38              
39             my $Debug = 1;
40              
41             =head1 NAME
42              
43             Robotics::Tecan - Control Tecan robotics hardware as Robotics module
44              
45             See L
46              
47             =head1 VERSION
48              
49             Version 0.23
50              
51             =cut
52              
53             our $VERSION = '0.23';
54              
55              
56             sub BUILD {
57             my ( $self, $params ) = @_;
58              
59             # Do only if called directly
60             return unless $self->connection;
61            
62             my $connection = "local";
63            
64             my $server = $self->serveraddr;
65             my $serverport;
66              
67             if ($server) {
68             my @host = split(":", $server);
69             $server = shift @host;
70             $serverport = shift @host || $self->port || 8090;
71             $connection = "remote";
72             }
73             if ($self->connection) {
74             $self->compile_package( (split(',', $self->connection))[1] );
75             if ($connection eq "local") {
76             # Use Gemini
77             warn "Opening Robotics::Tecan::Gemini->openPipe()\n" if $Debug;
78             $self->DATAPATH(
79             Robotics::Tecan::Gemini->new(
80             object => $self)
81             );
82             }
83             elsif ($connection eq "remote") {
84             # Use Robotics::Tecan socket protocol
85             warn "Opening Robotics::Tecan::Client to $server:$serverport\n" if $Debug;
86             $self->DATAPATH(
87             Robotics::Tecan::Client->new(
88             object => $self,
89             server => $server, port => $serverport,
90             simulate => $params->{"simulate"},
91             password => $self->password)
92             );
93             }
94            
95             $self->VERSION( undef );
96             $self->HWTYPE( undef );
97             $self->STATUS( undef );
98             $self->password( undef );
99             }
100             else {
101             die "must give 'connection' for ".__PACKAGE__."->new()\n";
102             }
103             }
104              
105             =head2 probe
106            
107             =cut
108             sub probe {
109             my ($self, $params) = @_;
110             my (%all, %found);
111              
112             # Find software interfaces then hardware interfaces
113             %found = %{Robotics::Tecan::Gemini->probe()};
114             %all = (%all, %found);
115             %found = %{Robotics::Tecan::Genesis->probe()};
116             %all = (%all, %found);
117            
118             return \%all;
119             }
120              
121             =head2 attach
122              
123             Start communication with the hardware.
124              
125             Arguments are:
126              
127             =item Robotics object: The variable returned from new().
128              
129             =item (optional) Flags. A string which specifies attach options
130             as single characters in the string: "o" for override
131              
132              
133             Returns: String containing hardware type and version from manufacturer "VERSION" output.
134              
135             Will not attach to "BUSY" hardware unless override flag is given.
136              
137             =cut
138              
139             sub attach {
140             my ($self) = shift;
141             my $flags = shift || "";
142             if ($self->DATAPATH()) {
143             $self->DATAPATH()->attach(option => $flags);
144             if ($self->DATAPATH()->attached &&
145             $self->compile_package) {
146             # Create a machine compiler for the attached hardware
147             $self->COMPILER($self->compile_package()->new());
148             # Compiler needs datapath for internal sub's
149             $self->COMPILER()->DATAPATH( $self->DATAPATH() );
150             }
151             }
152             return $self->VERSION();
153             }
154              
155             sub hw_get_version {
156             my $self = shift;
157             return $self->command("GET_VERSION");
158            
159             }
160              
161             =head2 Write
162              
163             Function to compile a command to hardware Robotics device driver
164             and send the command if attached to the hardware.
165              
166             =cut
167              
168             sub Write {
169             my $self = shift;
170             warn "! Write needs removal\n";
171             if ($self->DATAPATH() && $self->DATAPATH()->attached()) {
172             if ($self->HWTYPE() =~ /GENESIS/) {
173             # XXX temporary
174             my $selector = $self->DATAPATH();
175             my $rval = $selector->write(@_);
176             return $rval;
177             }
178             }
179             else {
180             warn "! attempted Write when not Attached\n";
181             return "";
182             }
183             }
184              
185             sub command {
186             my $self = shift;
187             if ($self->DATAPATH() && $self->DATAPATH()->attached()) {
188             if ($self->COMPILER) {
189             my $code = $self->COMPILER()->compile(@_);
190             return $self->DATAPATH()->write($code) if $code;
191             }
192             else {
193             warn "! No command compiler for ".$self->connection. "\n";
194             }
195             }
196             else {
197             warn "! attempted 'command' when not Attached\n";
198             return "";
199             }
200             }
201              
202             # sub command1 is for single(firmware) commands
203             sub command1 {
204             my $self = shift;
205             if ($self->DATAPATH() && $self->DATAPATH()->attached()) {
206             if ($self->COMPILER) {
207             my $code = $self->COMPILER()->compile1(@_);
208             return $self->DATAPATH()->write($code);
209             }
210             else {
211             warn "! No command compiler for ".$self->connection. "\n";
212             }
213             }
214             else {
215             warn "! attempted 'command' when not Attached\n";
216             return "";
217             }
218             }
219              
220             =head2 park
221              
222             Park robotics motor arm (perhaps running calibration), based on the motor name (see 'move')
223              
224             For parking roma-named arms, use the arguments:
225             =item (optional) grip - gripper (hand) action for parking:
226             "n" or false means unchanged grip (default), "p" for park the grip
227              
228             For parking liha-named arms, use the arguments:
229              
230              
231             For parking
232             Return status string.
233             May take time to complete.
234              
235             =cut
236              
237             sub park {
238             my $self = shift;
239             my $motor = shift || "roma0";
240             my $grip = shift || "0";
241             my $reply;
242             if ($motor =~ m/liha(\d*)/i) {
243             $self->command("LIHA_PARK", lihanum => $1) if $1;
244             $self->command("LIHA_PARK", lihanum => "0") if !$1;
245             }
246             elsif ($motor =~ m/roma(\d*)/i) {
247             my $motornum = 0;
248             # XXX: Check if \d is active arm, if not use SET_ROMANO to make active
249             if ($1 > 0) {
250             $motornum = $1;
251             }
252             $self->command("SET_ROMANO", romanum => $motornum);
253             $reply = $self->Read();
254             if ( $grip =~ m/p/i ) {
255             $grip = "1";
256             }
257             else {
258             $grip = "0";
259             }
260             $self->command("ROMA_PARK", grippos => $grip);
261             }
262             elsif ($motor =~ m/lihi(\d*)/i) {
263             # "arm number always zero"
264             my $arm = "0";
265             $self->command("LIHA_PARK", lihanum => $arm);
266             }
267             elsif ($motor =~ m/pnp(\d*)/i) {
268              
269             # XXX: allow user to set handpos (gripper)
270             my $handpos = 0;
271             $self->command("PNP_PARK", gripcommand => $handpos);
272             }
273             return $reply = $self->Read();
274             }
275              
276             =head2 grip
277              
278             Grip robotics motor gripper hand, based on the motor name (see 'move').
279              
280             For roma-named motors, the gripper hand motor name is the same as the arm motor name.
281              
282             For roma-named motors, use the arguments:
283             =item (optional) direction - "o" for hand open, or "c" for hand closed (default)
284             =item (optional) distance - numeric, 60..140 mm (default: 110)
285             =item (optional) speed - numeric, 0.1 .. 150 mm/s (default: 100)
286             =item (optional) force - numeric when moving hand closed, 1 .. 249 (default: 40)
287              
288             For pnp-named motors, use the arguments:
289             =item (optional) direction - "o" for hand open/release tube, or "c" for hand closed/grip (default)
290             =item (optional) distance - numeric, 7..28 mm (default: 16)
291             =item (optional) speed - numeric (unused)
292             =item (optional) force - numeric (unused)
293              
294              
295             Return status string.
296             May take time to complete.
297              
298             =cut
299              
300             sub grip {
301             my $self = shift;
302             my $motor = shift || "roma0";
303             my $dir = shift || "c";
304             my $distance = shift;
305             my $speed = shift;
306             my $force = shift;
307              
308             # ROMA_GRIP [distance;speed;force;strategy]
309             # Example: ROMA_GRIP;80;50;120;0
310             # PNP_GRIP [distance;speed;force;strategy]
311             # Example: PNP_GRIP;16;0;0;0
312             # TEMO_PICKUP_PLATE [grid;site;plate type]
313             # TEMO_DROP_PLATE [grid;site;plate type]
314             # CAROUSEL_DIRECT_MOVEMENTS [device;action;tower;command]
315              
316             # C=close/gripped=1, O=open/release=0
317             if ( $dir =~ m/c/i ) { $dir = "1"; }
318             else { $dir = "0"; }
319              
320             my $reply;
321             if ( $motor =~ m/roma(\d*)/i ) {
322             if (!$distance) { $distance = "110" };
323             if (!$speed) { $speed = "50" };
324             if (!$force) { $force = "50" };
325             # XXX: Check if \d is active arm, if not use SET_ROMANO to make active
326             $self->command("ROMA_GRIP",
327             distance => $distance, speed => $speed,
328             force => $force, gripcommand => $dir);
329             }
330             elsif ( $motor =~ m/pnp(\d*)/i ) {
331             # "speed, force: unused"
332             if (!$distance) { $distance = "16" };
333             $self->command("PNP_GRIP",
334             distance => $distance, speed => $speed,
335             force => $force, strategy => $dir);
336             }
337             return $reply = $self->Read();
338             }
339              
340              
341              
342             =head2 move
343              
344             Move robotics motor arm, based on the case-insensitive motor name and given coordinates.
345              
346             Note: The Gemini application asks the user for arm numbers 1,2,3... in the GUI application,
347             whereas the robotics command language (and this Perl module) use arm numbers 0,1,2,..
348             The motors are named as follows:
349              
350              
351             =item "roma0" .. "romaN" - access RoMa arm number 0 .. N. Automatically switches to make the arm
352             the current arm. Alternatively, "romaL" or "romal" can be used for the left arm (same as "roma0")
353             and "romaR" or "romar" can be use for the right arm (same as "roma1").
354              
355             =item "pnp0" .. "pnpN" - access PnP arm number 0 .. N. Alternatively, "pnpL" or "pnpl" can be used
356             for the left arm (same as "pnp0")
357             and "pnpR" or "pnpr" can be use for the right arm (same as "pnp1"). Note: The Gemini application
358             asks the user for arm numbers 1,2,3... in the GUI application, whereas the robotics command language
359             (and this Perl module) use arm numbers 0,1,2,..
360              
361             =item "temo0" .. "temoN" - access TeMo arm number 0 .. N.
362              
363             =item "liha0" .. "lihaN" - access LiHA arm number 0 .. N. (Note: no commands exist)
364            
365             For moving roma-named motors with Gemini-defined vectors, use the arguments:
366              
367             =item vector - name of the movement vector (programmed previously in Gemini)
368              
369             =item (optional) direction - "s" = travel to vector start, "e" = travel to vector end
370             (default: go to vector end)
371              
372             =item (optional) site - numeric, (default: 0)
373              
374             =item (optional) relative x,y,z - three arguments indicating relative positioning (default: 0)
375              
376             =item (optional) linear speed (default: not set)
377              
378             =item (optional) angular speed (default: not set)
379              
380             For moving roma-named motors with Robotics::Tecan points (this module's custom software),
381             use the arguments:
382              
383             =item point - name of the movement point (programmed previously)
384              
385             For moving pnp-named motors, use the arguments:
386              
387             =item TBD
388              
389             For moving temo-named motors, use the arguments:
390              
391             =item TBD
392              
393             For moving carousel-named motors, use the arguments:
394              
395             =item TBD
396              
397             Return status string.
398             May take time to complete.
399              
400             =cut
401              
402              
403             sub move_object {
404             my $self = shift;
405              
406             my %param = @_;
407             my $motor = $param{"motor"} || "roma0";
408             my $dest = $param{"to"} || "HOME1";
409             my $on = $param{"on"};
410             my $object = $param{"object"};
411             my $position = $param{"position"};
412             my $point1 = $param{"point_from"};
413             my $point2 = $param{"point_to"};
414            
415             if ((!$on && !$position) && (!$point1 || !$point2)) {
416             confess __PACKAGE__. "no object or point given, @_";
417             }
418            
419             if ($point1) {
420             # Do point-based move
421            
422             # move to point1
423             # grip close object
424             # move to point2
425             # grip open object
426             return;
427             }
428            
429             # Do object-lookup-based move
430            
431             my $coordref1;
432             $coordref1 = $self->_object_get_coord(
433             motor => $motor,
434             object => $object,
435             position => $position);
436             if (!defined($coordref1)) {
437             confess __PACKAGE__." no position for object @_";
438             }
439            
440             print YAML::XS::Dump($coordref1);
441             die;
442            
443             my $coordref2;
444             $coordref2 = $self->_object_get_coord(
445             motor => $motor,
446             on => $dest);
447             if (!defined($coordref1)) {
448             confess __PACKAGE__." no position for object @_";
449             }
450            
451             # Do the move to fetch
452             $self->move();
453            
454             # Do the move to discard
455            
456            
457             }
458              
459             sub move {
460             my ($self) = shift;
461            
462             my (%param) = @_;
463             my $motor = $param{"motor"} || "roma0";
464             my $name = $param{"to"} || "HOME1";
465             my $dir = $param{"dir"} || "0";
466             my $site = $param{"site"} || "0";
467             my $xdelta = $param{"xdelta"} || "0";
468             my $ydelta = $param{"ydelta"} || "0";
469             my $zdelta = $param{"zdelta"} || "0";
470             my $speedlinear = $param{"speedlinear"} || 0;
471             my $speedangular = $param{"speedangular"} || 0;
472             my $coordref = $param{"coord"};
473             my $grip = $param{"grip"};
474              
475             # ROMA_MOVE [vector;site;xOffset;yOffset;zOffset;direction;XYZSpeed;rotatorSpeed]
476             # Example: ROMA_MOVE;Stacker1;0;0;0;0;0
477             # PNP_MOVE [vector;site;position;xOffset;yOffset;zOffset;direction;XYZSpeed]
478             # TEMO_MOVE [site;stacker flag]
479             # Example: TEMO_MOVE;1
480             # CAROUSEL_DIRECT_MOVEMENTS [device;action;tower;command]
481              
482             # S=vector points to start=1, E=vector points to end=0
483             # ""0 = from safe to end position, 1 = from end to safe position""
484             if ( $dir =~ m/s/i ) { $dir = "1"; }
485             #elsif ( $dir =~ m/e/i ) { $dir = "0"; }
486             else { $dir = "0"; }
487            
488             my $reply;
489             if ( $motor =~ m/roma(\d*)/i ) {
490             # First check for Robotics::Tecan point
491             if (grep {$_ eq $name} keys %{$self->{POINTS}->{$motor}}) {
492             my $motornum = $1 + 1; # XXX motornum needs verification with docs
493              
494             # Verify motors are OK to move
495             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
496            
497             # Program the coords
498             my ($x, $y, $z, $r, $g, $speed) = split(",", $self->{POINTS}->{$motor}->{$name});
499             if (!defined($speed)) {
500             # note "speed=0" is ~1cm? per second.. *super* slow
501             $speed = "1";
502             }
503             if (!defined($g) && defined($grip)) {
504             $g = $grip;
505             }
506             $self->command1("SAA",
507             motorname => $motor,
508             index => 1,
509             x => $x,
510             y => $y,
511             z => $z,
512             r => $r,
513             g => $g,
514             speed => $speed);
515             ## No reply for SAA
516             my $reply = $self->Read();
517             my $result = $self->COMPILER()->decompile_reply($reply);
518             if ($result =~ /^E/ || !($reply =~ /^0/)) {
519             carp(__PACKAGE__. " $motor move error $result");
520             return "";
521             }
522             # Assume Program coords is OK
523             # Perform move
524             $self->command1("AAA",
525             motorname => $motor);
526             $reply = $self->Read();
527             $result = $self->COMPILER()->decompile_reply($reply);
528             if ($result =~ /^E/ || !($reply =~ /^0/)) {
529             carp(__PACKAGE__. " $motor move error $result");
530             return "";
531             }
532              
533             # Verify move is correct
534             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
535             return $reply;
536             }
537             else {
538             # Use ROMA_MOVE
539             my $motornum = 0;
540             # XXX: Check if \d is active arm, if not use SET_ROMANO to make active
541             if ($1 > 0) {
542             $motornum = $1;
543            
544             }
545             $self->command("SET_ROMANO", romanum => $motornum);
546             $reply = $self->Read();
547            
548             if ( $speedangular > 0 && $speedlinear < 1 ) {
549             # linear must be set if angular is set
550             $speedlinear = "400";
551             }
552             $self->command("ROMA_MOVE",
553             vectorname => $name, site => $site,
554             deltax => $xdelta, deltay => $ydelta, deltaz => $zdelta,
555             direction => $dir,
556             xyzspeed => $speedlinear,
557             rotatorspeed => $speedangular);
558            
559             return $reply = $self->Read();
560             }
561             }
562             elsif ( $motor =~ m/pnp(\d*)/i ) {
563              
564             # XXX: TBD
565             }
566             elsif ( $motor =~ m/liha(\d*)/i ) {
567             my $motornum = $1 + 1; # XXX motornum needs verification with docs
568            
569             if (defined($coordref)) {
570             # Do coordinate reference
571             # Verify motors are OK to move
572             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
573             # Perform movement command
574             $self->command1("SHZ",
575             unit => $motor,
576             ztravel1 => 2080, ztravel2 => 2080, ztravel3 => 2080, ztravel4 => 2080,
577             ztravel5 => 2080, ztravel6 => 2080, ztravel7 => 2080, ztravel8 => 2080);
578             $reply = $self->Read();
579             my ($x, $y, $ys, $z1, $z2, $z3, $z4, $z5, $z6, $z7, $z8) =
580             ($coordref->{x}, $coordref->{y}, $coordref->{ys},
581             $coordref->{z1}, $coordref->{z2}, $coordref->{z3},
582             $coordref->{z4}, $coordref->{z5}, $coordref->{z6},
583             $coordref->{z7}, $coordref->{z8});
584             # TODO: Add run-time offsets here if any
585             $self->command1("PAA",
586             unit => $motor,
587             x => $x, y => $y, yspace => $ys,
588             z1 => $z1, z2 => $z2, z3 => $z3,
589             z4 => $z4, z5 => $z5, z6 => $z6,
590             z7 => $z7, z8 => $z8);
591             $reply = $self->Read();
592             my $result = $self->COMPILER()->decompile_reply($reply);
593             if ($result =~ /^E/ || !($reply =~ /^0/)) {
594             carp(__PACKAGE__. " $motor move error $result");
595             return "";
596             }
597             # Verify move is correct
598             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
599             return $reply;
600             }
601             elsif (grep {$_ eq $name} keys %{$self->{POINTS}->{$motor}}) {
602             # Do Robotics::Tecan point
603            
604             # Verify motors are OK to move
605             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
606             # Perform movement command
607             $self->command1("SHZ",
608             unit => $motor,
609             ztravel1 => 2080, ztravel2 => 2080, ztravel3 => 2080, ztravel4 => 2080,
610             ztravel5 => 2080, ztravel6 => 2080, ztravel7 => 2080, ztravel8 => 2080);
611             $reply = $self->Read();
612             my ($x, $y, $ys, $z1, $z2, $z3, $z4, $z5, $z6, $z7, $z8) =
613             split(",", $self->{POINTS}->{$motor}->{$name});
614             # TODO: Add run-time offsets here if any
615             $self->command1("PAA",
616             unit => $motor,
617             x => $x, y => $y, yspace => $ys,
618             z1 => $z1, z2 => $z2, z3 => $z3,
619             z4 => $z4, z5 => $z5, z6 => $z6,
620             z7 => $z7, z8 => $z8);
621             $reply = $self->Read();
622             my $result = $self->COMPILER()->decompile_reply($reply);
623             if ($result =~ /^E/ || !($reply =~ /^0/)) {
624             carp(__PACKAGE__. " $motor move error $result");
625             return "";
626             }
627             # Verify move is correct
628             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
629             return $reply;
630             }
631             }
632             }
633              
634             =head2 move_path
635              
636             Move robotics motor arm along predefined path, based on the case-insensitive motor name and given coordinates. See move.
637              
638             Arguments:
639              
640             =item Name of motor.
641              
642             =item Array of Robotics::Tecan custom points (up to 100 for Genesis)
643              
644             Return status string.
645             May take time to complete.
646              
647             =cut
648              
649             sub move_path {
650             my $self = shift;
651             my $motor = shift || "roma0";
652             my @points = @_;
653             my $name;
654             my $reply;
655             if ( $motor =~ m/roma(\d*)/i ) {
656             my $motornum = $1 + 1; # XXX motornum needs verification with docs
657             # Verify motors are OK to move
658             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
659             my $p = 1;
660             foreach $name (@points) {
661             # First check for Robotics::Tecan point
662             if (grep {$_ eq $name} keys %{$self->{POINTS}->{$motor}}) {
663             # Program the coords
664             my ($x, $y, $z, $r, $g, $speed) = split(",", $self->{POINTS}->{$motor}->{$name});
665             if (!$speed) {
666             # note "speed=0" is ~1cm? per second.. *super* slow
667             $speed = "1";
668             }
669             $self->command1("SAA",
670             motorname => $motor,
671             index => $p,
672             x => $x,
673             y => $y,
674             z => $z,
675             r => $r,
676             g => $g,
677             speed => $speed);
678             ## No reply for SAA
679             my $reply = $self->Read();
680             my $result = $self->COMPILER()->decompile_reply($reply);
681             if ($result =~ /^E/ || !($reply =~ /^0/)) {
682             carp(__PACKAGE__. " $motor Error programming point '$name': $result");
683             return "";
684             }
685             $p++;
686             }
687             last if $p > 100;
688             }
689             if ($p > 1) {
690             # Program point is OK - Start Move
691             # Perform move
692             $self->command1("AAA",
693             motorname => $motor);
694             my $reply = $self->Read();
695             my $result = $self->COMPILER()->decompile_reply($reply);
696             if ($result =~ /^E/ || !($reply =~ /^0/)) {
697             carp(__PACKAGE__. " $motor move error $result");
698             return "";
699             }
700            
701             # Verify move is correct
702             $self->{COMPILER}->CheckMotorOK($motor, $motornum) || return "";
703             return $reply;
704             }
705             }
706             }
707              
708             # Find coords of "carrier" aka "fixed object"
709             sub _object_get_coord_offset_fixed {
710             my $self = shift;
711             my %param = @_;
712             my $fixedname = $param{"fixedname"} || confess;
713             my $fixedobjref = $param{"fixedref"} || die;
714             my $coordref = $param{"hashref"} || die;
715             my $position = $param{"position"} || "1,1,1";
716             my $axisref = $param{"axis"} || die;
717             my $movobjref = $param{"movableref"} || die;
718            
719             my $axismax = $#{@$axisref};
720             my @obj_pos = (split(",", $position), 0, 0, 0, 0, 0);
721             my $type = "fixed";
722             if ($fixedobjref) {
723             ## genesis->fixed->JCplateholder->move:
724             my $objmoveref = $fixedobjref->{move};
725             for my $index (0.. $axismax) {
726             my $axisname = $axisref->[$index];
727             my $axispos = $obj_pos[$index] || next;
728             ## genesis->fixed->JCplateholder->move->[xyz]->[1..n]
729             $coordref->{$axisname} = $objmoveref->{$axisname}->{$axispos}
730             if defined($objmoveref->{$axisname}) &&
731             defined($objmoveref->{$axisname}->{$axispos});
732             }
733             # Find the platform coordinates of the relative object defining the above
734             # and subtract out the relative offset
735             ## genesis->fixed->JCplateholder->move->relativeto:
736             my $relmoveref = $objmoveref->{"relativeto"};
737             if (defined($relmoveref->{"fixed"}) && !($relmoveref->{"fixed"} =~ /none/i)) {
738             for my $index (0.. $axismax) {
739             my $axisname = $axisref->[$index];
740             ## genesis->fixed->JCplateholder->move->relativeto->[xyz]
741             $coordref->{$axisname} -= $relmoveref->{$axisname}
742             if defined($relmoveref->{$axisname});
743             }
744             }
745             }
746             print "_object_get_coord_offset_fixed ". YAML::XS::Dump($coordref);
747             return 1;
748             }
749              
750             sub _object_get_coord {
751             my ($self, %param) = @_;
752            
753             my $object = $param{"object"};
754             my $grippos = $param{"grippos"};
755             my $couplingtype = $param{"couplingtype"};
756             my $couplingobj = $param{"coupling"};
757             my $orientation = $param{"orientation"};
758             my $liquidhandling = $param{"liquidaction"};
759             my $motor = $param{"motor"};
760             my $tipnum = $param{"tip"};
761            
762             if (!$object) {
763             confess __PACKAGE__." no object";
764             }
765             if (!$self->OBJECTS()) {
766             confess __PACKAGE__." no object table";
767             }
768            
769             # Check that object exists in the world
770             my $worldref = $self->WORLD();
771             my $worldobjref;
772             if (!($worldobjref = $worldref->{$object})) {
773             carp __PACKAGE__. "Object $object not placed yet";
774             }
775             my $parentname = $worldobjref->{"parent"};
776             my $pos = $worldobjref->{"position"};
777            
778             my @axis = $self->COMPILER()->_getAxisNames($motor);
779             my @axisalias;
780             my %welladdr;
781             my %action;
782             my $arm_offsetref;
783             if ($motor =~ /roma/i) {
784             $action{"g"} = $grippos || "open";
785             $action{"r"} = $orientation || "landscape";
786             # This offset is subtracted from final coord
787             $arm_offsetref = $self->OBJECTS()->{"genesis"}->{"arm_offset"}->{$motor};
788             }
789             elsif ($motor =~ /liha/i) {
790             # Convert well name to well address
791             my %welladdr = _convertWellToXY(
792             wellname => $param{"well"},
793             wellnum => $param{"wellnum"},
794             tips => $param{"tipnum"},
795             );
796             if (!%welladdr) {
797             confess __PACKAGE__. " no well address";
798             }
799            
800             # got wells, set couplingtype tip
801             if (defined($couplingobj) && !defined($couplingtype)) {
802             $couplingtype = "tips";
803             }
804             if (!defined($liquidhandling)) {
805             die __PACKAGE__. " liha action required";
806             }
807             for my $axisname (grep(/z/, @axis)) {
808             my $tip = $tipnum || "1";
809             if ($axisname eq "z$tip") {
810             # Active tip
811             # TODO: need to add multiple tip operation here
812             $action{$axisname} = $liquidhandling;
813             }
814             else {
815             # default axis or other tips use "free"
816             $action{$axisname} = "free";
817             }
818             }
819             # This offset is subtracted from final coord
820             $arm_offsetref = $self->OBJECTS()->{"genesis"}->{"arm_offset"}->{$motor};
821             }
822             else {
823             die __PACKAGE__. "no motorname reference";
824             }
825            
826             # Look up the references
827             my $carrierref;
828             if (grep {$_ eq $parentname} keys %{$self->OBJECTS()->{"fixed"}}) {
829             $carrierref = $self->OBJECTS()->{"fixed"}->{$parentname};
830             }
831             my $locref;
832             my $locrelativetofixedref;
833             if (grep {$_ eq $object} keys %{$self->OBJECTS()->{"movable"}}) {
834             $locref = $self->OBJECTS()->{"movable"}->{$object};
835             if (defined($locref->{"move"}) &&
836             defined($locref->{"move"}->{"relativeto"}) &&
837             defined($locref->{"move"}->{"relativeto"}->{"fixed"})) {
838             my $locrelativetofixedname = $locref->{"move"}->{"relativeto"}->{"fixed"};
839             if (grep {$_ eq $locrelativetofixedname} keys %{$self->OBJECTS()->{"fixed"}}) {
840             $locrelativetofixedref = $self->OBJECTS()->{"fixed"}->{$locrelativetofixedname};
841             }
842             }
843             }
844             my $couplingref;
845             if (defined($couplingobj) && defined($couplingtype) &&
846             (grep {$_ eq $couplingobj} keys %{$self->OBJECTS()->{$couplingtype}})) {
847             $couplingref = $self->OBJECTS()->{$couplingtype}->{$couplingobj};
848             }
849            
850             #
851             # Find the platform coordinates of what 'this object' is "on"
852             # i.e. calculate carrier grid/site coordinates
853             my %carrier_offset;
854             warn "Object Offset $parentname @ site $pos";
855             $self->_object_get_coord_offset_fixed(
856             fixedname => $parentname,
857             fixedref => $carrierref,
858             movableref => $locref,
859             relfixedref => $locrelativetofixedref,
860             position => $pos,
861             hashref => \%carrier_offset,
862             axis => \@axis);
863            
864             # Find the platform coordinates of 'this' (the object)
865             my %loc_offset;
866             if (defined($locref)) {
867             warn "Object Offset $object";
868             my $locposref = $locref->{numpositions};
869             my $locmoveref = $locref->{move};
870             for my $index (0 .. $#axis) {
871             my $axisname = $axis[$index];
872             my $axisoffset;
873             my $locmovename = $axisname;
874             if ($axisname =~ /^z/ && !defined($locmoveref->{$axisname}) && $motor =~ /liha/) {
875             # Map "z1".."z8" to alias ("z") if "z1".."z8" not defined, for liha
876             $locmovename = "z";
877             }
878             if (defined($action{$axisname})) {
879             # action is: z=(free|aspirate|dispense|max), for liha
880             # g=(open|close|force|speed), r=(landscape|portrait), for roma
881             $axisoffset = $locmoveref->{$locmovename}->{$action{$axisname}}
882             if defined($locmoveref->{$locmovename});
883             #warn "axis=$axisname locmovename=$locmovename action=$action{$axisname} axisoffset=$axisoffset";
884             }
885             elsif (defined($locmoveref->{$axisname}) && defined($welladdr{$axisname})) {
886             # look up offset in database by well address ("1", "2", ...)
887             $axisoffset = $locmoveref->{$axisname}->{$welladdr{$axisname}};
888             }
889             if (defined($axisoffset)) {
890             # this offset has an entry in the database
891             $loc_offset{$axisname} = $axisoffset;
892             }
893             elsif ($axisname =~ /^ys/ && $motor =~ /liha/ && (my $pos1 = $locmoveref->{$locmovename}->{"1"})) {
894             # Map values for ys to ys=1 as default
895             $loc_offset{$axisname} = $pos1;
896             }
897             elsif (defined($locmoveref->{$locmovename}) && defined($locposref->{$locmovename})) {
898             # Calculate from linear extrapolation
899             my $pos1 = $locmoveref->{$locmovename}->{"1"};
900             my $posn = $locmoveref->{$locmovename}->{$locposref->{$locmovename}};
901             if (defined($welladdr{$axisname})) {
902             # Calculate spot offset from well address
903             if (defined($pos1) && defined($posn)) {
904             $loc_offset{$axisname} = $pos1 +
905             int(($posn - $pos1) *
906             ($welladdr{$axisname}-1)/($locposref->{$locmovename}-1));
907             #warn "\tcalc spot_offset_$axisname=$loc_offset{$axisname} ".
908             # "from welladdr=$welladdr{$axisname}\n";
909             }
910             }
911             else {
912             # calculate offset from position
913             }
914             }
915             }
916            
917             # Find the platform coordinates of the relative object defining 'this'
918             # and subtract out the relative offset
919             # (this should be recursive, to allow
920             # objects within objects which are all relative)
921            
922             my $relmoveref = $locmoveref->{"relativeto"};
923             ## genesis->moveable->JCgreinerVbottom96->move->relativeto:
924             if (defined($relmoveref->{"fixed"}) && !($relmoveref->{"fixed"} =~ /none/i)) {
925             my $fixobj = $relmoveref->{"fixed"};
926             my $fixobjref = $self->OBJECTS()->{"fixed"}->{$fixobj}->{"move"};
927             ## genesis->fixed->JCplateholder->move:
928             my %relpos = ("x", $relmoveref->{"x"}, "y", $relmoveref->{"y"}, "z", $relmoveref->{"z"});
929             for my $index (0 .. $#axis) {
930             my $axisname = $axis[$index];
931             my $relposnum = $relpos{$axisname} if defined($relpos{$axisname});
932             ## genesis->moveable->JCgreinerVbottom96->move->relativeto->[xyz]
933             warn "($loc_offset{$axisname} -= $fixobjref->{$axisname}->{$relposnum} for site $axisname=$relposnum)"
934             if defined($fixobjref->{$axisname}) &&
935             defined($fixobjref->{$axisname}) && defined($relposnum);
936             $loc_offset{$axisname} -= ($fixobjref->{$axisname}->{$relposnum})
937             if defined($fixobjref) && defined($axisname) && defined($relposnum) &&
938             defined($fixobjref->{$axisname}) &&
939             defined($fixobjref->{$axisname}->{$relposnum});
940             }
941             }
942             }
943            
944             # Optimization note: if 'this object' is defined in the database
945             # with coords from the 'on object', at the same position,
946             # then the offset is added and then
947             # the relative offset from the relative object is subtracted
948             # resulting in a no-op. better to check if 'this object' was defined
949             # with coords as on the 'on object' and skip the offset+relative lookup.
950            
951             # Find the coupling-object offset, if an object is coupled.
952             # Example, a tip may be coupled to the pipette end
953             my %coupling_offset;
954             if (defined($couplingref)) {
955             for my $index (0 .. $#axis) {
956             my $axisname = $axis[$index];
957             my $objaxisname = $axisname;
958             my $tip = $tipnum || "1";
959             if ($axisname =~ m/^z([\d])/ && !defined($couplingref->{$axisname}) && $motor =~ /liha/) {
960             # Map "z1".."z8" to alias ("z") if "z1".."z8" not defined, for liha
961             $objaxisname = "z";
962             }
963             if (defined($action{$axisname}) && !($action{$axisname} =~ /free/)) {
964             $coupling_offset{$axisname} = $couplingref->{$objaxisname}->{length}
965             if defined($couplingref->{$objaxisname}) &&
966             defined($couplingref->{$objaxisname}->{length});
967             }
968             }
969             }
970            
971             my %coord;
972             for my $index (0 .. $#axis) {
973             my $axisname = $axis[$index];
974             $coord{$axisname} = 0;
975             $coord{$axisname} = $carrier_offset{$axisname} if defined($carrier_offset{$axisname});
976             $coord{$axisname} += $loc_offset{$axisname} if defined($loc_offset{$axisname});
977             # subtract the distance if an object (like a tip) is coupled to the arm
978             $coord{$axisname} -= $coupling_offset{$axisname} if defined($coupling_offset{$axisname});
979             $coord{$axisname} -= $arm_offsetref->{$axisname} if defined($arm_offsetref) && defined($arm_offsetref->{$axisname});
980             }
981             #print "on_offset ".YAML::XS::Dump(\%on_offset)."\n";
982             #print "loc_offset ".YAML::XS::Dump(\%loc_offset)."\n";
983             #print "coord ".YAML::XS::Dump($coord)."\n";
984             return \%coord;
985             }
986              
987             sub _get_aspirate_point {
988             my $self = shift;
989             my %param = @_;
990             my $name = $param{"at"};
991             my $motor = $param{"motor"} || "liha0";
992              
993             my $coords;
994             if (grep {$_ eq $name} keys %{$self->{POINTS}->{$motor}}) {
995             $coords = $self->{POINTS}->{$motor}->{$name};
996             }
997             else {
998             return undef;
999             }
1000             return $coords;
1001             }
1002              
1003             # Rename this method to better abstraction
1004             sub aspirate {
1005             my $self = shift;
1006             my %param = @_;
1007             my $coord;
1008             my $action = "aspirate";
1009             $coord = $self->_get_aspirate_point(@_);
1010             if (!defined($coord)) {
1011             $coord = $self->_object_get_coord(
1012             motor => "liha0",
1013             coupling => "tip200",
1014             @_, liquidaction => $action);
1015             }
1016             if (!defined($coord)) {
1017             confess __PACKAGE__. "destination unknown, @_";
1018             }
1019            
1020             # TODO: Get the motorname from a state variable
1021             if (!$self->move("liha0", coord => $coord)) {
1022             carp __PACKAGE__. " movement error";
1023             return "";
1024             }
1025              
1026             $self->COMPILER()->tip_aspirate(@_);
1027            
1028             }
1029             # Rename this method to better abstraction
1030             sub dispense {
1031             my $self = shift;
1032             my %param = @_;
1033             my $coord;
1034             my $action = "dispense";
1035             $coord = $self->_get_aspirate_point(@_);
1036             if (!defined($coord)) {
1037             $coord = $self->_object_get_coord(
1038             motor => "liha0",
1039             coupling => "tip200",
1040             @_, liquidaction => $action);
1041             }
1042             if (!defined($coord)) {
1043             confess __PACKAGE__. "destination unknown, @_";
1044             }
1045            
1046             # TODO: Get the motorname from a state variable
1047             if (!$self->move("liha0", coord => $coord)) {
1048             carp __PACKAGE__. " movement error";
1049             return "";
1050             }
1051             $self->COMPILER()->tip_dispense(@_);
1052            
1053             }
1054              
1055             sub WriteRaw {
1056             # This function provided for debug only - do not use
1057             my $self = shift;
1058             warn "! WriteRaw needs removal\n";
1059             my $data;
1060             if ($self->{ATTACHED}) {
1061             $data =~ s/[\r\n\t\0]//go;
1062             $data =~ s/^\s*//go;
1063             $data =~ s/\s*$//go;
1064             if ($self->{FID}) {
1065             $self->{FID}->Write($data . "\0");
1066             }
1067             elsif ($self->{SERVER}) {
1068             my $socket = $self->{SOCKET};
1069             print $socket ">$data\n";
1070             print STDERR ">$data\n" if $Debug;
1071             }
1072             }
1073             else {
1074             warn "! attempted Write when not Attached\n";
1075             return "";
1076             }
1077             warn "!! delete this function";
1078            
1079             }
1080             =head2 Read
1081              
1082             Low level function to read commands from hardware.
1083              
1084             =cut
1085             sub Read {
1086             my $self = shift;
1087             # Reading while unattached may hang depending on device
1088             # so always check attached()
1089             if ($self->DATAPATH() && $self->DATAPATH()->attached()) {
1090             my $data;
1091             if (!$self->DATAPATH()->EXPECT_RECV()) {
1092             warn "!! read when no reply expected; system hang is possible; ignoring Read()";
1093             carp;
1094             }
1095             my $selector = $self->DATAPATH();
1096             $data = $selector->read();
1097             }
1098             else {
1099             warn "! attempted Read when not Attached\n";
1100             return "";
1101             }
1102             }
1103              
1104              
1105             =head2 detach
1106              
1107             End communication to the hardware.
1108              
1109             =cut
1110              
1111             sub detach {
1112             my($self) = shift;
1113             if ($self->DATAPATH()) {
1114             $self->DATAPATH()->close();
1115             $self->DATAPATH( undef );
1116             }
1117             warn "\nThank you for using ". __PACKAGE__. " !\n".
1118             "Please support this open source project by emailing\n".
1119             "GEM scripts and logs to jcline\@ieee.org, thank you.\n\n";
1120             return;
1121             }
1122              
1123             =head2 status_hardware
1124              
1125             Read hardware type.
1126             Return hardware type string (should always be "GENESIS").
1127              
1128             =cut
1129              
1130             sub status_hardware {
1131             my $self = shift;
1132             my $reply;
1133             $reply = $self->command("GET_RSP");
1134             if (!($reply =~ m/genesis/i)) {
1135             warn "Expected response GENESIS from hardware"
1136             }
1137             return $reply;
1138             }
1139              
1140              
1141             =head2 configure
1142              
1143             Loads configuration data into memory.
1144              
1145             =item pathname of configuration file in YAML format
1146              
1147             Returns:
1148             0 if success,
1149             1 if file error,
1150             2 if configuration error.
1151              
1152             =cut
1153              
1154             sub configure {
1155             my $self = shift;
1156             my $infile = shift || croak "cant open configuration file";
1157              
1158             open(IN, $infile) || return 0;
1159             my $s = do { local $/ = };
1160             close(IN);
1161             return 2 unless $s;
1162             $self->CONFIG( YAML::XS::Load($s) );
1163            
1164             warn "Configuring from $infile\n";
1165             my $make;
1166             my $model;
1167             for $make (keys %{$self->CONFIG()}) {
1168             if ($make =~ m/tecan/i) {
1169             warn "Configuring $make\n";
1170             for $model (keys %{$self->{CONFIG}->{$make}}) {
1171             warn "Configuring $model\n";
1172             if ($model =~ m/genesis/i) {
1173             Robotics::Tecan::Genesis::configure(
1174             $self, $self->CONFIG()->{$make}->{$model});
1175             }
1176             }
1177             }
1178             }
1179             return 1;
1180             }
1181              
1182              
1183             sub configure_place {
1184             my ($self, %param) = @_;
1185              
1186             my $object = $param{"object"};
1187             my $parent = $param{"on"};
1188             my $pos = $param{"position"};
1189             my $replace = $param{"replace"};
1190            
1191             my $ref = $self->WORLD();
1192             if (!defined($ref)) {
1193             $self->WORLD( YAML::XS::Load("") );
1194             $ref = $self->WORLD();
1195             }
1196              
1197             if ($ref->{$object} && !$replace) {
1198             carp __PACKAGE__. " object $object already exists; overwriting placement for now";
1199             }
1200             $ref->{$object}->{"parent"} = $parent;
1201             $ref->{$object}->{"position"} = $pos;
1202            
1203             print __PACKAGE__. " Enviroment ". YAML::XS::Dump($ref);
1204             }
1205              
1206            
1207             =head2 status
1208              
1209             Read hardware status. Return status string.
1210              
1211             =cut
1212              
1213             sub status {
1214             my $self = shift;
1215             my $reply;
1216             $self->Write("GET_STATUS");
1217             return $reply = $self->Read();
1218             }
1219              
1220             =head2 initialize
1221              
1222             Quickly initialize hardware for movement (perhaps running quick calibration).
1223             Return status string.
1224             May take time to complete.
1225              
1226             =cut
1227              
1228             sub initialize {
1229             my $self = shift;
1230             my $reply;
1231            
1232             #$self->command("#".$self->{HWNAME}."PIS");
1233             #return $reply = $self->Read();
1234             return "0;IDLE";
1235             }
1236              
1237              
1238             =head2 initialize_full
1239              
1240             Fully initialize hardware for movement (perhaps running calibration).
1241             Return status string.
1242             May take time to complete.
1243              
1244             =cut
1245              
1246             sub initialize_full {
1247             my $self = shift;
1248             my $reply;
1249             return $self->command("INIT_RSP");
1250             }
1251              
1252              
1253             =head2 simulate_enable
1254              
1255             Robotics::Tecan internal hook for simulation and test. Not normally used.
1256              
1257             =cut
1258              
1259             sub simulate_enable {
1260             # Modify internals to do simulation instead of real communication
1261             $Robotics::Tecan::Gemini::PIPENAME = '/tmp/gemini';
1262             }
1263              
1264              
1265              
1266             =head1 REFERENCE ON NAMED PIPES
1267              
1268             Named pipes must be accessed as UNCs. This means that the computer name where the
1269             named pipe is running is a part of its name. Just like any UNC a share name must
1270             be specified. For named pipes the share name is pipe. Examples are:
1271              
1272             \\machinename\pipe\My Named Pipe
1273             \\machinename\pipe\Test
1274             \\machinename\pipe\data\Logs\user_access.log
1275              
1276             Notice how the third example makes use of an arbitrarly long path and that
1277             it has what appear to be subdirectories. Since a named pipe is not truly a part
1278             of the a disk based file system there is no need to create the data\logs subdirectories;
1279             they are simply part of the named pipes name.
1280             Also notice that the third example uses a file extension (.log). This extension does
1281             absolutely nothing and is (like the subdirectories) simply part of the named pipes name.
1282              
1283             When a client process attempts to connect to a named pipe it must specify a full UNC.
1284             If, however, the named pipe is on the same computer as the client process then the
1285             machine name part of the UNC can be replaced with a dot "." as in:
1286              
1287             \\.\pipe\My Named Pipe
1288              
1289              
1290             =head1 AUTHOR
1291              
1292             Jonathan Cline, C<< >>
1293              
1294             =head1 BUGS
1295              
1296             Please report any bugs or feature requests to C, or through
1297             the web interface at L. I will be notified, and then you'll
1298             automatically be notified of progress on your bug as I make changes.
1299              
1300              
1301              
1302              
1303             =head1 SUPPORT
1304              
1305             You can find documentation for this module with the perldoc command.
1306              
1307             perldoc Robotics::Tecan
1308              
1309              
1310             You can also look for information at:
1311              
1312             =over 4
1313              
1314             =item * RT: CPAN's request tracker
1315              
1316             L
1317              
1318             =item * AnnoCPAN: Annotated CPAN documentation
1319              
1320             L
1321              
1322             =item * CPAN Ratings
1323              
1324             L
1325              
1326             =item * Search CPAN
1327              
1328             L
1329              
1330             =back
1331              
1332              
1333              
1334             =head1 ACKNOWLEDGEMENTS
1335              
1336              
1337             =head1 COPYRIGHT & LICENSE
1338              
1339             Copyright 2009 Jonathan Cline.
1340              
1341             This program is free software; you can redistribute it and/or modify it
1342             under the terms of either: the GNU General Public License as published
1343             by the Free Software Foundation; or the Artistic License.
1344              
1345             See http://dev.perl.org/licenses/ for more information.
1346              
1347              
1348             =cut
1349              
1350             no Moose;
1351              
1352             __PACKAGE__->meta->make_immutable;
1353              
1354              
1355             1; # End of Robotics::Tecan
1356              
1357             __END__