Raspberry Pi mit OBD in AWS IoT: Optimiertes Script

Mit der in diesem Beitrag vorgestellten „Quick and Dirty“-Variante des modifizierten AWS-Beispiels lies sich zwar die grundlegende Kette vom Fahrzeug über den AWS IoT-Core zu Grafana aufbauen, allerdings ist sie keine besonders gute Basis für Erweiterungen und es zeigten sich auch ein paar Probleme mit der Robustheit der Datenübertragung. Daher habe ich das Script noch mal vor vorne neu geschrieben und werde es hier vorstellen.

Verwendete Hardware und Software

  • Raspberry PI 3 mit Raspbian Bullseye Lite vom 04.04.2022
  • USB GPS-Maus (Navilock NL-8002U mit uBlox-Chipsatz, sollte aber mit jeder anderen GPS-Maus auch funktionieren)
  • OBDLink SX ELM327-kompatibles OBD-Interface
  • Einen Zugang zu AWS (kostenlos sollte reichen)
  • Die pynmeagps-Bibliothek in Version 1.0.4
  • Die python-OBD-Bibliothek in Version 0.7.1
  • Das AWSIotPythonSDK in Version 1.4.0
  • Das awsiotsdk

Überblick über das neue Script

In der Abbildung rechts ist die Dateistruktur für das neue Script dargestellt. Neben der commandline-Version (cariot-cmdlind.py) ist auch eine GUI-Version in Vorbereitung (cariot-gui.py), doch dazu später mehr. im Unterverzeichnis „cariot“ befinden sich zwei Klassen für jede der Funktionalitäten AWS-Verbindung, GPS-Verbindung sowie OBD-Verbindung. In der *_gui.py-Datei befindet sich der Code für das GUI, in der *.py-Datei der für die entsprechende Verbindung. Jede der Verbindungen läuft in einem eigenen Thread. Falls es so zu einem Problem in einem der Threads kommt, z.B. OBD, können die anderen noch weiter laufen.

Grundsätzlich sind die Klassen für alle drei Verbindungen ähnlich aufgebaut: Neben dem Konstruktor gibt es eine „start()/stop()“-Funktion zum Starten oder Beenden des jeweiligen Threads sowie eine „loop()“-Funktion, in der die Daten von der jeweiligen Quelle abgeholt bzw. an AWS gesendet werden.

Verzeichnisstruktur des neuen Projektes

Schauen wir uns ein paar der Veränderungen im Detail an: Jede der Klassen hat eine „gui_update_fcn()“, die bei der Verwendung im Kommandozeilen-Script leer ist. Außerdem kann es passieren, dass einer der Threads blockiert oder crasht. Dafür ist ein „Alive“-Counter hinzugefügt, der beim Interpretieren der Daten auf dem Server zu verstehen hilft, ob noch neue Daten für die jeweilige Quelle empfangen wurden.

Der Datenaustausch zwischen den einlesenden GPS- und OBD-Threads sowie dem sendenden AWS-Thread geschieht über die gps_data bzw. obd_data-dictionaries der GPS/OBD-Klassen. Da das schreiben und lesen in verschiedenen Threads erfolgt, haben wir ein paar kritische Abschnitte (Details z.B. bei Wikipedia) welche mit „Locks“ abgesichert werden müssen. Diese finden sich beim Schreiben der Variablen in den loop()-Funktionen der einlesenden Threads sowie in den „get_data()“-Funktionen, in denen die Daten geschützt in den sendenden AWS-Thread kopiert werden.

Beim Aufbau der GPS-Verbindung werden mögliche Exceptions abgefangen. Etwas umfangreicher sind die Ergänzungen für die OBD-Verbindung: Während dem Verbindungsaufbau wird der Status des ELM-Devices und der OBD-Schnittstelle überwacht und an AWS gesendet. Außerdem werden die gesendeten Werde auf bei der Initalisierung auf -1 und bei einem Lesefehler auf -2 (bzw. einen physikalisch unmöglichen Wert) gesetzt. Beides dient dazu, Cloud-Seitig bessere Debug-Möglichkeiten zu haben. Außerdem wird die Gültigkeit der pyobd-„response“ überprüft, was die Robustheit des Scripts erhöht.

Herunterladen und Ausführen des Scripts

Das hier verwendete Script könnt ihr von unserem Github herunterladen:

pi@raspberrypi:~/cariot$ git clone -b v0.2 https://github.com/Vehicle-Hacks/cariot.git

Die Konfiguration des Scripts wird jetzt nicht mehr per Kommandozeile (bzw. im Script selbst) durchgeführt sondern es gibt die Datei „cariot-config.json“, in der alles relevante eingestellt werden kann. Hier ist der Inhalt der Datei:

{
    "thing": {
        "endpoint" : "a2dt0lfqh6su9o-ats.iot.eu-central-1.amazonaws.com",
        "certfile" : "thing-certificate.pem.crt",
        "keyfile" : "thing-private.pem.key",
        "rootCaFile" : "AmazonRootCA1.pem",
        "clientId" : "test-9601bf7d-809a-4cc7-8d9b-3e4090124c3b",
        "topic" : "cars/02"
    },
    "gps": {
        "interface" : "/dev/ttyACM0",
        "baudrate" : 115200
    }
}

Im Abschnitt „thing“ werden die für die Verbindung zum AWS IoT-Core relevanten Optionen konfiguriert. Im Prinzip sind es die gleichen, die beim „Quick & Dirty“-Scrip per Kommandozeile übergeben wurden: Die Adresse des AWS IoT-Core-Endpoints, die Zertifikate für das Thing und AWS sowie Client-ID und der Name des Topics, auf dem gesendet wird.

Der nächste Abschnitt enhält das Device und die Geschwindigkeit der GPS-Maus. Die pyobd-Bibliothek baut die Verbindung zum OBD-Adapter automatisch auf, so dass hierfür keine Konfiguration vorgesehen ist.

Nachdem der AWS-Endpoint und die GPS-Maus konfiguriert wurden, kann das Script testweise gestartet werden:

pi@raspberrypi:~/cariot$ ./cariot-cmdline

Das Script sollte sich mit GPS und dem OBD-Adapter verbinden und anfangen, Daten auf AWS hochzuladen. Einfach überprüfen könnte ihr das mit dem „MQTT Test Client“, der im Abschnitt „Ansehen der Daten in AWS“ im ersten Beitrag dieser Reihe beschrieben ist. Wenn alles funktioniert, kann mit „Strg+C“ kann das Script wieder beendet werden.

Das Script als Service starten

Die Einrichtung als Service erfolgt im Prinzip analog zum „Quick&Dirty“-Vorgehen. Da das python-script die Parameter jetzt aus der Konfigurationsdatei einliest, ist das shell-script zur Übergabe der Parameter aber nicht mehr erforderlich. Damit das script auch vom Service ausführbar ist, habe ich den ganzen aus git geclonten Inhalt des Projektes unter /usr/local/lib/cariot abgelegt. Dort befinden sich außerdem noch die AWS-Zertifikate. Der Inhalt des Verzeichnisses sieht damit wie folgt aus:

pi@raspberrypi:/usr/local/lib/cariot$ ls
AmazonRootCA1.pem  cariot_cmdline.py                      cmd.sh                 thing-certificate.pem.crt
AmazonRootCA3.pem  cariot-config.json                     LICENSE                thing-private.pem.key
AWS                cariot-initial-service.code-workspace  README.md              thing-public.pem.key
cariot             cariot-service.py                      simple_car_connect.py

Die Konfigurationsdatei für den Service liegt unter /etc/systemd/system/cariot.service und hat folgenden Inhalt:

[Unit]
Description=Simple Service for sending ELM327 OBD Data to AWS IoT Core
After=network.target ntp.service

[Service]
User=ralph
ExecStart=/usr/local/lib/cariot/cariot_cmdline.py

[Install]
WantedBy=multi-user.target

Mit „service cariot start“ kann der service testweise gestartet bzw. mit „service cariot stop“ getested werden. Dauerhaft aktiviert wird er mit „sudo systemctl enable cariot“. Weitere Details zum Service finden sich unter „Konfigurieren von Systemd“ im Beitrag „OBD/AWS IoT-Script als Service – Quick and Dirty“.

Die SD-Karte als „read only“ einrichten

Soll der raspberry pi mit dem hier vorgestellten Script einem einem Fahrzeug verwendet werden und dabei über die Zündung gestartet/ausgeschaltet werden, müssen analog dem „OBD/AWS IoT-Script als Service – Quick and Dirty„-Beitrag NTP eingerichtet werden und die IP-Adresse fix konfiguriert werden. Im Gegensatz zum dort verwendeten raspbian „buster lite“ wird bei der hier verwendeten version „bullseye“ der read only-Modus nicht mehr über das Script eingerichtet, statt dessen gibt es einen Menüpunkt in „raspi-config“. Dieses müsst ihr mit root-Rechten, also z.B. sudo aufrufen:

pi@raspberrypi:~ $ sudo raspi-config

In raspi-config wird dann „4 Performance Options“ und darin „P3 Overlay File System“ aufgerufen. Die beiden folgenden Fragen, „Would you like the overlay file system to be enabled?“ und „Would you like the boot partition to be write-protected?“ werden beide mit „yes“ beantwortet. Danach ist das System „read only“ und überlebt Power-Cycles auch ohne heruntergefahren zu werden.

Als Unterschied zum im letzten Beitrag verwendeten „read-only-script“ lassen sich dank des Overlay-Filesystems lassen sich jetzt während der Laufzeit Dateien auf dem System modifizieren, die Änderungen sind aber noch einem reboot verloren. Um dauerhafte Veränderungen vorzunehmen, müssen Overlay und den Schreibschutz wieder deaktiviert werden. Dazu kann wieder raspi-config verwendet werden, diesmal werden die Frage nach dem Overlay mit „no“ beantwortet. Danach ist ein Neustart erforderlich. Falls eine Änderung an der Boot-Partition erforderlich ist, kann diese nach dem Neustart auf read-write gesetzt werden.

Ergebnisse

Ein Raspberry Pi mit dem hier beschriebenen Script hat uns eine Weile auf einer Reise begleitet und dabei Daten in AWS hochgeladen.Im Bild unten ist ein mit Grafana erstellter Ausschnitt mit Motorlast, Drehzahl, Einlass- sowie Kühlwassertermperatur dargestellt. Grundsätzlich scheinen die hochgeladenen Daten plausibel zu sein. Die Ursache für die vielen „Aussetzer“ ist noch unklar: Das verwendete Fahrzeug hat tatsächlich gelegentlich Kommunikationsprobleme auf dem J1850VPW-Bus, die eine mögliche Ursache sind. Tatsächlich könnte es sich aber auch um ein Problem mit dem Script handeln. Es ist also noch viel Raum für weitere Entwicklung vorhanden.

Grafana-Plot von mit diesem Script an AWS übertragenen Motor-Last, -Drehzahl, Einlass- und Kühlwassertemperatur