Tiny4WD Robot Drive

Tiny4WD is a great first robot kit designed by Brian Corteil (@CannonFodder and @CoretecRobotics). You can buy the kits from the lovely, if money-sucking, pirates at @pimoroni from their online shop. The code below shows, with a few extra bits and pieces needed in case you’re running without the robot libraries, how to use this library to drive it around. The left analogue stick controls the robot, and the HOME button exits.

../_images/tiny4wd.jpg
  1# Code for Brian Corteil's Tiny 4WD robot, based on code from Brian as modified by Emma Norling.
  2# Subsequently modified by Tom Oinn to add dummy functions when no explorer hat is available,
  3# use any available joystick, use the new function in 1.0.6 of approxeng.input to get multiple
  4# axis values in a single call, use implicit de-structuring of tuples to reduce verbosity, add
  5# an exception to break out of the control loop on pressing HOME etc.
  6
  7from time import sleep
  8
  9try:
 10    # Attempt to import the Explorer HAT library. If this fails, because we're running somewhere
 11    # that doesn't have the library, we create dummy functions for set_speeds and stop_motors which
 12    # just print out what they'd have done. This is a fairly common way to deal with hardware that
 13    # may or may not exist! Obviously if you're actually running this on one of Brian's robots you
 14    # should have the Explorer HAT libraries installed, this is really just so I can test on my big
 15    # linux desktop machine when coding.
 16
 17    from explorerhat import motor
 18
 19    print('Explorer HAT library available.')
 20
 21
 22    def set_speeds(power_left, power_right):
 23        """
 24        As we have an motor hat, we can use the motors
 25        
 26        :param power_left: 
 27            Power to send to left motor
 28        :param power_right: 
 29            Power to send to right motor, will be inverted to reflect chassis layout
 30        """
 31        motor.one.speed(-power_right)
 32        motor.two.speed(power_left)
 33
 34
 35    def stop_motors():
 36        """
 37        As we have an motor hat, stop the motors using their motors call
 38        """
 39        motor.stop()
 40
 41except ImportError:
 42
 43    print('No explorer HAT library available, using dummy functions.')
 44
 45
 46    def set_speeds(power_left, power_right):
 47        """
 48        No motor hat - print what we would have sent to it if we'd had one.
 49        """
 50        print('Left: {}, Right: {}'.format(power_left, power_right))
 51        sleep(0.1)
 52
 53
 54    def stop_motors():
 55        """
 56        No motor hat, so just print a message.
 57        """
 58        print('Motors stopping')
 59
 60# All we need, as we don't care which controller we bind to, is the ControllerResource
 61from approxeng.input.selectbinder import ControllerResource
 62
 63
 64class RobotStopException(Exception):
 65    """
 66    The simplest possible subclass of Exception, we'll raise this if we want to stop the robot
 67    for any reason. Creating a custom exception like this makes the code more readable later.
 68    """
 69    pass
 70
 71
 72def mixer(yaw, throttle, max_power=100):
 73    """
 74    Mix a pair of joystick axes, returning a pair of wheel speeds. This is where the mapping from
 75    joystick positions to wheel powers is defined, so any changes to how the robot drives should
 76    be made here, everything else is really just plumbing.
 77    
 78    :param yaw: 
 79        Yaw axis value, ranges from -1.0 to 1.0
 80    :param throttle: 
 81        Throttle axis value, ranges from -1.0 to 1.0
 82    :param max_power: 
 83        Maximum speed that should be returned from the mixer, defaults to 100
 84    :return: 
 85        A pair of power_left, power_right integer values to send to the motor driver
 86    """
 87    left = throttle + yaw
 88    right = throttle - yaw
 89    scale = float(max_power) / max(1, abs(left), abs(right))
 90    return int(left * scale), int(right * scale)
 91
 92
 93# Outer try / except catches the RobotStopException we just defined, which we'll raise when we want to
 94# bail out of the loop cleanly, shutting the motors down. We can raise this in response to a button press
 95try:
 96    while True:
 97        # Inner try / except is used to wait for a controller to become available, at which point we
 98        # bind to it and enter a loop where we read axis values and send commands to the motors.
 99        try:
100            # Bind to any available joystick, this will use whatever's connected as long as the library
101            # supports it.
102            with ControllerResource(dead_zone=0.1, hot_zone=0.2) as joystick:
103                print('Controller found, press HOME button to exit, use left stick to drive.')
104                print(joystick.controls)
105                # Loop until the joystick disconnects, or we deliberately stop by raising a
106                # RobotStopException
107                while joystick.connected:
108                    # Get joystick values from the left analogue stick
109                    x_axis, y_axis = joystick['lx', 'ly']
110                    # Get power from mixer function
111                    power_left, power_right = mixer(yaw=x_axis, throttle=y_axis)
112                    # Set motor speeds
113                    set_speeds(power_left, power_right)
114                    # Get a ButtonPresses object containing everything that was pressed since the last
115                    # time around this loop.
116                    joystick.check_presses()
117                    # Print out any buttons that were pressed, if we had any
118                    if joystick.has_presses:
119                        print(joystick.presses)
120                    # If home was pressed, raise a RobotStopException to bail out of the loop
121                    # Home is generally the PS button for playstation controllers, XBox for XBox etc
122                    if 'home' in joystick.presses:
123                        raise RobotStopException()
124        except IOError:
125            # We get an IOError when using the ControllerResource if we don't have a controller yet,
126            # so in this case we just wait a second and try again after printing a message.
127            print('No controller found yet')
128            sleep(1)
129except RobotStopException:
130    # This exception will be raised when the home button is pressed, at which point we should
131    # stop the motors.
132    stop_motors()