Example #1
0
    def __init__(self):
        self.shutdown = False
        self.flipper = LargeMotor(OUTPUT_A)
        self.turntable = LargeMotor(OUTPUT_B)
        self.colorarm = MediumMotor(OUTPUT_C)
        self.color_sensor = ColorSensor()
        self.color_sensor.mode = self.color_sensor.MODE_RGB_RAW
        self.infrared_sensor = InfraredSensor()
        self.init_motors()
        self.state = ['U', 'D', 'F', 'L', 'B', 'R']
        self.rgb_solver = None
        signal.signal(signal.SIGTERM, self.signal_term_handler)
        signal.signal(signal.SIGINT, self.signal_int_handler)

        filename_max_rgb = 'max_rgb.txt'

        if os.path.exists(filename_max_rgb):
            with open(filename_max_rgb, 'r') as fh:
                for line in fh:
                    (color, value) = line.strip().split()

                    if color == 'red':
                        self.color_sensor.red_max = int(value)
                        log.info("red max is %d" % self.color_sensor.red_max)
                    elif color == 'green':
                        self.color_sensor.green_max = int(value)
                        log.info("green max is %d" %
                                 self.color_sensor.green_max)
                    elif color == 'blue':
                        self.color_sensor.blue_max = int(value)
                        log.info("blue max is %d" % self.color_sensor.blue_max)
#
# The program may be stopped by pressing any button on the brick.
#
# This demonstrates usage of motors, sound, sensors, buttons, and leds.

from time import sleep
from random import choice, randint

from ev3dev.motor import OUTPUT_B, OUTPUT_C, LargeMotor
from ev3dev.sensor.lego import InfraredSensor, TouchSensor
from ev3dev.button import Button
from ev3dev.led import Leds
from ev3dev.sound import Sound

# Connect two large motors on output ports B and C:
motors = [LargeMotor(address) for address in (OUTPUT_B, OUTPUT_C)]

# Connect infrared and touch sensors.
ir = InfraredSensor()
ts = TouchSensor()

print('Robot Starting')

# We will need to check EV3 buttons state.
btn = Button()


def start():
    """
    Start both motors. `run-direct` command will allow to vary motor
    performance on the fly by adjusting `duty_cycle_sp` attribute.
Example #3
0
class MindCuber(object):
    scan_order = [
        5, 9, 6, 3, 2, 1, 4, 7, 8, 23, 27, 24, 21, 20, 19, 22, 25, 26, 50, 54,
        51, 48, 47, 46, 49, 52, 53, 14, 10, 13, 16, 17, 18, 15, 12, 11, 41, 43,
        44, 45, 42, 39, 38, 37, 40, 32, 34, 35, 36, 33, 30, 29, 28, 31
    ]

    hold_cube_pos = 85
    rotate_speed = 400
    flip_speed = 300
    flip_speed_push = 400

    def __init__(self):
        self.shutdown = False
        self.flipper = LargeMotor(OUTPUT_A)
        self.turntable = LargeMotor(OUTPUT_B)
        self.colorarm = MediumMotor(OUTPUT_C)
        self.color_sensor = ColorSensor()
        self.color_sensor.mode = self.color_sensor.MODE_RGB_RAW
        self.infrared_sensor = InfraredSensor()
        self.init_motors()
        self.state = ['U', 'D', 'F', 'L', 'B', 'R']
        self.rgb_solver = None
        signal.signal(signal.SIGTERM, self.signal_term_handler)
        signal.signal(signal.SIGINT, self.signal_int_handler)

        filename_max_rgb = 'max_rgb.txt'

        if os.path.exists(filename_max_rgb):
            with open(filename_max_rgb, 'r') as fh:
                for line in fh:
                    (color, value) = line.strip().split()

                    if color == 'red':
                        self.color_sensor.red_max = int(value)
                        log.info("red max is %d" % self.color_sensor.red_max)
                    elif color == 'green':
                        self.color_sensor.green_max = int(value)
                        log.info("green max is %d" %
                                 self.color_sensor.green_max)
                    elif color == 'blue':
                        self.color_sensor.blue_max = int(value)
                        log.info("blue max is %d" % self.color_sensor.blue_max)

    def init_motors(self):

        for x in (self.flipper, self.turntable, self.colorarm):
            if not x.connected:
                log.error("%s is not connected" % x)
                sys.exit(1)
            x.reset()

        log.info("Initialize flipper %s" % self.flipper)
        self.flipper.on(SpeedDPS(-50), block=True)
        self.flipper.off()
        self.flipper.reset()

        log.info("Initialize colorarm %s" % self.colorarm)
        self.colorarm.on(SpeedDPS(500), block=True)
        self.colorarm.off()
        self.colorarm.reset()

        log.info("Initialize turntable %s" % self.turntable)
        self.turntable.off()
        self.turntable.reset()

    def shutdown_robot(self):
        log.info('Shutting down')
        self.shutdown = True

        if self.rgb_solver:
            self.rgb_solver.shutdown = True

        for x in (self.flipper, self.turntable, self.colorarm):
            # We are shutting down so do not 'hold' the motors
            x.stop_action = 'brake'
            x.off(False)

    def signal_term_handler(self, signal, frame):
        log.error('Caught SIGTERM')
        self.shutdown_robot()

    def signal_int_handler(self, signal, frame):
        log.error('Caught SIGINT')
        self.shutdown_robot()

    def apply_transformation(self, transformation):
        self.state = [self.state[t] for t in transformation]

    def rotate_cube(self, direction, nb):
        current_pos = self.turntable.position
        final_pos = 135 * round(
            (self.turntable.position + (270 * direction * nb)) / 135.0)
        log.info(
            "rotate_cube() direction %s, nb %s, current_pos %d, final_pos %d" %
            (direction, nb, current_pos, final_pos))

        if self.flipper.position > 35:
            self.flipper_away()

        self.turntable.on_to_position(SpeedDPS(MindCuber.rotate_speed),
                                      final_pos)

        if nb >= 1:
            for i in range(nb):
                if direction > 0:
                    transformation = [0, 1, 5, 2, 3, 4]
                else:
                    transformation = [0, 1, 3, 4, 5, 2]
                self.apply_transformation(transformation)

    def rotate_cube_1(self):
        self.rotate_cube(1, 1)

    def rotate_cube_2(self):
        self.rotate_cube(1, 2)

    def rotate_cube_3(self):
        self.rotate_cube(-1, 1)

    def rotate_cube_blocked(self, direction, nb):

        # Move the arm down to hold the cube in place
        self.flipper_hold_cube()

        # OVERROTATE depends on lot on MindCuber.rotate_speed
        current_pos = self.turntable.position
        OVERROTATE = 18
        final_pos = int(135 * round(
            (current_pos + (270 * direction * nb)) / 135.0))
        temp_pos = int(final_pos + (OVERROTATE * direction))

        log.info(
            "rotate_cube_blocked() direction %s nb %s, current pos %s, temp pos %s, final pos %s"
            % (direction, nb, current_pos, temp_pos, final_pos))

        self.turntable.on_to_position(SpeedDPS(MindCuber.rotate_speed),
                                      temp_pos)
        self.turntable.on_to_position(SpeedDPS(MindCuber.rotate_speed / 4),
                                      final_pos)

    def rotate_cube_blocked_1(self):
        self.rotate_cube_blocked(1, 1)

    def rotate_cube_blocked_2(self):
        self.rotate_cube_blocked(1, 2)

    def rotate_cube_blocked_3(self):
        self.rotate_cube_blocked(-1, 1)

    def flipper_hold_cube(self, speed=300):
        current_position = self.flipper.position

        # Push it forward so the cube is always in the same position
        # when we start the flip
        if (current_position <= MindCuber.hold_cube_pos - 10
                or current_position >= MindCuber.hold_cube_pos + 10):

            self.flipper.ramp_down_sp = 400
            self.flipper.on_to_position(SpeedDPS(speed),
                                        MindCuber.hold_cube_pos)
            sleep(0.05)

    def flipper_away(self, speed=300):
        """
        Move the flipper arm out of the way
        """
        log.info("flipper_away()")
        self.flipper.ramp_down_sp = 400
        self.flipper.on_to_position(SpeedDPS(speed), 0)

    def flip(self):
        """
        Motors will sometimes stall if you call on_to_position() multiple
        times back to back on the same motor. To avoid this we call a 50ms
        sleep in flipper_hold_cube() and after each on_to_position() below.

        We have to sleep after the 2nd on_to_position() because sometimes
        flip() is called back to back.
        """
        log.info("flip()")

        if self.shutdown:
            return

        # Move the arm down to hold the cube in place
        self.flipper_hold_cube()

        # Grab the cube and pull back
        self.flipper.ramp_up_sp = 200
        self.flipper.ramp_down_sp = 0
        self.flipper.on_to_position(SpeedDPS(self.flip_speed), 190)
        sleep(0.05)

        # At this point the cube is at an angle, push it forward to
        # drop it back down in the turntable
        self.flipper.ramp_up_sp = 200
        self.flipper.ramp_down_sp = 400
        self.flipper.on_to_position(SpeedDPS(self.flip_speed_push),
                                    MindCuber.hold_cube_pos)
        sleep(0.05)

        transformation = [2, 4, 1, 3, 0, 5]
        self.apply_transformation(transformation)

    def colorarm_middle(self):
        log.info("colorarm_middle()")
        self.colorarm.on_to_position(SpeedDPS(600), -750)

    def colorarm_corner(self, square_index):
        """
        The lower the number the closer to the center
        """
        log.info("colorarm_corner(%d)" % square_index)
        position_target = -580

        if square_index == 1:
            position_target -= 10

        elif square_index == 3:
            position_target -= 30

        elif square_index == 5:
            position_target -= 20

        elif square_index == 7:
            pass

        else:
            raise ScanError(
                "colorarm_corner was given unsupported square_index %d" %
                square_index)

        self.colorarm.on_to_position(SpeedDPS(600), position_target)

    def colorarm_edge(self, square_index):
        """
        The lower the number the closer to the center
        """
        log.info("colorarm_edge(%d)" % square_index)
        position_target = -640

        if square_index == 2:
            position_target -= 20

        elif square_index == 4:
            position_target -= 40

        elif square_index == 6:
            position_target -= 20

        elif square_index == 8:
            pass

        else:
            raise ScanError(
                "colorarm_edge was given unsupported square_index %d" %
                square_index)

        self.colorarm.on_to_position(SpeedDPS(600), position_target)

    def colorarm_remove(self):
        log.info("colorarm_remove()")
        self.colorarm.on_to_position(SpeedDPS(600), 0)

    def colorarm_remove_halfway(self):
        log.info("colorarm_remove_halfway()")
        self.colorarm.on_to_position(SpeedDPS(600), -400)

    def scan_face(self, face_number):
        log.info("scan_face() %d/6" % face_number)

        if self.shutdown:
            return

        if self.flipper.position > 35:
            self.flipper_away(100)

        self.colorarm_middle()
        self.colors[int(MindCuber.scan_order[self.k])] = self.color_sensor.rgb

        self.k += 1
        i = 1
        target_pos = 115
        self.colorarm_corner(i)

        # The gear ratio is 3:1 so 1080 is one full rotation
        self.turntable.reset()
        self.turntable.on_to_position(SpeedDPS(MindCuber.rotate_speed),
                                      1080,
                                      block=False)
        self.turntable.wait_until('running')

        while True:

            # 135 is 1/8 of full rotation
            if self.turntable.position >= target_pos:
                current_color = self.color_sensor.rgb
                self.colors[int(MindCuber.scan_order[self.k])] = current_color

                i += 1
                self.k += 1

                if i == 9:
                    # Last face, move the color arm all the way out of the way
                    if face_number == 6:
                        self.colorarm_remove()

                    # Move the color arm far enough away so that the flipper
                    # arm doesn't hit it
                    else:
                        self.colorarm_remove_halfway()

                    break

                elif i % 2:
                    self.colorarm_corner(i)

                    if i == 1:
                        target_pos = 115
                    elif i == 3:
                        target_pos = 380
                    else:
                        target_pos = i * 135

                else:
                    self.colorarm_edge(i)

                    if i == 2:
                        target_pos = 220
                    elif i == 8:
                        target_pos = 1060
                    else:
                        target_pos = i * 135

            if self.shutdown:
                return

        if i < 9:
            raise ScanError('i is %d..should be 9' % i)

        self.turntable.wait_until_not_moving()
        self.turntable.off()
        self.turntable.reset()
        log.info("\n")

    def scan(self):
        log.info("scan()")
        self.colors = {}
        self.k = 0
        self.scan_face(1)

        self.flip()
        self.scan_face(2)

        self.flip()
        self.scan_face(3)

        self.rotate_cube(-1, 1)
        self.flip()
        self.scan_face(4)

        self.rotate_cube(1, 1)
        self.flip()
        self.scan_face(5)

        self.flip()
        self.scan_face(6)

        if self.shutdown:
            return

        log.info("RGB json:\n%s\n" % json.dumps(self.colors))
        self.rgb_solver = RubiksColorSolverGeneric(3)
        self.rgb_solver.enter_scan_data(self.colors)
        self.rgb_solver.crunch_colors()
        self.cube_kociemba = self.rgb_solver.cube_for_kociemba_strict()
        log.info("Final Colors (kociemba): %s" % ''.join(self.cube_kociemba))

        # This is only used if you want to rotate the cube so U is on top, F is
        # in the front, etc. You would do this if you were troubleshooting color
        # detection and you want to pause to compare the color pattern on the
        # cube vs. what we think the color pattern is.
        '''
        log.info("Position the cube so that U is on top, F is in the front, etc...to make debugging easier")
        self.rotate_cube(-1, 1)
        self.flip()
        self.flipper_away()
        self.rotate_cube(1, 1)
        input('Paused')
        '''

    def move(self, face_down):
        log.info("move() face_down %s" % face_down)

        position = self.state.index(face_down)
        actions = {
            0: ["flip", "flip"],
            1: [],
            2: ["rotate_cube_2", "flip"],
            3: ["rotate_cube_1", "flip"],
            4: ["flip"],
            5: ["rotate_cube_3", "flip"]
        }.get(position, None)

        for a in actions:

            if self.shutdown:
                break

            getattr(self, a)()

    def run_kociemba_actions(self, actions):
        log.info('Action (kociemba): %s' % ' '.join(actions))
        total_actions = len(actions)

        for (i, a) in enumerate(actions):

            if self.shutdown:
                break

            if a.endswith("'"):
                face_down = list(a)[0]
                rotation_dir = 1
            elif a.endswith("2"):
                face_down = list(a)[0]
                rotation_dir = 2
            else:
                face_down = a
                rotation_dir = 3

            log.info("Move %d/%d: %s%s (a %s)" %
                     (i, total_actions, face_down, rotation_dir, pformat(a)))
            self.move(face_down)

            if rotation_dir == 1:
                self.rotate_cube_blocked_1()
            elif rotation_dir == 2:
                self.rotate_cube_blocked_2()
            elif rotation_dir == 3:
                self.rotate_cube_blocked_3()
            log.info("\n")

    def resolve(self):

        if self.shutdown:
            return

        cmd = ['kociemba', ''.join(map(str, self.cube_kociemba))]
        output = check_output(cmd).decode('ascii')

        if 'ERROR' in output:
            msg = "'%s' returned the following error\n%s\n" % (' '.join(cmd),
                                                               output)
            log.error(msg)
            print(msg)
            sys.exit(1)

        actions = output.strip().split()
        self.run_kociemba_actions(actions)
        self.cube_done()

    def cube_done(self):
        self.flipper_away()

    def wait_for_cube_insert(self):
        rubiks_present = 0
        rubiks_present_target = 10
        log.info('wait for cube...to be inserted')

        while True:

            if self.shutdown:
                break

            dist = self.infrared_sensor.proximity

            # It is odd but sometimes when the cube is inserted
            # the IR sensor returns a value of 100...most of the
            # time it is just a value less than 50
            if dist < 50 or dist == 100:
                rubiks_present += 1
                log.info("wait for cube...distance %d, present for %d/%d" %
                         (dist, rubiks_present, rubiks_present_target))
            else:
                if rubiks_present:
                    log.info('wait for cube...cube removed (%d)' % dist)
                rubiks_present = 0

            if rubiks_present >= rubiks_present_target:
                log.info('wait for cube...cube found and stable')
                break

            time.sleep(0.1)
Example #4
0
#!/usr/bin/env python3
#!/usr/bin/env python3
from ev3dev.motor import LargeMotor, OUTPUT_A, OUTPUT_B, LargeMotor, MoveSteering
from ev3dev.motor import SpeedDPS, SpeedRPM, SpeedRPS, SpeedDPM
from time import sleep

lm1 = LargeMotor(OUTPUT_A)
lm2 = LargeMotor(OUTPUT_B)
lm1.on_for_seconds(speed=50, seconds=3)
lm2.on_for_seconds(speed=50, seconds=3)
sleep(1)
lm1.on_for_seconds(speed=50, seconds=3)
sleep(1)
lm2.on_for_seconds(speed=50, seconds=3)
sleep(1)
steer_pair = MoveSteering(OUTPUT_A, OUTPUT_B, motor_class=LargeMotor)
steer_pair.on_for_seconds(steering=-100, speed=50, seconds=2)
Example #5
0
    def __init__(self,
                 gain_gyro_angle=1700,
                 gain_gyro_rate=120,
                 gain_motor_angle=7,
                 gain_motor_angular_speed=9,
                 gain_motor_angle_error_accumulated=3,
                 power_voltage_nominal=8.0,
                 pwr_friction_offset_nom=3,
                 timing_loop_msec=30,
                 motor_angle_history_length=5,
                 gyro_drift_compensation_factor=0.05,
                 left_motor_port=OUTPUT_D,
                 right_motor_port=OUTPUT_A,
                 debug=False):
        """Create GyroBalancer."""
        # Gain parameters
        self.gain_gyro_angle = gain_gyro_angle
        self.gain_gyro_rate = gain_gyro_rate
        self.gain_motor_angle = gain_motor_angle
        self.gain_motor_angular_speed = gain_motor_angular_speed
        self.gain_motor_angle_error_accumulated =\
            gain_motor_angle_error_accumulated

        # Power parameters
        self.power_voltage_nominal = power_voltage_nominal
        self.pwr_friction_offset_nom = pwr_friction_offset_nom

        # Timing parameters
        self.timing_loop_msec = timing_loop_msec
        self.motor_angle_history_length = motor_angle_history_length
        self.gyro_drift_compensation_factor = gyro_drift_compensation_factor

        # Power supply setup
        self.power_supply = PowerSupply()

        # Gyro Sensor setup
        self.gyro = GyroSensor()
        self.gyro.mode = self.gyro.MODE_GYRO_RATE

        # Touch Sensor setup
        self.touch = TouchSensor()

        # IR Buttons setup
        # self.remote = InfraredSensor()
        # self.remote.mode = self.remote.MODE_IR_REMOTE

        # Configure the motors
        self.motor_left = LargeMotor(left_motor_port)
        self.motor_right = LargeMotor(right_motor_port)

        # Sound setup
        self.sound = Sound()

        # Open sensor and motor files
        self.gyro_file = open(self.gyro._path + "/value0", "rb")
        self.touch_file = open(self.touch._path + "/value0", "rb")
        self.encoder_left_file = open(self.motor_left._path + "/position",
                                      "rb")
        self.encoder_right_file = open(self.motor_right._path + "/position",
                                       "rb")
        self.dc_left_file = open(self.motor_left._path + "/duty_cycle_sp", "w")
        self.dc_right_file = open(self.motor_right._path + "/duty_cycle_sp",
                                  "w")

        # Drive queue
        self.drive_queue = queue.Queue()

        # Stop event for balance thread
        self.stop_balance = threading.Event()

        # Debugging
        self.debug = debug

        # Handlers for SIGINT and SIGTERM
        signal.signal(signal.SIGINT, self.signal_int_handler)
        signal.signal(signal.SIGTERM, self.signal_term_handler)
Example #6
0
class GyroBalancer(object):
    """
    Base class for a robot that stands on two wheels and uses a gyro sensor.

    Robot will keep its balance.
    """

    def __init__(self,
                 gain_gyro_angle=1700,
                 gain_gyro_rate=120,
                 gain_motor_angle=7,
                 gain_motor_angular_speed=9,
                 gain_motor_angle_error_accumulated=3,
                 power_voltage_nominal=8.0,
                 pwr_friction_offset_nom=3,
                 timing_loop_msec=30,
                 motor_angle_history_length=5,
                 gyro_drift_compensation_factor=0.05,
                 left_motor_port=OUTPUT_D,
                 right_motor_port=OUTPUT_A,
                 debug=False):
        """Create GyroBalancer."""
        # Gain parameters
        self.gain_gyro_angle = gain_gyro_angle
        self.gain_gyro_rate = gain_gyro_rate
        self.gain_motor_angle = gain_motor_angle
        self.gain_motor_angular_speed = gain_motor_angular_speed
        self.gain_motor_angle_error_accumulated =\
            gain_motor_angle_error_accumulated

        # Power parameters
        self.power_voltage_nominal = power_voltage_nominal
        self.pwr_friction_offset_nom = pwr_friction_offset_nom

        # Timing parameters
        self.timing_loop_msec = timing_loop_msec
        self.motor_angle_history_length = motor_angle_history_length
        self.gyro_drift_compensation_factor = gyro_drift_compensation_factor

        # Power supply setup
        self.power_supply = PowerSupply()

        # Gyro Sensor setup
        self.gyro = GyroSensor()
        self.gyro.mode = self.gyro.MODE_GYRO_RATE

        # Touch Sensor setup
        self.touch = TouchSensor()

        # IR Buttons setup
        # self.remote = InfraredSensor()
        # self.remote.mode = self.remote.MODE_IR_REMOTE

        # Configure the motors
        self.motor_left = LargeMotor(left_motor_port)
        self.motor_right = LargeMotor(right_motor_port)

        # Sound setup
        self.sound = Sound()

        # Open sensor and motor files
        self.gyro_file = open(self.gyro._path + "/value0", "rb")
        self.touch_file = open(self.touch._path + "/value0", "rb")
        self.encoder_left_file = open(self.motor_left._path + "/position",
                                      "rb")
        self.encoder_right_file = open(self.motor_right._path + "/position",
                                       "rb")
        self.dc_left_file = open(self.motor_left._path + "/duty_cycle_sp", "w")
        self.dc_right_file = open(self.motor_right._path + "/duty_cycle_sp",
                                  "w")

        # Drive queue
        self.drive_queue = queue.Queue()

        # Stop event for balance thread
        self.stop_balance = threading.Event()

        # Debugging
        self.debug = debug

        # Handlers for SIGINT and SIGTERM
        signal.signal(signal.SIGINT, self.signal_int_handler)
        signal.signal(signal.SIGTERM, self.signal_term_handler)

    def shutdown(self):
        """Close all file handles and stop all motors."""
        self.stop_balance.set()  # Stop balance thread
        self.motor_left.stop()
        self.motor_right.stop()
        self.gyro_file.close()
        self.touch_file.close()
        self.encoder_left_file.close()
        self.encoder_right_file.close()
        self.dc_left_file.close()
        self.dc_right_file.close()

    def _fast_read(self, infile):
        """Function for fast reading from sensor files."""
        infile.seek(0)
        return(int(infile.read().decode().strip()))

    def _fast_write(self, outfile, value):
        """Function for fast writing to motor files."""
        outfile.truncate(0)
        outfile.write(str(int(value)))
        outfile.flush()

    def _set_duty(self, motor_duty_file, duty, friction_offset,
                  voltage_comp):
        """Function to set the duty cycle of the motors."""
        # Compensate for nominal voltage and round the input
        duty_int = int(round(duty*voltage_comp))

        # Add or subtract offset and clamp the value between -100 and 100
        if duty_int > 0:
            duty_int = min(100, duty_int + friction_offset)
        elif duty_int < 0:
            duty_int = max(-100, duty_int - friction_offset)

        # Apply the signal to the motor
        self._fast_write(motor_duty_file, duty_int)

    def signal_int_handler(self, signum, frame):
        """Signal handler for SIGINT."""
        log.info('"Caught SIGINT')
        self.shutdown()
        raise GracefulShutdown()

    def signal_term_handler(self, signum, frame):
        """Signal handler for SIGTERM."""
        log.info('"Caught SIGTERM')
        self.shutdown()
        raise GracefulShutdown()

    def balance(self):
        """Run the _balance method as a thread."""
        balance_thread = threading.Thread(target=self._balance)
        balance_thread.start()

    def _balance(self):
        """Make the robot balance."""
        while True and not self.stop_balance.is_set():

            # Reset the motors
            self.motor_left.reset()  # Reset the encoder
            self.motor_right.reset()
            self.motor_left.run_direct()  # Set to run direct mode
            self.motor_right.run_direct()

            # Initialize variables representing physical signals
            # (more info on these in the docs)

            # The angle of "the motor", measured in raw units,
            # degrees for the EV3).
            # We will take the average of both motor positions as
            # "the motor" angle, which is essentially how far the middle
            # of the robot has travelled.
            motor_angle_raw = 0

            # The angle of the motor, converted to RAD (2*pi RAD
            # equals 360 degrees).
            motor_angle = 0

            # The reference angle of the motor. The robot will attempt to
            # drive forward or backward, such that its measured position
            motor_angle_ref = 0
            # equals this reference (or close enough).

            # The error: the deviation of the measured motor angle from the
            # reference. The robot attempts to make this zero, by driving
            # toward the reference.
            motor_angle_error = 0

            # We add up all of the motor angle error in time. If this value
            # gets out of hand, we can use it to drive the robot back to
            # the reference position a bit quicker.
            motor_angle_error_acc = 0

            # The motor speed, estimated by how far the motor has turned in
            # a given amount of time.
            motor_angular_speed = 0

            # The reference speed during manouvers: how fast we would like
            # to drive, measured in RAD per second.
            motor_angular_speed_ref = 0

            # The error: the deviation of the motor speed from the
            # reference speed.
            motor_angular_speed_error = 0

            # The 'voltage' signal we send to the motor.
            # We calculate a new value each time, just right to keep the
            # robot upright.
            motor_duty_cycle = 0

            # The raw value from the gyro sensor in rate mode.
            gyro_rate_raw = 0

            # The angular rate of the robot (how fast it is falling forward
            # or backward), measured in RAD per second.
            gyro_rate = 0

            # The gyro doesn't measure the angle of the robot, but we can
            # estimate this angle by keeping track of the gyro_rate value
            # in time.
            gyro_est_angle = 0

            # Over time, the gyro rate value can drift. This causes the
            # sensor to think it is moving even when it is perfectly still.
            # We keep track of this offset.
            gyro_offset = 0

            # Start
            log.info("Hold robot upright. Press touch sensor to start.")
            self.sound.speak("Press touch sensor to start.")

            self.touch.wait_for_bump()

            # Read battery voltage
            voltage_idle = self.power_supply.measured_volts
            voltage_comp = self.power_voltage_nominal / voltage_idle

            # Offset to limit friction deadlock
            friction_offset = int(round(self.pwr_friction_offset_nom *
                                        voltage_comp))

            # Timing settings for the program
            # Time of each loop, measured in seconds.
            loop_time_target = self.timing_loop_msec / 1000
            loop_count = 0  # Loop counter, starting at 0

            # A deque (a fifo array) which we'll use to keep track of
            # previous motor positions, which we can use to calculate the
            # rate of change (speed)
            motor_angle_hist =\
                deque([0], self.motor_angle_history_length)

            # The rate at which we'll update the gyro offset (precise
            # definition given in docs)
            gyro_drift_comp_rate =\
                self.gyro_drift_compensation_factor *\
                loop_time_target * RAD_PER_SEC_PER_RAW_GYRO_UNIT

            # Calibrate Gyro
            log.info("-----------------------------------")
            log.info("Calibrating...")

            # As you hold the robot still, determine the average sensor
            # value of 100 samples
            gyro_calibrate_count = 100
            for i in range(gyro_calibrate_count):
                gyro_offset = gyro_offset + self._fast_read(self.gyro_file)
                time.sleep(0.01)
            gyro_offset = gyro_offset / gyro_calibrate_count

            # Print the result
            log.info("gyro_offset: " + str(gyro_offset))
            log.info("-----------------------------------")
            log.info("GO!")
            log.info("-----------------------------------")
            log.info("Press Touch Sensor to re-start.")
            log.info("-----------------------------------")
            self.sound.beep()

            # Remember start time
            prog_start_time = time.time()

            if self.debug:
                # Data logging
                data = OrderedDict()
                loop_times = OrderedDict()
                data['loop_times'] = loop_times
                gyro_readings = OrderedDict()
                data['gyro_readings'] = gyro_readings

            # Initial fast read touch sensor value
            touch_pressed = False

            # Driving and Steering
            speed, steering = (0, 0)

            # Record start time of loop
            loop_start_time = time.time()

            # Balancing Loop
            while not touch_pressed and not self.stop_balance.is_set():

                loop_count += 1

                # Check for drive instructions and set speed / steering
                try:
                    speed, steering = self.drive_queue.get_nowait()
                    self.drive_queue.task_done()
                except queue.Empty:
                    pass

                # Read the touch sensor (the kill switch)
                touch_pressed = self._fast_read(self.touch_file)

                # Read the Motor Position
                motor_angle_raw = ((self._fast_read(self.encoder_left_file) +
                                   self._fast_read(self.encoder_right_file)) /
                                   2.0)
                motor_angle = motor_angle_raw * RAD_PER_RAW_MOTOR_UNIT

                # Read the Gyro
                gyro_rate_raw = self._fast_read(self.gyro_file)

                # Busy wait for the loop to reach target time length
                loop_time = 0
                while(loop_time < loop_time_target):
                    loop_time = time.time() - loop_start_time
                    time.sleep(0.001)

                # Calculate most recent loop time
                loop_time = time.time() - loop_start_time

                # Set start time of next loop
                loop_start_time = time.time()

                if self.debug:
                    # Log gyro data and loop time
                    time_of_sample = time.time() - prog_start_time
                    gyro_readings[time_of_sample] = gyro_rate_raw
                    loop_times[time_of_sample] = loop_time * 1000.0

                # Calculate gyro rate
                gyro_rate = (gyro_rate_raw - gyro_offset) *\
                    RAD_PER_SEC_PER_RAW_GYRO_UNIT

                # Calculate Motor Parameters
                motor_angular_speed_ref =\
                    speed * RAD_PER_SEC_PER_PERCENT_SPEED
                motor_angle_ref = motor_angle_ref +\
                    motor_angular_speed_ref * loop_time_target
                motor_angle_error = motor_angle - motor_angle_ref

                # Compute Motor Speed
                motor_angular_speed =\
                    ((motor_angle - motor_angle_hist[0]) /
                     (self.motor_angle_history_length * loop_time_target))
                motor_angular_speed_error = motor_angular_speed
                motor_angle_hist.append(motor_angle)

                # Compute the motor duty cycle value
                motor_duty_cycle =\
                    (self.gain_gyro_angle * gyro_est_angle +
                     self.gain_gyro_rate * gyro_rate +
                     self.gain_motor_angle * motor_angle_error +
                     self.gain_motor_angular_speed *
                     motor_angular_speed_error +
                     self.gain_motor_angle_error_accumulated *
                     motor_angle_error_acc)

                # Apply the signal to the motor, and add steering
                self._set_duty(self.dc_right_file, motor_duty_cycle + steering,
                               friction_offset, voltage_comp)
                self._set_duty(self.dc_left_file, motor_duty_cycle - steering,
                               friction_offset, voltage_comp)

                # Update angle estimate and gyro offset estimate
                gyro_est_angle = gyro_est_angle + gyro_rate *\
                    loop_time_target
                gyro_offset = (1 - gyro_drift_comp_rate) *\
                    gyro_offset + gyro_drift_comp_rate * gyro_rate_raw

                # Update Accumulated Motor Error
                motor_angle_error_acc = motor_angle_error_acc +\
                    motor_angle_error * loop_time_target

            # Closing down & Cleaning up

            # Loop end time, for stats
            prog_end_time = time.time()

            # Turn off the motors
            self._fast_write(self.dc_left_file, 0)
            self._fast_write(self.dc_right_file, 0)

            # Wait for the Touch Sensor to be released
            while self.touch.is_pressed:
                time.sleep(0.01)

            # Calculate loop time
            avg_loop_time = (prog_end_time - prog_start_time) / loop_count
            log.info("Loop time:" + str(avg_loop_time * 1000) + "ms")

            # Print a stop message
            log.info("-----------------------------------")
            log.info("STOP")
            log.info("-----------------------------------")

            if self.debug:
                # Dump logged data to file
                with open("data.txt", 'w') as data_file:
                    json.dump(data, data_file)

    def _move(self, speed=0, steering=0, seconds=None):
        """Move robot."""
        self.drive_queue.put((speed, steering))
        if seconds is not None:
            time.sleep(seconds)
            self.drive_queue.put((0, 0))
        self.drive_queue.join()

    def move_forward(self, seconds=None):
        """Move robot forward."""
        self._move(speed=SPEED_MAX, steering=0, seconds=seconds)

    def move_backward(self, seconds=None):
        """Move robot backward."""
        self._move(speed=-SPEED_MAX, steering=0, seconds=seconds)

    def rotate_left(self, seconds=None):
        """Rotate robot left."""
        self._move(speed=0, steering=STEER_MAX, seconds=seconds)

    def rotate_right(self, seconds=None):
        """Rotate robot right."""
        self._move(speed=0, steering=-STEER_MAX, seconds=seconds)

    def stop(self):
        """Stop robot (balancing will continue)."""
        self._move(speed=0, steering=0)