Saturday 28 August 2021

Running Robot Code on Start-up with systemd

Following the excellent blog post by Brian Starkey @usedbytes to set up my robot programs to launch on start-up on a Raspberry Pi, I found several things which didn't work with my particular programs. This article aims to extend the information from Brian's post.

First a quick recap on why use systemd services instead of rc.local or autostart desktop files. For me the main advantages of this method are:

  • The automatic start up of the robot code on boot can be turned on and off, which really helps when developing code and you don't want the robot program to launch each time you boot up.
  • You can configure the code to be restarted if it stops for any reason. Great for if an unexpected bug crashes your robot code in the middle of a demo or competition. No need to wait for a reboot to get the code running again.
So what went wrong when I tried to implement the services as described in Brian's blog? First I want to point out that nothing in Brian's blog was wrong. I just needed some additional configuration for my programs. The first problem I ran into was that I had only installed the python packages my program needed as the pi user, but found that the service was running as root. This can be fixed by installing all the packages as root, but I found an alternative option. You can specify the user to run the service as by putting User=pi in the service definition file.

The second problem I ran into was that my service ran in a headless mode, even though my Raspberry Pi was running a full desktop OS. This works fine for a python program which does not use the display, but my robot programs are usually built on top of pygame and use this to display information and sometimes control menus in a GUI. Running my programs as a service caused pygame to exit with 'pygame.error: Unable to open a console terminal'. So I had to learn how to run a service with access to the display device. This required a couple of lines to be added to the [Service] section:

Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/pi/.Xauthority"

I also needed to change the [Install] section WantedBy value:

[Install]
WantedBy=graphical.target

 

I am not going to repeat all the information from Brian's blog article here. You should read that first if you are not familiar with how to set up and configure services to run on boot. Once I had everything working this is what my runrobot.service file looked like.

[Unit]
Description=Robot Runner

[Service]
Type=exec
ExecStart=/usr/bin/python3 /home/pi/pygame-controller/examples/SentinelBoard/Turbo4WD.py
User=pi
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/pi/.Xauthority"
Restart=always
RestartSec=5s

[Install]
WantedBy=graphical.target

Command Quick Reference

The service file needs to be copied into /etc/systemd/system in order to work:
sudo cp runrobot.service /etc/systemd/system

As long as the filename ends '.service' you do not need to specify the full filename in all the commands. You can just use the service name (runrobot in this case).

If you change the .service file then the changes will not be picked up until the next reboot, or after you run this command:
sudo systemctl daemon-reload

To enable service (make it run on boot):
sudo systemctl enable runrobot

Disable service (stop it running on boot):
sudo systemctl disable runrobot

Start service (run the robot program without doing a reboot):
sudo systemctl start runrobot

Stop service (stop it running):
sudo systemctl stop runrobot

View output from the service/program since last boot:
sudo journalctl -b0 --unit=runrobot