Skip to content

Commit a227613

Browse files
nshenoydwalton76
authored andcommitted
Adds Gyro Support to MoveTank (ev3dev#697)
* Adds Gyro Support to MoveTank Adds the following gyro supported functions to MoveTank: - follow_gyro_angle: angle following function that uses the same PID algorithm as the line follower to have the robot drive straight at a specific angle. - pivot_gyro: pivots the robot to a specific angle - calibrate_gyro: flips the mode of the sensor to zero out the gyro to reduce chance of drift * Fixes whitespace * Fixes some PR feedbac * Refactors Color and Gyro sensors to be properties Added explicit color and gyro setters/getters * Modifies private vars to use single underscore Addresses code review feedback including: - making private members use single instead of double underscore - `wiggle_room` added to `turn_to_angle_gyro` for user to control accuracy
1 parent 06604a6 commit a227613

File tree

1 file changed

+206
-5
lines changed

1 file changed

+206
-5
lines changed

ev3dev2/motor.py

Lines changed: 206 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,15 @@ def _block(self):
17901790
self.wait_until_not_moving()
17911791

17921792

1793+
# follow gyro angle classes
1794+
class FollowGyroAngleErrorTooFast(Exception):
1795+
"""
1796+
Raised when a gyro following robot has been asked to follow
1797+
an angle at an unrealistic speed
1798+
"""
1799+
pass
1800+
1801+
17931802
# line follower classes
17941803
class LineFollowErrorLostLine(Exception):
17951804
"""
@@ -1854,8 +1863,23 @@ def __init__(self, left_motor_port, right_motor_port, desc=None, motor_class=Lar
18541863
self.right_motor = self.motors[right_motor_port]
18551864
self.max_speed = self.left_motor.max_speed
18561865

1857-
# color sensor used by follow_line()
1858-
self.cs = None
1866+
# color sensor used by follow_line()
1867+
@property
1868+
def cs(self):
1869+
return self._cs
1870+
1871+
@cs.setter
1872+
def cs(self, cs):
1873+
self._cs = cs
1874+
1875+
# gyro sensor used by follow_gyro_angle()
1876+
@property
1877+
def gyro(self):
1878+
return self._gyro
1879+
1880+
@gyro.setter
1881+
def gyro(self, gyro):
1882+
self._gyro = gyro
18591883

18601884
def _unpack_speeds_to_native_units(self, left_speed, right_speed):
18611885
left_speed = self.left_motor._speed_native_units(left_speed, "left_speed")
@@ -2046,10 +2070,10 @@ def follow_line(self,
20462070
tank.stop()
20472071
raise
20482072
"""
2049-
assert self.cs, "ColorSensor must be defined"
2073+
assert self._cs, "ColorSensor must be defined"
20502074

20512075
if target_light_intensity is None:
2052-
target_light_intensity = self.cs.reflected_light_intensity
2076+
target_light_intensity = self._cs.reflected_light_intensity
20532077

20542078
integral = 0.0
20552079
last_error = 0.0
@@ -2059,7 +2083,7 @@ def follow_line(self,
20592083
MAX_SPEED = SpeedNativeUnits(self.max_speed)
20602084

20612085
while follow_for(self, **kwargs):
2062-
reflected_light_intensity = self.cs.reflected_light_intensity
2086+
reflected_light_intensity = self._cs.reflected_light_intensity
20632087
error = target_light_intensity - reflected_light_intensity
20642088
integral = integral + error
20652089
derivative = error - last_error
@@ -2099,6 +2123,183 @@ def follow_line(self,
20992123

21002124
self.stop()
21012125

2126+
def calibrate_gyro(self):
2127+
"""
2128+
Calibrates the gyro sensor.
2129+
2130+
NOTE: This takes 1sec to run
2131+
"""
2132+
assert self._gyro, "GyroSensor must be defined"
2133+
2134+
for x in range(2):
2135+
self._gyro.mode = 'GYRO-RATE'
2136+
self._gyro.mode = 'GYRO-ANG'
2137+
time.sleep(0.5)
2138+
2139+
def follow_gyro_angle(self,
2140+
kp, ki, kd,
2141+
speed,
2142+
target_angle=0,
2143+
sleep_time=0.01,
2144+
follow_for=follow_for_forever,
2145+
**kwargs
2146+
):
2147+
"""
2148+
PID gyro angle follower
2149+
2150+
``kp``, ``ki``, and ``kd`` are the PID constants.
2151+
2152+
``speed`` is the desired speed of the midpoint of the robot
2153+
2154+
``target_angle`` is the angle we want to maintain
2155+
2156+
``sleep_time`` is how many seconds we sleep on each pass through
2157+
the loop. This is to give the robot a chance to react
2158+
to the new motor settings. This should be something small such
2159+
as 0.01 (10ms).
2160+
2161+
``follow_for`` is called to determine if we should keep following the
2162+
desired angle or stop. This function will be passed ``self`` (the current
2163+
``MoveTank`` object). Current supported options are:
2164+
- ``follow_for_forever``
2165+
- ``follow_for_ms``
2166+
2167+
``**kwargs`` will be passed to the ``follow_for`` function
2168+
2169+
Example:
2170+
2171+
.. code:: python
2172+
2173+
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, MoveTank, SpeedPercent, follow_for_ms
2174+
from ev3dev2.sensor.lego import GyroSensor
2175+
2176+
# Instantiate the MoveTank object
2177+
tank = MoveTank(OUTPUT_A, OUTPUT_B)
2178+
2179+
# Initialize the tank's gyro sensor
2180+
tank.gyro = GyroSensor()
2181+
2182+
try:
2183+
# Calibrate the gyro to eliminate drift, and to initialize the current angle as 0
2184+
tank.calibrate_gyro()
2185+
2186+
# Follow the line for 4500ms
2187+
tank.follow_gyro_angle(
2188+
kp=11.3, ki=0.05, kd=3.2,
2189+
speed=SpeedPercent(30),
2190+
target_angle=0
2191+
follow_for=follow_for_ms,
2192+
ms=4500
2193+
)
2194+
except FollowGyroAngleErrorTooFast:
2195+
tank.stop()
2196+
raise
2197+
"""
2198+
assert self._gyro, "GyroSensor must be defined"
2199+
2200+
integral = 0.0
2201+
last_error = 0.0
2202+
derivative = 0.0
2203+
speed_native_units = speed.to_native_units(self.left_motor)
2204+
MAX_SPEED = SpeedNativeUnits(self.max_speed)
2205+
2206+
assert speed_native_units <= MAX_SPEED, "Speed exceeds the max speed of the motors"
2207+
2208+
while follow_for(self, **kwargs):
2209+
current_angle = self._gyro.angle
2210+
error = current_angle - target_angle
2211+
integral = integral + error
2212+
derivative = error - last_error
2213+
last_error = error
2214+
turn_native_units = (kp * error) + (ki * integral) + (kd * derivative)
2215+
2216+
left_speed = SpeedNativeUnits(speed_native_units - turn_native_units)
2217+
right_speed = SpeedNativeUnits(speed_native_units + turn_native_units)
2218+
2219+
if abs(left_speed) > MAX_SPEED:
2220+
log.info("%s: left_speed %s is greater than MAX_SPEED %s" %
2221+
(self, left_speed, MAX_SPEED))
2222+
self.stop()
2223+
raise FollowGyroAngleErrorTooFast(
2224+
"The robot is moving too fast to follow the angle")
2225+
2226+
if abs(right_speed) > MAX_SPEED:
2227+
log.info("%s: right_speed %s is greater than MAX_SPEED %s" %
2228+
(self, right_speed, MAX_SPEED))
2229+
self.stop()
2230+
raise FollowGyroAngleErrorTooFast(
2231+
"The robot is moving too fast to follow the angle")
2232+
2233+
if sleep_time:
2234+
time.sleep(sleep_time)
2235+
2236+
self.on(left_speed, right_speed)
2237+
2238+
self.stop()
2239+
2240+
def turn_to_angle_gyro(self,
2241+
speed,
2242+
target_angle=0,
2243+
wiggle_room=2,
2244+
sleep_time=0.01
2245+
):
2246+
"""
2247+
Pivot Turn
2248+
2249+
``speed`` is the desired speed of the midpoint of the robot
2250+
2251+
``target_angle`` is the target angle we want to pivot to
2252+
2253+
``wiggle_room`` is the +/- angle threshold to control how accurate the turn should be
2254+
2255+
``sleep_time`` is how many seconds we sleep on each pass through
2256+
the loop. This is to give the robot a chance to react
2257+
to the new motor settings. This should be something small such
2258+
as 0.01 (10ms).
2259+
2260+
Example:
2261+
2262+
.. code:: python
2263+
2264+
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, MoveTank, SpeedPercent
2265+
from ev3dev2.sensor.lego import GyroSensor
2266+
2267+
# Instantiate the MoveTank object
2268+
tank = MoveTank(OUTPUT_A, OUTPUT_B)
2269+
2270+
# Initialize the tank's gyro sensor
2271+
tank.gyro = GyroSensor()
2272+
2273+
# Calibrate the gyro to eliminate drift, and to initialize the current angle as 0
2274+
tank.calibrate_gyro()
2275+
2276+
# Pivot 30 degrees
2277+
tank.turn_to_angle_gyro(
2278+
speed=SpeedPercent(5),
2279+
target_angle(30)
2280+
)
2281+
"""
2282+
assert self._gyro, "GyroSensor must be defined"
2283+
2284+
speed_native_units = speed.to_native_units(self.left_motor)
2285+
target_reached = False
2286+
2287+
while not target_reached:
2288+
current_angle = self._gyro.angle
2289+
if abs(current_angle - target_angle) <= wiggle_room:
2290+
target_reached = True
2291+
self.stop()
2292+
elif (current_angle > target_angle):
2293+
left_speed = SpeedNativeUnits(-1 * speed_native_units)
2294+
right_speed = SpeedNativeUnits(speed_native_units)
2295+
else:
2296+
left_speed = SpeedNativeUnits(speed_native_units)
2297+
right_speed = SpeedNativeUnits(-1 * speed_native_units)
2298+
2299+
if sleep_time:
2300+
time.sleep(sleep_time)
2301+
2302+
self.on(left_speed, right_speed)
21022303

21032304
class MoveSteering(MoveTank):
21042305
"""

0 commit comments

Comments
 (0)