Controlling Your Robot: USB HID/Wireless Keyboards

In this guide we’ll learn about remote-controlling robots with a USB HID device of your choice. Preferably something with buttons like a Rii mini keyboard or a tiny remote control.

Of all the different ways to control a robot, using a USB device is probably one of the easiest methods. Not only do you probably already have one handy but it’s one of the more simple things to get up and running.

Installing The Software

First, you’re going to need some way of polling a HID device from Python. I’ve seen some creative attempts at this by reading one key at a time from the terminal. Don’t do this. It’s slow, unwieldy not at all fun to control.

The trick is to grab a little library called PyUSB. Before installing it, you'll need to download libusb-dev.

sudo apt-get install libusb-dev

Then go ahead and grab PyUSB from GitHub, and install as follows:

git clone https://github.com/walac/pyusb
cd pyusb
sudo python setup.py install

Finding Your Keyboard

Next you’ll need a USB keyboard, USB presenter remote or any other HID device that pretends to be a keyboard.

Warning! If you're working directly on your Pi you'll need a separate keyboard to type on! Once Python gloms onto a keyboard, you wont be able to use it for anything else until your script is finished.

To read your keyboard in Python you’ll need to know its Vendor ID and Product ID. These are easy to find by plugging it into your Pi and running:

sudo lsusb

Sometimes your device will be the blank line. You might see a number like 1997:2433. The first part is the Vendor ID, and the second part is the Product ID. 1997 is Rii's Vendor ID so if you have a Rii keyboard you should look for it.

If you see multiple blank entries using lsusb, or have trouble identifying your device in the list then you can try:

sudo lsusb -v

This will give you a very verbose output, which you’ll have to scroll through and locate your device. It can be a bit tricky to find the relevant line, but you only have to do this once.

Once you’ve got the Vendor and Device IDs you're going to need the Python code to plug these into. The first chunk of code we’ll use will help us see exactly what output each keypress on your device results in, this way we can start to map the controls we want to use.

Connecting

To get connected to your USB device, you'll need something along the lines of this:

Note: The Vendor ID and Product ID are hexadecimal numbers, this means you should prefix them with 0x like below.

import usb.core
import usb.util
import time

USB_IF      = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS

USB_VENDOR  = 0x1997 # Rii
USB_PRODUCT = 0x2433 # Mini Wireless Keyboard

dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)

endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
  dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

while True:
    control = None

  try:
    control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
    print control
  except:
    pass

  time.sleep(0.01) # Let CTRL+C actually exit

If everything works as expected then this code, when run as root, will show you what happens when you press some keys on your USB HID device.

Run the code, press some keys and see what happens.

If it fails, try double-checking your USB Device and Vendor ID, and make sure you're running as root.

Controlling A Robot

If you've successfully run the code and smushed some keys then you'll notice that the input is read into an array, a keyboard buffer if you will.

The dev.read call will success when a key is pressed or released, and the updated buffer will be received containing only the keys which are currently pressed.

Because it's an array, it can be checked against in a remarkably simple way:

if my_key in control:
    # My key is pressed
    my_key_pressed = True
else:
    # My key is released
    my_key_pressed = False

If you run a motor forward when my_key_pressed == True, and stop it when my_key_pressed == False then you've got a simple control interface. This is great for driving a left tank track forward.

Finding The Right Keys

Now that you've got a basic example that shows you which number corresponds to a keypress you should press the keys you want to use for your robot and write them down. You'll need them for the values of KEY_LEFT etc in a moment.

The Finished Code

Below you'll find a more complete example to expand upon, it's what I use to drive my tracked robot, which uses an Explorer HAT Pro and LEGO Technic Power Functions&right; motors.

Give this code a whirl, substituting your Vendor ID, Product ID and key bindings as necessary:

# !/usr/bin/env python

import usb.core
import usb.util
import explorerhat
import time

explorerhat.light.red.on()

USB_VENDOR  = 0x1997 # Rii
USB_PRODUCT = 0x2433 # Mini Wireless Keyboard

USB_IF      = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS

BTN_LEFT  = 80
BTN_RIGHT = 79
BTN_DOWN  = 81
BTN_UP    = 82
BTN_STOP  = 44 # Space
BTN_EXIT  = 41 # ESC

dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
  dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

explorerhat.light.red.off()
explorerhat.light.green.on()

while True:
    control = None
    try:
        control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
        print(control)
    except:
        pass

    if control != None:
        if BTN_DOWN in control:
            explorerhat.motor.backwards()

        if BTN_UP in control:
            explorerhat.motor.forwards()

        if BTN_LEFT in control:
            explorerhat.motor.two.forwards()
            explorerhat.motor.one.backwards()

        if BTN_RIGHT in control:
            explorerhat.motor.two.backwards()
            explorerhat.motor.one.forwards()

        if BTN_STOP in control:
            explorerhat.motor.stop()

        if BTN_EXIT in control:
            exit()

    time.sleep(0.02)

Once you've got this example up and running, it should be straight-forward to modify it for your needs.

Things To Try

You might want to experiment with giving each motor its own pair of keys, so you can drive them forwards/backwards independently. This is great for tank control. You might also want to check both the LEFT/RIGHT keys and the UP/DOWN keys together, and make your robot move forwards/backwards and turn at the same time for more fluid control. Whatever you do, these basics should get you off to a good start.

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.