RemoteKeyBorg2 - Control your PicoBorg car with LedBorg lights remotely with the keyboard

So you have a PicoBorg based robot and an LedBorg you want to control at the same time, then we need an upgrade to RemoteKeyBorg!
RemoteKeyBorg2 is a pair of scripts that takes RemoteKeyBorg and adds functionality for sequencing an LedBorg at the same time.
The example sequences for RemoteKeyBorg2C use the keyboard keys 1, 2, 3, and 0 to set the sequence the LedBorg will run.

If you need a way of connecting multiple expansion board to your Raspberry Pi (for this and many other things), check out TriBorg.
To get a PicoBorg robot base ready see PiCy for a full set of instructions, or RemoteKeyBorg for the motor connections only.


Now we need the new scripts.
RemoteKeyBorg2 is a script that comes in two parts:
  • RemoteKeyBorg2S.py (Server)
    This script runs on the Raspberry Pi with the PicoBorg + LedBorg and sets the PicoBorg drives when told to
    For remote control we suggest this Raspberry Pi is connected to the network using WiFi, so it is free of cables :)
  • RemoteKeyBorg2C.py (Client)
    This script runs on the Raspberry Pi which is controlling the robot, it loads a blank window which responds to up, left and right
    This script can be run on any computer connected to the same network, running Python and pygame, but it needs a GUI available (no telnets I am afraid...)
    If testing this may even be run on the same Raspberry Pi, but that would defeat the point :)
There are a few variables in the scripts you may wish to set to change options:
  • broadcastIP in RemoteKeyBorg2C.py, line 10
    IP address to send to (Raspberry Pi with the PicoBorg), may be a single machine (e.g. 192.168.1.5) or a broadcast (e.g. 192.168.1.255) where '255' is used to indicate that number is everybody
  • broadcastPort in RemoteKeyBorg2C.py, line 11
    Number used to identify who gets network messages, if two copies of RemoteKeyBorg2 are used in the same network this should be changed to identify which copy is which
  • leftDrive in RemoteKeyBorg2C.py, line 12
    Drive number for the left wheel, change this if your wiring does not match the example diagram
  • rightDrive in RemoteKeyBorg2C.py, line 13
    Drive number for the right wheel, change this if your wiring does not match the example diagram
  • interval in RemoteKeyBorg2C.py, line 14
    Delay between checking for keyboard updates, smaller numbers respond faster but will use more processor time
  • regularUpdate in RemoteKeyBorg2C.py, line 15
    Set to True the script will send a command at every interval, set to False it will only send a command when a key changes
  • ledSeq0 in RemoteKeyBorg2C.py, line 23
    Sets the LedBorg sequence (speed and colours) for the 0 key, default example is off
  • ledSeq1 in RemoteKeyBorg2C.py, line 24
    Sets the LedBorg sequence (speed and colours) for the 1 key, default example is a police siren red/blue pattern
  • ledSeq2 in RemoteKeyBorg2C.py, line 25
    Sets the LedBorg sequence (speed and colours) for the 2 key, default example is a cycle of the colour spectrum
  • ledSeq3 in RemoteKeyBorg2C.py, line 26
    Sets the LedBorg sequence (speed and colours) for the 3 key, default example is a slow changing red pulse
  • colours in RemoteKeyBorg2S.py, line 30
    The start-up LedBorg sequence, colour codes only, no delay time, default is no pattern / off
  • coloursDelay in RemoteKeyBorg2S.py, line 31
    The start-up LedBorg sequence speed, default is to change every 300 ms
  • portListen in RemoteKeyBorg2S.py, line 48
    If you change the port your RemoteKeyBorg2C.py is using, change this to match
Here's the code, you can download the RemoteKeyBorg2S script file as text here,
and you can download the RemoteKeyBorg2C script file as text here.
Save the text files on your Raspberry Pis as RemoteKeyBorg2S.py and RemoteKeyBorg2C.py respectively.
Make the scripts executable using
chmod +x RemoteKeyBorg2*.py
and run on the Raspberry Pi with the PicoBorg using
sudo ./RemoteKeyBorg2S.py
and run on the commanding Raspberry Pi using
./RemoteKeyBorg2C.py

RemoteKeyBorg2S

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

# Load library functions we want
import SocketServer
import time
import threading
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)

# Set which GPIO pins the drive outputs are connected to
DRIVE_1 = 4
DRIVE_2 = 18
DRIVE_3 = 8
DRIVE_4 = 7

# Set all of the drive pins as output pins
GPIO.setup(DRIVE_1, GPIO.OUT)
GPIO.setup(DRIVE_2, GPIO.OUT)
GPIO.setup(DRIVE_3, GPIO.OUT)
GPIO.setup(DRIVE_4, GPIO.OUT)

# Map of drives to pins
lDrives = [DRIVE_1, DRIVE_2, DRIVE_3, DRIVE_4]

# Setup the default LedBorg pattern
global colours
global coloursDelay
global coloursUpdated
colours = []                                    # Default running sequence
coloursDelay = 0.3                              # Time between updates in seconds for the LED
coloursUpdated = False                          # When True the sequence will be re-started

# Make a function to set the LedBorg colour
def SetColour(colour):
    LedBorg=open('/dev/ledborg','w')
    LedBorg.write(colour)
    LedBorg.close()

# Function to set all drives off
def MotorOff():
    GPIO.output(DRIVE_1, GPIO.LOW)
    GPIO.output(DRIVE_2, GPIO.LOW)
    GPIO.output(DRIVE_3, GPIO.LOW)
    GPIO.output(DRIVE_4, GPIO.LOW)

# Settings for the RemoteKeyBorg server
portListen = 9038                       # What messages to listen for (LEDB on an LCD)

# Class for running the LedBorg
class LedBorgSequencer(threading.Thread):
    # The code which will be run when the thread is started
    def run(self):
        global isRunning
        global colours
        global coloursDelay
        global coloursUpdated

        # Keep running while the system has not been terminated
        while isRunning:
            if len(colours) == 0:
                # No colours (not cycling), wait until the sequence is updated
                while not coloursUpdated:
                    time.sleep(coloursDelay)
                    if not isRunning:
                        # We have been terminated, skip to the outer loop
                        break
                coloursUpdated = False
            else:
                # Loop each colour in turn
                for colour in colours:
                    SetColour(colour)
                    time.sleep(coloursDelay)
                    if coloursUpdated:
                        # We have received a new colour sequence
                        coloursUpdated = False
                        if len(colours) == 0:
                            # The new sequence is empty, turn the LedBorg off
                            SetColour('000')
                        break
                    if not isRunning:
                        # We have been terminated, skip to the outer loop
                        break


# Class used to handle UDP messages
class PicoBorgHandler(SocketServer.BaseRequestHandler):
    # Function called when a new message has been received
    def handle(self):
        global isRunning
        global colours
        global coloursDelay
        global coloursUpdated

        request, socket = self.request          # Read who spoke to us and what they said
        request = request.upper()               # Convert command to upper case
        driveCommands = request.split(',')      # Separate the command into individual drives
        if len(driveCommands) == 1:
            # Special commands
            specialCommands = request.split(':')    # Separate the special command into parameters
            if specialCommands[0] == 'ALLOFF':
                # Turn all drives off
                MotorOff()
                print 'All drives off'
            elif specialCommands[0] == 'EXIT':
                # Exit the program
                isRunning = False
            elif specialCommands[0] == 'LEDBORG':
                # LedBorg command sequence
                if len(specialCommands) > 1:
                    # First parameter is the sequence rate
                    try:
                        coloursDelay = float(specialCommands[1])
                    except:
                        print 'LedBorg sequence speed "%s" is not a valid number!' % (specialCommands[1])
                    # Subsequent parameters make up the colour list
                    colours = specialCommands[2:]
                    if len(colours) > 0:
                        print 'LedBorg sequence: ',
                        for colour in colours:
                            print colour + ' ',
                        print 'at %f intervals' % (coloursDelay)
                    else:
                        print 'LedBorg sequence off, check at %f intervals' % (coloursDelay)
                    # Flag that we have a new colours list
                    coloursUpdated = True
                else:
                    print 'LedBorg sequence expects at least a speed to be defined, e.g. "LEDBORG:0.3"'
            else:
                # Unknown command
                print 'Special command "%s" not recognised' % (request)
        elif len(driveCommands) == len(lDrives):
            # For each drive we check the command
            for driveNo in range(len(driveCommands)):
                command = driveCommands[driveNo]
                if command == 'ON':
                    # Set drive on
                    GPIO.output(lDrives[driveNo], GPIO.HIGH)
                elif command == 'OFF':
                    # Set drive off
                    GPIO.output(lDrives[driveNo], GPIO.LOW)
                elif command == 'X':
                    # No command for this drive
                    pass
                else:
                    # Unknown command
                    print 'Drive %d command "%s" not recognised!' % (driveNo, command)
        else:
            # Did not get the right number of drive commands
            print 'Command "%s" did not have %d parts!' % (request, len(lDrives))

try:
    global isRunning

    # Start by turning all drives off
    MotorOff()
    raw_input('You can now turn on the power, press ENTER to continue')
    # Setup the UDP listener
    remoteKeyBorgServer = SocketServer.UDPServer(('', portListen), PicoBorgHandler)
    isRunning = True
    # Start the LedBorg sequence handler loop
    ledBorgSequencer = LedBorgSequencer()
    ledBorgSequencer.start()
    # Loop until terminated remotely
    while isRunning:
        remoteKeyBorgServer.handle_request()
    # Turn off the drives and release the GPIO pins
    print 'Finished'
    MotorOff()
    # Wait for the LedBorg sequence handler loop to terminate
    ledBorgSequencer.join()
    if len(colours) > 0:
        SetColour('000')
    raw_input('Turn the power off now, press ENTER to continue')
    GPIO.cleanup()
except KeyboardInterrupt:
    # CTRL+C exit, turn off the drives and release the GPIO pins
    isRunning = False
    print 'Terminated'
    MotorOff()
    # Wait for the LedBorg sequence handler loop to terminate
    ledBorgSequencer.join()
    if len(colours) > 0:
        SetColour('000')
    raw_input('Turn the power off now, press ENTER to continue')
    GPIO.cleanup()

RemoteKeyBorg2C

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

# Load library functions we want
import socket
import time
import pygame

# Settings for the RemoteKeyBorg client
broadcastIP = '192.168.0.255'           # IP address to send to, 255 in one or more positions is a broadcast / wild-card
broadcastPort = 9038                    # What message number to send with (LEDB on an LCD)
leftDrive = 1                           # Drive number for left motor
rightDrive = 4                          # Drive number for right motor
interval = 0.1                          # Time between keyboard updates in seconds, smaller responds faster but uses more processor time
regularUpdate = True                    # If True we send a command at a regular interval, if False we only send commands when keys are pressed or released

# Setup the connection for sending on
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)       # Create the socket
sender.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)                        # Enable broadcasting (sending to many IPs based on wild-cards)
sender.bind(('0.0.0.0', 0))                                                         # Set the IP and port number to use locally, IP 0.0.0.0 means all connections and port 0 means assign a number for us (do not care)

# LedBorg sequences, first entry is delay between steps in seconds
ledSeq0 = ['0.5']   # Off / no sequence
ledSeq1 = ['0.1', '100', '200', '100', '001', '002', '001']
ledSeq2 = ['0.3', '200', '210', '220', '120', '020', '021', '022', '012', '002', '102', '202', '201']
ledSeq3 = ['0.5', '100', '200', '211', '200']

# Setup pygame and key states
global hadEvent
global moveUp
global moveDown
global moveLeft
global moveRight
global moveQuit
global newLedSequence
global ledSequence
hadEvent = True
moveUp = False
moveDown = False
moveLeft = False
moveRight = False
moveQuit = False
newLedSequence = False
ledSequence = ledSeq0
pygame.init()
screen = pygame.display.set_mode([300,300])
pygame.display.set_caption("RemoteKeyBorg - Press [ESC] to quit")

# Function to handle pygame events
def PygameHandler(events):
    # Variables accessible outside this function
    global hadEvent
    global moveUp
    global moveDown
    global moveLeft
    global moveRight
    global moveQuit
    global newLedSequence
    global ledSequence
    # Handle each event individually
    for event in events:
        if event.type == pygame.QUIT:
            # User exit
            hadEvent = True
            moveQuit = True
        elif event.type == pygame.KEYDOWN:
            # A key has been pressed, see if it is one we want
            hadEvent = True
            if event.key == pygame.K_UP:
                moveUp = True
            elif event.key == pygame.K_DOWN:
                moveDown = True
            elif event.key == pygame.K_LEFT:
                moveLeft = True
            elif event.key == pygame.K_RIGHT:
                moveRight = True
            elif event.key == pygame.K_ESCAPE:
                moveQuit = True
            elif event.key == pygame.K_0:
                ledSequence = ledSeq0
                newLedSequence = True
            elif event.key == pygame.K_1:
                ledSequence = ledSeq1
                newLedSequence = True
            elif event.key == pygame.K_2:
                ledSequence = ledSeq2
                newLedSequence = True
            elif event.key == pygame.K_3:
                ledSequence = ledSeq3
                newLedSequence = True
        elif event.type == pygame.KEYUP:
            # A key has been released, see if it is one we want
            hadEvent = True
            if event.key == pygame.K_UP:
                moveUp = False
            elif event.key == pygame.K_DOWN:
                moveDown = False
            elif event.key == pygame.K_LEFT:
                moveLeft = False
            elif event.key == pygame.K_RIGHT:
                moveRight = False
            elif event.key == pygame.K_ESCAPE:
                moveQuit = False

try:
    print 'Press [ESC] to quit'
    # Loop indefinitely
    while True:
        # Get the currently pressed keys on the keyboard
        PygameHandler(pygame.event.get())
        if newLedSequence:
            newLedSequence = False
            # Send a command to change the LedBorg sequence
            command = 'LEDBORG:'
            for ledStep in ledSequence:
                command += ledStep + ':'
            command = command[:-1]                                  # Strip the trailing colon
            sender.sendto(command, (broadcastIP, broadcastPort))
        if hadEvent or regularUpdate:
            # Keys have changed, generate the command list based on keys
            hadEvent = False
            driveCommands = ['X', 'X', 'X', 'X']                    # Default to do not change
            if moveQuit:
                break
            elif moveLeft:
                driveCommands[leftDrive - 1] = 'OFF'
                driveCommands[rightDrive - 1] = 'ON'
            elif moveRight:
                driveCommands[leftDrive - 1] = 'ON'
                driveCommands[rightDrive - 1] = 'OFF'
            elif moveUp:
                driveCommands[leftDrive - 1] = 'ON'
                driveCommands[rightDrive - 1] = 'ON'
            else:
                # None of our expected keys, stop
                driveCommands[leftDrive - 1] = 'OFF'
                driveCommands[rightDrive - 1] = 'OFF'
            # Send the drive commands
            command = ''
            for driveCommand in driveCommands:
                command += driveCommand + ','
            command = command[:-1]                                  # Strip the trailing comma
            sender.sendto(command, (broadcastIP, broadcastPort))
        # Wait for the interval period
        time.sleep(interval)
    # Inform the server to stop
    sender.sendto('ALLOFF', (broadcastIP, broadcastPort))
except KeyboardInterrupt:
    # CTRL+C exit, inform the server to stop
    sender.sendto('ALLOFF', (broadcastIP, broadcastPort))
Subscribe to Comments for "RemoteKeyBorg2 - Control your PicoBorg car with LedBorg lights remotely with the keyboard"