MonsterBorg - The ultimate Pi robot

Use a game controller or joystick to drive MonsterBorg

Most of us want to be able to control our robots manually one way or another. Bluetooth wireless controllers such as a PS3 controller or Wiimote are great way to do this with Raspberry Pi based robots as the Pi 3 has Bluetooth built in to the board :)

In this example we will show how these game controllers can be used to drive your MonsterBorg. Any controller that the Raspberry Pi can recognise as a joystick should work.

Parts

In order to run this script you will only need:

  • A MonsterBorg
  • A wireless controller, such as a PS3 controller

Connecting the controller

Each controller is slightly different here, but the basic idea is to pair the controller using Bluetooth and it should show up as a joystick.

The PS3 is a little tricky as you need to tell the controller to talk with the Raspberry Pi first. See either:

You can use the same instructions for a PS4 remote with the following changes:

  1. Skip using sixpair, it is not needed
  2. When trying to pair the controller press the PS and Share buttons at the same time to put the controller into pairing mode

There are plenty of guides on the web for setting up other controllers.

Controls

We have kept the controls nice and simple so there is not much to remember:

Speed (left stick) controls the speed of the motors and Turn (right stick) changes the steering angle. In other words pushing the left stick fully up will drive at full speed, then pushing the right stick left or right at the same time will steer the MonsterBorg.

The Move slowly (L2) button limits the motors to 50% speed when held, useful for tricky maneuvering in tight spaces. The Enable tank steering (R2) button puts the left / right steering into tank mode when held, allowing the MonsterBorg to turn on the spot.

For other controllers you will probably need to change the values in "Settings for the joystick" to work properly. You can also change the values to swap buttons around to your preferred layout :)

Led indicator

When the script is running the ThunderBorg on-board LED will be in one of three states:

  1. Once the script is started and waiting the LED will be blue
  2. When the controller is connected the LED will indicate battery level between green and red
  3. If a potential motor issue is detected the LED will show purple

The LED may show a fault incorrectly when the MonsterBorg has not yet moved, this is normal. If purple is shown straight away try moving slowly and see if the fault clears itself.

For the standard 10x rechargeable AA battery pack we have found having the battery monitoring range set as 9.5 to 13.5 V works well:

These values can be set using the ~/thunderborg/tbSetBatteryLimits.py script.

Get the example

The example is part of the standard set of MonsterBorg examples installed during the getting started instructions: bash <(curl https://www.piborg.org/install-monsterborg.txt)

Run once

Go to the MonsterBorg code directory:
cd ~/monsterborg
and run the script using the simple launcher:
./runMonsterJoy.sh

Run at startup

Open /etc/rc.local to make an addition using:
sudo nano /etc/rc.local

Then add this line just above the exit 0 line:
/home/pi/monsterborg/runMonsterJoy.sh &

Finally press CTRL+O, ENTER to save the file followed by CTRL+X to exit nano.
Next time you power up the Raspberry Pi it should start the script for you :)

Full code listing - monsterJoy.py

#!/usr/bin/env python
# coding: Latin-1

# Load library functions we want
import time
import os
import sys
import pygame
import ThunderBorg

# Re-direct our output to standard error, we need to ignore standard out to hide some nasty print statements from pygame
sys.stdout = sys.stderr

# Setup the ThunderBorg
TB = ThunderBorg.ThunderBorg()
#TB.i2cAddress = 0x15                  # Uncomment and change the value if you have changed the board address
TB.Init()
if not TB.foundChip:
    boards = ThunderBorg.ScanForThunderBorg()
    if len(boards) == 0:
        print 'No ThunderBorg found, check you are attached :)'
    else:
        print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress)
        for board in boards:
            print '    %02X (%d)' % (board, board)
        print 'If you need to change the I²C address change the setup line so it is correct, e.g.'
        print 'TB.i2cAddress = 0x%02X' % (boards[0])
    sys.exit()
# Ensure the communications failsafe has been enabled!
failsafe = False
for i in range(5):
    TB.SetCommsFailsafe(True)
    failsafe = TB.GetCommsFailsafe()
    if failsafe:
        break
if not failsafe:
    print 'Board %02X failed to report in failsafe mode!' % (TB.i2cAddress)
    sys.exit()

# Settings for the joystick
axisUpDown = 1                          # Joystick axis to read for up / down position
axisUpDownInverted = False              # Set this to True if up and down appear to be swapped
axisLeftRight = 2                       # Joystick axis to read for left / right position
axisLeftRightInverted = False           # Set this to True if left and right appear to be swapped
buttonSlow = 8                          # Joystick button number for driving slowly whilst held (L2)
slowFactor = 0.5                        # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed
buttonFastTurn = 9                      # Joystick button number for turning fast (R2)
interval = 0.00                         # Time between updates in seconds, smaller responds faster but uses more processor time

# Power settings
voltageIn = 1.2 * 10                    # Total battery voltage to the ThunderBorg
voltageOut = 12.0 * 0.95                # Maximum motor voltage, we limit it to 95% to allow the RPi to get uninterrupted power

# Setup the power limits
if voltageOut > voltageIn:
    maxPower = 1.0
else:
    maxPower = voltageOut / float(voltageIn)

# Show battery monitoring settings
battMin, battMax = TB.GetBatteryMonitoringLimits()
battCurrent = TB.GetBatteryReading()
print 'Battery monitoring settings:'
print '    Minimum  (red)     %02.2f V' % (battMin)
print '    Half-way (yellow)  %02.2f V' % ((battMin + battMax) / 2)
print '    Maximum  (green)   %02.2f V' % (battMax)
print
print '    Current voltage    %02.2f V' % (battCurrent)
print

# Setup pygame and wait for the joystick to become available
TB.MotorsOff()
TB.SetLedShowBattery(False)
TB.SetLeds(0,0,1)
os.environ["SDL_VIDEODRIVER"] = "dummy" # Removes the need to have a GUI window
pygame.init()
#pygame.display.set_mode((1,1))
print 'Waiting for joystick... (press CTRL+C to abort)'
while True:
    try:
        try:
            pygame.joystick.init()
            # Attempt to setup the joystick
            if pygame.joystick.get_count() < 1:
                # No joystick attached, set LEDs blue
                TB.SetLeds(0,0,1)
                pygame.joystick.quit()
                time.sleep(0.1)
            else:
                # We have a joystick, attempt to initialise it!
                joystick = pygame.joystick.Joystick(0)
                break
        except pygame.error:
            # Failed to connect to the joystick, set LEDs blue
            TB.SetLeds(0,0,1)
            pygame.joystick.quit()
            time.sleep(0.1)
    except KeyboardInterrupt:
        # CTRL+C exit, give up
        print '\nUser aborted'
        TB.SetCommsFailsafe(False)
        TB.SetLeds(0,0,0)
        sys.exit()
print 'Joystick found'
joystick.init()
TB.SetLedShowBattery(True)
ledBatteryMode = True
try:
    print 'Press CTRL+C to quit'
    driveLeft = 0.0
    driveRight = 0.0
    running = True
    hadEvent = False
    upDown = 0.0
    leftRight = 0.0
    # Loop indefinitely
    while running:
        # Get the latest events from the system
        hadEvent = False
        events = pygame.event.get()
        # Handle each event individually
        for event in events:
            if event.type == pygame.QUIT:
                # User exit
                running = False
            elif event.type == pygame.JOYBUTTONDOWN:
                # A button on the joystick just got pushed down
                hadEvent = True
            elif event.type == pygame.JOYAXISMOTION:
                # A joystick has been moved
                hadEvent = True
            if hadEvent:
                # Read axis positions (-1 to +1)
                if axisUpDownInverted:
                    upDown = -joystick.get_axis(axisUpDown)
                else:
                    upDown = joystick.get_axis(axisUpDown)
                if axisLeftRightInverted:
                    leftRight = -joystick.get_axis(axisLeftRight)
                else:
                    leftRight = joystick.get_axis(axisLeftRight)
                # Apply steering speeds
                if not joystick.get_button(buttonFastTurn):
                    leftRight *= 0.5
                # Determine the drive power levels
                driveLeft = -upDown
                driveRight = -upDown
                if leftRight < -0.05:
                    # Turning left
                    driveLeft *= 1.0 + (2.0 * leftRight)
                elif leftRight > 0.05:
                    # Turning right
                    driveRight *= 1.0 - (2.0 * leftRight)
                # Check for button presses
                if joystick.get_button(buttonSlow):
                    driveLeft *= slowFactor
                    driveRight *= slowFactor
                # Set the motors to the new speeds
                TB.SetMotor1(driveRight * maxPower)
                TB.SetMotor2(driveLeft * maxPower)
        # Change LEDs to purple to show motor faults
        if TB.GetDriveFault1() or TB.GetDriveFault2():
            if ledBatteryMode:
                TB.SetLedShowBattery(False)
                TB.SetLeds(1,0,1)
                ledBatteryMode = False
        else:
            if not ledBatteryMode:
                TB.SetLedShowBattery(True)
                ledBatteryMode = True
        # Wait for the interval period
        time.sleep(interval)
    # Disable all drives
    TB.MotorsOff()
except KeyboardInterrupt:
    # CTRL+C exit, disable all drives
    TB.MotorsOff()
    TB.SetCommsFailsafe(False)
    TB.SetLedShowBattery(False)
    TB.SetLeds(0,0,0)
print
Subscribe to Comments for &quot;MonsterBorg - The ultimate Pi robot&quot;