File Coverage

blib/lib/Robotics/IRobot.pm
Criterion Covered Total %
statement 24 909 2.6
branch 0 278 0.0
condition 0 53 0.0
subroutine 8 134 5.9
pod 90 90 100.0
total 122 1464 8.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Robotics::IRobot;
4              
5             =head1 NAME
6              
7             Robotics::IRobot - provides interface to iRobot Roomba and Create robots
8              
9             =head1 SYNOPSIS
10              
11             use Robotics::IRobot;
12            
13             my $robot=Robotics::IRobot->new('/dev/rfcomm0');
14             #could be /dev/ttyUSB0, /dev/ttyS0, etc.
15            
16             #Initializes port and sends OI Init command
17             $robot->init();
18            
19             #Takes robot out of passive mode and enables
20             #hardware based safeties
21             $robot->startSafeMode();
22            
23             #Move robot forward at 100mm/s
24             $robot->forward(100);
25            
26             #Get sensor state hash ref
27             $robot->refreshSensors();
28             my $sensorState=$robot->getSensorState();
29            
30             #Wait until we have moved 500mm
31             while($sensorState->{totalDistance}<500) {
32             sleep 1;
33             $robot->refreshSensors();
34             }
35            
36             $robot->stop();
37            
38             $robot->close();
39              
40             =head1 VERSION
41              
42             Version 0.14
43              
44             =cut
45              
46             our $VERSION='0.14';
47              
48             =head1 REFERENCES
49              
50             IRobot Open Interface specification -- L
51              
52             =head1 REQUIRES
53              
54             Time::HiRes, Device::SerialPort, YAML::Tiny, POSIX, Math::Trig
55              
56             =head1 EXPORTS
57              
58             Nothing
59              
60             =head1 DESCRIPTION
61              
62             Robotics::IRobot provides an interface for controlling and accessing
63             sensor data from iRobot robots that support the OI Interface.
64              
65             This module provides on object oriented interface to the robot and allows
66             for both event-driven and polling-based reading of sensor data. It provides
67             all functionality defined in the OI Interface for Roomba 400 series and
68             Create robots. Also provided is some additional functionality such as primative
69             dead reckoning and enhanced use of the Create's song functionality.
70              
71             I
72             Roomba where applicable.>
73              
74             =head1 NOTICE
75              
76             I make no warranty as to the correct functioning of this module. Use may cause your
77             robot to fall down your basement stairs, catch fire, and burn your house down. Be prepared
78             to take physical control of robot at all times.
79              
80             I assume no responsibility or liability for damage to robot or its surroundings. If you do not agree to this, do not
81             use this module.
82              
83             =head1 DEVELOPMENT STATUS
84              
85             This software is currently in alpha status. Telemetry is still a work in progress.
86              
87             =cut
88              
89 1     1   21432 use strict;
  1         2  
  1         39  
90 1     1   872 use Time::HiRes qw(usleep ualarm time sleep);
  1         1808  
  1         5  
91 1     1   1446 use Device::SerialPort;
  1         33523  
  1         62  
92 1     1   10 use POSIX qw(fmod);
  1         2  
  1         7  
93 1     1   1133 use YAML::Tiny;
  1         5607  
  1         60  
94 1     1   55192 use Math::Trig;
  1         30448  
  1         228  
95 1     1   11 use Math::Trig ':pi';
  1         1  
  1         157  
96 1     1   6 use Math::Trig ':radial';
  1         1  
  1         10361  
97              
98             my $DEBUG=0;
99             my $EPSILON=0.015;
100             my $WHEEL_WIDTH=258;
101             my $ROBOT_WIDTH=330;
102              
103             #Class Data (defined at end of file)
104             my ($sensorSpecs,$sensorGroups,@sensorFields,
105             $calibrationDefaults, $notes, $keys, @sharps,
106             $sensorLocations, @cliffSensors);
107              
108             ########initialization Commands################
109             =head1 METHODS
110              
111             =head2 Creation and Initialization
112              
113             =over 4
114              
115             =item Robotics::IRobot->new($port,$indirectSensorsOn)
116              
117             Creates a new IRobot object using the given communications port (defaults to /dev/iRobot)
118             and enables indirect sensors if $indirectSensorsOn is true (this is the default).
119              
120             =cut
121              
122             sub new {
123 0     0 1   shift;
124 0   0       my $self={
      0        
125             portFile=>(shift || '/dev/iRobot'), indirectSensorsOn=>(shift || 1),
126             deadReckoning=>\&_correctiveDeadReckoning,
127             readBuffer=> '', sensorState=>{lastSensorRefresh=>time()}, ledState=>{},
128             pwmState=>[0,0,0], outputState=>[0,0,0],
129             sensorListeners=>[], safetyChecks=>1, scriptMode=>0,
130             timeEvents=>[],
131             nextListenerId=>0, lastCliff=>{}, gatherCliffStatistics=>0,
132             calibration=>$calibrationDefaults
133             };
134 0           bless($self);
135            
136 0           $self->markOrigin();
137              
138 0 0         $self->{indirectSensorsId}=$self->addSensorListener(0,\&_indirectSensors) if ($self->{indirectSensorsOn});
139            
140 0           $self->loadCalibrationData();
141            
142 0 0         die "Data not loaded" unless (defined $notes->{a});
143            
144 0           return $self;
145             }
146              
147             =item $robot->init()
148              
149             Initializes the port, connects to the robot, and initiates the OI Interface.
150              
151             I
152             actuator commands.>
153              
154             =cut
155              
156             sub init {
157 0     0 1   my $self=shift;
158            
159 0 0         $self->initPort() || die "Unable to initialize port";
160 0           sleep 1;
161            
162 0           $self->writeBytes(128);
163 0           my ($bytes,$startupMsg)=$self->{port}->read(255);
164            
165 0           $self->_writeTelem('R',$startupMsg);
166            
167 0           return $startupMsg;
168             }
169              
170             =item $robot->initForReplay($telemetryFile)
171              
172             Replays saved telemetry data, can be used for testing or analysis.
173              
174             See section TELEMETRY
175              
176             =back
177              
178             =cut
179              
180             sub initForReplay($$) {
181 0     0 1   my $self=shift;
182 0           my $file=shift;
183            
184 0           my $replay;
185            
186 0           open $replay, $file;
187 0           $self->{replay}=$replay;
188            
189 0           my ($time,$type,$data)=$self->_readTelem();
190            
191 0 0         die "Invalid replay file!" unless ($type eq 'B');
192            
193 0           $self->{replayDelta}=time-$time;
194            
195 0           while($type ne 'R') {
196 0           ($time,$type,$data)=$self->_readTelem();
197             }
198            
199             }
200              
201             ######Actuator Commands########
202              
203             =head2 Actuators
204              
205             =over 4
206              
207             =item $robot->reset()
208              
209             Does a soft-reset of robot. Needed to begin charging or recover after
210             triggering hardware based safeties.
211              
212             =cut
213              
214             sub reset($) {
215 0     0 1   my $self=shift;
216            
217 0           $self->writeBytes(7);
218            
219 0           $self->close();
220 0           sleep 5;
221            
222 0           return $self->init();
223             }
224              
225             =item $robot->startPassiveMode()
226              
227             Puts robot into passive mode.
228              
229             =cut
230              
231             sub startPassiveMode {
232 0     0 1   my $self=shift;
233            
234 0           $self->writeBytes(128);
235             }
236              
237             =item $robot->startSafeMode()
238              
239             Puts robot into safe mode. (Turns on hardware based safeties.)
240              
241             I
242             other actuator commands.>
243              
244             =cut
245              
246             sub startSafeMode {
247 0     0 1   my $self=shift;
248            
249 0           $self->writeBytes(131);
250            
251 0           sleep .03;
252             }
253              
254             =item $robot->startFullMode()
255              
256             Puts robot into full mode. (Turns off hardware based safeties.)
257              
258             I
259             other actuator commands.>
260              
261             =cut
262              
263             sub startFullMode {
264 0     0 1   my $self=shift;
265            
266 0           $self->writeBytes(132);
267            
268 0           sleep .03;
269             }
270              
271             =item $robot->startDemo($demoId)
272              
273             Puts robot in passive mode and starts built in demo. Consult OI Interface doc for
274             available demos.
275              
276             =cut
277              
278             sub startDemo($$) {
279 0     0 1   my $self=shift;
280 0           my $demo=shift;
281            
282 0           $self->writeBytes(136,$demo);
283             }
284              
285             =item $robot->stopDemo()
286              
287             Stops currently running demo.
288              
289             =cut
290              
291             sub stopDemo($) {
292 0     0 1   my $self=shift;
293            
294 0           $self->startDemo(255);
295             }
296              
297             =item $robot->drive($velocity, $radius)
298              
299             Sends robot command to make a turn with speed $velocity (in mm/s), negative values are reverse;
300             and turning radius $radius (in mm), positive values turn counter-clockwise, negative turn clockwise.
301              
302             =cut
303              
304             sub drive($$$) {
305 0     0 1   my $self=shift;
306            
307 0           my $velocity=shift;
308 0           my $radius=shift;
309            
310 0 0         $self->{lastVelocity}=$velocity if ($velocity!=0);
311            
312 0           $self->writeBytes(137,_convertFSS($velocity),_convertFSS($radius));
313             }
314              
315             =item $robot->driveDirect($rightVelocity,$leftVelocity)
316              
317             Sends robot command to drive right wheel at $rightVelocity (mm) and
318             left wheel at $leftVelocity (mm). Positive values are forward, negative values are
319             reverse.
320              
321             =cut
322              
323             sub driveDirect($$$) {
324 0     0 1   my $self=shift;
325 0           my $rightVelocity=shift;
326 0           my $leftVelocity=shift;
327            
328 0 0 0       $self->{lastVelocity}=(abs($rightVelocity)+abs($leftVelocity))/2 if ($rightVelocity!=0 && $leftVelocity!=0);
329            
330 0           $self->writeBytes(145,_convertFSS($rightVelocity),_convertFSS($leftVelocity));
331             }
332              
333             =item $robot->stop()
334              
335             Stops robot.
336              
337             =cut
338              
339             sub stop($) {
340 0     0 1   my $self=shift;
341            
342 0           $self->drive(0,0);
343             }
344              
345             =item $robot->forward($velocity)
346              
347             Moves robot forward at $velocity in (mm/s).
348              
349             I
350             a radius of 32768mm.>
351              
352             =cut
353              
354             sub forward($$) {
355 0     0 1   my $self=shift;
356 0           my $velocity=shift;
357            
358 0           $self->drive($velocity,32768);
359             }
360              
361             =item $robot->reverse($velocity)
362              
363             Moves robot in reverse at $velocity in (mm/s).
364              
365             =cut
366              
367             sub reverse($$) {
368 0     0 1   my $self=shift;
369 0           my $velocity=shift;
370              
371 0           $self->drive(-$velocity,32768);
372             }
373              
374             =item $robot->rotateRight($velocity)
375              
376             Rotates robot in place right (clockwise) at $velocity (in mm/s).
377              
378             =cut
379              
380             sub rotateRight($$) {
381 0     0 1   my $self=shift;
382 0           my $velocity=shift;
383              
384 0           $self->drive($velocity,-1);
385             }
386              
387             =item $robot->rotateLeft($velocity)
388              
389             Rotates robot in place left (counter-clockwise) at $velocity (in mm/s).
390              
391             =cut
392              
393             sub rotateLeft($$) {
394 0     0 1   my $self=shift;
395 0           my $velocity=shift;
396 0           $self->{turning}=1;
397              
398 0           $self->drive($velocity,1);
399             }
400              
401             =item $robot->setLEDs($powerColor, $powerIntensity, $playOn, $advanceOn)
402              
403             Sets robot LEDs
404              
405             $powerColor should be >=0 and <=255. 0 is green, 255 is red.
406             $powerIntensity should be >=0 and <=255
407             $playOn and $advanceOn are boolean
408              
409             =cut
410              
411             sub setLEDs($$$$$) {
412 0     0 1   my $self=shift;
413 0           my $powerColor=shift;
414 0           my $powerIntensity=shift;
415 0           my $playOn=shift;
416 0           my $advanceOn=shift;
417 0           my $ledState=$self->{ledState};
418            
419 0           $ledState->{play}=$playOn;
420 0           $ledState->{advance}=$advanceOn;
421 0           $ledState->{powerColor}=$powerColor;
422 0           $ledState->{powerIntensity}=$powerIntensity;
423            
424 0           $self->updateLEDs();
425             }
426              
427             =item $robot->setPlayLED($playOn)
428              
429             Sets "Play" LED on or off.
430              
431             =cut
432              
433             sub setPlayLED($$) {
434 0     0 1   my $self=shift;
435 0           my $playOn=shift;
436            
437 0           $self->{ledState}{play}=$playOn;
438            
439 0           $self->updateLEDs();
440             }
441              
442             =item $robot->togglePlayLED()
443              
444             Toggles "Play" LED.
445              
446             =cut
447              
448             sub togglePlayLED($) {
449 0     0 1   my $self=shift;
450            
451 0           $self->{ledState}{play}=!$self->{ledState}{play};
452            
453 0           $self->updateLEDs();
454             }
455              
456             =item $robot->setAdvanceLED($advanceOn)
457              
458             Sets "Advance" LED on or off.
459              
460             =cut
461              
462             sub setAdvanceLED($$) {
463 0     0 1   my $self=shift;
464 0           my $advanceOn=shift;
465            
466 0           $self->{ledState}{advance}=$advanceOn;
467            
468 0           $self->updateLEDs();
469             }
470              
471             =item $robot->toggleAdvanceLED()
472              
473             Toggles "Advance" LED.
474              
475             =cut
476              
477             sub toggleAdvanceLED($) {
478 0     0 1   my $self=shift;
479            
480 0           $self->{ledState}{advance}=!$self->{ledState}{advance};
481            
482 0           $self->updateLEDs();
483             }
484              
485             =item $robot->setPowerLED($powerColor,$powerIntensity)
486              
487             Sets "Power" LED
488              
489             $powerColor should be >=0 and <=255. 0 is green, 255 is red.
490             $powerIntensity should be >=0 and <=255
491              
492             =cut
493              
494             sub setPowerLED($$$) {
495 0     0 1   my $self=shift;
496 0           my $powerColor=shift;
497 0           my $powerIntensity=shift;
498 0           my $ledState=$self->{ledState};
499            
500 0           $ledState->{powerColor}=$powerColor;
501 0           $ledState->{powerIntensity}=$powerIntensity;
502            
503 0           $self->updateLEDs();
504             }
505              
506             =item $robot->getLEDState()
507              
508             Returns a hash reference with keys advance, play, powerColor, and powerIntensity that
509             give current LED state. If modified, calls to updateLEDs will send modifications to robot.
510              
511             I
512             on robot may be different.>
513              
514             =cut
515              
516             sub getLEDState($) {
517 0     0 1   my $self=shift;
518            
519 0           return $self->{ledState};
520             }
521              
522             =item $robot->updateLEDs()
523              
524             Writes current values in hash reference returned by getLEDState to robot.
525              
526             =cut
527              
528             sub updateLEDs($) {
529 0     0 1   my $self=shift;
530 0           my $ledState=$self->{ledState};
531            
532 0           $self->writeBytes(139,(1 & $ledState->{play})*2+(1 & $ledState->{advance}) * 8, $ledState->{powerColor},$ledState->{powerIntensity});
533             }
534              
535             =item $robot->setDigitalOutputs($output0,$output1,$output2)
536              
537             Sets state of robot's digital outputs. Values are boolean.
538              
539             =cut
540              
541             sub setDigitalOutputs($$$$) {
542 0     0 1   my $self=shift;
543            
544 0           $self->{outputState}=\@_;
545              
546 0           $self->updateDigitalOutputs;
547             }
548              
549             =item $robot->setDigitalOutput($output,$state)
550              
551             Sets state of output $output to $state.
552            
553             $output is >=0 and <=2.
554             $state is boolean.
555              
556             =cut
557              
558             sub setDigitalOutput($$$) {
559 0     0 1   my $self=shift;
560 0           my $output=shift;
561 0           my $state=shift;
562            
563 0           $self->{outputState}[$output]=$state;
564            
565 0           $self->updateDigitalOutputs();
566             }
567              
568             =item $robot->toggleDigitalOutput($output)
569              
570             Toggles state of digital output $output
571              
572             =cut
573              
574             sub toggleDigitalOutput($$) {
575 0     0 1   my $self=shift;
576 0           my $output=shift;
577            
578 0           $self->{outputState}[$output]=!$self->{outputState}[$output];
579            
580 0           $self->updateDigitalOutputs();
581             }
582              
583             =item $robot->getDigitalOutputs()
584              
585             Returns an array ref containing state of robots digital outputs.
586             If modified, calls to updateDigitalOutputs will send modifications to robot.
587              
588             I
589             on robot may be different.>
590              
591             =cut
592              
593             sub getDigitalOutputs($) {
594 0     0 1   my $self=shift;
595            
596 0           return $self->{outputState};
597             }
598              
599             =item $robot->updateDigitalOutputs()
600              
601             Writes current values in array reference returned by getDigitalOutputs to robot.
602              
603             =cut
604              
605             sub updateDigitalOutputs($) {
606 0     0 1   my $self=shift;
607            
608 0           my $byte=0;
609 0           my $outputState=$self->{outputState};
610            
611 0           for(my $i=2;$i>=0;$i--) {
612 0           $byte*=2;
613 0           $byte+=($outputState->[$i] & 1);
614             }
615            
616 0           $self->writeBytes(147,$byte);
617             }
618              
619             =item $robot->setPWMLoads($lsd0, $lsd1, $lsd2)
620              
621             Sets pwm duty cycle on low side drivers. Load values should be between 0 and 1 inclusive.
622              
623             0 is off, 1 is on, .5 is pulsed on 50% of the time, etc.
624              
625             =cut
626              
627             sub setPWMLoads($$$$) {
628 0     0 1   my $self=shift;
629            
630 0           foreach my $l (@_) {
631 0 0 0       return 0 if ($l<0 || $l > 1);
632             }
633            
634 0           $self->{pwmState}=[map "int($_*128)",@_];
635            
636 0           $self->updatePWMLoads();
637             }
638              
639             =item $robot->setPWMLoad($lsd, $load)
640              
641             Sets pwm duty cycle on low side driver $lsd to $load.
642             Load values should be between 0 and 1 inclusive.
643              
644             0 is off, 1 is on, .5 is pulsed on 50% of the time, etc.
645              
646             =cut
647              
648             sub setPWMLoad($$$) {
649 0     0 1   my $self=shift;
650 0           my $pwm=shift;
651 0           my $load=shift;
652            
653 0 0 0       $self->{pwmState}[$pwm]=int($load*128) if ($load <= 1 && $load >= 0);
654            
655 0           $self->updatePWMLoads();
656             }
657              
658             =item $robot->changePWMLoad($lsd, $loadDelta)
659              
660             Changes pwm duty cycle on low side driver $lsd by $loadDelta. Load Delta should
661             be between 0 and 1 inclusive.
662              
663             =cut
664              
665             sub changePWMLoad($$$) {
666 0     0 1   my $self=shift;
667 0           my $pwm=shift;
668 0           my $load=shift;
669            
670 0           $self->{pwmState}[$pwm]+=int($load*128);
671 0 0         $self->{pwmState}[$pwm]=0 if ($self->{pwmState}[$pwm]<0);
672 0 0         $self->{pwmState}[$pwm]=128 if ($self->{pwmState}[$pwm]>128);
673            
674 0           $self->updatePWMLoads();
675             }
676              
677             =item $robot->getPWMLoads()
678              
679             Returns an array reference that contains current duty cycles of low side drivers.
680             If modified, calls to updatePWMLoads will send modifications to robot.
681              
682             I
683             Also, values in array reflect history of commands sent through interface. Actual output state
684             on robot may be different.>
685              
686             =cut
687              
688             sub getPWMLoads($) {
689 0     0 1   my $self=shift;
690            
691 0           return $self->{pwmState};
692             }
693              
694             =item $robot->updatePWMLoads()
695              
696             Writes current values in array reference returned by getPWMLoads to robot.
697              
698             =cut
699              
700             sub updatePWMLoads($) {
701 0     0 1   my $self=shift;
702            
703 0           my $pwmState=$self->{pwmState};
704            
705 0           $self->writeBytes(144,CORE::reverse(@{$self->{pwmState}}));
  0            
706 0           print "Setting pwm loads: " . join(", ",@{$self->{pwmState}}) . "\n";
  0            
707             }
708              
709             =item $robot->setPWMOnOff($lsd0, $lsd1, $lsd2)
710              
711             Turns on and off low side drivers. Values are boolean.
712              
713             =cut
714              
715             sub setPWMOnOff($$$$) {
716 0     0 1   my $self=shift;
717            
718 0           my $lsd0=shift;
719 0           my $lsd1=shift;
720 0           my $lsd2=shift;
721            
722 0           my $byte=($lsd0 & 1) + ($lsd1 & 1) * 2 + ($lsd2 & 1) * 4;
723            
724 0           $self->writeBytes(138,$byte);
725             }
726              
727             =item $robot->sendIR($byte)
728              
729             Sends IR byte through LED hooked up to LSD1.
730              
731             See Open Interface doc for details.
732              
733             =cut
734              
735             sub sendIR($$) {
736 0     0 1   my $self=shift;
737            
738 0           my $irByte=shift;
739 0           $self->writeBytes(151,$irByte);
740             }
741              
742             =item $robot->setSongRaw($songId, @songBytes)
743              
744             Sets song $songId in robot's memory to @songBytes.
745             @songBytes can contain up to 16 notes.
746              
747             See Open Interface doc for details.
748              
749             =cut
750              
751             sub setSongRaw($$@) {
752 0     0 1   my $self=shift;
753 0           my $songId=shift;
754            
755 0           print "setting song: " . $songId . ": ". join(", ",@_) , "\n";
756            
757 0           $self->writeBytes(140,$songId,(@_/2),@_);
758             }
759              
760             =item $robot->playABCNotation($file, $callback)
761              
762             Loads song in ABC format (see abcnotation.com) from $file and begins playing on Create.
763             If passed, calls &$callback($robot) when done.
764              
765             I
766             for this method to work properly. Calling this method will overwrite any data contained in song banks 14 and 15>
767              
768             =cut
769              
770             sub playABCNotation($$$) {
771 0     0 1   my $self=shift;
772 0           my $file=shift;
773 0           my $callback=shift;
774            
775 0           $self->playLongSongRaw($callback,loadABCNotation($file));
776            
777             }
778              
779             =item $robot->playLongSongRaw($callback, @songBytes)
780              
781             Plays song contained in @songBytes (may be longer than 16 notes). If passed, calls &$callback($robot) when done.
782              
783             I
784             for this method to work properly. Calling this method will overwrite any data contained in song banks 14 and 15>
785              
786             =cut
787              
788             sub playLongSongRaw($$@) {
789 0     0 1   my $self=shift;
790 0           my $callback=shift;
791            
792 0           my @song=@_;
793            
794             #print "playing: " . join(", ",@song) . "\n";
795            
796 0           $self->setSongRaw(15,splice(@song,0,32));
797            
798             $self->addSensorEvent(300,sub{
799 0     0     my $self=shift;
800 0           my $listener=shift;
801            
802 0 0         if ($self->{sensorState}{songPlaying}) {
803 0 0         $listener->{param}=$listener->{param}==15?-14:-15 if ($listener->{param}>0);
    0          
804             } else {
805 0 0         if ($listener->{param}<0) {
806 0           $listener->{param}=-$listener->{param};
807 0           return 1;
808             }
809             }
810            
811 0           return 0;
812             },
813             sub {
814 0     0     my $self=shift;
815 0           my $listener=shift;
816            
817 0           my $last=(@song==0);
818            
819 0           $self->playSong($listener->{param});
820 0           print "Song length: " . (@song+0) . "\n";
821 0 0         if ($last) {
822 0           $self->removeSensorListener($listener->{id});
823 0 0         &$callback($self) if ($callback);
824             } else {
825 0 0         $self->setSongRaw(($listener->{param}==15?14:15),splice(@song,0,32));
826             }
827            
828 0           return 1;
829 0           },-15,0);
830              
831             }
832              
833             =item IRobot::loadABCNotation($file)
834              
835             Loads song in ABCNotation format from $file (see abcnotation.com).
836             Returns song in format defined in OI Interface. If smaller than 16 notes (32 bytes)
837             can be passed to setSongRaw. Otherwise resulting bytes can be passed to playLongSongRaw.
838              
839             =cut
840            
841              
842             sub loadABCNotation($) {
843             #my $self=shift;
844 0     0 1   my $file=shift;
845            
846 0           open ABC,$file;
847            
848 0           my $header=1;
849 0           my (@song,$key,$length,$nkey);
850            
851 0           $length=8;
852 0           $key=[];
853 0           $nkey=0;
854            
855 0           while() {
856 0           chomp;
857 0           my @line=split(/:/,$_);
858            
859 0 0 0       if ($line[0] eq 'K') {
    0          
    0          
    0          
860 0           my $nkey=$keys->{$line[1]};
861 0 0         if ($nkey>0) {
    0          
862 0           $key=[$sharps[0 .. $nkey-1]];
863             } elsif ($nkey<0) {
864 0           $key=[$sharps[@sharps+$nkey .. $#sharps]];
865             }
866             } elsif ($line[0] eq 'L') {
867 0           my @length=split(/\//,$line[1]);
868 0           $length=$length[1];
869             } elsif ($line[0]=~/^%/) {
870             #comment
871             } elsif (length($line[0])>1 || $line[0] eq '|') {
872 0           s/".{1,3}"//g;
873 0           s/\[.{1,3}\]//g;
874 0           s/[^[A-Ga-gz0-9\/,'\^_=]//g;
875 0           my $line=$_;
876 0           while ($line ne '') {
877 0           $line=~/^([\^_=]?)([A-Ga-gz])([',]*)(\d*\/?\d*)(.*)/;
878 0           my $sharp=$1;
879 0           my $note=$2;
880 0           my $octave=$3;
881 0           my $duration=$4;
882 0           $line=$5;
883            
884 0           my $noctave=6;
885 0 0         if (uc($note) eq $note) {
886 0           $noctave--;
887             }
888 0 0         if ($octave=~/'/) {
    0          
889 0           $noctave+=length($octave);
890             } elsif ($octave=~/,/) {
891 0           $noctave-=length($octave);
892             }
893            
894 0           my $nnote=_getNote($sharp,$note,$nkey,$key,$noctave);
895            
896 0           my $nlength=1;
897 0 0         if ($duration=~/(\d*)^\/(\d*)/) {
898 0   0       $nlength=($1 || 1)/($2 || 2);
      0        
899             } else {
900 0   0       $nlength=$duration || 1;
901             }
902            
903 0           $nlength/=$length;
904            
905 0           $nlength*=64;
906            
907 0           push @song,$nnote,int($nlength);
908            
909             }
910             }
911            
912             }
913            
914 0           close ABC;
915 0           return @song;
916            
917             }
918              
919              
920              
921             sub _getNote($$$) {
922 0     0     my ($sharp,$note,$nkey,$key,$octave)=@_;
923            
924 0 0         if ($note eq 'z') {
925 0           return 0;
926             }
927            
928 0 0 0       if ($sharp eq '' && $nkey) {
929            
930 0           my $k;
931 0           $sharp=0;
932 0           foreach $k (@$key) {
933 0 0         $sharp=($key<=>0) if (lc($note) eq $k);
934             }
935            
936             } else {
937 0 0         $sharp=($sharp eq '^')?1:(($sharp eq '_')?-1:0);
    0          
938             }
939            
940 0           return $notes->{lc($note)}+$sharp+$octave*12;
941              
942              
943             }
944              
945             =item $robot->setSong($songId,$song)
946              
947             Sets song bank $songId to the song specified in $song.
948              
949             $song is expected to be a whitespace seperated list of notes.
950             Each note is made up of an optional octave number, a note letter (a-g)
951             or r for a rest, an optional sharp designator (#), and a duration in 64ths of
952             a second. If no octave is given the last octave specified is used. If no octave
953             has yet been specified octave 4 is used. Example:
954              
955             d16 a16 r32 3a16 a#32 b16 2b8 c#16
956              
957             The example above will play a d in the 4th octave for 1/4 second, an a in the 4th
958             octave for 1/4 second, rest for 1/2 second, an a in the 3rd octave for 1/4 second,
959             and a sharp in the 3rd octage for 1/2 second, a b in the 3rd octave for 1/4 second,
960             a b in the 2nd octave for 1/8 second, and a c sharp in the 2nd octave for 1/4 second.
961              
962             The method will return the estimated duration in seconds the song will play. Using the example
963             above the method would return 2.375
964              
965             =cut
966              
967             sub setSong($$$) {
968 0     0 1   my $self=shift;
969            
970 0           my $songId=shift;
971 0           my $song=shift;
972            
973 0           my $lastOctave=4;
974            
975 0           my @song;
976             my $note;
977 0           my $totalDuration=0;
978 0           foreach $note (split(/\s/,lc($song))) {
979 0           $note=~/(\d?)([a-gr]#?)(\d{1,3})/;
980 0   0       my $octave=$1 || $lastOctave;
981 0           my $letter=$2;
982 0           my $duration=$3;
983            
984 0           my $num;
985 0 0         if ($letter eq 'r') {
986 0           $num=0;
987             } else {
988 0           $num=$notes->{$letter}+($octave+1)*12;
989             }
990            
991 0           push @song,$num,$duration;
992            
993 0           $lastOctave=$octave;
994 0           $totalDuration+=$duration;
995             }
996            
997 0           $self->setSongRaw($songId,@song);
998            
999 0           return $totalDuration/64;
1000             }
1001              
1002             =item $robot->playSong($songId)
1003              
1004             Plays song from bank $songId.
1005              
1006             =cut
1007              
1008             sub playSong($$) {
1009 0     0 1   my $self=shift;
1010 0           my $songId=shift;
1011            
1012             #print "playing song: $songId\n";
1013            
1014 0           $self->writeBytes(141,$songId);
1015             }
1016              
1017             =item $robot->turnTo($direction, $speed, $callback)
1018              
1019             Attempts to turn the robot to face $direction relative to the direction it
1020             was facing when $robot->init() was called or last $robot->markOrigin call.
1021             Robot will make the turn at $speed. Robot will stop once complete and call
1022             &$callback($robot) if $callback is passed.
1023              
1024             See section DEAD RECKONING for more information.
1025              
1026             I
1027             for this method to work properly.>
1028              
1029             =cut
1030              
1031             sub turnTo($$$) {
1032 0     0 1   my $self=shift;
1033 0           my $angle=shift;
1034 0           my $speed=shift;
1035 0           my $callback=shift;
1036            
1037 0           $angle=_normalizeAngle($angle);
1038 0           my $direction=$self->{sensorState}{direction};
1039 0           my $delta=_normalizeAngle($angle-$direction);
1040              
1041             #print join(", ",$angle,$delta,$speed) . "\n";
1042            
1043             $self->waitAngle(200,$delta-8*$EPSILON*$speed/$WHEEL_WIDTH, sub {
1044 0     0     $self->stop();
1045 0 0         &$callback($self,$delta) if ($callback);
1046 0           });
1047 0           $self->rotateLeft(($delta<=>0)*$speed);
1048            
1049             }
1050              
1051             =item $robot->goTo($x, $y, $speed, $callback)
1052              
1053             Attempts to drive the robot to position ($x,$y) relative to its location
1054             when $robot->init() was called or last $robot->markOrigin call.
1055             Robot will make the proceed at $speed. Robot will stop once complete
1056             and call &$callback($robot) if $callback is passed.
1057              
1058             See section DEAD RECKONING for more information.
1059              
1060             I
1061             for this method to work properly.>
1062              
1063             =cut
1064              
1065             sub goTo($$$$$) {
1066 0     0 1   my ($self,$destX,$destY,$speed,$callback)=@_;
1067            
1068 0           my $x=$self->{sensorState}{x};
1069 0           my $y=$self->{sensorState}{y};
1070            
1071 0           my $deltaX=$destX-$x;
1072 0           my $deltaY=$destY-$y;
1073            
1074 0           my ($distance,$angle)=cartesian_to_cylindrical($x,$y);
1075 0           $angle+=pip2; #so 0 is along +y axis
1076            
1077             $self->turnTo($angle,$speed,
1078             sub {
1079 0     0     $self->forward($speed);
1080             $self->waitDistance(200,$distance,
1081             sub {
1082 0           $self->stop();
1083 0 0         &$callback($self,$distance) if ($callback);
1084             }
1085 0           );
1086             }
1087 0           );
1088             }
1089              
1090             ######Sensor Commands##########
1091             =back
1092              
1093             =head2 Sensors
1094              
1095             The robot's sensor data can be retrieved in several different ways. The easiest is
1096             to simply call $robot->refreshSensors on a regular basis. This will retrieve all sensor
1097             data from the robot, which can then be accessed from the hash returned by
1098             $robot->getSensorState(). If you do not want all sensor data to be retrieved, then
1099             you can use the $robot->getSensor($id) method. This will only retrieve data for
1100             one sensor (or sensor set) but, it is not recommended.
1101              
1102             Consult the OI Interface document for more details on sensor ids.
1103              
1104             Another method is to use the iRobot's sensor streaming functionality. When the
1105             robot is put in streaming mode it will send back sensor data once every 15ms. Use the
1106             $robot->startSteam, $robot->pauseStream. $robot->resumeStream method to start and
1107             stop the stream. The $robot->getStreamFrame method should be called at least every
1108             15ms to read in the sensor data and update the sensor state hash. As with the polling
1109             method, you can pass a sensor ids to $robot->startStream to have the robot stream data
1110             for only particular sensors, but again, this is not recommeded.
1111              
1112             The third method is to use the event-driven approach. Your program can register sensor listeners
1113             or events to listen for using the $robot->addSensorListener, $robot->addSensorEvent,
1114             $robot->runEvery, $robot->waitTime, $robot->waitDistance, and $robot->waitAngle methods. Once these
1115             have been registered the $robot->runSensorLoop and $robot->exitSensorLoop methods will put the robot in
1116             streaming mode then read sensor data as it comes in while updating the sensor state hash and calling any
1117             sensor listeners or events.
1118              
1119             =over 4
1120              
1121             =item $robot->getSensorState()
1122              
1123             Returns a hash reference containing last read values from robot sensors.
1124              
1125             =cut
1126              
1127             sub getSensorState() {
1128 0     0 1   my $self=shift;
1129            
1130 0           return $self->{sensorState};
1131             }
1132              
1133             =item $robot->getDockSignal()
1134              
1135             Returns an array indicating the presense of a "home base" docking station and any docking beacons seen.
1136              
1137             Example:
1138              
1139             my ($dockPresent,$greenBeacon,$forceField,$redBeacon)=$robot->getDockSignal();
1140              
1141             =cut
1142              
1143             sub getDockSignal($) {
1144 0     0 1   my $self=shift;
1145 0           my $irByte=$self->{sensorState}{irByte};
1146            
1147 0           my $dock=(($irByte & 241)==240);
1148            
1149             #print join(", ",$irByte,($irByte & 241)) . "\r\n";
1150            
1151 0 0         if ($dock) {
1152 0           my $green=($irByte & 4) >> 2;
1153 0           my $red=($irByte & 8) >> 3;
1154 0           my $force=($irByte & 2) >> 1;
1155            
1156 0           return (1,$green,$force,$red);
1157             } else {
1158 0           return (0,0,0,0);
1159             }
1160             }
1161              
1162              
1163             =item $robot->getSensorLocation($sensor)
1164              
1165             Gets the current location of a sensor relative to the origin. Possible sensors:
1166              
1167             =over 4
1168              
1169             =item cliffLeft
1170              
1171             =item cliffFrontLeft
1172              
1173             =item cliffFrontRight
1174              
1175             =item cliffRight
1176              
1177             =item bumpLeft
1178              
1179             =item bumpCenter
1180              
1181             =item bumpRight
1182              
1183             =item caster
1184              
1185             =item irSensor
1186              
1187             =item wheelLeft
1188              
1189             =item wheelRight
1190              
1191             =item
1192              
1193             =back
1194              
1195             =cut
1196              
1197             sub getSensorLocation($$) {
1198 0     0 1   my $self=shift;
1199 0           my $sensor=shift;
1200            
1201 0           my $sensorState=$self->{sensorState};
1202 0           my ($x,$y);
1203              
1204 0 0         if (defined($sensorLocations->{$sensor})) {
1205 0           my $sensorLocation=$sensorLocations->{$sensor};
1206 0           ($x,$y)=cylindrical_to_cartesian($sensorLocation->[0],$sensorLocation->[1]+$self->{sensorState}{direction});
1207             } else {
1208 0           ($x,$y)=0;
1209             }
1210            
1211 0           return ($sensorState->{x}+$x,$sensorState->{y}+$y);
1212             }
1213              
1214             =item $robot->refreshSensors()
1215              
1216             Retrieves all sensor data, refreshes sensor state hash, and triggers any sensor listeners or events. This method will
1217             block for up to 15ms if called more than once every 15ms.
1218              
1219             If you are not calling this method more than once every few seconds. You may wish to switch the movement correction
1220             mode to 'robot' or 'raw', as these may be more accurate in this situation. See setMovementCorrectionMode method.
1221              
1222             =cut
1223              
1224             sub refreshSensors($) {
1225 0     0 1   my $self=shift;
1226            
1227 0           my $sinceLastRefresh=(time()-$self->{sensorState}{lastSensorRefresh});
1228            
1229 0 0         sleep($EPSILON - $sinceLastRefresh) if ($sinceLastRefresh < $EPSILON);
1230            
1231 0           $self->getSensor(6);
1232             }
1233              
1234             =item $robot->getSensor($sensorId)
1235              
1236             Retreives data from a single sensor, refreshes sensor state hash, and triggers any sensor listeners or events.
1237             This method is generally not recommedended. $robot->refreshSensors() should be used instead.
1238              
1239             If you are not polling the distance and angle sensors more than once every few seconds. You may wish to switch the dead reckoning
1240             mode to 'robot' or 'raw', as these may be more accurate in this situation. See setMovementCorrectionMode method.
1241              
1242             See OI Documentation for sensor ids.
1243              
1244             =cut
1245              
1246             sub getSensor($$) {
1247 0     0 1   my $self=shift;
1248 0           my $sensorId=shift;
1249              
1250 0           $self->writeBytes(142,$sensorId);
1251 0           my @data=$self->_readSensorData($sensorId);
1252            
1253 0           $self->_triggerSensorEvents([$sensorId]);
1254            
1255 0 0         return wantarray ? @data : $data[0];
1256             }
1257              
1258             =item $robot->getSensors($sensorId1, $sensorId2, ... )
1259              
1260             Retrieves data for a particular sensor, refreshes sensor state hash, and triggers any sensor listeners or events. This method is
1261             generally not recommended. $robot->refreshSensors() should be used instead.
1262              
1263             If you are not polling the distance and angle sensors more than once every few seconds. You may wish to switch the dead reckoning
1264             mode to 'robot' or 'raw', as these may be more accurate in this situation. See setMovementCorrectionMode method.
1265              
1266             See OI Documentation for sensor ids.
1267              
1268             =cut
1269              
1270             sub getSensors($@) {
1271 0     0 1   my $self=shift;
1272            
1273 0           $self->writeBytes(149,(@_+0),@_);
1274            
1275 0           my @retArr;
1276            
1277             my $sensorId;
1278 0           foreach $sensorId (@_) {
1279 0           push @retArr,$self->_readSensorData($sensorId);
1280             }
1281            
1282 0           $self->_triggerSensorEvents(\@_);
1283            
1284 0           return @retArr;
1285             }
1286              
1287             =item $robot->runSensorLoop()
1288              
1289             Begins streaming sensor data from the robot. Updates sensor state hash every 15ms and triggers any
1290             sensor listeners or events. This method will block until $robot->exitSensorLoop() is called.
1291              
1292             =cut
1293              
1294             sub runSensorLoop($) {
1295 0     0 1   my $self=shift;
1296            
1297 0           $self->{exitLoop}=0;
1298              
1299 0           $self->startStream(6);
1300              
1301 0           while(!$self->{exitLoop}) {
1302 0           $self->getStreamFrame();
1303             }
1304            
1305 0           $self->pauseStream();
1306             }
1307              
1308             =item $robot->exitSensorLoop()
1309              
1310             Stops streaming data from robot. Causes any previous call to runSensorLoop to return.
1311              
1312             =cut
1313              
1314             sub exitSensorLoop($) {
1315 0     0 1   my $self=shift;
1316              
1317 0           $self->{exitLoop}=1;
1318             }
1319              
1320             =item $robot->startStream()
1321              
1322             =item $robot->startStream($sensorId)
1323              
1324             Puts robot into streaming mode. If a $sensorId is passed only streams that sensor (not recommended). Otherwises streams data from
1325             all sensors.
1326              
1327             See OI Documentation for more details
1328              
1329             =cut
1330              
1331             sub startStream($@) {
1332 0     0 1   my $self=shift;
1333            
1334 0 0         push @_,6 unless (@_ > 0);
1335            
1336 0           $self->writeBytes(148,(@_+0),@_);
1337 0           $self->{isStreaming}=1;
1338            
1339 0           $self->_syncStream();
1340             }
1341              
1342             =item $robot->pauseStream()
1343              
1344             Pauses the sensor data stream.
1345              
1346             =cut
1347              
1348             sub pauseStream($) {
1349 0     0 1   my $self=shift;
1350            
1351 0           $self->writeBytes(150,0);
1352 0           $self->{isStreaming}=0;
1353             }
1354              
1355             =item $robot->resumeStream()
1356              
1357             Resumes a previously paused sensor stream.
1358              
1359             =cut
1360              
1361             sub resumeStream($) {
1362 0     0 1   my $self=shift;
1363            
1364 0           $self->writeBytes(150,1);
1365 0           $self->{isStreaming}=1;
1366             }
1367              
1368             =item $robot->getStreamFrame()
1369              
1370             Gets one frame of sensor data, updates sensor data hash, and triggers any sensor listeners or events.
1371             Should be called at least once every 15ms. Method will block
1372             until one frame of sensor data has been read.
1373              
1374             See OI Documentation for more details.
1375              
1376             =cut
1377              
1378             sub getStreamFrame($) {
1379 0     0 1   my $self=shift;
1380            
1381 0           my (@data,$readBytes);
1382            
1383 0 0         if ($self->{isStreaming}) {
1384 0           while ($data[0]!=19) {
1385 0           $readBytes=$self->readData(2);
1386 0           @data=unpack('CC',$readBytes);
1387            
1388            
1389 0 0         print "Read bytes: " . join(", ",@data) . "\n" if ($DEBUG);
1390 0 0         if ($data[0]!=19) {
1391 0           print "Stream lost. Attempting to re-sync.\n";
1392 0           $self->_syncStream();
1393             }
1394             }
1395            
1396 0           my $packetLength=$data[1];
1397            
1398 0           $readBytes=$self->readData($packetLength+1);
1399 0 0         print "Read bytes: " . join(", ",unpack('C*',$readBytes)) . "\n" if ($DEBUG);
1400            
1401 0           my $i=0;
1402 0           my @sensorIds;
1403 0           while($i<$packetLength) {
1404 0           my $sensorId=unpack('C',substr($readBytes,$i,1));
1405 0           $i++;
1406            
1407 0           my ($readLength, $packString)=@{$sensorSpecs->[$sensorId]};
  0            
1408            
1409 0           my @data=unpack($packString,substr($readBytes,$i,$readLength));
1410 0           _updateSensorState($sensorId,$self->{sensorState},$self->{slipFactor},@data);
1411            
1412 0           push @sensorIds,$sensorId;
1413            
1414 0           $i+=$readLength;
1415             }
1416 0           $self->_triggerSensorEvents(\@sensorIds);
1417            
1418             }
1419            
1420             }
1421              
1422             =item $robot->addSensorListener($priority,$action,$param)
1423              
1424             Adds a sensor listener. This listener will be called whenever sensor data is retrieved, either as a group
1425             such as when $robot->refreshSensors or $robot->getStreamFrame is called or when individual sensors
1426             are retrieved using $robot->getSensors.
1427              
1428             The priority parameter is used to determine the order in which listeners are called. Lower priorities are called first.
1429             Any listeners with a negative priority will be called before indirect sensors (dead reckoning) is calculated. Listeners with
1430             a priority less than 200 will be called before triggers for waitDistance, waitAngle, etc. events are called.
1431              
1432             On each sensor data retrieval &$action($robot,$listener,$sensorIds) will be called. $sensorIds is a array ref containing the read sfensorIds.
1433             $listener is a hash containing the following keys:
1434              
1435             =over 5
1436              
1437             =item id:
1438             listener id -- used to remove listener
1439              
1440             =item priority:
1441             listener priority -- do not changed this value
1442              
1443             =item action:
1444             the function being called
1445              
1446             =item param:
1447             the value of $param passed to addSensorListener
1448              
1449             =back
1450              
1451             The same hash ref is returned with each call, so this can be used by the action callback to store values.
1452              
1453             Additionally, setting $listener->{stop} to true will prevent listeners with a higher priority value from executing. This is useful for listeners
1454             which implement safeties.
1455              
1456             $robot->addSensorListener returns the listener id. This can be used to remove the listener later.
1457              
1458             =cut
1459              
1460             sub addSensorListener($$$$) {
1461 0     0 1   my $self=shift;
1462 0           my $id=$self->{nextListenerId};
1463 0           my $priority=shift;
1464 0           my $action=shift;
1465 0           my $param=shift;
1466            
1467 0           $self->{nextListenerId}++;
1468            
1469 0           my $newSensorListener={id=>$id, priority=>$priority, action=>$action,param=>$param};
1470            
1471 0           my $sensorListeners=$self->{sensorListeners};
1472            
1473 0           my $added=0;
1474 0   0       for(my $i=0;!$added && $i<@{$sensorListeners};$i++) {
  0            
1475 0 0         if (($sensorListeners->[$i]{priority}) > $priority) {
1476 0           splice(@{$sensorListeners},$i,0,$newSensorListener);
  0            
1477 0           $added=1;
1478             }
1479             }
1480            
1481 0 0         push @{$sensorListeners},$newSensorListener unless ($added);
  0            
1482            
1483 0           return $id;
1484             }
1485              
1486             =item $robot->removeSensorListener($id)
1487              
1488             Remove listener or event with $id.
1489              
1490             =cut
1491              
1492             sub removeSensorListener($$) {
1493 0     0 1   my $self=shift;
1494 0           my $id=shift;
1495            
1496 0           for(my $i=0;$i<@{$self->{sensorListeners}};$i++) {
  0            
1497 0 0         if ($self->{sensorListeners}[$i]{id}==$id) {
1498 0           splice(@{$self->{sensorListeners}},$i,1);
  0            
1499 0           last;
1500             }
1501             }
1502             }
1503              
1504              
1505             =item $robot->addSensorEvent($priority,$test,$action,$param,$oneTime)
1506              
1507             Executes &$test($robot,$listener,$sensorIds) each time sensor data is retrieved. $listener
1508             is a hash reference (see addSensorListener). $sensorIds is the array ref containing ids of the sensors read. &$action($robot,$listener,$sensorIds)
1509             is called if $test returns true. $param is included in the $listener hash ref. If $oneTime is true, the created listener is
1510             automatically removed the first time $test returns true.
1511              
1512             As with addSensorListener, setting $listener->{stop} to true will stop further listeners and events from processing.
1513              
1514             This method returns an id which can be passed to removeSensorListener to remove the event.
1515              
1516             =cut
1517              
1518             sub addSensorEvent($$$$$$) {
1519 0     0 1   my ($self,$priority,$test,$action,$param,$oneTime)=@_;
1520            
1521             my $eventListener=sub {
1522 0 0   0     if (&$test(@_)) {
1523 0 0         $_[0]->removeSensorListener($_[1]->{id}) if ($oneTime);
1524 0           return &$action(@_);
1525             } else {
1526 0           return 1;
1527             }
1528 0           };
1529            
1530 0           return $self->addSensorListener($priority,$eventListener,$param);
1531             }
1532              
1533             =item $robot->runEvery($priority,$time,$callback)
1534              
1535             Creates a sensor event with $priority that calls &$callback($robot) every $time seconds. Returns an id
1536             that can be passed to removeSensorListener.
1537              
1538             =cut
1539              
1540             sub runEvery($$$$) {
1541 0     0 1   my ($self,$priority,$time,$callback) = @_;
1542            
1543             my $test=sub {
1544 0     0     my $self=shift;
1545 0           my $listener=shift;
1546            
1547 0 0         if (time()>$listener->{param}) {
1548 0           $listener->{param}+=$time;
1549 0           return 1;
1550             } else {
1551 0           return 0;
1552             }
1553 0           };
1554            
1555 0           return $self->addSensorEvent($priority,$test,$callback,time(),0);
1556             }
1557              
1558             =item $robot->waitDistance($priority,$distance,$callback)
1559              
1560             Creates a one time sensor event with $priority that calls &$callback($robot) once the robot has traveled $distance mm.
1561             Distance must be positive. Distances traveled in reverse will be used in determining total distance traveled.
1562             Returns an id that can be passed to removeSensorListener.
1563              
1564             I
1565              
1566             =cut
1567              
1568             sub waitDistance($$$) {
1569 0     0 1   my $self=shift;
1570            
1571 0 0         if ($self->{scriptMode}) {
1572 0           my $distance=shift;
1573            
1574 0           $self->scriptWaitDistance($distance);
1575             } else {
1576 0           my $priority=shift;
1577 0           my $distance=shift;
1578 0           my $callback=shift;
1579            
1580             my $listener=sub ($$$) {
1581 0     0     my ($self,$listener,$sensorId)=@_;
1582            
1583 0           $listener->{param}-=abs($self->{sensorState}{distance});
1584            
1585 0 0         if ($listener->{param}<0) {
1586 0           $self->removeSensorListener($listener->{id});
1587 0 0         &$callback($self) if ($callback);
1588             }
1589            
1590 0           return 1
1591 0           };
1592            
1593 0           return $self->addSensorListener(200,$listener,$distance);
1594             }
1595             }
1596              
1597             =item $robot->waitAngle($priority,$angle,$callback)
1598              
1599             Creates a one time sensor event with $priority that will call &$callback($robot) after robot has turned $angle radians in either direction.
1600             $angle must be positive. Turns in either direction will be used to determine total angle turned.
1601             Returns an id that can be passed to removeSensorListener.
1602              
1603             I
1604              
1605             =cut
1606              
1607             sub waitAngle($$$$) {
1608 0     0 1   my $self=shift;
1609            
1610 0 0         if ($self->{scriptMode}) {
1611 0           my $angle=shift;
1612            
1613 0           $self->scriptWaitAngle($angle);
1614             } else {
1615 0           my $priority=shift;
1616 0           my $angle=shift;
1617 0           my $callback=shift;
1618            
1619             my $listener=sub ($$$) {
1620 0     0     my ($self,$listener,$sensorId)=@_;
1621            
1622 0           $listener->{param}-=abs($self->{sensorState}{actualAngle});
1623             #print "Angle: " . $listener->{param} . "\n";
1624            
1625 0 0         if ($listener->{param}<0) {
1626 0           $self->removeSensorListener($listener->{id});
1627 0 0         &$callback($self) if ($callback);
1628             }
1629            
1630 0           return 1;
1631 0           };
1632            
1633 0           return $self->addSensorListener(200,$listener,abs($angle));
1634             }
1635             }
1636              
1637             =item $robot->waitTillFacing($priority,$direction,$maxDelta,$callback)
1638              
1639             Creates a one-time sensor event with priority $priority that will call &$callback($robot) when robot is
1640             within $maxDelta of absolute direction $direction (in radians). 0 radians is the direction the robot was facing
1641             when last $robot->init or $robot->markOrigin was called. $direction should be between -PI and PI.
1642             Returns an id that can be passed to removeSensorListener.
1643              
1644              
1645             =cut
1646              
1647             sub waitTillFacing($$$$) {
1648 0     0 1   my $self=shift;
1649 0           my $priority = shift;
1650 0           my $direction=shift;
1651 0           my $maxDelta=shift;
1652 0           my $callback=shift;
1653            
1654             return $self->addSensorEvent($priority,
1655             sub {
1656 0     0     return (abs($self->{sensorState}{direction}-$direction)<$maxDelta);
1657             },
1658 0           $callback,
1659             0,
1660             1
1661             );
1662             }
1663              
1664             =item $robot->waitTime($priority,$time,$callback)
1665              
1666             Creates a one-time sensor event with priority $priority that will call &$callback($robot) after $time seconds.
1667             Returns an id that can be passed to removeSensorListener.
1668              
1669             I
1670              
1671             =cut
1672              
1673             sub waitTime($$$) {
1674 0     0 1   my $self=shift;
1675            
1676 0 0         if ($self->{scriptMode}) {
1677 0           my $time=shift;
1678              
1679 0           $self->scriptWaitTime($time);
1680             } else {
1681 0           my $priority=shift;
1682 0           my $time=shift;
1683 0           my $callback=shift;
1684            
1685 0     0     return $self->addSensorEvent($priority,sub {return (time()>$_[1]->{param});},$callback,time()+$time,1);
  0            
1686             }
1687             }
1688              
1689             =item $robot->markOrigin()
1690              
1691             Sets the robot's current position as (0,0) and the direction the robot is currently facing as 0.
1692              
1693             =cut
1694              
1695             sub markOrigin($) {
1696 0     0 1   my $self=shift;
1697            
1698 0           $self->{sensorState}{x}=0;
1699 0           $self->{sensorState}{y}=0;
1700 0           $self->{sensorState}{direction}=0;
1701             }
1702              
1703             =item $robot->setPosition($x,$y,$direction)
1704              
1705             Sets the robots current position as ($x,$y) and the direction $direction (in radians). 0 is along the +y axis.
1706              
1707             =back
1708              
1709             =cut
1710              
1711             sub setPosition($$$$) {
1712 0     0 1   my $self=shift;
1713            
1714 0           $self->{sensorState}{x}=shift;
1715 0           $self->{sensorState}{y}=shift;
1716 0           $self->{sensorState}{direction}=shift;
1717            
1718             }
1719              
1720             sub _triggerSensorEvents($$) {
1721 0     0     my $self=shift;
1722 0           my $sensorIds=shift;
1723              
1724 0           my $sensorListener;
1725 0           foreach $sensorListener (@{$self->{sensorListeners}}) {
  0            
1726 0           $sensorListener->{stop}=0;
1727 0           &{$sensorListener->{action}}($self,$sensorListener,$sensorIds);
  0            
1728 0 0         last if ($sensorListener->{stop});
1729             }
1730            
1731             }
1732              
1733             sub _readSensorData($$) {
1734 0     0     my $self=shift;
1735 0           my $sensorId=shift;
1736            
1737 0           my ($readLength, $packString)=@{$sensorSpecs->[$sensorId]};
  0            
1738            
1739 0           my $readBytes=$self->readData($readLength);
1740              
1741 0 0         print "Read $readLength bytes: " . join(', ',unpack('C*',$readBytes)) . "\n" if ($DEBUG);
1742              
1743 0           my @data=unpack($packString,$readBytes);
1744            
1745 0           _updateSensorState($sensorId,$self->{sensorState},$self->{slipFactor},@data);
1746            
1747 0           return @data;
1748             }
1749              
1750             sub _syncStream($) {
1751 0     0     my $self=shift;
1752            
1753 0           my $synched=0;
1754            
1755 0           while(!$synched) {
1756 0           my $buffer='';
1757 0           my $checksum=19;
1758 0           my $read='';
1759            
1760              
1761 0           while (ord($buffer)!=19) {
1762 0           $buffer=$self->readData(1);
1763             }
1764              
1765            
1766 0           $read.=$buffer;
1767 0           $buffer=$self->readData(1);
1768            
1769 0           my $count=ord($buffer);
1770 0           $checksum+=$count;
1771 0           $read.=$buffer;
1772            
1773 0           $buffer=$self->readData($count + 1);
1774            
1775 0           $read.=$buffer;
1776 0           my @buffer=unpack('C*',$buffer);
1777            
1778 0           my $byte;
1779 0           foreach $byte (@buffer) {
1780 0           $checksum+=$byte;
1781             }
1782            
1783 0           $synched=(($checksum & 255)==0);
1784              
1785 0 0         substr($read,0,1,'') unless ($synched);
1786              
1787 0           $self->{readBuffer}=$read . $self->{readBuffer};
1788            
1789             }
1790            
1791 0           $self->logTelemetry(16);
1792             }
1793              
1794             =head2 Display
1795              
1796             =over 4
1797              
1798             =item $robot->getSensorString()
1799              
1800             Returns a string listing each sensor value from the sensor state hash.
1801              
1802             =cut
1803              
1804             sub getSensorString($) {
1805 0     0 1   my $self=shift;
1806            
1807 0           my $sensorState=$self->{sensorState};
1808 0           my $c=0;
1809 0           my $output='';
1810 0           my @keys=sort keys %{$self->{sensorState}};
  0            
1811            
1812 0           foreach my $key (@keys) {
1813 0 0         $output .= $key . ": " . ($key eq 'direction'?rad2deg($sensorState->{$key}):$sensorState->{$key}) . ((($c % 4)==3) ? "\r\n" : "\t");
    0          
1814 0           $c++;
1815             }
1816 0           return "$output---------------------------------\n";
1817             }
1818              
1819             =item $robot->getCondensedString()
1820              
1821             =item $robot->getCondensedString($lineEnding)
1822              
1823             Returns a string condensed version of the current state of the robots sensors. Suitable for output in a command line
1824             program.
1825              
1826             Example:
1827              
1828             Bu Bp Cliff DIn AIn Chrg Wl Whl Oc Sng Md
1829             AP LR LFlFrR 0123 0704 HbIn AV LCR 012LR P00 1
1830             Batt: 2606/2702 +1087@17200 (32) C Ir: 254
1831             (+00003,+00052) -002 DGFR
1832             Cliff: 0000 0000 00000 00000 Wall: 00000
1833              
1834             In the first 2 lines, Bu shows the status of the buttons on the robot. In the example above both the Advance and Play
1835             Buttons are pressed. Bp shows the status of the left (L) and right (R) bump sensors. Cliff shows the status of left (L),
1836             front-left (Fl), front-right (Fr), and right (R) sensors. DIn shows the state of the 4 digital inputs, AIn shows the value of
1837             the analog input. Chrg shows if charging is available from the home base (Hb) or internal (In) chargers.
1838             Wl shows the state of the actual (A) and virtual (V) wall sensors. Whl shows the state of the left (L),
1839             caster (C), and right (R) wheeldrop sensors. Oc shows the state of the overcurrent sensors for the 3 low side drivers,
1840             (0-2) and the left (L) and right (R) wheels. Sng indicates is a song is playing (P) and the currently selected song bank.
1841             Md shows the current mode number.
1842              
1843             The battery line shows the battery charge and maxium capacity, the current and voltage flowing from the battery,
1844             the battry temperature, the charging state. Also, the byte recieved by the IR sensor is shown on this line. In the example above,
1845             the battery has charge of 2606 mAh out of a maximum capacity of 2702 mAh. The battery is charging at 1087 mA and 17200 mV.
1846             Positive current values indicate charging, negative ones indicate discharge. The battery temperature is 32 degrees C. The C indicates
1847             the battery is charging. The IR sensor is receiving byte 254.
1848              
1849             The fourth line shows the estimated x and y position in mm, and the current direction in degrees. Also shown is any docking
1850             indicators: dock present (D), green beacon (G), force field (F), red beacon (R).
1851              
1852             The last line indicates the raw signal values of the four cliff sensors (left, front-left, front-right, and right) and the wall
1853             sensor.
1854              
1855             =back
1856              
1857             =cut
1858              
1859             sub getCondensedString($) {
1860 0     0 1   my $self=shift;
1861 0   0       my $endLine = shift || "\n";
1862            
1863 0           my $sensorState=$self->{sensorState};
1864 0           my $inputs='';
1865 0           for(my $i=0;$i<4;$i++) {
1866 0 0         $inputs.=($sensorState->{'digitalInput' . $i}?$i:' ');
1867             }
1868 0           my $ocLDs='';
1869 0           for(my $i=0;$i<3;$i++) {
1870 0 0         $ocLDs.=($sensorState->{'ocLD' . $i}?$i:' ');
1871             }
1872 0           my @dock=$self->getDockSignal();
1873            
1874 0 0         return "Bu Bp Cliff DIn AIn Chrg Wl Whl Oc Sng Md$endLine"
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1875             .($sensorState->{playButton}?'P':' ') . ($sensorState->{advanceButton}?'A':' ') .' '
1876             .($sensorState->{bumpLeft}?'L':' ').($sensorState->{bumpRight}?'R':' ') . ' '
1877             .($sensorState->{cliffLeft}?'L':' ').($sensorState->{cliffFrontLeft}?'Fl':' ')
1878             .($sensorState->{cliffFrontRight}?'Fr':' ') .($sensorState->{cliffRight}?'R':' ') . ' '
1879             .$inputs
1880             .' '
1881             .sprintf("%0*s ",4,$sensorState->{analogIn})
1882             .($sensorState->{homeBaseAvailable}?'Hb':' ').($sensorState->{internalCharger}?'In':' ') . ' '
1883             .($sensorState->{wall}?'A':' ').($sensorState->{virtualWall}?'V':' ') . ' '
1884             .($sensorState->{wheeldropLeft}?'L':' ').($sensorState->{wheeldropCaster}?'C':' ')
1885             .($sensorState->{wheeldropRight}?'R':' ') . ' '
1886             .$ocLDs
1887             .($sensorState->{ocLeftWheel}?'L':' ').($sensorState->{ocRightWheel}?'R':' ') . ' '
1888             .($sensorState->{songPlaying}?'P':' ').sprintf("%0*d ",2,$sensorState->{songNumber})
1889             .$sensorState->{oiMode}.$endLine
1890             .sprintf("Batt: %0*d/%0*d %+0*d@%0*d (%0*d) ",4,$sensorState->{batteryCharge},
1891             4,$sensorState->{batteryCapacity},5,$sensorState->{current},
1892             5,$sensorState->{voltage},2,$sensorState->{batteryTemp})
1893             .($sensorState->{chargingState}?'C ':' ')
1894             .sprintf("Ir: %0*d",3,$sensorState->{irByte})
1895             .$endLine
1896             .sprintf("(%+0*d,%+0*d) %+0*d ",6,$sensorState->{x},
1897             6,$sensorState->{y},
1898             4,rad2deg($sensorState->{direction}))
1899             .($dock[0]?'D':' ').($dock[1]?'G':' ').($dock[2]?'F':' ').($dock[3]?'R':' ').$endLine
1900             .sprintf("Cliff: %0*d %0*d %0*d %0*d Wall: %0*d",
1901             4,$sensorState->{cliffLeftSignal},
1902             4,$sensorState->{cliffFrontLeftSignal},
1903             5,$sensorState->{cliffFrontRightSignal},
1904             5,$sensorState->{cliffRightSignal},
1905             5,$sensorState->{wallSignal})
1906             .$endLine;
1907             }
1908              
1909              
1910              
1911             ###########Utility functions################
1912             sub _convertFSS($) {
1913 0     0     unpack('C2',pack('n!',shift()));
1914             }
1915              
1916             sub _normalizeAngle($) {
1917 0     0     my $angle=shift;
1918 0           $angle=fmod($angle,pi2);
1919 0 0         $angle+=pi2 if ($angle<0);
1920 0 0         $angle-=pi2 if ($angle>pi);
1921 0           return $angle;
1922             }
1923              
1924             #############Scripting commands####################
1925             =head2 Scripting
1926              
1927             These commands make use of the builtin scripting functionality of the iRobot create.
1928              
1929             See OI Documentation for more details.
1930              
1931             =over 4
1932              
1933             =item $robot->startScript()
1934              
1935             Puts robot in scripting mode. Subsequent commands will not be sent to robot,
1936             but instead be saved to robots script memory. Create can store a maximum of 100 bytes
1937             of scripting commands.
1938              
1939             =cut
1940              
1941             sub startScript($) {
1942 0     0 1   my $self=shift;
1943            
1944 0           $self->{scriptMode}=1;
1945 0           $self->{scriptBytes}=[];
1946             }
1947              
1948             =item $robot->runScript()
1949              
1950             Runs the script currently stored in robot. This will cause the robot to go into passive mode
1951             and not respond to commands until script is complete.
1952              
1953             =cut
1954              
1955             sub runScript($) {
1956 0     0 1   my $self=shift;
1957            
1958 0           $self->writeBytes(153);
1959             }
1960              
1961             =item $robot->repeatScript()
1962              
1963             If called while in scripting mode, causes script to repeat from begining.
1964              
1965             =cut
1966              
1967             sub repeatScript($) {
1968 0     0 1   my $self=shift;
1969            
1970 0           $self->runScript();
1971             }
1972              
1973             =item $robot->scriptMode()
1974              
1975             Returns true if currently in scripting mode, false otherwise.
1976              
1977             =cut
1978              
1979             sub scriptMode($) {
1980 0     0 1   my $self=shift;
1981            
1982 0           return $self->{scriptMode};
1983             }
1984              
1985             =item $robot->cancelScript()
1986              
1987             Leaves scripting mode without writing script to robot.
1988              
1989             =cut
1990              
1991             sub cancelScript($) {
1992 0     0 1   my $self=shift;
1993            
1994 0           $self->{scriptMode}=1;
1995             }
1996              
1997             =item $robot->endScript()
1998              
1999             Leaves scripting mode and writes script to robot.
2000              
2001             =cut
2002              
2003             sub endScript($) {
2004 0     0 1   my $self=shift;
2005            
2006 0           $self->{scriptMode}=0;
2007 0           my $scriptBytes=$self->{scriptBytes};
2008            
2009 0           $self->writeBytes(152,(@{$scriptBytes}+0),@$scriptBytes);
  0            
2010             }
2011              
2012              
2013             =item $robot->scriptWaitTime($time)
2014              
2015             Waits $time seconds (rounded to nearest tenth). Robot will not respond to commands during this time.
2016             Not recommended to be used outside of scripting mode.
2017              
2018             =cut
2019              
2020             sub scriptWaitTime($$) {
2021 0     0 1   my $self=shift;
2022 0           my $timeSecs=shift;
2023            
2024 0           $self->writeBytes(155,int($timeSecs*10));
2025             }
2026              
2027             =item $robot->scriptWaitDistance($distance)
2028              
2029             Waits until robot travels $distance mm. Robot will not respond to commands during this time.
2030             Not recommended to be used outside of scripting mode.
2031              
2032             =cut
2033              
2034             sub scriptWaitDistance($$) {
2035 0     0 1   my $self=shift;
2036 0           my $distance=shift;
2037            
2038 0           $self->writeBytes(156,_convertFSS($distance));
2039             }
2040              
2041             =item $robot->scriptWaitAngle($angle)
2042              
2043             Waits until robot turns through $angle degrees. Robot will not respond to commands during this time.
2044             Not recommended to be used outside of scripting mode.
2045              
2046             =cut
2047              
2048             sub scriptWaitAngle($$) {
2049 0     0 1   my $self=shift;
2050 0           my $angle=shift;
2051            
2052 0           $self->writeBytes(157,_convertFSS($angle));
2053             }
2054              
2055             =item $robot->waitEvent($eventId)
2056             =item $robot->scriptWaitEvent($eventId)
2057              
2058             Waits until event with id $eventId occurs. Robot will not respond to commands during this time.
2059             Not recommended to be used outside of scripting mode.
2060              
2061             See OI Documentation for list of event ids.
2062              
2063             =back
2064              
2065             =cut
2066              
2067 0     0 1   sub waitEvent($$) { scriptWaitEvent(@_); }
2068             sub scriptWaitEvent($$) {
2069 0     0 1   my $self=shift;
2070 0           my $event=shift;
2071            
2072 0           $self->writeBytes(158,$event);
2073             }
2074              
2075             #######Telemetry############
2076             =head2 Telemetry
2077              
2078             This module has the ability to record telemetry data containing bytes sent to and from robot. And any debuging
2079             data that can be provided by client programs.
2080              
2081             =over 4
2082              
2083             =item $robot->startTelemetry($file,$overwrite)
2084              
2085             Begins writing telemetry data to $file. Appends data unless $overwrite is true.
2086              
2087             =cut
2088              
2089             sub startTelemetry($$$) {
2090 0     0 1   my $self=shift;
2091 0           my $file=shift;
2092 0   0       my $overwrite=shift || 0;
2093            
2094 0 0         if (!$self->{telem}) {
2095 0 0         open my $telem,">" . ($overwrite?'':'>') .$file;
2096 0           $self->{telem}=$telem;
2097            
2098 0           print $telem pack("QAC",int(time*1000),"B",0);
2099             }
2100             }
2101              
2102             =item $robot->stopTelemetry()
2103              
2104             Stops recording telemetry data.
2105              
2106             =cut
2107              
2108             sub stopTelemetry($) {
2109 0     0 1   my $self=shift;
2110            
2111 0 0         if ($self->{telem}) {
2112 0           print {$self->{telem}} pack("QAC",int(time*1000),"E",0);
  0            
2113             }
2114            
2115 0           $self->{telem}=0;
2116              
2117             }
2118              
2119             =item $robot->logTelemetry($type,$data)
2120              
2121             Write client application provided data to telemetry file. The $type value should use the following specification
2122             for maxium compatibility with other applications. $data can be any binary value.
2123              
2124             Types:
2125             0- indirect sensor field
2126             16 - stream sync indicator
2127             32 - 63 -- reserved for client UI
2128             32 - key press
2129             64-127 -- Debug info
2130             64 - general debug
2131             255 - String message
2132              
2133             =back
2134              
2135             =cut
2136              
2137             sub logTelemetry($$$) {
2138 0     0 1   my $self=shift;
2139 0           my $type=shift;
2140 0   0       my $data=shift || '';
2141              
2142 0           $self->_writeTelem('M',chr($type) . $data);
2143             }
2144              
2145             sub _readTelem($) {
2146 0     0     my $self=shift;
2147            
2148 0           my ($bytes,$data,$time,$type,$length,$tdata,$timeLow,$timeHigh);
2149            
2150 0           $length=10;
2151              
2152 0           $data='';
2153 0           while($length>0) {
2154 0           $bytes=read $self->{replay}, $tdata,$length;
2155 0 0         return (0,'E','') unless ($bytes);
2156 0           $length-=$bytes;
2157 0           $data.=$tdata;
2158             }
2159            
2160 0           ($timeLow,$timeHigh,$type,$length)=unpack("LLAC",$data);
2161            
2162 0           $time=($timeHigh * 0xFFFFFFFF) + $timeLow;
2163              
2164 0           $data='';
2165 0           while($length>0) {
2166 0           $bytes=read $self->{replay}, $tdata, $length;
2167 0 0         return (0,'E','') unless ($bytes);
2168 0           $length-=$bytes;
2169 0           $data.=$tdata;
2170             }
2171              
2172 0           return ($time/1000,$type,$data);
2173             }
2174              
2175             sub _writeTelem($$$) {
2176 0     0     my $self=shift;
2177 0           my $type=shift;
2178 0           my $data=shift;
2179            
2180 0           my $time=int(time()*1000);
2181 0           my $timeLow=$time & 0xFFFFFF;
2182 0           my $timeHigh=$time >> 32;
2183            
2184 0 0         print {$self->{telem}} pack("LLACa*",$timeLow,$timeHigh,$type,length($data),$data) if ($self->{telem});
  0            
2185             }
2186              
2187             =head2 Sensor Calibration
2188              
2189             Sensor calibration can be used to correct for some inconsistancies and inaccuracies of the robot. 2 sets of sensors can be calibrated.
2190              
2191             The first is the angle sensor. This sensor is supposed to return the angle turned in degrees since the last sensor reading. In practice it
2192             is extremely inaccurate. The accuracy can be some what improved by calculating the ratio between actual angle turned and angle reported
2193             at different velocities and then applying this ratio to read sensor values. This corrected value can be seen in the actualAngle and totalActualAngle
2194             indirect sensors.
2195              
2196             The second set are the cliff signal sensors. These sensors report the IR reflectivity of 4 sections of floor near the front of the robot.
2197             These values could be used for example, to follow a dark line drawn on the ground.
2198             The sensors tend to be accurate individually, but each sensor will have a slightly different bias-- meaning each sensor will read the same
2199             section of floor differently. To correct for this we can generate statistics describing the range of values seen by each sensor
2200             over the same section of floor. We can then use this to determine the distance from the mean in standard deviations for
2201             a value read by a particular sensor based on the previous values read for that sensor. This value should be the same for each sensor
2202             when reading the same section of floor. These values are seen in the cliff...SignalDev indirect sensors.
2203              
2204             Once these calibration data are created. They can be saved and retrieved using the saveCalibrationData and loadCalibrationData methods.
2205              
2206             Note: default calibration values are provided, these were generated from calibration of my robot on my carpet. However, it is strongly
2207             recommended to calibrate your own robot and create a calibration file for each type of flooring on which you plan to operate the robot. The
2208             type of flooring will change both the wheel slipage and IR reflectivity, so both the angle and cliff signal sensors will need to be recalibrated.
2209              
2210              
2211             See section Indirect sensors for more information.
2212              
2213             =over 4
2214              
2215             =item $robot->loadCalibrationData($file)
2216              
2217             Loads saved calibration file from $file or calibration.yaml if no file is given. This is called automatically on initialization. So, it is only
2218             necessary if you wish to load calibration data from another file.
2219              
2220             =cut
2221              
2222             sub loadCalibrationData($$) {
2223 0     0 1   my $self=shift;
2224 0   0       my $file=shift || 'calibration.yaml';
2225            
2226 0           my $calibData=YAML::Tiny->read($file);
2227            
2228 0 0         $self->{calibration}=$calibData->[0] if ($calibData);
2229             }
2230              
2231             =item $robot->saveCalibrationData($file)
2232              
2233             Saves calibration data to $file or calibration.yaml if no file is given.
2234              
2235             =cut
2236              
2237             sub saveCalibrationData($$) {
2238 0     0 1   my $self=shift;
2239 0   0       my $file=shift || 'calibration.yaml';
2240            
2241 0           my $calibData=YAML::Tiny->new;
2242            
2243 0           $calibData->[0]=$self->{calibration};
2244            
2245 0           $calibData->write($file);
2246             }
2247              
2248             =item $robot->calibrate($sensor)
2249              
2250             Calibrates indirect sensors. Method will block until calibration is complete. Robot will go though several complete rotations.
2251             Be sure to follow any instructions in calicration proceedure below. Returns true on calibration success, false otherwise.
2252             $sensor can be one of the following:
2253              
2254             =over 5
2255              
2256             =item actualAngle:
2257             Calibrates actualAngle correction factors for dead reckoning
2258              
2259             =item cliffDev:
2260             Calibrates cliffSignalDev sensors
2261              
2262             =item all:
2263             Calibrates all sensors.
2264              
2265             =back
2266              
2267             Calibration procedure: For cliffDev, the robot needs only to be on the type of surface it will be used on most often. For actualAngle, the
2268             procedure is more complicated. A "Home Base" docking station and a way to block of part of the robots IR sensor is required. Block all but a small
2269             section in the front of the IR Sensor. I usually do this with a strip of aluminum foil. Then place the robot a few feet away from the docking station and
2270             in a position so that the docking station can only be seen though the small gap you left in the IR sensor.
2271              
2272             Note: You will need to call $robot->saveCalibrationData afterwards to save calibration to file.
2273              
2274             =cut
2275              
2276             sub calibrate($$) {
2277 0     0 1   my $self=shift;
2278 0           my $sensor=shift;
2279            
2280 0           my $wasStreaming=$self->{isStreaming};
2281 0           my @calibration;
2282            
2283 0     0     my $seeDock=sub { ($self->getDockSignal())[0]; };
  0            
2284 0     0     my $notSeeDock=sub { !(($self->getDockSignal())[0]); };
  0            
2285            
2286 0 0 0       if ($sensor eq 'actualAngle' || $sensor eq 'all') {
2287            
2288 0 0         if ($sensor eq 'all') {
2289 0           $self->{gatherCliffStatistics}=1;
2290 0           $self->{calibration}{cliffStatistics}={};
2291             }
2292            
2293 0           my $rejectCount=0;
2294            
2295 0           my $testSpeed=100;
2296            
2297 0           while ($testSpeed<=500) {
2298 0           my ($startAngle,$endAngle);
2299            
2300 0           $self->rotateLeft(200);
2301            
2302             $self->addSensorEvent(50,$seeDock,
2303             sub {
2304             $self->waitTime(50,1,sub {
2305             $self->addSensorEvent(50,$notSeeDock,
2306             sub {
2307             $self->waitTime(50,1,sub {
2308 0           $self->stop();
2309             $self->waitTime(50,2,sub {
2310 0           $self->rotateRight($testSpeed);
2311             $self->addSensorEvent(50,$seeDock,
2312             sub {
2313 0           $startAngle=$self->{sensorState}{totalAngle};
2314            
2315             $self->addSensorEvent(50,$notSeeDock,
2316             sub {
2317             $self->addSensorEvent(50,$seeDock,
2318             sub {
2319 0           $endAngle=$self->{sensorState}{totalAngle};
2320 0           $self->exitSensorLoop();
2321             }
2322 0           ,0,1);
2323             }
2324 0           ,0,1);
2325             }
2326 0           ,0,1);
2327 0           });
2328 0           });
2329             }
2330 0           ,0,1);
2331 0     0     });
2332             }
2333 0           ,0,1);
2334              
2335 0           $self->runSensorLoop();
2336              
2337 0           my $testAngle=($startAngle-$endAngle)*($testSpeed<=>0);
2338            
2339 0 0 0       if ($testAngle<400 && $testAngle>200) {
2340 0           push @calibration,(2*pi)/$testAngle;
2341            
2342 0 0         if ($testSpeed==100) {
2343 0           for (my$i=0;$i<2;$i++) {
2344 0           push @calibration,(2*pi)/$testAngle;
2345             }
2346             }
2347            
2348 0           $testSpeed+=50;
2349             } else {
2350 0           $rejectCount++;
2351 0 0         return 0 if ($rejectCount>5);
2352             }
2353             }
2354              
2355 0           $self->stop();
2356            
2357 0           sleep 2;
2358            
2359 0           $self->{calibration}{angleCorrection}=\@calibration;
2360             }
2361            
2362 0 0         if ($sensor eq 'cliffDev') {
2363 0           $self->{calibration}{cliffStatistics}={};
2364 0           $self->{gatherCliffStatistics}=1;
2365            
2366 0     0     $self->waitAngle(50,4*pi,sub { $self->exitSensorLoop(); });
  0            
2367            
2368 0           $self->rotateLeft(200);
2369            
2370 0           $self->runSensorLoop();
2371            
2372 0           $self->stop();
2373             }
2374            
2375 0           $self->{gatherCliffStatistics}=0;
2376            
2377            
2378 0 0         $self->startStream(6) if ($wasStreaming);
2379            
2380 0           return 1;
2381            
2382             }
2383              
2384             =item $robot->setMovementCorrectionMode($mode)
2385              
2386             Sets the movementCorrection method used by the module. Can be one of the following:
2387              
2388             =over 5
2389              
2390             =item calibration:
2391             (default) Uses result of calibration to correct reported sensor values. This works best when robot is limited to straight movement and rotation in place. See Sensor Calibration.
2392              
2393             =item time:
2394             Ignores angle sensor values and relies solely on requested movement and time.
2395              
2396             =item robot:
2397             Trusts value reported by robot angle sensor is accurate. Assumes
2398             sensor value is difference between distance traveled by left wheel
2399             and distance travel by right wheel. (This seems to actually be the case.)
2400              
2401             =item raw:
2402             Trusts value reported by robot angle sensor is accurate. Assumes
2403             sensor value is degrees rotated. (This is the value reported by the OI
2404             doc, but does not actually seem to be the case.)
2405              
2406             =back
2407              
2408             I<> you can pass your own sub to perform movement correction. When called it will be passed $robot, $listener, and $sensorIds.
2409             $sensorIds is a array ref containing the read sensorIds. $listener is a hash containing the details of the sensor listener used to calculate
2410             indirect sensor values. See addSensorListener for more details. The sub must return a list containing actual distance traveled in mm
2411             followed by actual angle rotated in radians.
2412              
2413             =back
2414              
2415             =cut
2416              
2417             sub setMovementCorrectionMode($$) {
2418 0     0 1   my $self=shift;
2419 0           my $deadReckoner=shift;
2420              
2421 0 0         if ($deadReckoner eq 'calibration') {
    0          
    0          
    0          
2422 0           $deadReckoner=\&_correctiveDeadReckoning;
2423             } elsif ($deadReckoner eq 'time') {
2424 0           $deadReckoner=\&_timeDeadReckoning;
2425             } elsif ($deadReckoner eq 'robot') {
2426 0           $deadReckoner=\&_robotDeadReckoning;
2427             } elsif ($deadReckoner eq 'raw') {
2428 0           $deadReckoner=\&_rawDeadReckoning;
2429             }
2430            
2431 0           $self->{deadReckoning}=$deadReckoner;
2432            
2433             }
2434              
2435             =head2 Closing Connection
2436              
2437             =over 4
2438              
2439             =item $robot->close()
2440              
2441             Stops the robot motion and sensor streaming and closes communication port.
2442              
2443             =back
2444              
2445             =cut
2446              
2447              
2448             sub close($) {
2449 0     0 1   my $self=shift;
2450            
2451 0           $self->stop();
2452            
2453 0 0         $self->stopTelemetry if ($self->{telem});
2454            
2455 0 0         if ($self->{replay}) {
2456 0           close $self->{replay};
2457             } else {
2458 0           $self->{port}->close();
2459             }
2460              
2461             }
2462              
2463             =head1 SENSORS
2464              
2465             The sensor state hash can be retrieved from the $robot->getSensorState() method. This only need be retrieved once
2466             as subsequent updates will be made to the same hash. Each direct and indirect sensor reading can be retrieved from this
2467             hash using the keys below.
2468              
2469             =head2 Direct Sensors
2470              
2471             These are sensor values that are read directly from the robot.
2472              
2473             =head3 Keys
2474              
2475             =over 5
2476              
2477             =item wheeldropCaster --
2478             wheeldrop sensor on front caster (boolean)
2479              
2480             =item wheeldropLeft --
2481             wheeldrop sensor on left wheel (boolean)
2482              
2483             =item wheeldropRight --
2484             wheeldrop sensor on right wheel (boolean)
2485              
2486             =item bumpLeft --
2487             left bump sensor (boolean)
2488              
2489             =item bumpRight --
2490             right bump sensor (boolean)
2491              
2492             =item wall --
2493             physical wall sensor (boolean)
2494              
2495             =item cliffLeft --
2496             left cliff sensor (boolean)
2497              
2498             =item cliffFrontLeft --
2499             front-left cliff sensor (boolean)
2500              
2501             =item cliffFrontRight --
2502             front-right cliff sensor (boolean)
2503              
2504             =item virtualWall --
2505             virtual wall sensor (boolean)
2506              
2507             =item ocLeftWheel --
2508             overcurrent on left wheel (boolean)
2509              
2510             =item ocRightWheel --
2511             overcurrent on right wheel (boolean)
2512              
2513             =item ocLD0 --
2514             overcurrent on low side driver 0 (boolean)
2515              
2516             =item ocLD1 --
2517             overcurrent on low side driver 1 (boolean)
2518              
2519             =item ocLD2 --
2520             overcurrent on low side driver 2 (boolean)
2521              
2522             =item irByte --
2523             byte received by IR sensor (unsigned byte)
2524              
2525             =item advanceButton --
2526             advance button state (boolean)
2527              
2528             =item playButton --
2529             play button state (boolean)
2530              
2531             =item distance --
2532             distance travelled in mm since last sensor refresh (signed short)
2533              
2534             =item angle --
2535             angle turned in degrees since last sensor refresh (signed short)
2536            
2537             positive angles are counter-clockwise
2538             negative are clockwise
2539             NOTE: This sensor is extremely inaccurate (see actualAngle)
2540              
2541             =item chargingState --
2542             indicates if robot is charging (boolean)
2543              
2544             =item voltage --
2545             voltage of battery (unsigned short)
2546              
2547             =item current --
2548             current of battery charge/discharged (signed short)
2549            
2550             positive values indicate charging
2551             negative indicate discharging
2552              
2553             =item batteryTemp --
2554             temperature of battery in degrees C (unsigned byte)
2555              
2556             =item batteryCharge --
2557             current battery charge in mAh (unsigned short)
2558              
2559             =item batteryCapacity --
2560             maximum battery capacity in mAh (unsigned short)
2561              
2562             =item wallSignal --
2563             raw signal value of wall sensor (unsigned short)
2564              
2565             =item cliffLeftSignal --
2566             raw signal value of left cliff sensor (unsigned short)
2567              
2568             =item cliffFrontLeftSignal --
2569             raw signal value of front-left sensor (unsigned short)
2570              
2571             =item cliffFrontRightSignal --
2572             raw signal value of front-right cliff sensor (unsigned short)
2573              
2574             =item cliffRightSignal --
2575             raw signal value of right cliff sensor (unsigned short)
2576              
2577             =item deviceDetect --
2578             state of robot's device detect pin (boolean)
2579              
2580             =item digitalInput0 --
2581             state of digital input 0 (boolean)
2582              
2583             =item digitalInput1 --
2584             state of digital input 1 (boolean)
2585              
2586             =item digitalInput2 --
2587             state of digital input 2 (boolean)
2588              
2589             =item digitalInput3 --
2590             state of digital input 3 (boolean)
2591              
2592             =item analogIn --
2593             value of analog input (unsigned short)
2594              
2595             =item homeBaseAvailable --
2596             true if robot is connected to home base (boolean)
2597              
2598             =item internalCharger --
2599             true if robot can charge battery using internal charger (boolean)
2600              
2601             =item oiMode --
2602             OI Interface mode (unsigned byte)
2603              
2604             =item songNumber --
2605             last selected song bank (unsigned byte)
2606              
2607             =item songPlaying --
2608             true if song is playing (boolean)
2609              
2610             =item numPackets --
2611             number of packets sent in last stream (byte)
2612              
2613             =item requestedVelocity --
2614             last requested velocity (signed short)
2615              
2616             =item requestedRadius --
2617             last requested turning radius (signed short)
2618              
2619             =item requestedRightVelocity --
2620             last requested right wheel velocity (signed short)
2621              
2622             =item requestedLeftVelocity --
2623             last requested left wheel velocity (signed short)
2624              
2625             =back
2626              
2627             =head2 Indirect Sensors
2628              
2629             These are sensor values that are derived from direct sensors.
2630              
2631             =head3 Keys
2632              
2633             =over 5
2634              
2635             =item totalAngle --
2636             sum of all previous angle readings
2637              
2638             =item totalDistance --
2639             sum of all previous distance readings
2640              
2641             =item lastSensorReading --
2642             Timestamp in seconds of last reading
2643              
2644             =item deltaT --
2645             time in seconds since last reading
2646              
2647             =item deltaX --
2648             change in x coordinate since last reading
2649              
2650             =item deltaY --
2651             change in y coordinate since last reading
2652              
2653             =item x --
2654             x coordinate of current position
2655              
2656             =item y --
2657             y coordinate of current position
2658              
2659             =item direction --
2660             direction robot is currently facing in radians (between -PI and PI)
2661              
2662             =item actualAngle --
2663             an attempt to correct angle sensor; the actual angle turned in radians
2664              
2665             =item totalActualAngle --
2666             sum of all previous actual angle readings
2667              
2668             =item actualDistance --
2669             an attempt to correct distance sensor; the distance traveled in mm
2670              
2671             =item totalActualDistance --
2672             an attempt to correct distance sensor; the distance traveled in mm
2673              
2674             =item turningRadius --
2675             estimated actual turning radius
2676              
2677             =item cliffFrontSignalDelta, cliffFrontRightSignalDelta,
2678             cliffLeftSignalDelta, cliffFrontLeftSignalDelta --
2679             change in cliff sensors since last reading
2680              
2681             =item cliffFrontSignalDev, cliffFrontRightSignalDev,
2682             cliffLeftSignalDev, cliffFrontLeftSignalDev --
2683             difference from mean of current cliff sensor
2684             value in standard deviations
2685              
2686             =back
2687              
2688             =head1 DEAD RECKONING
2689              
2690             This module attempts to do some sensor correction and dead reckoning using sensor readings.
2691              
2692             =head2 Coordinate System
2693              
2694             When the $robot->new or $robot->markOrigin is called. The x,y, and direction values of the robot
2695             are set to 0. The robot is then assumed to be facing along the positive y-axis (direction 0). The positive x-axis is
2696             90 clockwise. Positive directions are counter-clockwise, negative directions are clockwise.
2697              
2698             =head2 Sensor Correction
2699              
2700             An attempt is made to correct inaccurate angle readings. The correction is done using error factors determined
2701             experimentally on a Create. Your mileage may vary.
2702              
2703             It is recommended that you calibrate your robot using the calibrate method before relying on dead reckoning.
2704              
2705             =cut
2706              
2707             sub _updateSensorState($$$@);
2708             sub _updateSensorState($$$@) {
2709 0     0     my $sensorId=shift;
2710 0           my $sensorState=shift;
2711 0           my $slipFactor=shift;
2712 0           my $data=$_[0];
2713              
2714 0 0 0       if ($sensorId<7) {
    0          
    0          
    0          
    0          
    0          
    0          
2715 0           my $sensorGroup=$sensorGroups->[$sensorId];
2716 0           my $start=$sensorGroup->[0];
2717 0           my $end=$sensorGroup->[1];
2718 0           for(my $i=0;$i<=($end-$start);$i++) {
2719 0           _updateSensorState($i+$start,$sensorState,$slipFactor,$_[$i]);
2720             }
2721             } elsif ($sensorId==7) {
2722 0           $sensorState->{wheeldropCaster}=(($data&16)>>4);
2723 0           $sensorState->{wheeldropLeft}=(($data&8)>>3);
2724 0           $sensorState->{wheeldropRight}=(($data&4)>>2);
2725 0           $sensorState->{bumpLeft}=(($data&2)>>1);
2726 0           $sensorState->{bumpRight}=$data&1;
2727             } elsif ($sensorId==14) {
2728 0           $sensorState->{ocLeftWheel}=($data&16)>>4;
2729 0           $sensorState->{ocRightWheel}=($data&8)>>3;
2730 0           $sensorState->{ocLD2}=($data&4)>>2;
2731 0           $sensorState->{ocLD1}=($data&2)>>1;
2732 0           $sensorState->{ocLD0}=($data&1);
2733             } elsif ($sensorId==15 || $sensorId==16) {
2734             #reserved sensor packet ids
2735             } elsif ($sensorId==18) {
2736 0           $sensorState->{advanceButton}=($data&4)>>2;
2737 0           $sensorState->{playButton}=($data&1);
2738             } elsif ($sensorId==32) {
2739 0           $sensorState->{deviceDetect}=($data&16)>>4;
2740 0           $sensorState->{digitalInput3}=($data&8)>>3;
2741 0           $sensorState->{digitalInput2}=($data&4)>>2;
2742 0           $sensorState->{digitalInput1}=($data&2)>>1;
2743 0           $sensorState->{digitalInput0}=($data&1);
2744             } elsif ($sensorId==34) {
2745 0           $sensorState->{homeBaseAvailable}=($data&2)>>1;
2746 0           $sensorState->{internalCharger}=($data&1);
2747             } else {
2748 0           my $sensorField=$sensorFields[$sensorId];
2749 0           $sensorState->{$sensorField}=$data;
2750             }
2751             }
2752              
2753             sub _timeDeadReckoning($$$) {
2754 0     0     my $self=shift;
2755 0           my $listener=shift;
2756 0           my $sensorIds=shift;
2757            
2758 0           my $sensorState=$self->{sensorState};
2759            
2760 0           my $requestedVelocity=$sensorState->{requestedVelocity};
2761 0           my $requestedRadius=$sensorState->{requestedRadius};
2762 0 0         my $deltaT=$self->{isStreaming}?$EPSILON:$sensorState->{deltaT};
2763            
2764 0           my $turnDistance=$deltaT*$requestedVelocity;
2765 0           my $actualAngle=($requestedRadius<=>0)*$turnDistance/(abs($requestedRadius)+$WHEEL_WIDTH/2);
2766 0           my $distance=$sensorState->{distance};
2767            
2768 0           return ($distance,$actualAngle);
2769             }
2770              
2771             sub _correctiveDeadReckoning($$$) {
2772 0     0     my $self=shift;
2773 0           my $listener=shift;
2774 0           my $sensorIds=shift;
2775            
2776 0           my $sensorState=$self->{sensorState};
2777            
2778 0           my $distance=$sensorState->{distance};
2779 0           my $angle=$sensorState->{angle};
2780              
2781 0           my $lastVelocity=$self->{lastVelocity};
2782 0           my $angleCorrection=$self->{calibration}{angleCorrection}[int(abs($lastVelocity)/50)];
2783              
2784 0           my $actualAngle=$angle*$angleCorrection;
2785              
2786              
2787 0           return ($distance,$actualAngle);
2788            
2789             }
2790              
2791             sub _robotDeadReckoning($$$) {
2792 0     0     my $self=shift;
2793 0           my $listener=shift;
2794 0           my $sensorIds=shift;
2795            
2796 0           my $sensorState=$self->{sensorState};
2797            
2798 0           my $distance=$sensorState->{distance};
2799 0           my $angle=$sensorState->{angle};
2800            
2801 0           my $actualAngle= 2* $angle/$WHEEL_WIDTH;
2802            
2803 0           return ($distance,$actualAngle);
2804             }
2805              
2806             sub _rawDeadReckoning($$$) {
2807 0     0     my $self=shift;
2808 0           my $listener=shift;
2809 0           my $sensorIds=shift;
2810            
2811 0           my $sensorState=$self->{sensorState};
2812            
2813 0           my $distance=$sensorState->{distance};
2814 0           my $angle=$sensorState->{angle};
2815            
2816 0           my $actualAngle=pi * $angle / 180;
2817            
2818 0           return ($distance,$actualAngle);
2819             }
2820            
2821              
2822             sub _indirectSensors($$$) {
2823 0     0     my $self=shift;
2824 0           my $listener=shift;
2825 0           my $sensorIds=shift;
2826              
2827 0           my $sensorState=$self->{sensorState};
2828            
2829 0           my $now=time();
2830 0           $sensorState->{deltaT}=$now-$self->{lastSensorRefresh};
2831 0           $sensorState->{lastSensorRefresh}=$now;
2832            
2833 0           my $angle=$sensorState->{angle};
2834 0           my $direction=$sensorState->{direction};
2835            
2836 0           my ($distance,$actualAngle)=&{$self->{deadReckoning}}($self,$listener,$sensorIds);
  0            
2837 0           my ($dxf,$dyf)=_getRelativeMovement($actualAngle,$distance);
2838 0           my ($dx,$dy)=_getAbsoluteMovement($direction,$dxf,$dyf);
2839            
2840            
2841 0           $sensorState->{totalAngle}+=$angle;
2842 0           $sensorState->{totalDistance}+=$distance;
2843            
2844 0           $sensorState->{deltaX}=$dx;
2845 0           $sensorState->{deltaY}=$dy;
2846 0           $sensorState->{x}+=$dx;
2847 0           $sensorState->{y}+=$dy;
2848            
2849 0           $direction=_normalizeAngle($direction+$actualAngle);
2850 0           $sensorState->{direction}=$direction;
2851 0           $sensorState->{actualDistance}=$distance;
2852 0           $sensorState->{actualAngle}=$actualAngle;
2853 0           $sensorState->{totalActualAngle}+=$actualAngle;
2854 0           $sensorState->{totalActualDistance}+=$distance;
2855 0 0         my $turningRadius=$actualAngle!=0?$distance/$actualAngle:undef;
2856 0           $sensorState->{turningRadius}=$turningRadius;
2857            
2858 0 0         $self->_updateCliffStatistics() if ($self->{gatherCliffStatistics});
2859            
2860 0           my $lastCliff=$self->{lastCliff};
2861            
2862 0           foreach my $sensor (@cliffSensors) {
2863 0           my $signal=$sensorState->{$sensor . 'Signal'};
2864 0           $sensorState->{$sensor . 'SignalDev'}=$self->_getCliffDeviation($sensor,$signal);
2865 0 0         $sensorState->{$sensor . 'SignalDelta'}=defined($lastCliff->{$sensor})?$signal-$lastCliff->{$sensor}:0;
2866 0           $lastCliff->{$sensor}=$signal;
2867             }
2868            
2869 0           return 1;
2870             }
2871              
2872             sub _getRelativeMovement($$) {
2873 0     0     my ($actualAngle,$distance)=@_;
2874            
2875 0 0         if ($actualAngle==0) {
2876 0           return (0,$distance);
2877             } else {
2878 0           my $turningRadius=$distance/$actualAngle;
2879 0           my $dxf=$turningRadius*(1-cos($actualAngle));
2880 0           my $dyf=$turningRadius*sin($actualAngle);
2881            
2882 0           return ($dxf,$dyf);
2883             }
2884             }
2885              
2886             sub _getAbsoluteMovement($$$) {
2887 0     0     my ($direction,$dxf,$dyf)=@_;
2888            
2889 0           return ($dyf*sin(-$direction)+$dxf*cos(-$direction),
2890             $dyf*cos(-$direction)-$dxf*sin(-$direction));
2891              
2892             }
2893              
2894             sub _updateCliffStatistics($) {
2895 0     0     my $self=shift;
2896 0           my $sensorState=$self->{sensorState};
2897 0           my $cliffStatistics=$self->{calibration}{cliffStatistics};
2898            
2899 0 0         if (!defined($cliffStatistics->{$cliffSensors[0]})) {
2900 0           $cliffStatistics={};
2901 0           $self->{calibration}{cliffStatistics}=$cliffStatistics;
2902            
2903 0           for my $sensor (@cliffSensors) {
2904 0           $cliffStatistics->{$sensor}=[0,0,0];
2905             }
2906             }
2907            
2908 0           for my $sensor (@cliffSensors) {
2909 0           my $sensorStats=$cliffStatistics->{$sensor};
2910 0           my $signal=$sensorState->{$sensor.'Signal'};
2911            
2912 0           $sensorStats->[0]++;
2913 0           $sensorStats->[1]+=$signal;
2914 0           $sensorStats->[2]+=($signal*$signal);
2915             }
2916            
2917             #print Dump($self->{calibration});
2918             }
2919              
2920             sub _getCliffDeviation($$) {
2921 0     0     my $self=shift;
2922 0           my $sensor=shift;
2923 0           my $signal=shift;
2924            
2925 0           my $sensorStats=$self->{calibration}{cliffStatistics}{$sensor};
2926            
2927 0 0         my $mean=$sensorStats->[0]?$sensorStats->[1]/$sensorStats->[0]:0;
2928 0 0         my $stddev=$sensorStats->[0]?sqrt($sensorStats->[2]/$sensorStats->[0]-$mean*$mean):1;
2929            
2930 0 0         return $stddev==0?0:($signal-$mean)/$stddev;
2931             }
2932            
2933              
2934             ##########Serial Port Comm##############
2935              
2936             =head1 RAW COMMUNICATION
2937              
2938             The below methods can be used to for raw communication with the robot. Use of these within the same application as
2939             the rest of the methods in this module is strongly discouraged.
2940              
2941             =over 4
2942              
2943             =item $robot->initPort()
2944              
2945             Initializes the communications port. Returns true on success, false otherwise.
2946              
2947             =cut
2948              
2949             sub initPort($) {
2950 0     0 1   my $self=shift;
2951            
2952 0           my $port;
2953            
2954 0           my $retries=5;
2955              
2956 0   0       while(!$port && $retries>0) {
2957 0           $port=Device::SerialPort->new($self->{portFile});
2958 0 0         unless ($port) {
2959 0           sleep 1;
2960 0           $retries--;
2961             }
2962             }
2963            
2964 0 0         if ($retries>0) {
2965 0           $port->databits(8);
2966 0           $port->baudrate(57600);
2967 0           $port->parity("none");
2968 0           $port->stopbits(1);
2969 0           $port->read_char_time(0);
2970 0           $port->read_const_time(15);
2971              
2972 0           $self->{port}=$port;
2973            
2974 0           return 1;
2975             } else {
2976 0           return 0;
2977             }
2978             }
2979              
2980             =item $robot->writeBytes(@bytes)
2981              
2982             Writes bytes in @bytes to robot.
2983              
2984             =cut
2985              
2986             sub writeBytes(@) {
2987 0     0 1   my $self=shift;
2988            
2989 0 0         if ($self->{scriptMode}) {
2990 0           push @{$self->{scriptBytes}}, @_;
  0            
2991             } else {
2992 0 0         print "Writing bytes: " . join(", ",@_) . "\n" if ($DEBUG);
2993            
2994 0           my $data=pack('C*',@_);
2995            
2996 0           $self->_writeTelem('W',$data);
2997              
2998 0 0         $self->{port}->write($data) unless ($self->{replay});
2999             }
3000             }
3001              
3002             sub _handleReplayWriteBytes($@) {
3003 0     0     my $self=shift;
3004             }
3005              
3006             sub _handleReplayWrite($$$) {
3007 0     0     my $self=shift;
3008 0           my $time=shift;
3009 0           my $data=shift;
3010            
3011 0           my $command=ord($data);
3012            
3013 0 0         if ($command==137) {
3014 0           my ($command,$velocity,$radius)=unpack('Cn!2',$data);
3015 0 0         $self->{lastVelocity}=$velocity unless ($velocity==0);
3016             #print "LastVelocity: " . $self->{lastVelocity} . "\n";
3017             }
3018             }
3019              
3020             sub _handleReplayMessage($$$$) {
3021 0     0     my $self=shift;
3022 0           my $time=shift;
3023 0           my $data=shift;
3024            
3025             }
3026              
3027             sub _readReplayData($) {
3028 0     0     my $self=shift;
3029              
3030 0           my $replayDelta=$self->{replayDelta};
3031            
3032 0           my ($time,$type,$data);
3033            
3034 0           while($type ne 'R') {
3035 0           ($time,$type,$data)=$self->_readTelem();
3036 0 0         die "End of Telemetry Data" if ($type eq 'E');
3037 0 0         if ($type eq 'W') {
    0          
3038 0           $self->_handleReplayWrite($time,$data);
3039             } elsif ($type eq 'M') {
3040 0           $self->_handleReplayMessage($time,$data);
3041             }
3042             }
3043            
3044 0           my $now=time();
3045            
3046 0 0         if ($now<($time+$replayDelta)) {
3047 0           sleep (($time+$replayDelta)-$now);
3048             }
3049            
3050 0           return (length($data),$data);
3051             }
3052              
3053             =item $robot->readData($length)
3054              
3055             Reads $length bytes from robot. Blocks until bytes are read. Returns bytes read as string.
3056              
3057             Data returned by this method will probably need to be passed to unpack. For example, to get an array of 4 bytes from robot use:
3058              
3059             unpack('C*',$robot->readData(4))
3060              
3061             =back
3062              
3063             =cut
3064              
3065             sub readData($$) {
3066 0     0 1   my $self=shift;
3067 0           my $length=shift;
3068 0           my $data='';
3069            
3070 0 0         if (length($self->{readBuffer})>=$length) {
3071 0           return substr($self->{readBuffer},0,$length,'');
3072             }
3073            
3074 0           $data=$self->{readBuffer};
3075 0           $self->{readBuffer}='';
3076 0           $length-=length($data);
3077              
3078 0           while($length>0) {
3079 0 0         my ($got,$saw)=$self->{replay}?$self->_readReplayData():$self->{port}->read($length);
3080 0           $self->_writeTelem('R',$saw);
3081              
3082 0 0         if ($got>=$length) {
3083 0           $data.=substr($saw,0,$length,'');
3084 0           $length=0;
3085 0           $self->{readBuffer}.=$saw;
3086             } else {
3087 0           $length-=$got;
3088 0           $data.=$saw;
3089             }
3090             #dsleep 0.01;
3091             }
3092              
3093 0           return $data;
3094              
3095             }
3096              
3097             ###########Data#############
3098             $sensorSpecs=[[26,'C12n!n!Cnn!Cnn'],
3099             [16,'C10'],[6,'CCn!n!'],[10,'Cnn!Cnn'],
3100             [14,'n5CnC'],[12,'C4n!n!n!n!'],
3101             [52,'C12n!n!Cnn!Cn7CnC5n!n!n!n!'],
3102             [1,'C'],[1,'C'],[1,'C'],[1,'C'],[1,'C'],
3103             [1,'C'],[1,'C'],[1,'C'],[1,'C'],[1,'C'],
3104             [1,'C'],[1,'C'],
3105             [2,'n!'],[2,'n!'],
3106             [1,'C'],[2,'n'],[2,'n!'],[1,'C'],
3107             [2,'n'],[2,'n'],[2,'n'],[2,'n'],
3108             [2,'n'],[2,'n'],[2,'n'],
3109             [1,'C'],[2,'n'],
3110             [1,'C'],[1,'C'],[1,'C'],[1,'C'],[1,'C'],
3111             [2,'n!'],[2,'n!'],[2,'n!'],[2,'n!']
3112             ];
3113              
3114             $sensorGroups=[[7,26],[7,16],[17,20],[21,26],[27,34],[35,42],[7,42]];
3115              
3116             @sensorFields=('','','','','','','','','wall','cliffLeft',
3117             'cliffFrontLeft','cliffFrontRight','cliffRight','virtualWall','','undef1','undef2','irByte','','distance',
3118             'angle','chargingState','voltage','current','batteryTemp','batteryCharge','batteryCapacity','wallSignal','cliffLeftSignal','cliffFrontLeftSignal',
3119             'cliffFrontRightSignal','cliffRightSignal','','analogIn','','oiMode','songNumber','songPlaying','numPackets','requestedVelocity',
3120             'requestedRadius','requestedRightVelocity','requestedLeftVelocity');
3121              
3122             $calibrationDefaults={angleCorrection=>[0.0240735069240597, 0.0240735069240597, 0.0240735069240597,
3123             0.0160695276398455, 0.0212269773891202, 0.0162776821429523,
3124             0.0165346981767884, 0.0185893056425432, 0.0181071622685291,
3125             0.0178499582590329, 0.0179008128409675],
3126             cliffStatistics=>{cliffRight=>[5811,4813494,4025842820],
3127             cliffFrontRight=>[5811,3588708,2232898560],
3128             cliffFrontLeft=>[5811,2352886,960201844],
3129             cliffLeft=>[5811,3137619,1706701033]}
3130             };
3131              
3132             $notes={
3133             'c'=>0,
3134             'c#'=>1,
3135             'd'=>2,
3136             'd#'=>3,
3137             'e'=>4,
3138             'f'=>5,
3139             'f#'=>6,
3140             'g'=>7,
3141             'g#'=>8,
3142             'a' => 9,
3143             'a#' => 10,
3144             'b' =>11
3145             };
3146              
3147             $keys={'Cb'=>-7,
3148             'Gb'=>-6,'Db'=>-5,'Ab'=>-4,'Eb'=>-3,'Bb'=>-2,
3149             'F'=>-1,'C'=>0,'G'=>1,'D'=>2,'A'=>3,'E'=>4,
3150             'B'=>5,'F#'=>6,'C#'=>7};
3151              
3152             @sharps=('f','c','g','d','a','e','b');
3153              
3154             $sensorLocations={cliffLeft=>[$ROBOT_WIDTH/2-10,deg2rad(150)],
3155             cliffFrontLeft=>[$ROBOT_WIDTH/2-10,deg2rad(105)],
3156             cliffFrontRight=>[$ROBOT_WIDTH/2-10,deg2rad(75)],
3157             cliffRight=>[$ROBOT_WIDTH/2-10,deg2rad(30)],
3158             bumpLeft=>[$ROBOT_WIDTH/2-10,deg2rad(135)],
3159             bumpCenter=>[$ROBOT_WIDTH/2-10,deg2rad(90)],
3160             bumpRight=>[$ROBOT_WIDTH/2-10,deg2rad(45)],
3161             caster=>[$ROBOT_WIDTH/2-10,deg2rad(90)],
3162             irSensor=>[$ROBOT_WIDTH/2-10,deg2rad(90)],
3163             wheelLeft=>[$ROBOT_WIDTH/2-10,deg2rad(180)],
3164             wheelRight=>[$WHEEL_WIDTH/2,0]
3165             };
3166              
3167             @cliffSensors=('cliffLeft','cliffFrontLeft','cliffFrontRight','cliffRight');
3168              
3169              
3170             =head1 EXAMPLES
3171              
3172             =head2 Wall bouncing
3173              
3174             Moves robot foward until a bump sensor is triggered, then backs up and
3175             turns a random angle before continuing forward. Runs unil user hits the 'c' (close) or 's' (stop) keys.
3176              
3177             #!/usr/bin/perl
3178            
3179             use Robotics::IRobot;
3180             use Term::ReadKey;
3181             use Math::Trig; #for deg2rad
3182            
3183             print "Connecting...\n";
3184             my $iRobot=Robotics::IRobot->new('/dev/rfcomm0');
3185            
3186             #init robot and turn on hardware safeties
3187             $iRobot->init();
3188             print "Connected\n";
3189             $iRobot->startSafeMode();
3190            
3191             ReadMode 4,STDIN;
3192            
3193             my $sensorState=$iRobot->{sensorState};
3194            
3195             #check for a key press and display sensor output with every sensor read
3196             $iRobot->addSensorListener(300,sub {
3197             my $c=ReadKey -1, STDIN;
3198            
3199             if ($c) {
3200             if ($c eq 's') {
3201             #stop robot on s
3202             $iRobot->stop();
3203             } elsif ($c eq 'c') {
3204             #stop loop and exit program on c
3205             $iRobot->exitSensorLoop();
3206             }
3207             }
3208            
3209             #display sensor output
3210             print $iRobot->getCondensedString("\n") . "\n";
3211            
3212             },0);
3213            
3214             #action=0 moving foward
3215             #action=1 turning left
3216             #action=2 turning right
3217             #used to track whether we are responding to a bump event
3218             my $action=0;
3219            
3220             #trigger event when we are not responding to a bump event and one of
3221             #the bump sensors activates.
3222             $iRobot->addSensorEvent(200,sub {!$action && ($sensorState->{bumpRight} || $sensorState->{bumpLeft})},
3223             sub {
3224             #figure out which way to turn
3225             $action=$sensorState->{bumpRight}?1:2;
3226            
3227             #back up
3228             $iRobot->reverse(200);
3229            
3230             #after .5s
3231             $iRobot->waitTime(200,.5,sub {
3232            
3233             #rotate away from bump
3234             if ($action==1) {
3235             $iRobot->rotateLeft(200);
3236             } else {
3237             $iRobot->rotateRight(200);
3238             }
3239            
3240             #wait for random angle
3241             $iRobot->waitAngle(200,deg2rad(10 + 120*rand()),
3242             sub {
3243            
3244             #then continue forward
3245             $iRobot->forward(300);
3246             $action=0;
3247             }
3248             );
3249             });
3250             },0,0);
3251            
3252             #begin moving forward
3253             $iRobot->forward(300);
3254            
3255             #start sensor loop and event processing
3256             $iRobot->runSensorLoop();
3257            
3258             #close connection
3259             $iRobot->close();
3260            
3261             ReadMode 0, STDIN;
3262              
3263             =head2 Scripting
3264              
3265             Uses robot's on board scripting to continuously move 500mm back and forth. Robot will continue to do this until power button is pressed.
3266              
3267             See OI Documentation for more details.
3268              
3269             #!/usr/bin/perl
3270              
3271             use Robotics::IRobot;
3272              
3273             my $robot=Robotics::IRobot->new();
3274              
3275             $robot->init();
3276             $robot->startSafeMode();
3277              
3278             $robot->startScript();
3279              
3280             $robot->forward(300);
3281             $robot->waitDistance(500);
3282             $robot->rotateLeft(200);
3283             $robot->waitAngle(180);
3284             $robot->forward(300);
3285             $robot->waitDistance(500);
3286             $robot->rotateLeft(200);
3287             $robot->waitAngle(180);
3288             $robot->repeatScript();
3289              
3290             $robot->endScript();
3291              
3292             $robot->runScript();
3293              
3294              
3295             =head1 AUTHOR
3296              
3297             Michael Ratliff, C<< <$_='email@michaelratlixx.com'; s/x/f/g; print;> >>
3298              
3299             =head1 BUGS
3300              
3301             Please report any bugs or feature requests to C, or through
3302             the web interface at L. I will be notified, and then you'll
3303             automatically be notified of progress on your bug as I make changes.
3304              
3305             I only have an iRobot Create to use for testing. So, any Roomba bugs that are unable to be reproduced on a Create may
3306             go unresolved.
3307              
3308             =head1 SUPPORT
3309              
3310             You can find documentation for this module with the perldoc command.
3311              
3312             perldoc Robotics::IRobot
3313              
3314              
3315             You can also look for information at:
3316              
3317             =over 4
3318              
3319             =item * RT: CPAN's request tracker
3320              
3321             L
3322              
3323             =item * AnnoCPAN: Annotated CPAN documentation
3324              
3325             L
3326              
3327             =item * CPAN Ratings
3328              
3329             L
3330              
3331             =item * Search CPAN
3332              
3333             L
3334              
3335             =back
3336              
3337              
3338             =head1 LICENSE AND COPYRIGHT
3339              
3340             Copyright 2011 Michael Ratliff.
3341              
3342             This program is free software; you can redistribute it and/or modify it
3343             under the terms of either: the GNU General Public License as published
3344             by the Free Software Foundation; or the Artistic License.
3345              
3346             See section NOTICE at the top of this document.
3347              
3348             See http://dev.perl.org/licenses/ for more information.
3349              
3350              
3351             =cut
3352              
3353             1; # End of Robotics::IRobot
3354