Raspberry Pi 4 NetworkManager Konfigurationstool

Ziel war es, eine Qt-Anwendung für den Raspberry Pi 4 zu schreiben, mit der man zwischen verschiedenen WLAN Access Points umschalten kann und die zugehörigen Credentials speichern kann.
Verwendet habe ich als Ausgangsbasis ein raspbian-buster-lite Image und eine Qt-Installation wie in Qt auf dem Raspberry Pi 4 beschrieben.
Zusätzlich habe ich den NetworkManager installiert, mit dem man per shell command (nmcli ...) Netzwerkverbindungen erstellen, konfigurieren und verwalten kann.
Informationen dazu findet man unter https://wiki.debian.org/de/NetworkManager oder https://developer.gnome.org/NetworkManager/stable/nmcli.html.
Der NetworkManager bietet einen Befehl an, mit dem man einen Monitoring-Prozess starten kann, der dann Änderungen an den verschiedenen Schnittstellen (wlan0 oder eth0) kommentiert (z.B. unavailable, disconnected, connecting, connected, ...).
Dieses Monitoring wollte ich verwenden, um in der GUI die verschiedenen Stati der Netzwerkstellen darzustellen. Dabei traten 2 Probleme zum Vorschein:

  • wenn mehrere nmcli-Befehle schnell hintereinander abgesetzt wurden, dann kamen die Rückmeldungen über die verschiedenen Stati zeitverzögert an und konnten nicht live in der GUI angezeigt werden.
  • die Rückmeldungen der nmcli-Befehle wurden in verschiedenen Slots geschickt und konnten so schlecht koordiniert werden.

Monitoring-Funktion

Als Erstes brauchen wir die Monitoring-Funktion (monitorDevices) und die Public slot Funktion, die die Monitoring-Ausgabe abfängt und auswertet und z.B. dann die Status-Meldungen an die GUI schickt.
In der Funktion "monitorDevices", die beim Start der Anwendung automatisch gestartet wird, wird der nmcli-Befehl mit sudo gestartet: sudo nmcli monitor
Die Anweisung "setProcessChannelMode(QProcess::MergedChannels)" besagt, daß "Standard-Output" und "Standard-Error" in einem Channel zusammen angezeigt werden und so mit einer Funktion ausgewertet werden können.
Durch die Zeile "connect(device_monitoring_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()))" wird immer, wenn eine Meldung angezeigt wurde (readyReadStandardOutput), diese an den Slot "processOutput" geschickt wird.

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()));
}

Auswertungs-Funktion

Die Auswertung der Meldungen wird dann in der Funktion "processOutput" vorgenommen. Der QProcess senderProcess fängt hier alle Meldungen aller nmcli-Befehle ab - nicht nur die des Monitoring-Befehls.

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();
    }
}

Weiteren nmcli-Prozess starten

Startet man nun mit der untenstehenden Funktion einen weiteren nmcli-Befehl, wird die Ausgabe durch die obige Monitoring-Funktion abgefangen und kann dann in dem "outputProcess" ausgewertet und weiterverarbeitet werden.
Diese Funktion schaltet zu einer bestehenden Netzwerkverbindung um und startet diese. Wichtig dabei ist, nicht etwa ein "set_wifi_process->waitForReadyRead()" oder "set_wifi_process->waitForFinished()" einzubauen, denn dann wird die Ausgabe aller Meldungen solange blockiert, bis dieser Prozess fertig ist.

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()));
}

Die Zeile "connect(set_wifi_process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput()))" leitet die Ausgabemeldungen dieses Prozesses wieder in den "processOutput" und kann dort an zentraler Stelle wieder ausgewertet werden.