Running an application with systemd when booting Linux

Running an application with systemd when booting Linux

This week, we are going to talk about a topic which is not linked only to Yoctopuce modules but which will certainly be of interest to those of our readers who use Raspberry Pi machines or other Linux systems: How to automatically run an application when Linux starts.





To illustrate the different possible solutions, we are going to write a small program which displays the current time on a YoctoDisplay connected by USB. For this post, we are going to use a Raspberry Pi with a Raspbian image, but any Linux machine with a recent distribution should work in an identical manner.

Note: If you are not at ease with Python or with our library, we wrote a post which explains how to use our modules with Python.

This small program is written in Python and uses our pip "yoctopuce" package. To install it, run the following command:

sudo pip install yoctopuce



The Python script is basic, we display some information when the application starts and we initialize the Yoctopuce API with YAPI.RegisterHub(). Then, we check whether there is a connected YoctoDisplay and, if it is the case, we enter an endless loop which displays the time in the center of the screen.

The yclock.py Python script:


#!/usr/bin/python
# -*- coding: utf-8 -*-

# import Yoctopuce Python library (installed form PyPI)
from yoctopuce.yocto_api import *
from yoctopuce.yocto_display import *
import getpass


def get_current_time():
    now = datetime.datetime.now()
    res = now.strftime('%a %d %b %Y / %X')
    return res


print("YClock v1.0")
print("Linux user : " + getpass.getuser())
print("Yoctopuce API : " + YAPI.GetAPIVersion())
print("Start time : " + get_current_time())

# Sets up the API to use local USB devices
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
    sys.exit("RegisterHub error" + str(errmsg))

display = YDisplay.FirstDisplay()
if display is None:
    sys.exit("No YoctoDisplay connected")

try:
    display.resetAll()

    layer1 = display.get_displayLayer(1)  # type: YDisplayLayer
    layer2 = display.get_displayLayer(2)  # type: YDisplayLayer
    layer1.hide()
    layer2.unhide()

    w = display.get_displayWidth()
    h = display.get_displayHeight()
    while True:
        msg = get_current_time()
        layer1.clear()
        layer1.drawText(w / 2, h / 2, YDisplayLayer.ALIGN.CENTER, msg)
        display.swapLayerContent(1, 2)
        time.sleep(1)

except Exception as error:
    sys.exit(error)

 



Note that, by design, this script doesn't manage disconnections/reconnections of the YoctoDisplay. If no screen is detected, or if it is disconnected while the script is running, the sys.exit() function is called with the corresponding error message. The aim here is to illustrate how to manage software errors when booting Linux. In an application used in production, it would be more efficient to manage the connection/disconnection of Yoctopuce modules, for example with the YAPI.RegisterDeviceArrivalCallback() and YAPI.RegisterDeviceRemoveCallback() functions.

Finally, we must give execution privileges to our script and check that it works.

pi@raspberrypi:~/autostart $ chmod +x yclock.py pi@raspberrypi:~/autostart $ sudo ./yclock.py YClock v1.0 Linux user : root Yoctopuce API : 1.10.30760 (1.10.30760) Start time : Wed 23 May 2018 / 15:05:01 Start never ending loop



Now that our test application works, we need to configure the Raspberry Pi so that it runs our yclock.py script at startup.

The basic solution

The most basic solution to automatically run a program at startup is to add the following line in the /etc/rc.local file, right before the exit(0) line:

/home/pi/autostart/yclock.py > /dev/null 2>&1 &



Let's see in details what this line means...

  • /home/pi/autostart/yclock.py : It's the executable which must be run. In our case, its our yclock.py file. Make sure to use the file absolute path.
  • > /dev/null : redirects the standard output to /dev/null
  • 2>&1 : redirects error messages on the standard output (which is already redirected to /dev/null)
  • & : runs the executable as a background task


This solution is acceptable for small internal projects, as it is very easy to configure. Incidentally we use it quite regularly in our internal projects. However, this solution has some limitations which make it hardly recommendable for professional projects which must work continuously during months, or even years, on end.

First, log messages and potential error messages are ignored, which makes debugging very complex. One solution would be to redirect messages to a file instead of /dev/null, but this file will grow indefinitely and if your application is very verbose, your file may even grow so much as to completely fill the disk and make Linux crash.

Second, if there is an error, for example if the YoctoDisplay is disconnected, the program stops and doesn't start again. You must reboot the machine in order for the system to work again.

Finally, the application is started with the root user. For this example, it's not much of an issue but for more complex applications which are connected to the Internet, this may create security issues.

You can work around these limitations with shell scripts but it's complex and there is a much cleaner solution...

A better solution: systemd


The best solution is to use the "new" initialization system from Linux: systemd. Since 2015, this system is used by most Linux distributions, including Debian, Ubuntu, and Raspbian. systemd replaces the old system V advantageously, because it is simpler to use and it offers more possibilities, such as managing execution logs.

Each service generated by systemd is configured by a .service file located in the /etc/systemd/system directory.

For our example, we must therefore create a /etc/systemd/system/yclock.service file with at least the following content:

[Unit] Description=Displays the time on a YoctoDisplay [Service] Type=simple ExecStart=/home/pi/autostart/yclock.py [Install] WantedBy=multi-user.target



The Description parameter provides a description which is displayed with some commands from systemctl.

The ExecStart parameter in the Service section defines the command to be run to start the application, in our case it is our /home/pi/autostart/yclock.py script. The Type parameter allows us to select the type of service; unless your application makes forks, the "simple" value is enough.

Finally, the WantedBy parameter defines at which moment during the boot the script must be started. The "multi-user.target" value is the value which should correspond to 99% of the cases, that is right before the login screen when the network and other services have already started.

The systemctl tool allows your to control systemd. Its main commands are:

  • enable: enables the service, which means that the service is started during future boots.
  • disable: disables the service, which means that the service is ignored during future boots.
  • status: displays the current state of the service.
  • start: immediately starts the service.
  • stop: immediately stops the service.


Therefore, for our script to automatically start during the next boots, we must run the following command:

sudo systemctl enable yclock.service



From the start, you can understand a first advantage of this system: you can stop and restart the service without rebooting the OS. This allows you, for example, to modify the Python script on a system in production. But that's not all.

The journalctl command allows you to display the execution logs of the program. Moreover, these logs cannot fill the whole disk because systemd stores in the same way all the OS logs. So, depending on the configuration of your distribution, systemd keeps the X logs the most recent of the service, so as to avoid saturating the disk.

The command to display the logs of our yclock.py application:

sudo journalctl -u yclock.service



Other parameters of the file allow you to fine-tune the configuration to indicate how and when the service must start. For example, with the Restart parameter you can specify if the service must be automatically restarted when the executable ends in an error. The User and Group parameters allow you to select with which user and group the script is run.

To get back to our example, here is the complete file:

[Unit] Description=Displays the time on a YoctoDisplay [Service] Type=simple ExecStart=/home/pi/autostart/yclock.py Restart=on-failure RestartSec=30 User=pi Group=pi [Install] WantedBy=multi-user.target



On top of this, this file indicates that the script must be run by the "pi" user and the "pi" group. Moreover, if the script ends with an error code, for example if no YoctoDisplay is connected, systemd must try to restart the script after 30 seconds.

If you need a more specific configuration, you can always consult the project documentation at this address: https://freedesktop.org/wiki/Software/systemd

Conclusion

We implemented a number of autonomous DIY based on Raspberry Pi and Yoctopuce modules, but we had never explained how to automatically start the applications. We hope that this small post on systemd will make your life easier when implementing Headless systems based on Raspberry Pi, or other machines under Linux.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.