Robot remote-control by Python GUI
Posted on Tue 04 October 2022 in electronics
Overview
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: https://github.com/sjchiass/trilobot_controls
Videos
:robot:
Tech
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: https://www.binpress.com/building-text-editor-pyqt-1/
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.
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
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).
@app.get("/commands/capture_camera")
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!