Raspberry Pi 4 NetworkManager configuration tool

The aim was to write a Qt application for the Raspberry Pi 4 with which you can switch between different WLAN access points and save the associated credentials. As a starting point, I used a raspbian-buster-lite image and a Qt installation as described in Qt on the Raspberry Pi 4. I also installed the NetworkManager, which allows you to use the shell command (nmcli...) to create, configure and manage network connections. You can find information on this at https://wiki.debian.org/de/NetworkManager or at https://developer.gnome.org/NetworkManager/stable/nmcli.html. The NetworkManager offers a command which you can use to start a monitoring process that then comments on changes to the various interfaces (wlan0 or eth0) (e.g., unavailable, disconnected, connecting, connected...). I wanted to use this monitoring to display the various statuses of the network points in the GUI. Two problems emerged:

  • When several nmcli commands were issued in quick succession, the feedback about the various statuses was delayed and could not be displayed live in the GUI.
    • The feedback of the nmcli commands was sent in different slots and could therefore not be coordinated well.

Monitoring function

First we need the monitoring function (monitorDevices) and the public slot function, which intercepts and evaluates the monitoring output and, for example, then sends the status messages to the GUI. In the "monitorDevices" function, which launches automatically when the application is started, the nmcli command is launched with sudo: sudo nmcli monitor The instruction "setProcessChannelMode(QProcess::MergedChannels)" states that "Standard-Output" and "Standard-Error" are displayed together in one channel and can thus be evaluated with one function. By using the line "connect (device_monitoring_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()))", this means that whenever a message has been displayed (readyReadStandardOutput), it is sent to the slot "processOutput".

void CmdLauncher::monitorDevices() {
    QStringList device_monitoring_on = {"nmcli", "monitor"};
    device_monitoring_process = new QProcess(this);
    device_monitoring_process->setProcessChannelMode(QProcess::MergedChannels);
    device_monitoring_process->start("sudo", device_monitoring_on);
    connect(device_monitoring_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()));
}

## Evaluation function The evaluation of the messages is then carried out in the "processOutput" function. Here, the QProcess senderProcess intercepts all messages from all nmcli commands – not just those from the monitoring command.

void CmdLauncher::processOutput() {
    QProcess* senderProcess = qobject_cast(sender());
    senderProcess->setReadChannel(QProcess::StandardOutput);

    QString message = QString::fromLocal8Bit(senderProcess->readAllStandardOutput());

    qDebug() << "CmdLauncher::processOutput message: " << message << endl;
    if (message.contains("Error:")) {
        message.remove(QString("\n"));
        error_messages = message;
        emit getErrorMessagesChanged();
        if (message.contains(QString("Error: unknown connection"))) {
            secrets_required = true;
            emit getSecretsRequiredChanged();
        }
    }
    // wifi
    if (message.contains("wlan0: connected") || message.contains("wlan0:connected")) {
        wifi_device_state = "Wifi-Connected";
        emit getWifiDeviceStateChanged();
        error_messages = "";
        emit getErrorMessagesChanged();
        rescanWifi();
        testInternetConnection();
    }
}

## Launching another nmcli process If you now launch another nmcli command with the function below, the output will be intercepted by the above monitoring function and can then be evaluated and further processed in the "outputProcess". This function switches over to an existing network connection and launches it. What is important here is not to incorporate, for example, a "set_wifi_process->waitForReadyRead()" or a "set_wifi_process->waitForFinished()", because then the output of all messages will be blocked until this process is complete.

void CmdLauncher::setWifi(QString ssid) {
    error_messages = "";
    emit getErrorMessagesChanged();

    QString uuid = "";
    for (int i = 0 ; i < networkConnections.length() ; i++) {
        QStringList connections = networkConnections[i].split(":");
        if (connections[1] == ssid)
        {
            uuid = connections[2];
        }
    }

    QStringList args_wifi_exist = {"nmcli", "connection", "up", uuid};
    set_wifi_process = new QProcess(this);
    set_wifi_process->setProcessChannelMode(QProcess::MergedChannels);
    set_wifi_process->start("sudo", args_wifi_exist);
    connect(set_wifi_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()));
}

The line "connect(set_wifi_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()))" directs the output messages of this process back into the "processOutput" and can be evaluated again there at a central point.