Raspberry Pi 4 NetworkManager Configuration Tool

The goal was to write a Qt application for the Raspberry Pi 4 that can be used to switch between different WLAN access points and store the associated credentials.
I used a raspbian-buster-lite image and a Qt installation as described in Qt on the Raspberry Pi 4 as a starting point.
In addition, I have installed the NetworkManager, which can be used by shell command (nmcli ...) create, configure, and manage network connections.
Information on this can be found under https://wiki.debian.org/de/NetworkManager or https://developer.gnome.org/NetworkManager/stable/nmcli.html.
The NetworkManager offers a command that can be used to start a monitoring process, which 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 different statuses of the network locations in the GUI. 2 problems emerged:

  • if several nmcli commands were issued in quick succession, then the feedback about the different statuses arrived with a time delay and could not be displayed live in the GUI.
  • the feedback of the nmcli commands was sent in different slots and could thus be poorly coordinated.

Monitoring function

First of all, we need the monitoring function (monitorDevices) and the public slot function, which intercepts and evaluates the monitoring output and then sends the status messages to the GUI, for example.
In the "monitorDevices" function, which starts automatically when the application starts, the nmcli command is started with sudo: sudo nmcli monitor
The statement "setProcessChannelMode(QProcess::MergedChannels)" states that "Standard Output" and "Standard Error" are displayed together in a channel and can thus be evaluated with a function.
The line "connect(device_monitoring_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()))" is used whenever a message is 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()));
}

</:code1:>

Evaluation function

The evaluation of the messages is then carried out in the "processOutput" function. The QProcess senderProcess intercepts all messages of all nmcli commands - not just those of the monitoring command.

void CmdLauncher::processOutput() {
QProcess* senderProcess = qobject_cast<QProcess*>(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();
}

}

</:code2:>

Start another nmcli process

If you now start another nmcli command with the function below, the output is intercepted by the above monitoring function and can then be evaluated and further processed in the "outputProcess".
This function switches to an existing network connection and starts it. It is important not to include a "set_wifi_process->waitForReadyRead()" or "set_wifi_process->waitForFinished()", because then the output of all messages will be blocked until this process is finished.

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()))" forwards the output messages of this process back to the "processOutput" and can be evaluated there again at a central location. </:code3:>