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.
And that’s it. Connect Trilobot to its power source and you should be good to go!