7676 raise Exception ("Unsupported platform '%s'" % platform )
7777
7878
79+ class SpeedInvalid (ValueError ):
80+ pass
81+
82+
7983class 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
152160class 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
176185class 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
200210class 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
224235class 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+
248271class 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