Hast du schon immer mal vorgehabt, ein paar Daten aus dem Auto zu loggen und auszuwerten – zur Fehlersuche oder aus Interesse? Eine moderne Möglichkeit dafür ist, das Fahrzeug als IoT-Device mit „der Cloud“ zu verbinden. In diesem Beitrag verwenden wir einen Raspberry PI, eine GPS(GNSS)-Maus und ein ELM327-kompatibles OBD-Dongle, um dies zu realisieren. Um eine kurze Übersicht zu geben: Der Raspberry PI liest mit einem Python-Script Daten von GPS und über OBD ein und sendet diese JSON-Kodiert über MQTT an den AWS IoT Core. Dort werden sie in eine AWS Timestream-Datenbank geschrieben, von wo sie weiter verarbeitet werden können. Sie können z.B. in einem Grafana-Dashboard angezeigt werden, wie wir in einem Folgebeitrag zeigen werden.
Verwendete Hardware und Software
- Raspberry PI 3 mit Raspbian Buster Lite 07.05.2021
- 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
Einrichten des Raspberry PI
Als erstes wird auf üblichem Weg die SD-Karte für den Raspberry PI erstellt. Wer sich nicht mehr sicher ist, kann den Abschnitt „SD-Karte erstellen unter Linux“ auf dieser Seite als Anleitung nehmen. Nachdem das Image auf die SD-Karte geschrieben und der Raspberry gebootet ist, updaten wir zunächst raspbian und installieren pip3 (den Python-Paketmanager) sowie die benötigten Python3-Pakete:
pi@raspberrypi:~ $ sudo apt-get update
pi@raspberrypi:~ $ sudo apt-get upgrade
pi@raspberrypi:~ $ sudo apt-get install python3-pip
pi@raspberrypi:~ $ sudo pip3 install AWSIotPythonSDK awsiotsdk obd pynmeagps
Nach dem Installieren der Pakete ruft ihr das Tool „raspi-config“ als root auf. Dort müssen wir das Land für den WiFi-Adapter einstellen und dann das Netzwerk konfigurieren. Ich habe für den Internetzugang vom Raspberry PI hier den „persönlichen Hotspot“ von meinem Handy über WiFi benutzt. Das Tool startet ihr wie folgt:
pi@raspberrypi:~ $ sudo raspi-config
Dann wählt ihr „5 Localization Options -> L4 WLAN Country“ und wählt dort das richtige Land aus. Alsnächstes wählt ihr dann „1 System Options -> S1 Wireless LAN“ und stellt dort SSID und Passwort von eurem „Persönlichen Hotspot“ ein.
AWS konfigurieren
Nachdem ihr euch bei AWS angemeldet habt, müsst ihr zunächst die Region auswählen, in der ihr Services anlegen wollt. Falls z.B. nach einem Login die konfigurierten Services, Things etc. plötzlich „verschwunden“ sind, liegt das oft daran, das noch die falsche Region ausgewählt ist. Ich habe hier als Region Frankfurt („eu-central-1“) gewählt:

Nachdem die Region gewählt ist, öffnet ihr dann links oben unter „Services -> Internet of Things“ den IoT Core. Wenn ihr ein „Thing“ anlegt, müsst ihr eine Policy zuordnen, die die Zugriffsrechte regelt. Also legen wir als erstes diese Policy an. Dazu wählt ihr „Secure -> Policies“ und dann „Create“, um eine neue Policy zu erstellen. In dem sich öffnenden Dialog wählt ihr „Advanced Mode“, dann könnt ihr in dem Text-Fenster die folgende Policy anlegen:
Achtung – die hier gezeigte Policy ist sehr offen, was die Berechtigungen angeht. Wir werden noch eine eingeschränktere Version testen und nachreichen.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Publish",
"iot:Receive"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Subscribe"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:eu-central-1:811602309128:client/test-*"
]
}
]
}
Als nächstes legen wir das „Thing“ an. Dazu kommt ihr über „Manage -> Things“ in die List der Things, die jetzt natürlich noch leer ist. Über „Create things“ könnt ihr euer erstes Thing anlegen. Nachdem ihr auf „Create“ geklickt habt, wählt ihr „Create single thing“. Im nächsten Dialog könnt gebt ihr dem „Thing“ einen Namen, könnt aber die anderen Einstellungen zunächst auf den Default-Werten lassen wie im folgenden Screenshot.

Über „Next“ gelangt ihr einen Dialog weiter und wählt dort „Auto-generate a new certificate“. Im anschließenden Dialog die am Anfang angelegte „CarPolicy“ und abschließend „Create Thing“. Es öffnet sich ein Dialog, in dem ihr die Zertifikate für das „Thing“ herunterladen könnte. Auf jeden Fall müsst ihr das „Device certificate“, das „Public key file“ und das „Private key file“ runterladen. Den Public und Private-Key könnt ihr nur jetzt in diesem Dialog heruntergeladen! Die „Root CA“-Zertifikate gelten für den Endpunkt (pro Region), daher müsst ihr sie nur einmal herunterladen und könnt sie für mehrere Things verwenden.

Die vom „Thing“ gesendeten Daten wollen wir in der „Amazon Timestream“-Datenbank ablegen. Dafür müssen wir zunächst eine Datenbank erstellen. Das geht theoretisch auch aus den IoT-Core-Menüs heraus, wir legen sie aber über den Service an. Dazu wählt ihr „Services -> Database -> Amazon Timestream“ und im sich öffnenden Fenster „Create Database“. Den Typ stellt ihr auf „Standard database“, dann könnt ihr noch einen Namen vergeben. Im Beispiel habe ich „carDB“ vergeben. Über „Create Database“ wird die Datenbank erstellt.
Ihr landet jetzt in der Liste mit den Datenbanken. Wenn ihr jetzt links im Menü „Tables“ auswählt, bekommt ihr eine (noch leere) Liste mit Tabellen angezeigt. Hier könnt ihr über „Create table“ eine neue Tabelle anlegen. Im sich öffnenden Dialog wählt ihr oben die soeben angelegte Datenbank aus (carDB) und vergebt einen Namen für die Tabelle (im Beispiel „carTable“). Data retention stellt ihr z.B. auf einen Tag für „Memory“und einen Monat für „Magnetic Storage“. Ihr könnt die Werte aber beliebig wählen. Der Dialog mit den von mir gewählten Einstellungen ist im Screenshot unten gezeigt. Über „Create Table“ wird die Tabelle angelegt.

Jetzt könnt ihr eine Regel erstellen, über die ausgewählte Daten vom „Thing“ in der Amazon Timestream-Datenbank abgelegt werden. Dazu geht ihr wieder auf den IoT Core und wählt links „Act->Rules“ und im sich öffnenden Fenster „Create Rule“. Im sich jetzt öffnenden Fenster könnt ihr der Regel zunächst einen Namen (hier: „CarThingsRule“) und eine Beschreibung geben. Wichtig ist das „Rule query statement“, über das die Werte aus den per MQTT erhaltenen JSON-Botschaften (siehe unten) ausgewählt werden, die dann von der Regel weiterverarbeitet werden. Als query statement gebt ihr folgenden Text ein:
SELECT latitude, longitude, altitude, quality, coolant, voltage, oil, load, intake, counter FROM 'cars/#'
Der Dialog sollte dann wie folgt aussehen:

Anschließend klickt ihr auf „Add action“. Es öffnet sich eine Liste mit Aktionen, dort wählt ihr „Write a message into a Timestream table“ aus der Liste und klickt auf „Configure action“. Es öffnet sich der im Screenshot unten dargestellte Dialog. Dort wählt ihr die soeben angelegte Datenbank („carDB“) und Tabelle („carTable“) aus. Die „Dimensions“ helfen bei der Identifizierung und Zuordnung der „Things“. Wir verwenden hier als Dimension „Car_ID“ aus dem JSON-Feld „ID“. Danach müsst ihr im Dialog unten über „Create Role“ noch eine Rolle anlegen, die ich hier „CarRuleRole“ genannt habe. Anschließend klickt ihr auf „Update“ und fügt dir Action zur Rule hinzu.
Zurück im vorherigen Dialog scrollt ihr ganz nach unten und legt über „Create rule“ die Regel im AWS IoT Core an.

Als letztes brauchen wir noch die Adresse des „Device data endpoint“, mit dem sich die „Things“ verbinden können. Um diesen auszulesen, klickt ihr links im AWS IoT-Menu auf „Settings“. Im sich öffnenden Dialog findet ihr oben die Adresse des Endpoints in der Form „XXX-ats.iot.eu-central-1.amazonaws.com“:
Jetzt sind wir mit der Konfiguration von AWS fertig und können uns um das Python-Script für den Raspberry PI kümmern, dass die Daten per MQTT hochlädt.
Python-Script auf dem Raspberry PI
Das Python-Script, was ich für diesen Beitrag verwendet habe, ist eine modifizierte Version des „pubsub.py-Samples“ aus dem AWS IoT Device SDK v2 for Python. Eine Einführung in das Original-Beispiel findet ihr auch in der AWS-Dokumentation. Das modifizierte Beispiel könnt ihr aus unserem Github herunterladen:
pi@raspberrypi:~/tmp$ git clone https://github.com/Vehicle-Hacks/cariot.git
Gegenüber dem Original sind ab Zeile 57 ein paar globale Variablen und zwei Funktionen eingefügt. Schauen wir uns zunächst den GPS-Teil an: Vom GPS-Empfänger wird über die serielle Schnittstelle ein kontinuierlicher Strom von „NMEA0183“-Daten geschickt. Diese muss dekodiert werden und es müssen die Botschaften, die wir an den AWS IoT Core weiter leiten wollen, herausgefiltert werden. Am einfachsten funktioniert das, indem wir einen separaten Thread aufmachen, der sich nur um die NMEA0183-Daten kümmert. Die Kommunikation zwischen dem „Main“-Thread und dem „GPS-Dekoder“ erfolgt über die globalen Variablen, die zuerst definiert werden:
# GPS Globals
gpsInterface = '/dev/ttyACM0'
gpsSpeed = 115200
gpsRunning = True
gpsAvailable = False
lat = 0;
lon = 0;
alt = 0;
quality = 0;
Es ist wichtig, die Variablen „gpsInterface“ und „gpsSpeed“ an euer Device und euren Port für das GPS anzupassen. Darunter wird die Funktion definiert, die den GPS-NMEA0183-Datenstrom mit Hilfe von pynmeagps dekodiert und die gewünschten Signale in die globalen Variablen schreibt, von wo sie der Main-Thread per MQTT ian den AWS IoT Core schickt:
def gps_thread():
global lat, lon, alt, quality, gpsRunning, gpsAvailable
gpsStream = Serial(gpsInterface, gpsSpeed, timeout=3)
nms = NMEAReader(gpsStream)
print('GPS started')
while gpsRunning:
try:
(raw_data, parsed_data) = nms.read()
if parsed_data:
if parsed_data.msgID == "GGA":
lat = parsed_data.lat
lon = parsed_data.lon
alt = parsed_data.alt
quality = parsed_data.quality
gpsAvailable = True
except (
nme.NMEAStreamError,
nme.NMEAMessageError,
nme.NMEATypeError,
nme.NMEAParseError,
) as err:
print(f"Something went wrong {err}")
continue
Am Anfang werden die globalen Variablen im Kontext der Funktion deklariert und anschließend GPS-Interface und pynmeagps initialisiert. Dann läuft der Thread so lange in einer Endlosschleife, bis gpsRunning beim beenden des Scripts vom Main-Thread auf „False“ gesetzt wird. Während der Thread läuft, wird in jeder Iteration ein NMEA0183-Paket von pynmeagps dekodiert. Falls es ein „GGA“-Paket ist werden Länge, Breite, Höhe und Quality ausgelesen und über die globalen Variablen an den Main-Thread übergeben. Außerdem wird „gpsAvailable“ auf True gesetzt, damit der Main-Thread weiß, dass ein gültiger Fix empfangen wurde und der GPS-Receiver sich nicht noch in der Initialisierung befindet.
Ähnlich ist der Teil für OBD aufgebaut: Das Interface von der der python-OBD-Library automatisch detektiert, daher sind nur die Variablen für die Thread-Steuerung und die Übergabe der ausgelesenen Werte definiert. Der Thread initialisiert das Interface und fragt dann, bis er beendet wird, ein paar OBD-PID’s ab. Es wird nicht geprüft, ob die PID’s vom Fahrzeug unterstütz werden, daher kann das Script an dieser Stelle mit einer exception hängen bleiben! Ihr könnt dann die entsprechende PID, ähnlich der Öltemperatur hier, auskommentieren (bzw. weitere hinzufügen):
# OBD Globals
obdRunning = True
obdAvailable = False
obdVoltage = 0
obdCoolant = 0
obdOil = 0
obdLoad = 0
obdIntake = 0
# OBD Thread
def obd_thread():
global obdRunning, obdAvailable, obdVoltage, obdCoolant, obdOil, obdLoad, obdIntake
obdConnection = obd.OBD()
print("OBD connected")
while obdRunning:
cmd = obd.commands.COOLANT_TEMP
response = obdConnection.query(cmd)
obdCoolant = response.value.magnitude
cmd = obd.commands.ELM_VOLTAGE
response = obdConnection.query(cmd)
obdVoltage = response.value.magnitude
#cmd = obd.commands.OIL_TEMP
#response = obdConnection.query(cmd)
#obdOil = response.value
cmd = obd.commands.ENGINE_LOAD
response = obdConnection.query(cmd)
obdLoad = response.value.magnitude
cmd = obd.commands.INTAKE_TEMP
response = obdConnection.query(cmd)
obdIntake = response.value.magnitude
obdAvailable = True
time.sleep(0.5)
Um das Script zu starten, müssen ihm einige Parameter übergeben werden:
–endpoint | Der Endpoint, wir ihr in aus dem IoT-Core ausgelesen habt |
–root-ca | Die Datei „AmazonRootCA1.pem“, die ihr beim Erstellen des Things heruntergeladen habt. |
–cert | Das Zertifikat, das ihr beim Erstellen des Things heruntergeladen habt (.pem.crt-Datei) |
–key | Das Private-Key-File, das ihr beim Erstellen des Things heruntergeladen habt (-private.pem.key-Datei) |
–topic | Das MQTT-Topic, auf dem gesendet wird |
–count | Die Anzahl von Botschaften, die geschickt wird, 0 für unendlich |
Ein kompletter Aufruf sieht damit wie folgt aus (XXX ist der Endpoint, YYY das Thing):
python3 simple_car_connect.py --endpoint XXX-ats.iot.eu-central-1.amazonaws.com --root-ca AmazonRootCA1.pem --cert YYY-certificate.pem.crt --key YYY-private.pem.key --topic cars/01 --count=0
Ansehen der Daten in AWS
Nachdem das Script läuft, können wir uns die Daten in AWS IoT Core ansehen. Ein erstes einfaches, aber hilfreiches Tool ist der MQTT test client. Ihr erreicht ihn über „Services -> IoT Core -> Test“. Unter „Topic filter“ müsst ihr einen Filter für die MQTT-Topics eingeben, um subscriben zu können. In unserem Beispiel sorgt „cars/#“ mit dem Wildcard „#“ dafür, das MQTT-Botschaften von allen „cars“-Things empfangen werden. Nachdem ihr auf „Subscribe“ klickt, solltet ihr im „Subscriptions“-Feld das konfigurierte Topic und daneben die vom Raspberry PI gesendeten Pakete sehen. Noch ein Hinweis: Als ich den Screenshot erstellt habe, hatte ich keinen Zugriff auf einen OBD-Port, daher sind alle über OBD erhaltenen Werte Null.

Als nächstes könnt ihr prüfen, ob die Daten korrekt in Amazon Timestream ankommen. Dazu öffnet ihr „Services -> Amazon Timestream“ und öffnet das Menü links indem ihr auf die drei übereinander liegenden Balken klickt. Anschließend wählt ihr „Tables -> carTable -> Query Table“ und gebt dort folgende Abfrage ein:
SELECT * from "carDB"."carTable" ORDER BY time DESC
Nach dem Ausführen der Abfrage solltet ihr eine Ausgabe ähnlich der im folgenden Screenshot erhalten:

Ergebnis und nächste Schritte
Wir sind jetzt in der Lage, OBD2 und GPS-Daten über einen Raspberry PI einzulesen und über den AWS IoT Core in eine Amazon Timestream-Datenbank abzulegen. Das Script über die Shell zu starten ist im Fahrzeug natürlich unbequem – aber über ein Einrichten als Service lässt es sich noch komfortabler gestalten. Und die Daten sollen natürlich nicht nur in der Datenbank gesammelt, sondern auch weiter genutzt werden. Eine einfache Möglichkeit ist es, ein Dashboard mit Grafana zu erstellen. Zu beiden Themen gibt es mehr in den folgenden Beiträgen.
Pingback: OBD2 mit Raspberry PI in AWS IoT - Vehicle-Hacks