Skip to Content

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


The animation shows a Pi Zero W being powered up and booting into the clock display.


Pi Zero Booting

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())
 4    pygame.display.init()
 5    pygame.mouse.set_visible(False)
 7    screen = pygame.display.set_mode((1280, 720))
 8    clock = pygame.time.Clock()
10    while True:
11        events = pygame.event.get()
12        if list(filter(lambda e: e.type == pygame.QUIT, events)):
13            return
15        draw(screen)
17        pygame.display.flip()
18        clock.tick(10)
19except KeyboardInterrupt:
20    pass
22    pygame.display.quit()
Line 1
A systemd service is stopped by sending a SIGTERM 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.

 2Description=Pygame Console Demo
 9SupplementaryGroups=video input dialout spi i2c gpio
21ExecStartPre=/bin/chvt 8
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.


If you want to try for yourself, get the 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
sudo install -t /etc/systemd/system console-pygame.service
sudo systemctl daemon_reload
sudo systemctl enable console-pygame
sudo systemctl start console-pygame


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