Monitoring Sys Files with QFileSystemWatcher

When the driver turns off the ignition in a vehicle, display computers like terminals in harvesters or infotainment systems in cars should be notified. The display computers should save important data and shut down orderly.

When the display computer runs on Linux, it learns the status of the ignition by monitoring the GPIO for clamp 15. Linux writes the value of the GPIO to a special file in the sys filesystem. On one of my display computers, clamp15 is mapped to the special file /sys/class/gpio/gpio72/value. When the ignition is on, this file contains the string “1”. When off, this file contains “0”. The number of GPIO (here: 72) varies from display computer to display computer.

On the Linux command line, we get the value of clamp 15 with this command:

$ cat /sys/class/gpio/gpio72/value
1

The command returns “0” when the ignition is off and “1” when it is on.

How can a Qt application monitor the status of the GPIO clamp 15 such that it can shut down orderly when the driver turns off the ignition?

Our first idea most likely is to use QFileSystemWatcher on the file /sys/class/gpio/gpio72/value. QFileSystemWatcher does exactly what we want. When the watched file is modified, QFileSystemWatcher emits the signal fileChanged(const QString &path). We connect a slot to this signal and read the value from the sys file.

// In the constructor of SomeClass
    auto clamp15Monitor = new QFileSystemWatcher{this};
    clamp15Monitor->addPath("/sys/class/gpio/gpio72/value");
    connect(clamp15Monitor, &QFileSystemWatcher::fileChanged,
            this, &HarvesterServicesImpl::onClamp15FileChanged);

The slot onClamp15FileChanged does nothing else but the command cat /sys/class/gpio/gpio72/value. It reads the value from the sys file and prints it.

void SomeClass::onClamp15FileChanged(const QString &path) 
{
    QFile clamp15File{filePath};
    if (!clamp15File.open(QIODevice::ReadOnly)) {
        qWarning() << "ERROR: Could not open clamp15 file.";
        return;
    }
    auto clamp15 = clamp15File.readAll();
    if (!clamp15.isEmpty()) {
        qDebug() << "INFO: clamp15 = " << clamp15.at(0);
    }
}

When we run this code, we are flooded with INFO messages. The messages show the correct value of clamp 15 - many times per second. However, we want to see an INFO message only when the value of clamp 15 changes.

We could give up at this point and set up a QTimer, which calls onClamp15FileChanged() every second. We poll the status of the clamp-15 file once a second. This works, but it wastes many CPU cycles.

Fortunately, we can do better. The file /sys/class/gpio/gpio72/edge comes to our rescue. Section "GPIO and sysfs" (p. 394ff) of John Madieu's book "Linux Device Drivers Development" points us in the right direction.

edge determines the signal edge that will let the poll or select function return. Allowed values are none, rising, falling, or both. This file is readable and writable, and exists only if the pin can be configured as an interrupt-generating input pin.

The system functions poll and select monitor I/O activities on file descriptors. John Madieu explains:

[...] one can call the poll (2) system call on that file [/sys/class/gpio/gpio72/value] and poll (2) will return whenever the interrupt was triggered. Using poll (2) will require setting the [event] POLLPRI [...] If one uses select (2) instead, one should set the file descriptor in exceptfds. After poll (2) returns, either lseek (2) to the beginning of the sysfs file and read the new value or close the file and re-open it to read the value.

In other words: If the Linux kernel (the GPIO device driver, to be exact) sees an edge on a GPIO, it triggers an interrupt. The interrupt writes the changed value into the sysfs file corresponding to the GPIO. poll and select see this activity on the monitored file descriptor and return from blocking. The caller of these system functions can read the contents of the sys file.

As QFileSystemWatcher is implemented with one of these system functions, we should get it working properly. The trick is to configure the GPIO device driver to check for edges on GPIO 72. This isn't the case, as the command

$ cat /sys/class/gpio/gpio72/edge
none

shows. This explains why QFileSystemWatcher has been misbehaving so far. We configure GPIO 72 to check for "both" edges with the following command:

$ echo "both" > /sys/class/gpio/gpio72/edge

When we run our application now, we will see INFO messages only when the value of /sys/class/gpio/gpio72/value changes. We never see the same value twice in a row. QFileSystemWatcher emits its signal fileChanged only if the driver turns the ignition from "on" to "off" or from "off" to "on".

4 Comments

  1. Marek
    2018/09/19

    From linux 4.8 sysfs gpio interface is deprecated and new library libgpiod was created: https://github.com/brgl/libgpiod This makes handling of gpio in userspace easier (including events).

    1. Burkhard Stubert
      2018/09/19

      How would you solve monitoring GPIOs with libgpiod? And, how would it be easier?

  2. Marek
    2018/09/20

    There is an api in libgpiod which can notify about change of gpio (also can watch for multiple gpios):

    /**
    * @brief Wait for events on a single GPIO line.
    * @param device Name, path, number or label of the gpiochip.
    * @param offset GPIO line offset to monitor.
    * @param active_low The active state of this line – true if low.
    * @param consumer Name of the consumer.
    * @param timeout Maximum wait time for each iteration.
    * @param poll_cb Callback function to call when waiting for events.
    * @param event_cb Callback function to call for each line event.
    * @param data User data passed to the callback.
    * @return 0 if no errors were encountered, -1 if an error occurred.
    * @note The way the ctxless event loop works is described in detail in
    * ::gpiod_ctxless_event_loop_multiple – this is just a wrapper aound
    * this routine which calls it for a single GPIO line.
    */
    int gpiod_ctxless_event_loop(const char *device, unsigned int offset,
    bool active_low, const char *consumer,
    const struct timespec *timeout,
    gpiod_ctxless_event_poll_cb poll_cb,
    gpiod_ctxless_event_handle_cb event_cb,
    void *data) GPIOD_API;

  3. Burkhard Stubert
    2018/09/22

    The described solution relies on GPIOs being exposed as “normal” files in the sys file system. This allows you to use a general component like QFileSystemWatcher to monitor value changes of these files and hence of the GPIOs.

    QFileSystemWatcher uses poll/select to monitor files. If you had to use something different like gpio_ctxless_event_loop instead of poll/select, you couldn’t use QFileSystemWatcher any more. You would have to implement a class for accessing GPIOs using functions from gpiod. It would certainly be possible, but it would lack the beauty of using files.

    Does the new GPIO solution have a file interface, which can be used like the sys files?

Comments are closed.

Scroll to top