This fall, my sister sent me a video of my 4-year-old niece, deep in the midst of her astronaut phase, showing off a cardboard spaceship for the assessment of her engineer uncles. My brother and I loved it, and discussed how cool it'd be if we sent her some kind of control panel of knobs and switches. Wouldn't it be especially cool if we could play with her from afar by sending video messages? So that's what we set out to make for her Hanukah present. The outcome was -- like most of my projects -- a steaming pile of learning. But we got it done and I love it. This took about 100 hrs and $150, but would be half of each if starting with what I know now.
The goal was to produce a small, fun, user-friendly package that could be operated by a child. I wanted the user to be able to hang the device over the edge of a cardboard box to make it feel like a part of a cardboard box vehicle. I drew on fond memories playing with a Compaq Portable when I was a kid. All it had was a calendar program, so my brother, sister, and I built a fort around it and scrolled through the calendar to travel though time in our imaginary time machine.
Considering how quickly kids' interests change, my goal was to imagine use cases a kid would enjoy while also making it flexible enough to be whatever they might imagine.
Once I could visualize the form-factor and some buttons I had to decide what the user experience would be like. I decided I wanted it to boot into a menu that offered two options: one to display a dashboard on the screen and one that would display a list of video messages available for playing. I decided to dedicate one button to cycling between options and one button to executing the current selection. I eventually added a second menu before this one because the device needed to first connect to Wi-Fi and check for new video messages and download them if available before the main program ran. This offered the user the option of playing whatever the latest message was once it finished checking or skipping ahead to the main menu.
Lastly, I decided to power it off a USB battery, accessible by opening a clear door panel covering the lower half of the housing.
The following outline roughly follows my design process, minus all the trips back to the drawing board. Once we had a concept, the next step was to create a schematic that clarified what hardware components would need to fit into the enclosure. Then we could select parts and model the enclosure around them. I constructed a prototype on a breadboard while installing the necessary software and coding the programs to achieve the desired functionality. Below is a Gantt chart that got thrown out when we had a mid-project burnout.
This diagram was prepared during the project's early stage, so it doesn't actually match the final product, especially in the pinouts. But it demonstrates the key components required.
The Single Board Computer
We decided from the start to use a Raspberry Pi in order to be able to display content to a screen and to be able to receive videos over WiFi. We ultimately went with a Raspberry Pi 3A, which remains my favorite Pi. It's got a built in WiFi module and a full size HDMI cable, but it has a smaller footprint than a traditional Pi. It sacrifices three USB ports and the Ethernet port, but I think it's a great trade, not just for size but because I think it suffers less from the constant current shortage that plagues most of the recent Pis.
The Display Screen
This is the second Pi project I've done with a screen. The previous one had a 7" display connected using an HDMI-to-TFT board. We decided to use a 3.5" screen early on because it'd be hard to fit anything larger inside, and also because I wanted to be frugal with power consumption. We decided to connect the screen using the GPIO pins instead of the HDMI port because I thought it would reduce complexity, cost, and possibly power consumption, although in the end I don't know if it really did any of the three. We tried two different screens, and both suffered from a lack of documentation (I hate to criticize, as I appreciate the tons of work the developers did! But there were still some obstacles).
The Power System
It was decided early on to power the dashboard with a USB power pack. It delivers the correct power and current, it's rechargeable and compact, and it includes its own charging circuitry and a level display. Best of all, this works thematically! A four year old can see the current power level, and if it runs out in the middle of playtime, replacing the ship's fuel cell can easily become part of the adventure. Be aware, though, that some power packs require the user to press a button to turn them on. We used one that supplies power as soon as it detects a device connection.
The USB A connector leads to a physical switch, which worked great (eventually).
The switches are crucial. I thought back to the funnest buttons to play with on trips to Radio Shack as a kid and added three standard toggles, a covered illuminated toggle, a slider, and an arcade button, plus two modest momentary buttons. Unfortunately, I wasn't able to use the slider because I didn't realize at the outset that the analog-to-digital converter I planned to use required SPI pins that were required by the display. I wasn't able to map reactions to the arcade button or illuminated toggle either because several pins I thought were available were not, and I ran out of time. But the toggles and buttons that do work provide satisfying beeps and chirps and lots of responsive actions, so I'm happy anyway.
The Output Indicators
I wanted a light that would blink to indicate when a new message had been detected, and ordered an addressable RGB LED which is controlled using a WS2812 integrated circuit. This uses the NeoPixel library. Be aware that this IC can only be controlled by GPIO pins 10, 12, 18, or 21 (and pin 10 and 18 are unavailable if using the PiTFT screen).
The necessary code is described in this guide: NeoPixel control in Python. It does not appear in my code because I didn't include this LED during prototyping, so by the time I discovered that I needed to connect it to pin 12 or 21 I didn't have the time to do so.
The device needed a speaker to play beep noises as well as the audio of incoming transmissions. I was surprised that I couldn't find a compact speaker board and amplifier. I found a few things that seemed like they might work, but I couldn't find examples of someone using them and I wasn't sure what volume they produced, so I ended up using a hamburger speaker I had lying around. In order to fit it into the housing I disassembled it and mounted it on a custom board mount that's in the 3D CAD files.
Raspberry Pi setup
Set up a clean installation of Raspberry Pi OS as described here: Install Raspberry Pi OS using Raspberry Pi Imager
Install the display drivers to mirror the HDMI display through the GPIO pins as described here: Adafruit PiTFT 3.5" Touch Screen Easy Install
There is one other component that was very hard to find and absolutely critical: when running a pygame in a python script through the terminal the output is displayed on whatever screen is displaying the terminal. This presents a problem when running a pygame instance from start-up, since there is no display assigned. The solution is to assign a display by prefixing the python command that calls the script with "DISPLAY=:0" like this:
$ DISPLAY=:0 python3 /home/pi/script.py
Also, I needed to disable the screensaver. The easiest way to do this on Raspberry Pi OS is to install xscreensaver, which creates a settings menu from which the screensaver can be disabled.
Setting up Pygame
Pygame is pretty incredible. It allows you to output text, images, and shapes to a display as well as play sound clips. Follow the instructions here: Getting Started - pygame wiki. The important commands I used are in
pygame.init() : Initiate pygame
screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN) : Assign the display surface and instruct it to fill the screen
win_obs = pygame.image.load('/home/pi/Rocket-dashboard/Images/win-observatory.png') : Load an image and assign it to a variable
beep1 = pygame.mixer.Sound('/home/pi/Rocket-dashboard/Sounds/beep1.wav') : Load a sound and assign it to a variable
beep1.play() : Play a sound
screen.blit(image_object, (0,0)) : Assign the location of an image on the screen. In this case, its upper left corner is in the upper left corner of the screen.
font1=pygame.font.SysFont(None,50) : Define a font
text1 = font1.render('NEW TRANSMISSION RECEIVED', True, (255,102,178)) : Define a text object, including its contents and color.
screen.blit(text1, (50,30)) : Assign the position of a text object
pygame.display.update() : Output the specified objects to the screen
Creating the Visual Assets
This was perhaps the step which was most easy and fun. In GIMP I laid out some text and images, and added a Star Trek Starfleet logo. I also made a similar image for the desktop background on the Raspberry Pi in order to keep stylistic consistency when pygame isn't running, such as during start-up and when switching between some screens.
Running from Startup
For something as common as this, I thought there'd be a clear and official guide online, but it turns out that what you want to do has a big impact on which method you want to use. In this case, we want to start a program after the desktop and WiFi has initialized. I used crontab, which can be edited with the command "crontab -e". From there, I added a startup command to the end of the file:
@reboot DISPLAY=:0 python /home/pi/startup.py
Playing video turned out to be a fiasco. This was the biggest challenge of the whole project, and in truth I never got it to work as well as I wanted. There were several approaches tried, and each suffered some kind of glitch, and so in order to get the project to a state of completion in a crunch I simply instructed the python program to issue a terminal command opening a selected video in VLC media player externally. This introduced some segmentation faults that I worked around with some shell scripts to patch over problems, but if I had more time I'd have troubleshooted it until I found a cleaner fix.Automated Video Download
From the start, a core inspiration for this device was the hope that I could play remotely with my niece by recording videos alerting her to adventures and missions in need of her heroics. To achieve this we used Rclone to check a Google Drive folder upon start-up. If the folder contained a video not found in a local folder, the program would download it to the device and alert the user that a new transmission had been received. In either case, the program would ask the user if they wanted to play the most recent video, and the most recent four could be accessed from the radio menu.Troubleshooting
I added the WiFi network credentials into the wpa supplicant list before I sent the finished product to my niece, but if working with Raspberry Pi has taught me one thing its that setting everything up ostensibly perfectly is no guarantee that it will actually work. So to make it possible to troubleshoot or enter in a WiFi password manually, I downloaded matchbox on-screen keyboard and left a shortcut on the desktop. The touch screen is covered with a small polycarbonate screen protector, but if necessary this pops off to allow touch-screen troubleshooting. I also added a kill command into the main script that is triggered by pressing both the cycle and select buttons at the same time.Conclusions
This project was full of ups and downs. I've glossed over many headaches and frustrations, and I very much wish that I was able to make the start-up process shorter (it currently takes over 30 seconds after powering on before the screen gives the user any information). But I'm still thrilled with the way I was able to execute the ambitious vision that started this project. And best of all, it's for a precocious four year-old: so after it arrived I was informed both that it was not performing most of its functions and also that troubleshooting was unnecessary, as my niece immediately began playing with it anyway and confidently explained that she already knew exactly how her spaceship's computer worked.