The Lego Mindstorms EV3 is the programmable ‘brick’ at the heart of the Lego Mindstorms system. However one thing lacking in Mindstorms is remote control options. There is an infrared controller, but this has a limited range of about 2 meters. You can associate a PS4 game controller, but options for an Xbox controller seem to be limited. Using the menus, we could not get one to pair. It needed some further investigation and it was clear the power of Python (rather than the default graphical language) was going to be needed. Rather pleasingly, when you run Python, it is actually running a small Linux distribution, giving you a great deal of power and control of the EV3 unit.
With a SD card to provide an image, you can install python by following the instructions at Lego Education. This will run the Pybricks distribution of MicroPython, a limited version of python designed for microcontrollers. Because of this it does not have all the modules available that you may expect from a full blown version of Python.
To see what we have available, boot the EV3 into the Python image (EV3DEV), start a new blank project and add the line help("modules")
. You should see a list of modules similar to below:
__main__ mmap pybricks/tools ufcntl
_thread nxtdevices_c pybricks/uev3dev/__init__ uhashlib
array parameters_c pybricks/uev3dev/_alsa uheapq
bluetooth_c pybricks/__init__ pybricks/uev3dev/_wand uio
btree pybricks/bluetooth pybricks/uev3dev/display ujson
builtins pybricks/display pybricks/uev3dev/i2c umachine
cmath pybricks/ev3brick pybricks/uev3dev/messaging uos
core pybricks/ev3devices pybricks/uev3dev/sound urandom
ev3devices_c pybricks/ev3devio pybricks/uev3dev/util ure
experimental_c pybricks/experimental robotics_c uselect
ffi pybricks/hubs sys usignal
framebuf pybricks/iodevices termios usocket
gc pybricks/media/ev3dev tools ussl
hubs_c pybricks/messaging ubinascii ustruct
iodevices_c pybricks/nxtdevices ucollections utime
math pybricks/parameters ucryptolib utimeq
media_ev3dev_c pybricks/robotics uctypes uwebsocket
micropython pybricks/speaker uerrno uzlib
Using a wireless keyboard
A wireless keyboard is probably the easiest remote input, as wireless keyboards are very common and follow a well defined input standard. However there is no native support within Pybricks. You can read from stdin, but if you don’t have a remote console, that is not going to work. But, a wireless keyboard will appear like a standard Linux input device. From Visual Studio Code, open an SSH session to the device and then (once you have plugged the wireless keyboard USB dongle into the USB port), type lsusb
. If it is listed then keyboard input can be tested with evtest
. Select the input device that looks like your keyboard (usually event2) and tap a few keys. If you see output, then it can receive input from the keyboard.
All very well, but how can this be used from python? Hard coding in the device event number, the following code will show keyboard presses in terms of a code number.
#!/usr/bin/env pybricks-micropython
from pybricks.hubs import EV3Brick
from pybricks.ev3devices import (Motor, TouchSensor, ColorSensor,
InfraredSensor, UltrasonicSensor, GyroSensor)
from pybricks.parameters import Port, Stop, Direction, Button, Color
from pybricks.tools import wait, StopWatch, DataLog
from pybricks.robotics import DriveBase
from pybricks.media.ev3dev import SoundFile, ImageFile
import struct
import time
import sys
# Reading raw keyboard input from /dev/input, see:
# https://stackoverflow.com/questions/5060710/format-of-dev-input-event
# Create your objects here.
ev3 = EV3Brick()
device="/dev/input/event2"
"""
FORMAT represents the format used by linux kernel input event struct
See https://github.com/torvalds/linux/blob/v5.5-rc5/include/uapi/linux/input.h#L28
Stands for: long int, long int, unsigned short, unsigned short, unsigned int
"""
FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize(FORMAT)
#open file in binary mode
in_file = open(device, "rb")
event = in_file.read(EVENT_SIZE)
print("Starting keyboard test....")
while event:
(tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)
if type != 0 or code != 0 or value != 0:
print("Event type %u, code %u, value %u at %d.%d" % \
(type, code, value, tv_sec, tv_usec))
else:
# Events with code, type and value == 0 are "separator" events
print("===========================================")
event = in_file.read(EVENT_SIZE)
in_file.close()
That is not immediately useful. What key is being pressed? How does the code relate to something useful like ‘A’? All we receive is the raw code number for each key.
There is a lookup table in the linux headers, /usr/include/linux/input-event-codes.h
, on most linux systems, but not on pybricks. If you have access to a linux system, a file of ‘<key> <code>’ can be created with grep '#define' /usr/include/linux/input-event-codes.h | grep 'KEY_' | awk '{printf("%s %s\n", $2, $3)}' > keyMap
, or my copy can be downloaded from the github page.
In addition we don’t need to print all the event output. A keyboard event is type=1, the key pressed will be returned in the code. A key up event has a value of 0 and a key down has a value of 1. If we only want to look at key down events, we can simply use an if statement to check for type=1, value=1.
To make things a little more interesting, if a B is pressed, it makes the EV3 beep. This can now become a framework for a keyboard controlled program, triggering motors or servos.
#!/usr/bin/env pybricks-micropython
from pybricks.hubs import EV3Brick
from pybricks.ev3devices import (Motor, TouchSensor, ColorSensor,
InfraredSensor, UltrasonicSensor, GyroSensor)
from pybricks.parameters import Port, Stop, Direction, Button, Color
from pybricks.tools import wait, StopWatch, DataLog
from pybricks.robotics import DriveBase
from pybricks.media.ev3dev import SoundFile, ImageFile
import struct
import time
import sys
# Reading raw keyboard input from /dev/input, see:
# https://stackoverflow.com/questions/5060710/format-of-dev-input-event
# Create your objects here.
ev3 = EV3Brick()
device="/dev/input/event2"
# Read keyboard event codes from keyMap
keysIn=open("keyMap", "r")
kbdCodes={}
for line in keysIn:
# Split the line on whitespace
sp=line.split()
# Ensure there are at least 2 things on the line
if(len(sp)>=2):
if(sp[0].startswith("KEY_")):
key=sp[0]
# The code may not be valid hex, ignore
try:
code=int(sp[1],0)
#print("Keycode {} to {}".format(code, key))
kbdCodes[code]=key
except:
pass
"""
FORMAT represents the format used by linux kernel input event struct
See https://github.com/torvalds/linux/blob/v5.5-rc5/include/uapi/linux/input.h#L28
Stands for: long int, long int, unsigned short, unsigned short, unsigned int
"""
FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize(FORMAT)
#open file in binary mode
in_file = open(device, "rb")
event = in_file.read(EVENT_SIZE)
print("Starting keyboard test....")
while event:
(tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)
if type==1 and value==1:
# We should have a keycode mapping for this, but use try, just in case
try:
key=kbdCodes[code]
except:
key="NONE"
print(key,"pressed")
# Lets try something with the brick, beep on a B
if(key=="KEY_B"):
ev3.speaker.beep()
event = in_file.read(EVENT_SIZE)
in_file.close()
Controlling with an Xbox controller
Although documentation is limited, you can pair an Xbox controller to a EV3, but need to add a wireless card and pull in an extra package first. Eventually I discovered this excellent guide. Add a wireless card, open a SSH session and:
sudo apt update
sudo apt upgrade # This may take a while
sudo apt install sysfsutils
sudo sh -c 'echo module/bluetooth/parameters/disable_ertm = 1 >> /etc/sysfs.conf'
sudo service sysfsutils restart
bluetoothctl
[bluetooth]# power off
[bluetooth]# power on
[bluetooth]# scan on
[bluetooth]# trust <mac>
[bluetooth]# pair <mac>
[bluetooth]# connect <mac>
Once done, the Xbox controller will typically appear as /dev/input/event2 (assuming you have unplugged the wireless keyboard). It can be tested with evtest in the same way as above. Like the keyboard, you can read and process the raw events in the same way. Hugbug has provided a similar program frame work at https://github.com/hugbug/ev3/blob/master/xbox-info/xbox-info.py.
Network control
With a wireless network card, you could use the sockets library, provide a socket to connect to and send commands to the EV3. I will expand on this later, but a rough guide, see the focus client and server on my Astroberry repository.