Skip to content

Commit 1856e12

Browse files
committed
Added ev3dev/helper.py and demo/lego_official_projects/
1 parent 83da801 commit 1856e12

File tree

10 files changed

+317
-0
lines changed

10 files changed

+317
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
import sys
5+
from ev3dev.auto import OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D
6+
from ev3dev.helper import RemoteControlledTank, MediumMotor
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class TRACK3R(RemoteControlledTank):
12+
"""
13+
Base class for all TRACK3R variations. The only difference in the child
14+
classes are in how the medium motor is handled.
15+
16+
To enable the medium motor toggle the beacon button on the EV3 remote.
17+
"""
18+
19+
def __init__(self, medium_motor, left_motor, right_motor):
20+
RemoteControlledTank.__init__(self, left_motor, right_motor)
21+
self.medium_motor = MediumMotor(medium_motor)
22+
23+
if not self.medium_motor.connected:
24+
log.error("%s is not connected" % self.medium_motor)
25+
sys.exit(1)
26+
27+
self.medium_motor.reset()
28+
29+
30+
class TRACK3RWithBallShooter(TRACK3R):
31+
32+
def __init__(self, medium_motor=OUTPUT_A, left_motor=OUTPUT_B, right_motor=OUTPUT_C):
33+
TRACK3R.__init__(self, medium_motor, left_motor, right_motor)
34+
self.remote.on_beacon = self.fire_ball
35+
36+
def fire_ball(self, state):
37+
if state:
38+
self.medium_motor.run_to_rel_pos(duty_cycle_sp=100, position_sp=3*360)
39+
else:
40+
self.medium_motor.stop()
41+
42+
43+
class TRACK3RWithSpinner(TRACK3R):
44+
45+
def __init__(self, medium_motor=OUTPUT_A, left_motor=OUTPUT_B, right_motor=OUTPUT_C):
46+
TRACK3R.__init__(self, medium_motor, left_motor, right_motor)
47+
self.remote.on_beacon = self.spinner
48+
49+
def spinner(self, state):
50+
if state:
51+
self.medium_motor.run_forever(duty_cycle_sp=50)
52+
else:
53+
self.medium_motor.stop()
54+
55+
56+
class TRACK3RWithClaw(TRACK3R):
57+
58+
def __init__(self, medium_motor=OUTPUT_A, left_motor=OUTPUT_B, right_motor=OUTPUT_C):
59+
TRACK3R.__init__(self, medium_motor, left_motor, right_motor)
60+
self.remote.on_beacon = self.move_claw
61+
62+
def move_claw(self, state):
63+
if state:
64+
self.medium_motor.run_to_rel_pos(duty_cycle_sp=40, position_sp=-75)
65+
else:
66+
self.medium_motor.run_to_rel_pos(duty_cycle_sp=40, position_sp=75)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
from TRACK3R import TRACK3RWithBallShooter
5+
6+
logging.basicConfig(level=logging.DEBUG,
7+
format='%(asctime)s %(levelname)5s: %(message)s')
8+
log = logging.getLogger(__name__)
9+
10+
log.info("Starting TRACK3RWithBallShooter")
11+
tracker = TRACK3RWithBallShooter()
12+
tracker.main()
13+
log.info("Exiting TRACK3RWithBallShooter")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
from TRACK3R import TRACK3RWithClaw
5+
6+
logging.basicConfig(level=logging.DEBUG,
7+
format='%(asctime)s %(levelname)5s: %(message)s')
8+
log = logging.getLogger(__name__)
9+
10+
log.info("Starting TRACK3RWithClaw")
11+
tracker = TRACK3RWithClaw()
12+
tracker.main()
13+
log.info("Exiting TRACK3RWithClaw")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
from TRACK3R import TRACK3RWithSpinner
5+
6+
logging.basicConfig(level=logging.DEBUG,
7+
format='%(asctime)s %(levelname)5s: %(message)s')
8+
log = logging.getLogger(__name__)
9+
10+
log.info("Starting TRACK3RWithSpinner")
11+
tracker = TRACK3RWithSpinner()
12+
tracker.main()
13+
log.info("Exiting TRACK3RWithSpinner")

ev3dev/brickpi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
OUTPUT_B = 'ttyAMA0:outB'
3434
OUTPUT_C = 'ttyAMA0:outC'
3535
OUTPUT_D = 'ttyAMA0:outD'
36+
OUTPUTS = (OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D)
3637

3738
INPUT_1 = 'ttyAMA0:in1'
3839
INPUT_2 = 'ttyAMA0:in2'
3940
INPUT_3 = 'ttyAMA0:in3'
4041
INPUT_4 = 'ttyAMA0:in4'
42+
INPUTS = (INPUT_1, INPUT_2, INPUT_3, INPUT_4)
4143

4244

4345
class Leds(object):

ev3dev/core.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def __init__(self, class_name, name_pattern='*', name_exact=False, **kwargs):
163163

164164
classpath = abspath(Device.DEVICE_ROOT_PATH + '/' + class_name)
165165
self._attribute_cache = FileCache()
166+
self.kwargs = kwargs
166167

167168
def get_index(file):
168169
match = Device._DEVICE_INDEX.match(file)
@@ -182,8 +183,16 @@ def get_index(file):
182183
self._device_index = get_index(name)
183184
self.connected = True
184185
except StopIteration:
186+
self._path = None
187+
self._device_index = None
185188
self.connected = False
186189

190+
def __str__(self):
191+
if 'address' in self.kwargs:
192+
return "%s(%s)" % (self.__class__.__name__, self.kwargs.get('address'))
193+
else:
194+
return self.__class__.__name__
195+
187196
def _matches(self, attribute, pattern):
188197
"""Test if attribute value matches pattern (that is, if pattern is a
189198
substring of attribute value). If pattern is a list, then a match with
@@ -1893,6 +1902,9 @@ class ButtonBase(object):
18931902
Abstract button interface.
18941903
"""
18951904

1905+
def __str__(self):
1906+
return self.__class__.__name__
1907+
18961908
@staticmethod
18971909
def on_change(changed_buttons):
18981910
"""

ev3dev/ev3.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
OUTPUT_B = 'outB'
3434
OUTPUT_C = 'outC'
3535
OUTPUT_D = 'outD'
36+
OUTPUTS = (OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D)
3637

3738
INPUT_1 = 'in1'
3839
INPUT_2 = 'in2'
3940
INPUT_3 = 'in3'
4041
INPUT_4 = 'in4'
42+
INPUTS = (INPUT_1, INPUT_2, INPUT_3, INPUT_4)
4143

4244

4345
class Leds(object):

ev3dev/helper.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
import sys
5+
import time
6+
import ev3dev.auto
7+
from ev3dev.auto import OUTPUTS, RemoteControl, list_motors
8+
from math import pi
9+
from time import sleep
10+
11+
log = logging.getLogger(__name__)
12+
13+
14+
def wait_for(condition, timeout=1e5, interval=0.01):
15+
tic = time.time() + timeout
16+
done = condition()
17+
18+
while time.time() < tic and not done:
19+
time.sleep(interval)
20+
done = condition()
21+
22+
return done
23+
24+
25+
# =============
26+
# Motor classes
27+
# =============
28+
class MotorMixin(object):
29+
30+
def wait_for_running(self):
31+
return wait_for(lambda: 'running' in self.state, 1)
32+
33+
def wait_for_stop(self):
34+
return wait_for(lambda: 'running' not in self.state, 1)
35+
36+
37+
class LargeMotor(ev3dev.auto.LargeMotor, MotorMixin):
38+
pass
39+
40+
41+
class MediumMotor(ev3dev.auto.MediumMotor, MotorMixin):
42+
pass
43+
44+
45+
# ============
46+
# Tank classes
47+
# ============
48+
class Tank(object):
49+
50+
def __init__(self, left_motor, right_motor, polarity='normal'):
51+
52+
for motor in (left_motor, right_motor):
53+
if motor not in OUTPUTS:
54+
log.error("%s in an invalid motor, choices are %s" % (motor, ', '.join(OUTPUTS)))
55+
sys.exit(1)
56+
57+
self.left_motor = LargeMotor(left_motor)
58+
self.right_motor = LargeMotor(right_motor)
59+
60+
for x in (self.left_motor, self.right_motor):
61+
if not x.connected:
62+
log.error("%s is not connected" % x)
63+
sys.exit(1)
64+
65+
self.left_motor.reset()
66+
self.right_motor.reset()
67+
self.duty_cycle_sp = 90
68+
self.left_motor.duty_cycle_sp = self.duty_cycle_sp
69+
self.right_motor.duty_cycle_sp = self.duty_cycle_sp
70+
self.set_polarity(polarity)
71+
72+
def set_polarity(self, polarity):
73+
valid_choices = ('normal', 'inversed')
74+
assert polarity in valid_choices,\
75+
"%s is an invalid polarity choice, must be %s" % (polarity, ', '.join(valid_choices))
76+
77+
self.left_motor.polarity = polarity
78+
self.right_motor.polarity = polarity
79+
80+
81+
class RemoteControlledTank(Tank):
82+
83+
def __init__(self, left_motor, right_motor, polarity='inversed'):
84+
Tank.__init__(self, left_motor, right_motor, polarity)
85+
self.remote = RemoteControl(channel=1)
86+
87+
if not self.remote.connected:
88+
log.error("%s is not connected" % self.remote)
89+
sys.exit(1)
90+
91+
self.remote.on_red_up = self.make_move(self.left_motor, self.duty_cycle_sp)
92+
self.remote.on_red_down = self.make_move(self.left_motor, self.duty_cycle_sp * -1)
93+
self.remote.on_blue_up = self.make_move(self.right_motor, self.duty_cycle_sp)
94+
self.remote.on_blue_down = self.make_move(self.right_motor, self.duty_cycle_sp * -1)
95+
96+
def make_move(self, motor, dc_sp):
97+
def move(state):
98+
if state:
99+
motor.run_forever(duty_cycle_sp=dc_sp)
100+
else:
101+
motor.stop()
102+
return move
103+
104+
def main(self):
105+
106+
try:
107+
while True:
108+
self.remote.process()
109+
time.sleep(0.01)
110+
111+
# Exit cleanly so that all motors are stopped
112+
except (KeyboardInterrupt, Exception) as e:
113+
log.exception(e)
114+
115+
for motor in list_motors():
116+
motor.stop()
117+
118+
119+
# =====================
120+
# Wheel and Rim classes
121+
# =====================
122+
class Wheel(object):
123+
"""
124+
A base class for various types of wheels, tires, etc
125+
All units are in mm
126+
"""
127+
128+
def __init__(self, diameter, width):
129+
self.diameter = float(diameter)
130+
self.width = float(width)
131+
self.radius = float(diameter/2)
132+
self.circumference = diameter * pi
133+
134+
135+
# A great reference when adding new wheels is http://wheels.sariel.pl/
136+
class EV3RubberWheel(Wheel):
137+
138+
def __init__(self):
139+
Wheel.__init__(self, 43.2, 21)

utils/move_motor.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Used to adjust the position of a motor in an already assembled robot
5+
where you can"t move the motor by hand.
6+
"""
7+
8+
from ev3dev.auto import OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D
9+
from ev3dev.helper import LargeMotor
10+
import argparse
11+
import logging
12+
import sys
13+
14+
# command line args
15+
parser = argparse.ArgumentParser(description="Used to adjust the position of a motor in an already assembled robot")
16+
parser.add_argument("motor", type=str, help="A, B, C or D")
17+
parser.add_argument("degrees", type=int)
18+
parser.add_argument("-s", "--speed", type=int, default=50)
19+
args = parser.parse_args()
20+
21+
# logging
22+
logging.basicConfig(level=logging.DEBUG,
23+
format="%(asctime)s %(levelname)5s: %(message)s")
24+
log = logging.getLogger(__name__)
25+
26+
# For this it doesn't really matter if it is a LargeMotor or a MediumMotor
27+
if args.motor == "A":
28+
motor = LargeMotor(OUTPUT_A)
29+
elif args.motor == "B":
30+
motor = LargeMotor(OUTPUT_B)
31+
elif args.motor == "C":
32+
motor = LargeMotor(OUTPUT_C)
33+
elif args.motor == "D":
34+
motor = LargeMotor(OUTPUT_D)
35+
else:
36+
raise Exception("%s is invalid, options are A, B, C, D")
37+
38+
if not motor.connected:
39+
log.error("%s is not connected" % motor)
40+
sys.exit(1)
41+
42+
motor.reset()
43+
log.info("Motor %s, move to position %d" % (args.motor, args.degrees))
44+
motor.run_to_rel_pos(duty_cycle_sp=args.speed, position_sp=args.degrees)
45+
motor.wait_for_running()
46+
log.info("Motor %s is running" % args.motor)
47+
motor.wait_for_stop()
48+
log.info("Motor %s stopped, final position %d" % (args.motor, motor.position))

utils/stop_all_motors.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Stop all motors
5+
"""
6+
from ev3dev.auto import list_motors
7+
8+
for motor in list_motors():
9+
motor.stop()

0 commit comments

Comments
 (0)