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:
- How to connect a PS3 remote to the new Raspberry Pi Zero W if you are running a recent version of Raspbian with Pixel
- PlayStation 3 controller help sheet if you are using a terminal or older version of Raspbian
You can use the same instructions for a PS4 remote with the following changes:
- Skip using
sixpair
, it is not needed - 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:
- Once the script is started and waiting the LED will be blue
- When the controller is connected the LED will indicate battery level between green and red
- 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