Start Pygame Application at Boot
A TV coupled with a Raspberry Pi makes a good information monitor. We’ve used this in my day job to provide to provide build radiators. As the dashboard showing the build status is browser based, this means we have to configure:
- Autologin to the desktop GUI
- Autostart the web browser
- start page is dashboard
- start in full-screen mode
This works, but can be a bit slow to start, and is beyond the means of a Raspberry Pi Zero.
Prompted by pub conversations about a track diagram for a minature railway, I wondered about developing an application that could be started automatically at boot, but not require a heavyweight desktop and browser.
Why Python & Pygame?
Python is a great language for accessing peripherals connected to the Pi, with modules providing easy access to web services, GPIO, I2C or SPI.
Pygame uses SDL for access to graphics, and works using “x11” when run from the desktop GUI, and “fbcon” when run on the console. This dual-mode operation means that development can be done using your favourite IDE (on a more powerful Pi), but actual operation is bare-metal console.
There are a couple of considerations that need to be made to achieve this dual-mode. I’ll show how to get an application that will run under both “x11” and “fbcon”, and then how to get the application started at boot using a systemd unit file
Demonstration
The animation shows a Pi Zero W being powered up and booting into the clock display.
This takes about 40 seconds from power on. The time jumping forward from 3:00:57 to 3:01:58 will be when the time is set correctly.
Code Walkthrough
I’ll just talk about the key points. The full example is available in the console-pygame repository. The application is just a digital clock, but could be whatever you want it to be.
Main Function
This is the main
function taken from the demo application.
1signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2
3try:
4 pygame.display.init()
5 pygame.mouse.set_visible(False)
6
7 screen = pygame.display.set_mode((1280, 720))
8 clock = pygame.time.Clock()
9
10 while True:
11 events = pygame.event.get()
12 if list(filter(lambda e: e.type == pygame.QUIT, events)):
13 return
14
15 draw(screen)
16
17 pygame.display.flip()
18 clock.tick(10)
19except KeyboardInterrupt:
20 pass
21finally:
22 pygame.display.quit()
- Line 1
- A
systemd
service is stopped by sending aSIGTERM
signal. This signal handler causes the application to exit. - Lines 4–5
- I only initialise the Pygame modules being used, rather than using
pygame.init()
. The mouse cursor is hidden as it not appropriate for running on the console. You have to get used to it not being drawn as your mouse moves across the desktop window. - Line 7
- I picked the arbitrary window size of 1280x720. This corresponds to 720p on a TV, and is likely to be a suitable size for your desktop environment.
- Lines 8,18
- The Pygame
Clock
class allows you to control the frame rate. I’ve picked 10 FPS. - Lines 11–13
- This looks for the
QUIT
event when the window system close is clicked when running on your desktop. - Line 15
- The function
draw
is responsible for updating the display, and is where you get creative. - Lines 19–20
- If you are running from a terminal window, then this stops
Control-C
spitting out a stack trace. - Line 21–22
- Tidy up any Pygame resources. Not actually required here, as it would be done automatically as application exits.
If you were using other Pygame modules (e.g. pygame.font
) then you need
to initialise those too.
In my clock example I have no external inputs. If I needed to interact with the outside world, I would use some form of MVC approach to update a shared model:
- Interrupt driven from GPIO pins
- Thread reading from serial port
- Thread periodically accessing web service
Systemd Unit File
This is the “unit file” used by systemd
to start the application.
1[Unit]
2Description=Pygame Console Demo
3
4[Service]
5Type=idle
6
7User=nobody
8Group=nogroup
9SupplementaryGroups=video input dialout spi i2c gpio
10
11StandardInput=tty
12StandardOutput=journal
13StandardError=journal
14
15TTYPath=/dev/tty8
16TTYVHangup=yes
17TTYVTDisallocate=yes
18
19Environment=PYTHONUNBUFFERED=TRUE
20
21ExecStartPre=/bin/chvt 8
22ExecStart=/usr/local/lib/console-demo.py
23
24[Install]
25WantedBy=multi-user.target
- Line 5
- The type is set to
idle
to delay execution until all active jobs are dispatched. - Lines 7–9
- To avoid running as “root” the application is run as “nouser”/“nogroup”. The group permissions required for my clock example are “video” and “input”, but I’ve put in the other groups for things you might need for your data collection.
- Lines 11–13
- The output is sent to the system journal to avoid it clashing with display, and so any diagnostic messages can be viewed. The input is connected to the allocated TTY for SDL to detect..
- Line 15
- The TTY allocated is the 8th virtual terminal, which is otherwise unused.
- Line 16
- This ensures that there are no other processes connected to the TTY.
- Line 17
- When the service stops, the VT is deallocated to ensure no stray output is visible.
- Line 19
- Set the output to unbuffered, so any messages are captured by the systemd journal.
- Line 21
- Switch to the VT so the application is visible.
- Line 22
- Path to where the script has been placed.
- Line 25
- Script will be started when system is ready for multi-user operation.
Installation
If you want to try for yourself, get the console-demo.py
and console-pygame.service
files from the
console-pygame repository.
The following commands will place the Python script in /usr/local/lib
,
configure it to run as a service, and start it.
sudo apt install -y python3-pygame
sudo install -t /usr/local/lib console-demo.py
sudo install -t /etc/systemd/system console-pygame.service
sudo systemctl daemon_reload
sudo systemctl enable console-pygame
sudo systemctl start console-pygame
Monitoring
To view the status of the service:
sudo systemctl status console-pygame
To view the output from the service:
sudo journalctl -u console-pygame
To stop and disable:
sudo systemctl stop console-pygame
sudo systemctl disable console-pygame