-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnoinit_easygopigo3.py
executable file
·1320 lines (956 loc) · 54.8 KB
/
noinit_easygopigo3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# FILE: noinit_easygopigo3.py
# - Differs from official easygopigo3.py
# - Does not reset speed to DEFAULT_SPEED when initialized if noinit=True
from __future__ import print_function
from __future__ import division
# from builtins import input
import sys
# import tty
# import select
import time
import os
import math
import json
import easysensors
from I2C_mutex import Mutex
from math import pi
__version__ = "1.3.2.1"
try:
from di_sensors import easy_line_follower, easy_distance_sensor, easy_light_color_sensor, easy_inertial_measurement_unit
di_sensors_available = True
except ImportError as err:
di_sensors_available = False
print("Importing di_sensors error: {}".format(err))
except Exception as err:
di_sensors_available = False
print("Importing di_sensors error: {}".format(err))
mutex = Mutex(debug=False)
hardware_connected = True
try:
import gopigo3
except ImportError:
hardware_connected = False
print("Cannot import gopigo3 library")
except Exception as e:
hardware_connected = False
print("Unknown issue while importing gopigo3")
print(e)
# try:
# from line_follower import line_sensor
# from line_follower import scratch_line
# # is_line_follower_accessible not really used, just in case
# is_line_follower_accessible = True
# except:
# try:
# sys.path.insert(0, '/home/pi/GoPiGo/Software/Python/line_follower')
# import line_sensor
# import scratch_line
# is_line_follower_accessible = True
# except:
# is_line_follower_accessible = False
##########################
def debug(in_str):
if False:
print(in_str)
#####################################################################
#
# EASYGOPIGO3
#
#####################################################################
class EasyGoPiGo3(gopigo3.GoPiGo3):
"""
This class is used for controlling a `GoPiGo3`_ robot.
With this class you can do the following things with your `GoPiGo3`_:
* Connect to the robot when another program has already initialized the hardware
* Drive your robot in any number of directions.
* Have precise control over the direction of the robot.
* Set the speed of the robot.
* Turn *on* or *off* the blinker LEDs.
* Control the `GoPiGo3`_' Dex's *eyes*, *color* and so on ...
.. needs revisiting
.. warning::
Without a battery pack connected to the `GoPiGo3`_, the robot won't move.
"""
def __init__(self, config_file_path="/home/pi/Dexter/gpg3_config.json", use_mutex=False, noinit=False):
"""
This constructor sets the variables to the following values:
:param str config_file_path = "/home/pi/Dexter/gpg3_config.json": Path to JSON config file that stores the wheel diameter and wheel base width for the GoPiGo3.
:param boolean use_mutex = False: When using multiple threads/processes that access the same resource/device, mutex has to be enabled.
:var int speed = 300: The speed of the motors should go between **0-1000** DPS.
:var tuple(int,int,int) left_eye_color = (0,255,255): Set Dex's left eye color to **turqoise**.
:var tuple(int,int,int) right_eye_color = (0,255,255): Set Dex's right eye color to **turqoise**.
:var int DEFAULT_SPEED = 300: Starting speed value: not too fast, not too slow.
:raises IOError: When the GoPiGo3 is not detected. It also debugs a message in the terminal.
:raises gopigo3.FirmwareVersionError: If the GoPiGo3 firmware needs to be updated. It also debugs a message in the terminal.
:raises Exception: For any other kind of exceptions.
"""
try:
if sys.version_info[0] < 3:
super(self.__class__, self).__init__(config_file_path=config_file_path)
else:
super().__init__(config_file_path=config_file_path)
except IOError as e:
print("FATAL ERROR:\nGoPiGo3 is not detected.")
raise e
except gopigo3.FirmwareVersionError as e:
print("FATAL ERROR:\nTo update the firmware on Raspbian for Robots you need to run DI Software Update and choose Update Robot")
raise e
except Exception as e:
raise e
self.sensor_1 = None
self.sensor_2 = None
self.DEFAULT_SPEED = 300
self.NO_LIMIT_SPEED = 1000
if noinit == False: self.set_speed(self.DEFAULT_SPEED)
self.left_eye_color = (0, 255, 255)
self.right_eye_color = (0, 255, 255)
self.use_mutex = use_mutex
def volt(self):
"""
This method returns the battery voltage of the `GoPiGo3`_.
:return: The battery voltage of the `GoPiGo3`_.
:rtype: float
"""
voltage = self.get_voltage_battery()
return voltage
def set_speed(self, in_speed):
"""
This method sets the speed of the `GoPiGo3`_ specified by ``in_speed`` argument.
The speed is measured in *DPS = degrees per second* of the robot's wheel(s).
:param int in_speed: The speed at which the robot is set to run - speed between **0-1000** DPS.
.. warning::
**0-1000** DPS are the *preferred* speeds for the `GoPiGo3`_ robot.
The speed variable can be basically set to any positive value, but factors like *voltage*, *load*, *battery amp rating*, etc, will determine the effective speed of the motors.
Experiments should be run by every user, as each case is unique.
"""
try:
self.speed = int(in_speed)
except:
self.speed = self.DEFAULT_SPEED
self.set_motor_limits(self.MOTOR_LEFT + self.MOTOR_RIGHT,
dps=self.speed)
def get_speed(self):
"""
Use this method for getting the speed of your `GoPiGo3`_.
:return: The speed of the robot measured between **0-1000** DPS.
:rtype: int
"""
return int(self.speed)
def reset_speed(self):
"""
This method resets the speed to its original value.
"""
self.set_speed(self.DEFAULT_SPEED)
def stop(self):
"""
This method stops the `GoPiGo3`_ from moving.
It brings the `GoPiGo3`_ to a full stop.
.. note::
This method is used in conjuction with the following methods:
* :py:meth:`~easygopigo3.EasyGoPiGo3.backward`
* :py:meth:`~easygopigo3.EasyGoPiGo3.right`
* :py:meth:`~easygopigo3.EasyGoPiGo3.left`
* :py:meth:`~easygopigo3.EasyGoPiGo3.forward`
"""
self.set_motor_dps(self.MOTOR_LEFT + self.MOTOR_RIGHT, 0)
time.sleep(0.1)
self.set_motor_power(self.MOTOR_LEFT + self.MOTOR_RIGHT, self.MOTOR_FLOAT)
time.sleep(0.1)
def forward(self):
"""
Move the `GoPiGo3`_ forward.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
"""
self.set_motor_dps(self.MOTOR_LEFT + self.MOTOR_RIGHT,
self.NO_LIMIT_SPEED)
def drive_cm(self, dist, blocking=True):
"""
Move the `GoPiGo3`_ forward / backward for ``dist`` amount of centimeters.
| For moving the `GoPiGo3`_ robot forward, the ``dist`` parameter has to be *positive*.
| For moving the `GoPiGo3`_ robot backward, the ``dist`` parameter has to be *negative*.
:param float dist: The distance in ``cm`` the `GoPiGo3`_ has to move.
:param boolean blocking = True: Set it as a blocking or non-blocking method.
``blocking`` parameter can take the following values:
* ``True`` so that the method will wait for the `GoPiGo3`_ robot to finish moving.
* ``False`` so that the method will exit immediately while the `GoPiGo3`_ robot will continue moving.
"""
# dist is in cm
# if dist is negative, this becomes a backward move
dist_mm = dist * 10
# the number of degrees each wheel needs to turn
WheelTurnDegrees = ((dist_mm / self.WHEEL_CIRCUMFERENCE) * 360)
# get the starting position of each motor
StartPositionLeft = self.get_motor_encoder(self.MOTOR_LEFT)
StartPositionRight = self.get_motor_encoder(self.MOTOR_RIGHT)
self.set_motor_position(self.MOTOR_LEFT,
(StartPositionLeft + WheelTurnDegrees))
self.set_motor_position(self.MOTOR_RIGHT,
(StartPositionRight + WheelTurnDegrees))
if blocking:
while self.target_reached(
StartPositionLeft + WheelTurnDegrees,
StartPositionRight + WheelTurnDegrees) is False:
time.sleep(0.1)
def drive_inches(self, dist, blocking=True):
"""
Move the `GoPiGo3`_ forward / backward for ``dist`` amount of inches.
| For moving the `GoPiGo3`_ robot forward, the ``dist`` parameter has to be *positive*.
| For moving the `GoPiGo3`_ robot backward, the ``dist`` parameter has to be *negative*.
:param float dist: The distance in ``inches`` the `GoPiGo3`_ has to move.
:param boolean blocking = True: Set it as a blocking or non-blocking method.
``blocking`` parameter can take the following values:
* ``True`` so that the method will wait for the `GoPiGo3`_ robot to finish moving.
* ``False`` so that the method will exit immediately while the `GoPiGo3`_ robot will continue moving.
"""
self.drive_cm(dist * 2.54, blocking)
def drive_degrees(self, degrees, blocking=True):
"""
Move the `GoPiGo3`_ forward / backward for ``degrees / 360`` wheel rotations.
| For moving the `GoPiGo3`_ robot forward, the ``degrees`` parameter has to be *positive*.
| For moving the `GoPiGo3`_ robot backward, the ``degrees`` parameter has to be *negative*.
:param float degrees: Distance based on how many wheel rotations are made. Calculated by ``degrees / 360``.
:param boolean blocking = True: Set it as a blocking or non-blocking method.
``blocking`` parameter can take the following values:
* ``True`` so that the method will wait for the `GoPiGo3`_ robot to finish rotating.
* ``False`` so that the method will exit immediately while the `GoPiGo3`_ robot will continue rotating.
For instance, the following function call is going to drive the `GoPiGo3`_ robot forward for *310 / 360* wheel rotations, which equates to aproximately *86%*
of the `GoPiGo3`_'s wheel circumference.
.. code-block:: python
gpg3_obj.drive_degrees(310)
On the other hand, changing the polarity of the argument we're passing, is going to make the `GoPiGo3`_ robot move backward.
.. code-block:: python
gpg3_obj.drive_degrees(-30.5)
This line of code makes the `GoPiGo3`_ robot go backward for *30.5 / 360* rotations, which is roughly *8.5%* of the `GoPiGo3`_'s wheel circumference.
"""
# these degrees are meant to be wheel rotations.
# 360 degrees would be a full wheel rotation
# not the same as turn_degrees() which is a robot rotation
# degrees is in degrees, not radians
# if degrees is negative, this becomes a backward move
# get the starting position of each motor
StartPositionLeft = self.get_motor_encoder(self.MOTOR_LEFT)
StartPositionRight = self.get_motor_encoder(self.MOTOR_RIGHT)
self.set_motor_position(self.MOTOR_LEFT,
(StartPositionLeft + degrees))
self.set_motor_position(self.MOTOR_RIGHT,
(StartPositionRight + degrees))
if blocking:
while self.target_reached(
StartPositionLeft + degrees,
StartPositionRight + degrees) is False:
time.sleep(0.1)
return
def backward(self):
"""
Move the `GoPiGo3`_ backward.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to ``300`` - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
"""
self.set_motor_dps(self.MOTOR_LEFT + self.MOTOR_RIGHT,
self.NO_LIMIT_SPEED * -1)
def right(self):
"""
Move the `GoPiGo3`_ to the right.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
.. important::
| The robot will activate only the left motor, whilst the right motor will be completely stopped.
| This causes the robot to rotate in very short circles.
"""
self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED)
self.set_motor_dps(self.MOTOR_RIGHT, 0)
def spin_right(self):
"""
Rotate the `GoPiGo3` towards the right while staying on the same spot.
This differs from the :py:meth:`~easygopigo3.EasyGoPiGo3.right` method as both wheels will be rotating but in different directions.
This causes the robot to spin in place, as if doing a pirouette.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
.. important::
You can achieve the same effect by calling ``steer(100, -100)`` (method :py:meth:`~easygopigo3.EasyGoPiGo3.steer`).
"""
self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED)
self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED * -1)
def left(self):
"""
Move the `GoPiGo3`_ to the left.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
.. important::
| The robot will activate only the right motor, whilst the left motor will be completely stopped.
| This causes the robot to rotate in very short circles.
"""
self.set_motor_dps(self.MOTOR_LEFT, 0)
self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED)
def spin_left(self):
"""
Rotate the `GoPiGo3` towards the left while staying on the same spot.
This differs from the :py:meth:`~easygopigo3.EasyGoPiGo3.left` method as both wheels will be rotating but in different directions.
This causes the robot to spin in place, as if doing a pirouette.
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
.. important::
You can achieve the same effect by calling ``steer(-100, 100)`` (method :py:meth:`~easygopigo3.EasyGoPiGo3.steer`).
"""
self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED * -1)
self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED )
def steer(self, left_percent, right_percent):
"""
Control each motor in order to get a variety of turning movements.
Each motor is assigned a percentage of the current speed value.
While there is no limit on the values of ``left_percent`` and ``right_percent`` parameters,
they are expected to be between **-100** and **100**.
:param int left_percent: Percentage of current speed value that gets applied to left motor. The range is between **-100** (for backward rotation) and **100** (for forward rotation).
:param int right_percent: Percentage of current speed value that gets applied to right motor. The range is between **-100** (for backward rotation) and **100** (for forward rotation).
For setting the motor speed, use :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed`.
Default ``speed`` is set to **300** - see :py:meth:`~easygopigo3.EasyGoPiGo3.__init__`.
.. important::
Setting both ``left_percent`` and ``right_percent`` parameters to **100** will result in the same behavior as the :py:meth:`~easygopigo3.EasyGoPiGo3.forward` method.
The other behavior for :py:meth:`~easygopigo3.EasyGoPiGo3.backward` method will be experienced if both parameters are set to **-100**.
Setting both ``left_percent`` and ``right_percent`` to **0** will stop the GoPiGo from moving.
"""
self.set_motor_dps(self.MOTOR_LEFT, self.get_speed() * left_percent / 100)
self.set_motor_dps(self.MOTOR_RIGHT, self.get_speed() * right_percent / 100)
def orbit(self, degrees, radius_cm=0, blocking=True):
"""
Control the GoPiGo so it will orbit around an object.
:param int degrees: Degrees to steer. **360** for full rotation. Negative for left turn.
:param int radius_cm: Radius in `cm` of the circle to drive. Default is **0** (turn in place).
:param boolean blocking = True: Set it as a blocking or non-blocking method.
.. important::
Note that while in non-blocking mode the speed cannot be changed before the end of the orbit as it would negate all orbit calculations.
After a non-blocking call, :py:meth:`~easygopigo3.EasyGoPiGo3.set_speed` has to be called before any other movement.
"""
speed = self.get_speed()
radius = radius_cm * 10
# the total distance to drive in mm
drive_distance = math.pi * abs(radius) * abs(degrees) / 180 # / 180 is shorter than radius * 2 / 360
# the distance in mm to add to one motor and subtract from the other
drive_difference = ((self.WHEEL_BASE_CIRCUMFERENCE * degrees) / 360)
# the number of degrees each wheel needs to turn on average to get the necessary distance
distance_degrees = ((drive_distance / self.WHEEL_CIRCUMFERENCE) * 360)
# the difference between motor travel in degrees
difference_degrees = ((drive_difference / self.WHEEL_CIRCUMFERENCE) * 360)
# the distance each wheel needs to turn
left_target = (distance_degrees + difference_degrees)
right_target = (distance_degrees - difference_degrees)
# if it's a left turn
if degrees < 0:
MOTOR_FAST = self.MOTOR_RIGHT
MOTOR_SLOW = self.MOTOR_LEFT
fast_target = right_target
slow_target = left_target
else:
MOTOR_FAST = self.MOTOR_LEFT
MOTOR_SLOW = self.MOTOR_RIGHT
fast_target = left_target
slow_target = right_target
# determine driving direction from the speed
direction = 1
speed_with_direction = speed
if speed < 0:
direction = -1
speed_with_direction = -speed
# calculate the motor speed for each motor
fast_speed = speed_with_direction
slow_speed = abs((speed_with_direction * slow_target) / fast_target)
# set the motor speeds
self.set_motor_limits(MOTOR_FAST, dps = fast_speed)
self.set_motor_limits(MOTOR_SLOW, dps = slow_speed)
# get the starting position of each motor
StartPositionLeft = self.get_motor_encoder(self.MOTOR_LEFT)
StartPositionRight = self.get_motor_encoder(self.MOTOR_RIGHT)
# Set each motor target position
self.set_motor_position(self.MOTOR_LEFT, (StartPositionLeft + (left_target * direction)))
self.set_motor_position(self.MOTOR_RIGHT, (StartPositionRight + (right_target * direction)))
if blocking:
while self.target_reached(
StartPositionLeft + (left_target * direction),
StartPositionRight + (right_target * direction)) is False:
time.sleep(0.1)
# reset to original speed once done
# if non-blocking, then the user is responsible in resetting the speed
self.set_speed(speed)
return
def target_reached(self, left_target_degrees, right_target_degrees):
"""
Checks if (*wheels have rotated for a given number of degrees*):
* The left *wheel* has rotated for ``left_target_degrees`` degrees.
* The right *wheel* has rotated for ``right_target_degrees`` degrees.
If both conditions are met, it returns ``True``, otherwise it's ``False``.
:param int left_target_degrees: Target degrees for the *left* wheel.
:param int right_target_degrees: Target degrees for the *right* wheel.
:return: Whether both wheels have reached their target.
:rtype: boolean.
For checking if the `GoPiGo3`_ robot has moved **forward** for ``360 / 360`` wheel rotations, we'd use the following code snippet.
.. code-block:: python
# both variables are measured in degrees
left_motor_target = 360
right_motor_target = 360
# reset the encoders
gpg3_obj.reset_encoders()
# and make the robot move forward
gpg3_obj.forward()
while gpg3_obj.target_reached(left_motor_target, right_motor_target):
# give the robot some time to move
sleep(0.05)
# now lets stop the robot
# otherwise it would keep on going
gpg3_obj.stop()
On the other hand, for moving the `GoPiGo3`_ robot to the **right** for ``187 / 360`` wheel rotations of the left wheel, we'd use the following code snippet.
.. code-block:: python
# both variables are measured in degrees
left_motor_target = 187
right_motor_target = 0
# reset the encoders
gpg3_obj.reset_encoders()
# and make the robot move to the right
gpg3_obj.right()
while gpg3_obj.target_reached(left_motor_target, right_motor_target):
# give the robot some time to move
sleep(0.05)
# now lets stop the robot
# otherwise it would keep on going
gpg3_obj.stop()
.. note::
You *can* use this method in conjunction with the following methods:
* :py:meth:`~easygopigo3.EasyGoPiGo3.drive_cm`
* :py:meth:`~easygopigo3.EasyGoPiGo3.drive_inches`
* :py:meth:`~easygopigo3.EasyGoPiGo3.drive_degrees`
when they are used in *non-blocking* mode.
And almost *everytime* with the following ones:
* :py:meth:`~easygopigo3.EasyGoPiGo3.backward`
* :py:meth:`~easygopigo3.EasyGoPiGo3.right`
* :py:meth:`~easygopigo3.EasyGoPiGo3.left`
* :py:meth:`~easygopigo3.EasyGoPiGo3.forward`
"""
tolerance = 5
min_left_target = left_target_degrees - tolerance
max_left_target = left_target_degrees + tolerance
min_right_target = right_target_degrees - tolerance
max_right_target = right_target_degrees + tolerance
current_left_position = self.get_motor_encoder(self.MOTOR_LEFT)
current_right_position = self.get_motor_encoder(self.MOTOR_RIGHT)
if current_left_position > min_left_target and \
current_left_position < max_left_target and \
current_right_position > min_right_target and \
current_right_position < max_right_target:
return True
else:
return False
def reset_encoders(self, blocking=True):
"""
Resets both the encoders back to **0**.
:param boolean blocking = True: Set it as a blocking or non-blocking method.
``blocking`` parameter can take the following values:
* ``True`` so that the method will wait for the `GoPiGo3`_ motors to finish resetting.
* ``False`` so that the method will exit immediately while the `GoPiGo3`_ motors will continue to reset. Sending another motor command to the motors while the reset is under way might lead to confusing behavior.
"""
self.set_motor_power(self.MOTOR_LEFT + self.MOTOR_RIGHT, 0)
left_target = self.get_motor_encoder(self.MOTOR_LEFT)
right_target = self.get_motor_encoder(self.MOTOR_RIGHT)
self.offset_motor_encoder(self.MOTOR_LEFT, left_target)
self.offset_motor_encoder(self.MOTOR_RIGHT, right_target)
motor_left_previous = None
motor_right_previous = None
if blocking:
# Note to self. using target_reached() here didn't work.
# So instead let's keep waiting till the motors stop moving
while motor_left_previous != self.MOTOR_LEFT or motor_right_previous != self.MOTOR_RIGHT:
motor_left_previous = self.MOTOR_LEFT
motor_right_previous = self.MOTOR_RIGHT
time.sleep(0.025)
def read_encoders(self):
"""
Reads the encoders' position in degrees. 360 degrees represent 1 full rotation (or 360 degrees) of a wheel.
:returns: A tuple containing the position in degrees of each encoder. The 1st element is for the left motor and the 2nd is for the right motor.
:rtype: tuple(int,int)
"""
left_encoder = self.get_motor_encoder(self.MOTOR_LEFT)
right_encoder = self.get_motor_encoder(self.MOTOR_RIGHT)
encoders = (left_encoder, right_encoder)
return encoders
def read_encoders_average(self, units="cm"):
"""
Reads the encoders' position in degrees. 360 degrees represent 1 full rotation (or 360 degrees) of a wheel.
:param string units: By default it's "cm", but it could also be "in" for inches. Anything else will return raw encoder average.
:returns: The average of the two wheel encoder values.
:rtype: int
"""
left, right = self.read_encoders()
average = (left+right)/2
if units=="cm":
average = ((average / 360 ) * self.WHEEL_CIRCUMFERENCE) / 10
elif units=="in" or units=="inches" or units=="inch":
average = ((average / 360 ) * self.WHEEL_CIRCUMFERENCE) / (10 * 2.54)
else:
pass
# do no conversion
return average
def turn_degrees(self, degrees, blocking=True):
"""
Makes the `GoPiGo3`_ robot turn at a specific angle while staying in the same spot.
:param float degrees: The angle in degress at which the `GoPiGo3`_ has to turn. For rotating the robot to the left, ``degrees`` has to negative, and make it turn to the right, ``degrees`` has to be positive.
:param boolean blocking = True: Set it as a blocking or non-blocking method.
``blocking`` parameter can take the following values:
* ``True`` so that the method will wait for the `GoPiGo3`_ robot to finish moving.
* ``False`` so that the method will exit immediately while the `GoPiGo3`_ robot will continue moving.
In order to better understand what does this method do, let's take a look at the following graphical representation.
.. image:: ../images/gpg3_robot.svg
In the image, we have multiple identifiers:
* The "*heading*": it represents the robot's heading. By default, "rotating" the robot by 0 degrees is going to make the robot stay in place.
* The "*wheel circle circumference*": this is the circle that's described by the 2 motors moving in opposite direction.
* The "*GoPiGo3*": the robot we're playing with. The robot's body isn't draw in this representation as it's not the main focus here.
* The "*wheels*": these are just the `GoPiGo3`_'s wheels - selfexplanatory.
The effect of this class method is that the `GoPiGo3`_ will rotate in the same spot (depending on ``degrees`` parameter), while the wheels will be describing a perfect circle.
So, in order to calculate how much the motors have to spin, we divide the *angle* (at which we want to rotate the robot) by 360 degrees and we get a float number between 0 and 1 (think of it as a percentage).
We then multiply this value with the *wheel circle circumference* (which is the circumference of the circle the robot's wheels describe when rotating in the same place).
At the end we get the distance each wheel has to travel in order to rotate the robot by ``degrees`` degrees.
"""
# this is the method to use if you want the robot to turn 90 degrees
# or any other amount. This method is based on robot orientation
# and not wheel rotation
# the distance in mm that each wheel needs to travel
WheelTravelDistance = ((self.WHEEL_BASE_CIRCUMFERENCE * degrees) / 360)
# the number of degrees each wheel needs to turn
WheelTurnDegrees = ((WheelTravelDistance / self.WHEEL_CIRCUMFERENCE) *
360)
# get the starting position of each motor
StartPositionLeft = self.get_motor_encoder(self.MOTOR_LEFT)
StartPositionRight = self.get_motor_encoder(self.MOTOR_RIGHT)
# Set each motor target
self.set_motor_position(self.MOTOR_LEFT,
(StartPositionLeft + WheelTurnDegrees))
self.set_motor_position(self.MOTOR_RIGHT,
(StartPositionRight - WheelTurnDegrees))
if blocking:
while self.target_reached(
StartPositionLeft + WheelTurnDegrees,
StartPositionRight - WheelTurnDegrees) is False:
time.sleep(0.1)
def blinker_on(self, id):
"""
Turns *ON* one of the 2 red blinkers that `GoPiGo3`_ has.
:param int|str id: **0** / **1** for the right / left led or string literals can be used : ``"right"`` and ``"left"``.
"""
if id == 1 or id == "left":
self.set_led(self.LED_LEFT_BLINKER, 255)
if id == 0 or id == "right":
self.set_led(self.LED_RIGHT_BLINKER, 255)
def blinker_off(self, id):
"""
Turns *OFF* one of the 2 red blinkers that `GoPiGo3`_ has.
:param int|str id: **0** / **1** for the right / left led or string literals can be used : ``"right"`` and ``"left"``.
"""
if id == 1 or id == "left":
self.set_led(self.LED_LEFT_BLINKER, 0)
if id == 0 or id == "right":
self.set_led(self.LED_RIGHT_BLINKER, 0)
def led_on(self, id):
"""
Turns *ON* one of the 2 red blinkers that `GoPiGo3`_ has.
The same as :py:meth:`~easygopigo3.EasyGoPiGo3.blinker_on`.
:param int|str id: **0** / **1** for the right / left led or string literals can be used : ``"right"``` and ``"left"``.
"""
self.blinker_on(id)
def led_off(self, id):
"""
Turns *OFF* one of the 2 red blinkers that `GoPiGo3`_ has.
The same as :py:meth:`~easygopigo3.EasyGoPiGo3.blinker_off`.
:param int|str id: **0** / **1** for the right / left led or string literals can be used : ``"right"`` and ``"left"``.
"""
self.blinker_off(id)
def set_left_eye_color(self, color):
"""
Sets the LED color for Dexter mascot's left eye.
:param tuple(int,int,int) color: 8-bit RGB tuple that represents the left eye's color.
:raises TypeError: When ``color`` parameter is not valid.
.. important::
After setting the eye's color, call :py:meth:`~easygopigo3.EasyGoPiGo3.open_left_eye` or :py:meth:`~easygopigo3.EasyGoPiGo3.open_eyes` to update the color,
or otherwise the left eye's color won't change.
"""
if isinstance(color, tuple) and len(color) == 3:
self.left_eye_color = color
else:
raise TypeError("Eye color not valid")
def set_right_eye_color(self, color):
"""
Sets the LED color for Dexter mascot's right eye.
:param tuple(int,int,int) color: 8-bit RGB tuple that represents the right eye's color.
:raises TypeError: When ``color`` parameter is not valid.
.. important::
After setting the eye's color, call :py:meth:`~easygopigo3.EasyGoPiGo3.open_right_eye` or :py:meth:`~easygopigo3.EasyGoPiGo3.open_eyes` to update the color,
or otherwise the right eye's color won't change.
"""
if isinstance(color, tuple) and len(color) == 3:
self.right_eye_color = color
else:
raise TypeError("Eye color not valid")
def set_eye_color(self, color):
"""
Sets the LED color for Dexter mascot's eyes.
:param tuple(int,int,int) color: 8-bit RGB tuple that represents the eyes' color.
:raises TypeError: When ``color`` parameter is not valid.
.. important::
After setting the eyes' color, call :py:meth:`~easygopigo3.EasyGoPiGo3.open_eyes` to update the color of both eyes,
or otherwise the color won't change.
"""
self.set_left_eye_color(color)
self.set_right_eye_color(color)
def open_left_eye(self):
"""
Turns *ON* Dexter mascot's left eye.
"""
self.set_led(self.LED_LEFT_EYE,
self.left_eye_color[0],
self.left_eye_color[1],
self.left_eye_color[2],
)
def open_right_eye(self):
"""
Turns *ON* Dexter mascot's right eye.
"""
self.set_led(self.LED_RIGHT_EYE,
self.right_eye_color[0],
self.right_eye_color[1],
self.right_eye_color[2],
)
def open_eyes(self):
"""
Turns *ON* Dexter mascot's eyes.
"""
self.open_left_eye()
self.open_right_eye()
def close_left_eye(self):
"""
Turns *OFF* Dexter mascot's left eye.
"""
self.set_led(self.LED_LEFT_EYE, 0, 0, 0)
def close_right_eye(self):
"""
Turns *OFF* Dexter mascot's right eye.
"""
self.set_led(self.LED_RIGHT_EYE, 0, 0, 0)
def close_eyes(self):
"""
Turns *OFF* Dexter mascot's eyes.
"""
self.close_left_eye()
self.close_right_eye()
def init_light_sensor(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.LightSensor` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.LightSensor` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.LightSensor` class.
"""
return easysensors.LightSensor(port, self, use_mutex=self.use_mutex)
def init_sound_sensor(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.SoundSensor` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.SoundSensor` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.SoundSensor` class.
"""
return easysensors.SoundSensor(port, self, use_mutex=self.use_mutex)
# def init_loudness_sensor(self, port = "AD1"):
# """
# | Initialises a :py:class:`~easysensors.LoudnessSensor` object and then returns it.
# """
def init_loudness_sensor(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.LoudnessSensor` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.LoudnessSensor` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.LoudnessSensor` class.
"""
return easysensors.LoudnessSensor(port, self, use_mutex=self.use_mutex)
def init_ultrasonic_sensor(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.UltraSonicSensor` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.UltraSonicSensor` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.UltraSonicSensor` class.
"""
return easysensors.UltraSonicSensor(port, self, use_mutex=self.use_mutex)
def init_buzzer(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.Buzzer` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.Buzzer` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.Buzzer` class.
"""
return easysensors.Buzzer(port, self, use_mutex=self.use_mutex)
def init_led(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.Led` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.Led` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.Led` class.
"""
return easysensors.Led(port, self, use_mutex=self.use_mutex)
def init_button_sensor(self, port="AD1"):
"""
Initialises a :py:class:`~easysensors.ButtonSensor` object and then returns it.
:param str port = "AD1": Can be either ``"AD1"`` or ``"AD2"``. By default it's set to be ``"AD1"``.
:returns: An instance of the :py:class:`~easysensors.ButtonSensor` class and with the port set to ``port``'s value.
The ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~easysensors.ButtonSensor` class.
"""
return easysensors.ButtonSensor(port, self, use_mutex=self.use_mutex)
def init_line_follower(self, port = "I2C"):
"""
Initialises a :py:class:`~di_sensors.easy_line_follower.EasyLineFollower` object and then returns it.
:param str port = "I2C": The only option for this parameter for the old Line Follower Sensor (red board) is ``"I2C"`` and for the `Line Follower Sensor`_ (black board) it can also be ``"AD1"``/``"AD2"``. The default value for this parameter is already set to ``"I2C"``.
:returns: An instance of the :py:class:`~di_sensors.easy_line_follower.EasyLineFollower` class and with the port set to ``port``'s value.
The ``"I2C"``, ``"AD1"`` and ``"AD2"`` ports are mapped to the following :ref:`hardware-ports-section`.
.. tip::
| The sensor can be connected to any of the I2C ports.
| You can connect different I2C devices simultaneously provided that:
* The I2C devices have different addresses.
* The I2C devices are recognizeable by the `GoPiGo3`_ platform.
The ``use_mutex`` parameter of the :py:meth:`~easygopigo3.EasyGoPiGo3.__init__` constructor is passed down to the constructor of :py:class:`~di_sensors.easy_line_follower.EasyLineFollower` class.