Robot remote-control by Python GUI

Posted on Tue 04 October 2022 in electronics


I now have a PyQt GUI to control my robot, instead of using a shell over SSH. The GUI has a bunch of controls and has a serviceable video stream.

Source code:




I use two technologies.

The GUI is made with PyQt5. It's a Python interface for the Qt graphical interface library. If you're unfamiliar with it, it makes desktop applications. For example, here's a text editor made in PyQt:

The robot controls are made with FastAPI. Why use FastAPI? Well, I hadn't found any easier options online, and FastAPI can accept a lot requests simulataneously. Even as I use it to send images over my LAN it still works alright.

My cat next to the robot

How it works

The FastAPI server runs on the Raspberry Pi. The PyQt GUI runs on a desktop and makes GET requests to the API. When the API gets a request, it acts accordingly.

Available controls

  • 9 direction controls using the keyboard numpad
  • 0-100% motor power controls
  • -50%/+50% balance control for motor power, to compensate with a weaker motor
  • Servo controls, 3 positions (my servo motor seems to have lost of its range of motion)
  • RGB control of the TriloBot under-chasis LEDs
  • BrightPi controls: visible light and infrared light, plus a nightvision mode for the camera
  • A readout for the ultrasonic sensor

The GUI with its controls and camera feed

I've tried to implement a kind of "dead man's switch" so that the robot shuts down if it doesn't receive any requests after a few seconds. This would be good if your connection drops in front of stairs or a steep cliff. This kind of works with a background thread but it isn't perfect.

The API is able to send the images at a reasonable pace. You'd expect this to be painfully slow. Except that it's just a big sluggish. (I use the new Pi camera library, which has a bit less documentation online but does come with examples).

async def capture_camera():
    arr = picam2.capture_array()
    im = Image.fromarray(arr)

    # save image to an in-memory bytes buffer
    with io.BytesIO() as buf:
        im.convert('RGB').save(buf, format='JPEG', quality=50)
        im_bytes = buf.getvalue()

    headers = {'Content-Disposition': 'inline; filename="feed.jpeg"'}
    return Response(im_bytes, headers=headers, media_type='image/jpeg')

All of the API

FastAPI makes its own API interactive documentation. Great for testing the API without the GUI!

All of the API methods