I still had an unused Raspberry Pi 4 lying around and used it to build Trilobot, a mid-level robot learning platform from Pimoroni. The bot is easy to assemble and guarantees hours of fun tweaking its code to implement different functionalities.
So far I’ve only managed to assign a few functions to the helpful buttons that are integrated on the bot. The code is mostly based on the extensive trilobot-python library that Pimoroni kindly provides to get you started. If you’re setting up Trilobot for the first time, please follow their step-by-step tutorial before you read on here.
With a few tweaks to the original examples, Trilobot can be remote-controlled and activate its camera all at the same time. In my case it also automatically runs the script at startup. In the weeks to come I’m planning on extending it further with sensors and face recognition, but the list of options is open-ended.
Find the full script below and on my github page. Further instructions can be found in this blog post. I will also add to them as I extend my bots functionality. Please keep in mind that this is only a very simple script to get Trilobot up and running. No fancy programming involved!
Getting Trilobot up and running
The script below adds three functions to the Trilobot buttons: remote control, camera and an off-switch.
#!/usr/bin/env python3
import time
import os
import math
import threading
from trilobot import Trilobot, BUTTON_A, BUTTON_B, BUTTON_X
from trilobot.simple_controller import SimpleController
"""
This script assigns functions such as remote and camera control to the Trilobot
buttons and is automatically started at boot.
"""
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
tbot = Trilobot()
def camera_active():
"""Activate the camera using libcamera. Connect to localhost with a client.
"""
while True:
try:
print("Camera up and running. Connect client to stream.")
os.system("libcamera-vid -t 0 --width 960 --height 635 -q 100 -n --inline --listen -o tcp://0.0.0.0:8888")
time.sleep(5)
except KeyboardInterrupt:
print("Camera disabled.")
os._exit(0)
def create_8bitdo_lite_controller():
""" Create a controller class for the 8BitDo Lite controller.
"""
controller = SimpleController("8BitDo Lite gamepad")
# Button and axis registrations for 8BitDo Lite
controller.register_button("A", 305)
controller.register_button("B", 304)
controller.register_button("X", 307)
controller.register_button("Y", 306)
controller.register_button("Plus", 311, alt_name="Start")
controller.register_button("Minus", 310, alt_name="Select")
controller.register_button("L1", 308, alt_name="LB")
controller.register_axis_as_button("L2", 2, 0, 1023, alt_name="LT")
controller.register_button("R1", 309, alt_name="RB")
controller.register_axis_as_button("R2", 5, 0, 1023, alt_name="RT")
controller.register_button("Home", 139)
controller.register_axis_as_button("L_Left", 0, 0, 32768)
controller.register_axis_as_button("L_Right", 0, 65535, 32768)
controller.register_axis_as_button("L_Up", 1, 0, 32768)
controller.register_axis_as_button("L_Down", 1, 65535, 32768)
controller.register_axis_as_button("R_Left", 3, 0, 32768)
controller.register_axis_as_button("R_Right", 3, 65535, 32768)
controller.register_axis_as_button("R_Up", 4, 0, 32768)
controller.register_axis_as_button("R_Down", 4, 65535, 32768)
controller.register_axis_as_button("Left", 16, -1, 0)
controller.register_axis_as_button("Right", 16, 1, 0)
controller.register_axis_as_button("Up", 17, -1, 0)
controller.register_axis_as_button("Down", 17, 1, 0)
controller.register_axis("LX", 0, 0, 65536)
controller.register_axis("LY", 1, 0, 65536)
controller.register_axis("RX", 3, 0, 65536)
controller.register_axis("RY", 4, 0, 65536)
return controller
def remote_active():
"""Connect the 8BitDo gamepad to Trilobot. Remote needs a paired bluetooth connection.
"""
print("Remote up and running.")
# Connect to 8BitDo Lite gamepad
print("Connecting to 8BitDo Lite gamepad...")
controller = create_8bitdo_lite_controller()
# Attempt to connect to the created controller
controller.connect()
# Run an animation on the underlights to show a controller has been selected
for led in range(NUM_UNDERLIGHTS):
tbot.clear_underlighting(show=False)
tbot.set_underlight(led, RED)
time.sleep(0.1)
tbot.clear_underlighting(show=False)
tbot.set_underlight(led, GREEN)
time.sleep(0.1)
tbot.clear_underlighting(show=False)
tbot.set_underlight(led, BLUE)
time.sleep(0.1)
tbot.clear_underlighting()
h = 0
v = 0
spacing = 1.0 / NUM_UNDERLIGHTS
tank_steer = False
while True:
if not controller.is_connected():
# Attempt to reconnect to the controller if 10 seconds have passed since the last attempt
controller.reconnect(10, True)
try:
# Get the latest information from the controller. This will throw a RuntimeError if the
# controller connection is lost
controller.update()
except RuntimeError:
# Lost contact with the controller, so disable the motors to stop Trilobot if it was moving
tbot.disable_motors()
if controller.is_connected():
# Read the controller bumpers to see if the tank steer mode has been enabled or disabled
try:
if controller.read_button("L1") and tank_steer:
tank_steer = False
print("Tank Steering Disabled")
if controller.read_button("R1") and not tank_steer:
tank_steer = True
print("Tank Steering Enabled")
except ValueError:
# Cannot find 'L1' or 'R1' on this controller
print("Tank Steering Not Available")
try:
if tank_steer:
# Have the left stick's Y axis control the left motor, and the right stick's Y axis
# control the right motor
ly = controller.read_axis("LY")
ry = controller.read_axis("RY")
tbot.set_left_speed(-ly)
tbot.set_right_speed(-ry)
else:
# Have the left stick control both motors
lx = controller.read_axis("LX")
ly = 0 - controller.read_axis("LY")
tbot.set_left_speed(ly + lx)
tbot.set_right_speed(ly - lx)
except ValueError:
# Cannot find 'LX', 'LY', or 'RY' on this controller
print("Motors disabled")
tbot.disable_motors()
# Run a rotating rainbow effect on the RGB underlights
for led in range(NUM_UNDERLIGHTS):
led_h = h + (led * spacing)
if led_h >= 1.0:
led_h -= 1.0
try:
if controller.read_button("A"):
tbot.set_underlight_hsv(led, 0.0, 0.0, 0.7, show=False)
else:
tbot.set_underlight_hsv(led, led_h, show=False)
except ValueError:
# Cannot find 'A' on this controller
tbot.set_underlight_hsv(led, led_h, show=False)
tbot.show_underlighting()
# Advance the rotating rainbow effect
h += 0.5 / 360
if h >= 1.0:
h -= 1.0
else:
# Run a slow red pulsing animation to show there is no controller connected
val = (math.sin(v) / 2.0) + 0.5
tbot.fill_underlighting(val * 127, 0, 0)
v += math.pi / 200
time.sleep(0.01)
def deactivate_programme():
"""Switch off the lights, button LEDs and exit the programme.
"""
tbot.clear_underlighting()
tbot.set_button_led(BUTTON_A, 0)
tbot.set_button_led(BUTTON_B, 0)
tbot.set_button_led(BUTTON_X, 0)
time.sleep(2)
os._exit(0)
def activate_button():
"""Buttons are hardwired to three specific functions: camera (A), remote (B) and off-switch (X).
"""
last_state_a = False
last_state_b = False
last_state_x = False
while True:
# Read the button
button_state_a = tbot.read_button(BUTTON_A)
button_state_b = tbot.read_button(BUTTON_B)
button_state_x = tbot.read_button(BUTTON_X)
if button_state_a != last_state_a:
# Button A was pressed
if button_state_a:
print("Camera is being activated.")
# Turn the button's neighboring LED on or off
tbot.set_button_led(BUTTON_A, 0.1)
# Turn on the camera
threading.Thread(target=camera_active).start()
# Update our record of the button state
last_state_a = button_state_a
elif button_state_b != last_state_b:
# Button B was pressed
if button_state_b:
print("Remote control is being activated.")
# Turn the button's neighboring LED on or off
tbot.set_button_led(BUTTON_B, 0.1)
# Turn on the remote control
threading.Thread(target=remote_active).start()
# Update our record of the button state
last_state_b = button_state_b
elif button_state_x != last_state_x:
# Button X was pressed
if button_state_x:
print("Program exit, quitting threads.")
# Turn the button's neighboring LED on or off
tbot.set_button_led(BUTTON_X, 0.5)
# Deactivate programme
deactivate_programme()
# Update our record of the button state
last_state_x = button_state_x
if __name__ == "__main__":
try:
# creating first thread
t1 = threading.Thread(target=activate_button)
# starting first thread to recognise when a button is pressed
t1.start()
t1.join()
while True:
time.sleep(100)
# exiting all threads and turning off underlighting and leds
except (KeyboardInterrupt, SystemExit):
print("\nReceived keyboard interrupt, quitting threads.\n")
tbot.clear_underlighting()
tbot.set_button_led(BUTTON_A, 0)
tbot.set_button_led(BUTTON_B, 0)
tbot.set_button_led(BUTTON_X, 0)
os._exit(0)
Getting the camera to work
For the 64-bit Raspberry Pi OS, which I am currently using, libcamera is the only working option for now. Unfortunately, it doesn’t have any Python bindings but it works well via the command line. If you’re still on the 32-bit OS, you can also use picamera to stream video. It should come with your OS by default.
To get the camera working, Trilobot has to open a TCP/IP stream on the network. The port is defined via the listen option in the code above. On the other end, the client (in my case a second Linux computer) has to open the tcp connection using an appropriate video player for network streaming.
I’m using the ffplay video player on Linux for this. Typing the following command should open the video stream for you.
ffplay tcp://<IP of Trilobot>:8888 -vf "setpts=N/30" -fflags nobuffer -flags low_delay -framedrop
A word on remotes
For this example I’m using my 8BitDo Lite gamepad, which is supported by Trilobot.
The mappings for this and several other controllers are included in the Pimoroni library. Just copy them into your code to enable remote-control for Trilobot.
Running the script at startup
Getting code to run automatically at startup is sometimes a bit tricky on the Raspberry Pi. In this case the rc.local method works nicely. Just edit the rc.local config file with an editor of your choice.
sudo nano /etc/rc.local
Add the command to execute the python script and don’t forget to use the absolute path to the file. As the program will run in a loop and is not likely to exit, also add the & at the end.
Leave the exit 0 at the end and then save the file. In nano, type Ctrl-x and Y to save and exit.
python3 /home/pi/sample.py &
Now reboot the Pi.
sudo reboot
And that’s it. Connect Trilobot to its power source and you should be good to go!
I also have a trilobot and assembled it with no problems. then i looked at remote controlling and looked to use a wiimote as that is what i have. Someone, unfortunately i cannot remember who sent me a controller mapping py program to use. Initially this would not work and had several snytax errors which i seemed to correct so the program would run under geany with no issues. I then tried to use porimoni program remote_control.py which highlighted an error in the mapping. It said that something was not defined. Being very ignorant on programming i do not know how to solve this issue. Can you help?
I haven’t tried to connect a different controller that wasn’t on the list so far, but theoretically you should be able to add a function for your wiimote and then map the buttons yourself. I’m assuming you’ve tried this using the instructions by Pimoroni? https://github.com/pimoroni/trilobot-python/tree/main/library/trilobot#remote-control If you post your code and error messages here I’m happy to take a look.