Skip to content

Commit d5db729

Browse files
authored
MoveTank turn_degrees convert speed to SpeedValue object (#724)
1 parent 6e03f0a commit d5db729

File tree

2 files changed

+73
-65
lines changed

2 files changed

+73
-65
lines changed

debian/changelog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ python-ev3dev2 (2.1.0) UNRELEASED; urgency=medium
66
* format code via yapf
77
* MoveDifferential use gyro for better accuracy. Make MoveTank and
88
MoveDifferential "turn" APIs more consistent.
9+
* MoveTank turn_degrees convert speed to SpeedValue object
910

1011
[Matěj Volf]
1112
* LED animation fix duration None

ev3dev2/motor.py

Lines changed: 72 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@
7676
raise Exception("Unsupported platform '%s'" % platform)
7777

7878

79+
class SpeedInvalid(ValueError):
80+
pass
81+
82+
7983
class SpeedValue(object):
8084
"""
8185
A base class for other unit types. Don't use this directly; instead, see
@@ -108,14 +112,14 @@ class SpeedPercent(SpeedValue):
108112
"""
109113
Speed as a percentage of the motor's maximum rated speed.
110114
"""
111-
def __init__(self, percent):
112-
assert -100 <= percent <= 100,\
113-
"{} is an invalid percentage, must be between -100 and 100 (inclusive)".format(percent)
114-
115+
def __init__(self, percent, desc=None):
116+
if percent < -100 or percent > 100:
117+
raise SpeedInvalid("invalid percentage {}, must be between -100 and 100 (inclusive)".format(percent))
115118
self.percent = percent
119+
self.desc = desc
116120

117121
def __str__(self):
118-
return str(self.percent) + "%"
122+
return "{} ".format(self.desc) if self.desc else "" + str(self.percent) + "%"
119123

120124
def __mul__(self, other):
121125
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -132,11 +136,12 @@ class SpeedNativeUnits(SpeedValue):
132136
"""
133137
Speed in tacho counts per second.
134138
"""
135-
def __init__(self, native_counts):
139+
def __init__(self, native_counts, desc=None):
136140
self.native_counts = native_counts
141+
self.desc = desc
137142

138143
def __str__(self):
139-
return "{:.2f}".format(self.native_counts) + " counts/sec"
144+
return "{} ".format(self.desc) if self.desc else "" + "{:.2f}".format(self.native_counts) + " counts/sec"
140145

141146
def __mul__(self, other):
142147
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -146,18 +151,22 @@ def to_native_units(self, motor=None):
146151
"""
147152
Return this SpeedNativeUnits as a number
148153
"""
154+
if self.native_counts > motor.max_speed:
155+
raise SpeedInvalid("invalid native-units: {} max speed {}, {} was requested".format(
156+
motor, motor.max_speed, self.native_counts))
149157
return self.native_counts
150158

151159

152160
class SpeedRPS(SpeedValue):
153161
"""
154162
Speed in rotations-per-second.
155163
"""
156-
def __init__(self, rotations_per_second):
164+
def __init__(self, rotations_per_second, desc=None):
157165
self.rotations_per_second = rotations_per_second
166+
self.desc = desc
158167

159168
def __str__(self):
160-
return str(self.rotations_per_second) + " rot/sec"
169+
return "{} ".format(self.desc) if self.desc else "" + str(self.rotations_per_second) + " rot/sec"
161170

162171
def __mul__(self, other):
163172
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -167,21 +176,22 @@ def to_native_units(self, motor):
167176
"""
168177
Return the native speed measurement required to achieve desired rotations-per-second
169178
"""
170-
assert abs(self.rotations_per_second) <= motor.max_rps,\
171-
"invalid rotations-per-second: {} max RPS is {}, {} was requested".format(
172-
motor, motor.max_rps, self.rotations_per_second)
179+
if abs(self.rotations_per_second) > motor.max_rps:
180+
raise SpeedInvalid("invalid rotations-per-second: {} max RPS is {}, {} was requested".format(
181+
motor, motor.max_rps, self.rotations_per_second))
173182
return self.rotations_per_second / motor.max_rps * motor.max_speed
174183

175184

176185
class SpeedRPM(SpeedValue):
177186
"""
178187
Speed in rotations-per-minute.
179188
"""
180-
def __init__(self, rotations_per_minute):
189+
def __init__(self, rotations_per_minute, desc=None):
181190
self.rotations_per_minute = rotations_per_minute
191+
self.desc = desc
182192

183193
def __str__(self):
184-
return str(self.rotations_per_minute) + " rot/min"
194+
return "{} ".format(self.desc) if self.desc else "" + str(self.rotations_per_minute) + " rot/min"
185195

186196
def __mul__(self, other):
187197
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -191,21 +201,22 @@ def to_native_units(self, motor):
191201
"""
192202
Return the native speed measurement required to achieve desired rotations-per-minute
193203
"""
194-
assert abs(self.rotations_per_minute) <= motor.max_rpm,\
195-
"invalid rotations-per-minute: {} max RPM is {}, {} was requested".format(
196-
motor, motor.max_rpm, self.rotations_per_minute)
204+
if abs(self.rotations_per_minute) > motor.max_rpm:
205+
raise SpeedInvalid("invalid rotations-per-minute: {} max RPM is {}, {} was requested".format(
206+
motor, motor.max_rpm, self.rotations_per_minute))
197207
return self.rotations_per_minute / motor.max_rpm * motor.max_speed
198208

199209

200210
class SpeedDPS(SpeedValue):
201211
"""
202212
Speed in degrees-per-second.
203213
"""
204-
def __init__(self, degrees_per_second):
214+
def __init__(self, degrees_per_second, desc=None):
205215
self.degrees_per_second = degrees_per_second
216+
self.desc = desc
206217

207218
def __str__(self):
208-
return str(self.degrees_per_second) + " deg/sec"
219+
return "{} ".format(self.desc) if self.desc else "" + str(self.degrees_per_second) + " deg/sec"
209220

210221
def __mul__(self, other):
211222
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -215,21 +226,22 @@ def to_native_units(self, motor):
215226
"""
216227
Return the native speed measurement required to achieve desired degrees-per-second
217228
"""
218-
assert abs(self.degrees_per_second) <= motor.max_dps,\
219-
"invalid degrees-per-second: {} max DPS is {}, {} was requested".format(
220-
motor, motor.max_dps, self.degrees_per_second)
229+
if abs(self.degrees_per_second) > motor.max_dps:
230+
raise SpeedInvalid("invalid degrees-per-second: {} max DPS is {}, {} was requested".format(
231+
motor, motor.max_dps, self.degrees_per_second))
221232
return self.degrees_per_second / motor.max_dps * motor.max_speed
222233

223234

224235
class SpeedDPM(SpeedValue):
225236
"""
226237
Speed in degrees-per-minute.
227238
"""
228-
def __init__(self, degrees_per_minute):
239+
def __init__(self, degrees_per_minute, desc=None):
229240
self.degrees_per_minute = degrees_per_minute
241+
self.desc = desc
230242

231243
def __str__(self):
232-
return str(self.degrees_per_minute) + " deg/min"
244+
return "{} ".format(self.desc) if self.desc else "" + str(self.degrees_per_minute) + " deg/min"
233245

234246
def __mul__(self, other):
235247
assert isinstance(other, (float, int)), "{} can only be multiplied by an int or float".format(self)
@@ -239,12 +251,23 @@ def to_native_units(self, motor):
239251
"""
240252
Return the native speed measurement required to achieve desired degrees-per-minute
241253
"""
242-
assert abs(self.degrees_per_minute) <= motor.max_dpm,\
243-
"invalid degrees-per-minute: {} max DPM is {}, {} was requested".format(
244-
motor, motor.max_dpm, self.degrees_per_minute)
254+
if abs(self.degrees_per_minute) > motor.max_dpm:
255+
raise SpeedInvalid("invalid degrees-per-minute: {} max DPM is {}, {} was requested".format(
256+
motor, motor.max_dpm, self.degrees_per_minute))
245257
return self.degrees_per_minute / motor.max_dpm * motor.max_speed
246258

247259

260+
def speed_to_speedvalue(speed, desc=None):
261+
"""
262+
If ``speed`` is not a ``SpeedValue`` object, treat it as a percentage.
263+
Returns a ``SpeedValue`` object.
264+
"""
265+
if isinstance(speed, SpeedValue):
266+
return speed
267+
else:
268+
return SpeedPercent(speed, desc)
269+
270+
248271
class Motor(Device):
249272
"""
250273
The motor class provides a uniform interface for using motors with
@@ -937,13 +960,7 @@ def wait_while(self, s, timeout=None):
937960
return self.wait(lambda state: s not in state, timeout)
938961

939962
def _speed_native_units(self, speed, label=None):
940-
941-
# If speed is not a SpeedValue object we treat it as a percentage
942-
if not isinstance(speed, SpeedValue):
943-
assert -100 <= speed <= 100,\
944-
"{}{} is an invalid speed percentage, must be between -100 and 100 (inclusive)".format("" if label is None else (label + ": ") , speed)
945-
speed = SpeedPercent(speed)
946-
963+
speed = speed_to_speedvalue(speed, label)
947964
return speed.to_native_units(self)
948965

949966
def _set_rel_position_degrees_and_speed_sp(self, degrees, speed):
@@ -2047,7 +2064,7 @@ def follow_line(self,
20472064
follow_for=follow_for_ms,
20482065
ms=4500
20492066
)
2050-
except Exception:
2067+
except LineFollowErrorTooFast:
20512068
tank.stop()
20522069
raise
20532070
"""
@@ -2062,8 +2079,8 @@ def follow_line(self,
20622079
last_error = 0.0
20632080
derivative = 0.0
20642081
off_line_count = 0
2082+
speed = speed_to_speedvalue(speed)
20652083
speed_native_units = speed.to_native_units(self.left_motor)
2066-
MAX_SPEED = SpeedNativeUnits(self.max_speed)
20672084

20682085
while follow_for(self, **kwargs):
20692086
reflected_light_intensity = self._cs.reflected_light_intensity
@@ -2079,16 +2096,6 @@ def follow_line(self,
20792096
left_speed = SpeedNativeUnits(speed_native_units - turn_native_units)
20802097
right_speed = SpeedNativeUnits(speed_native_units + turn_native_units)
20812098

2082-
if left_speed > MAX_SPEED:
2083-
log.info("%s: left_speed %s is greater than MAX_SPEED %s" % (self, left_speed, MAX_SPEED))
2084-
self.stop()
2085-
raise LineFollowErrorTooFast("The robot is moving too fast to follow the line")
2086-
2087-
if right_speed > MAX_SPEED:
2088-
log.info("%s: right_speed %s is greater than MAX_SPEED %s" % (self, right_speed, MAX_SPEED))
2089-
self.stop()
2090-
raise LineFollowErrorTooFast("The robot is moving too fast to follow the line")
2091-
20922099
# Have we lost the line?
20932100
if reflected_light_intensity >= white:
20942101
off_line_count += 1
@@ -2102,7 +2109,12 @@ def follow_line(self,
21022109
if sleep_time:
21032110
time.sleep(sleep_time)
21042111

2105-
self.on(left_speed, right_speed)
2112+
try:
2113+
self.on(left_speed, right_speed)
2114+
except SpeedInvalid as e:
2115+
log.exception(e)
2116+
self.stop()
2117+
raise LineFollowErrorTooFast("The robot is moving too fast to follow the line")
21062118

21072119
self.stop()
21082120

@@ -2150,15 +2162,16 @@ def follow_gyro_angle(self,
21502162
# Initialize the tank's gyro sensor
21512163
tank.gyro = GyroSensor()
21522164
2165+
# Calibrate the gyro to eliminate drift, and to initialize the current angle as 0
2166+
tank.gyro.calibrate()
2167+
21532168
try:
2154-
# Calibrate the gyro to eliminate drift, and to initialize the current angle as 0
2155-
tank.gyro.calibrate()
21562169
2157-
# Follow the line for 4500ms
2170+
# Follow the target_angle for 4500ms
21582171
tank.follow_gyro_angle(
21592172
kp=11.3, ki=0.05, kd=3.2,
21602173
speed=SpeedPercent(30),
2161-
target_angle=0
2174+
target_angle=0,
21622175
follow_for=follow_for_ms,
21632176
ms=4500
21642177
)
@@ -2173,10 +2186,8 @@ def follow_gyro_angle(self,
21732186
integral = 0.0
21742187
last_error = 0.0
21752188
derivative = 0.0
2189+
speed = speed_to_speedvalue(speed)
21762190
speed_native_units = speed.to_native_units(self.left_motor)
2177-
MAX_SPEED = SpeedNativeUnits(self.max_speed)
2178-
2179-
assert speed_native_units <= MAX_SPEED, "Speed exceeds the max speed of the motors"
21802191

21812192
while follow_for(self, **kwargs):
21822193
current_angle = self._gyro.angle
@@ -2189,20 +2200,15 @@ def follow_gyro_angle(self,
21892200
left_speed = SpeedNativeUnits(speed_native_units - turn_native_units)
21902201
right_speed = SpeedNativeUnits(speed_native_units + turn_native_units)
21912202

2192-
if abs(left_speed) > MAX_SPEED:
2193-
log.info("%s: left_speed %s is greater than MAX_SPEED %s" % (self, left_speed, MAX_SPEED))
2194-
self.stop()
2195-
raise FollowGyroAngleErrorTooFast("The robot is moving too fast to follow the angle")
2196-
2197-
if abs(right_speed) > MAX_SPEED:
2198-
log.info("%s: right_speed %s is greater than MAX_SPEED %s" % (self, right_speed, MAX_SPEED))
2199-
self.stop()
2200-
raise FollowGyroAngleErrorTooFast("The robot is moving too fast to follow the angle")
2201-
22022203
if sleep_time:
22032204
time.sleep(sleep_time)
22042205

2205-
self.on(left_speed, right_speed)
2206+
try:
2207+
self.on(left_speed, right_speed)
2208+
except SpeedInvalid as e:
2209+
log.exception(e)
2210+
self.stop()
2211+
raise FollowGyroAngleErrorTooFast("The robot is moving too fast to follow the angle")
22062212

22072213
self.stop()
22082214

@@ -2254,6 +2260,7 @@ def turn_degrees(self, speed, target_angle, brake=True, error_margin=2, sleep_ti
22542260
raise DeviceNotDefined(
22552261
"The 'gyro' variable must be defined with a GyroSensor. Example: tank.gyro = GyroSensor()")
22562262

2263+
speed = speed_to_speedvalue(speed)
22572264
speed_native_units = speed.to_native_units(self.left_motor)
22582265
target_angle = self._gyro.angle + target_angle
22592266

0 commit comments

Comments
 (0)