LoRaWAN UDP Packet Forwarder 1
17.12.2024
Elektronik | Funk | Software
Der Technik-Blog
Bei der Payload handelt es sich um die Nutzdaten, die zwischen Sensor und dem LoRaWAN Netzwerk ausgetauscht werden. LoRa ist nicht für große Datenmengen konzipiert, daher gilt es, die zu übertragenden Datenmengen auf ein Minimum zu reduzieren. In diesem Artikel geht um das generelle Decodieren und Encodiern der LoRa Payload sowie um die Aufbereitung verschiedener Datentypen und Zahlenwerte. Außerdem geht es um ein paar Best Practice Beispiele, wie mehrere Messwerte von einem Sensor sparsam übertragen werden können.
LoRaWAN & TTN mit ESP32 Board
Dragino LG-02 LoRaWAN Gateway installieren
Daten vom TTN auf die eigene Webseite senden
Grundsätzlich gibt es keine Vorgabe, wie eine Payload aufzubauen ist bzw. welche Art von Datentypen verwendet werden sollen. Das LoRa Endgerät (Thing) verfügt in der Regel über mehrere Sensoren. Als Beispiel eignet sich dafür eine kleine Wetterstation mit vier Sensoren (Temperatur, Feuchtigkeit, Windgeschwindigkeit, Luftdruck). Alle Messwerte haben unterschiedliche Größen und Bereiche, in denen sich die Zahlenwerte bewegen. In der Station werden jetzt alle Werte zusammengefasst und auf ein einheitliches Datenformat (8-Bit Array) gebracht. Außerdem werden bestimmte Werte gerundet, Kommastellen reduziert und eventuell negative Werte in positive Werte gewandelt. Durch diesen Prozess wird die Payload deutlich verkleinert. Anschließend wird die Payload im Endgerät verschlüsselt und abgesendet. Ist das LoRaWAN-Paket am Applikation-Server angekommen, wird dort die Payload wieder entschlüsselt und als hexadezimales Zahlenpaar angezeigt. Damit die Daten von anderen Anwendungen weiterverarbeitet werden können, muss die Payload wieder in ihre ursprünglichen Teile zerlegt werden. Diese Aufgabe übernimmt der Decoder und gibt anschließend die ursprünglichen Daten z. B. im JSON-Format weiter. Die folgenden Beispiele zeigen, wie die Payload beispielsweise von einem Arduino oder ESP32 encodiert wird inklusive den dazugehörigen Decoder, der bei TTN integriert wird:
Die kleinste Größe, die übertragen werden kann, ist ein Byte. Ein Byte besteht aus acht Bit. Ein Bit wiederum kann nur Null oder Eins bzw. High oder Low sein. Durch die binäre Kombination dieser acht Zustände ergeben sich 2^8 (256) mögliche Zustände, die ein Byte haben kann. Es ist also möglich, eine Zahl von 0-255 (nur Positive) mit nur einem Byte darzustellen. Im folgenden Beispielcode wird am Arduino eine Zahl als Byte deklariert, die anschließend über LoRaWAN zu TTN übertragen wird:
Im ersten Beispiel wird ein Byte deklariert, dass die Zahl 143 enthält und somit im möglichen Bereich von 0-255 liegt. Die Payload besteht in diesem Fall nur aus einem Byte. Eine Umwandlung der Zahl 143 in hexadezimal ergibt 8F, so wie es auch bei TTN empfangen wird.
Im folgenden Arduino Codebeispiel wird eine unsigned 8-Bit Integer Array (uint8_t) mit einer Größe von 1 erstellt. Übrigens gibt das "_t" gibt an, dass es sich um einen Datentyp handelt. Dadurch werden Verwechselungen mit Variablennamen vermieden. Anschließend wird eine neue Variable (var) angelegt, die als Byte deklariert ist und den zu übertragenden Wert 143 enthält. Hinweis: Ein Array wird für ein Byte nicht zwingend benötigt, da aber meistens mehr als 1 Byte bzw. Wert übertragen wird, kann man auch hier bereits auf ein Array setzen. Anschließend wird dieses Byte an die Position 0 (erste Stelle) im Array gesetzt. Das Array wird in weiterer Folge der Library übergeben und die Daten werden ausgesendet.
uint8_t payload[1]; byte var = 143; payload[0] = var;
Kommt das Datenpaket bei TTN an, so wird dies als hexadezimale Zahl angezeigt. TTN übergibt die hexadezimale Payload an den Decoder der jeweiligen Application. Die Aufgabe vom Decoder ist, dieses einzelne hexadezimale Byte wieder in eine Dezimalzahl zu wandeln.
Der Encoder bekommt die komplette Payload als Bytes-Array übergeben und muss diese jetzt wieder in ihre ursprünglichen Bestandteile zerlegen. Das Byte mit 148 wurde an die Stelle 0 im Array abgelegt. Daher wird die Variable "decoded_var" mit dem ersten Byte vom Array befüllt. Anschließend steht die Variable "var" mit dem Inhalt 148 im Klartext zur Verfügung und kann in TTN gelesen werden.
function Decoder(bytes, port) { var decoded_var = bytes[0]; return { var: decoded_var } }
Das nächste Beispiel zeigt die Übertragung einer größeren Zahl. Ein 16-Bit unsigned Integer kann einen Zahlenwert im Bereich von 0-65535 enthalten. Ein Integer besteht aus 16 einzelnen Bits, die jetzt in zwei Bytes aufgeteilt werden. Wie im ersten Beispiel wird jetzt wieder ein 8-Bit Integer Payload Array mit einer Größe von 2 Bytes erstellt. Mittels Bit-Shifting werden jetzt die ersten 8 Bit vom Integer um genau 8 Bit nach rechts verschoben und in die erste Stelle des Payload-Arrays gespeichert. Die übrigen 8 Bit werden an die zweite Stelle im Payload-Array gespeichert.
Die Zahl 12450 entspricht jetzt der hexadezimalen Payload 30A2. Das folgende Beispiel wandelt ein 2-Byte Integer in die entsprechende Payload um, sodass diese von TTN wieder decodiert werden kann:
uint8_t payload[2]; int var = 12450; payload[0] = var >> 8; payload[1] = var;
Die empfangene und entschlüsselte Payload wird bei TTN wieder als hexadezimales Zahlenpaar angezeigt. Damit aus den zwei Bytes wieder ein Integer entsteht, werden die ersten 8 Bits wieder um 8 Stellen nach links geschoben und die restlichen 8 Bits aus dem zweiten Byte hinten angehängt. Aus der Payload 30A2 wird wieder die Zahl 12450.
Wird mit Bit-Shifting gearbeitet, findet am Decoder immer das Gegenteil vom Encoder statt. Werden Bits im Endgerät nach rechts verschoben, so müssen die Bits vom Decoder wieder nach links verschoben werden. Über eine Und-Verknüpfung werden beide Bytes wieder zu einem 16-Bit Integer zusammengefügt.
function Decoder(bytes, port) { var decoded_var = bytes[0] << 8 | bytes[1]; return { var: decoded_var } }
Ein unsigned Long funktioniert gleich wie ein Integer, jedoch können hier noch größere Zahlenwerte abgespeichert werden. Ein Long besteht aus 4 einzelnen Bytes, die zusammen 32 Bit ergeben. Ein 4-Byte Long kann ein Zahlenbereich von 0-4294967295 darstellen. Die Größe der Payload-Array liegt bei 4. An der ersten Stelle des Arrays werden wieder die ersten 8 Bit vom Long gespeichert. An zweiter Stelle befinden sich die nächsten 8 Bit bis hin zur vierten Stelle, wo sich die letzten 8 Bit vom Long befinden.
Der Encoder funktioniert gleich wie beim Integer, jedoch werden für ein Long vier Bytes benötigt. Folgender Code wandelt ein Long in ein 8-Bit Payload Array um:
uint8_t payload[4]; long var = 1234578690; payload[0] = var >> 24; payload[1] = var >> 16; payload[2] = var >> 8; payload[3] = var;
Der Decoder funktioniert gleich wie mit einem Integer. Auch hier wird wieder gegenteilig zum Encoder gearbeitet. Beim ersten Byte werden 24 Bit nach links verschoben. Anschließend wird das zweite Byte (16-Bit Linksverschiebung) und das dritte Byte (8-Bit Linksverschiebung) sowie das letzte Byte zusammengefügt. Daraus ergibt sich aus der Payload 49962D02 die Dezimalzahl 1234567890.
Der Decoder wandelt das 4-Byte Array wieder in eine 32-Bit Dezimalzahl um:
function Decoder(bytes, port) { var decoded_var = bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]; return { var: decoded_var } }
Ein String kann mehrere Buchstaben, Zahlen und Sonderzeichen beinhalten. Die Anzahl an Bytes hängt von der Länge des Strings ab. Die maximale Länge eines Strings variiert und hängt in der Regel von den Speichereigenschaften des Controllers ab. Achtung: Generell gilt es, die Übertragung von Texten und Zeichenketten zu vermeiden. Außerdem ist die Anzahl der Bytes in jedem LoRaWAN Netzwerk limitiert. Bei TTN variiert die maximale Nachrichtenlänge entsprechend den SF-Faktor. Mit einem niedrigen SF-Faktor können trotzdem etwa 200 Zeichen in einem Paket übertragen werden, was etwas mehr als die Länge einer SMS-Kurznachricht entspricht.
Wenn die Payload lediglich aus einem String besteht, ist die Übertragung sehr einfach. Ein Beispiel mit Encoder/Decoder dazu gibt es am Ende dieses Artikels: ESP32 mit LoRaWAN/TTN verbinden
Eine andere Möglichkeit wäre, den String in der Länge zu beschränken. Im folgenden Beispiel wird der String "AEQWEB" übertragen. Dieser String enthält 6 Zeichen und wird wieder dementsprechend auf die einzelnen Bytes aufgeteilt. Ein Bit-Shifting ist nicht erforderlich, da hier jedes Zeichen genau aus einem Byte besteht und nicht wie bei einem Integer auf zwei Bytes aufgeteilt werden muss.
Ein String besteht aus mehreren Zeichen, die eine sogenannte Zeichenkette bilden. In der ASCII-Tabelle ist für jedes Zeichen (Buchstabe, Zahl & Sonderzeichen) eine Binärzahl definiert. Es ist daher möglich, einen Klartext zwischen verschiedenen Systemen zu übertragen, sodass am Ende wieder die korrekte Zeichenkette angezeigt wird. Möchte man einen statischen String in die LoRa-Payload bringen, so wird einfach jedes Zeichen vom String an eine freie Stelle der Payload-Array zugewiesen. Der Encoder sieht wie folgt aus:
uint8_t payload[6]; String var = "AEQWEB"; payload[0] = var[0]; payload[1] = var[1]; payload[2] = var[2]; payload[3] = var[3]; payload[4] = var[4]; payload[5] = var[5];
Der Application-Server muss die Payload wieder entsprechend decodieren. Im Gegensatz zu einem Integer oder Long werden jetzt die Bytes nicht zusammengeführt, sondern zuerst nach der ASCII-Tabelle decodiert und als Character-Array wieder zu einem String zusammengeführt.
Der folgende Payload-Decoder wandelt die einzelnen Bytes wieder eine Zeichenkette um und gibt diese als String aus:
function Decoder(bytes, port) { var decoded_var = String.fromCharCode.apply(null, bytes.slice(0, 6)); return { var: decoded_var } }
Die Übertragung von Zahlen, die mit einem negativen Vorzeichen behaftet sind, halbieren die Maximalzahl des Datentyps. Eine Integer-Variable mit 16-Bit kann somit nicht mehr höchstens 65.535 betragen, sondern nur noch 32.676. Allerdings kann das 16-Bit Integer dadurch auch negative Zahlen bis -32.678 beinhalten.
Wie in der oberen Grafik zu erkennen, wird bei einer negativen Zahl die binäre Logik mit Ausnahme des vordersten Bits invertiert. Das erste Bit ist immer gleich und wird als MSB (Most Significant Bit) bezeichnet. Über diese Bitwertigkeit wird das Vorzeichen bestimmt.
uint8_t payload[2]; int var = -12345; payload[0] = var >> 8; payload[1] = var;
Der Mikrocontroller invertiert die Logik bei negativen Zahlen automatisch, weshalb die Encodierung der Payload gleich funktioniert wie mit einem positiven Integer.
Der Decoder untersucht das erste Byte des jeweiligen Datentyps und tauscht, wenn erforderlich, das Vorzeichenbit aus. Anschließend werden die restlichen Bits gleich wie bei positiven Zahlen an die Variable angehängt. Der TTN-Decoder sieht wie folgt aus:
function Decoder(bytes, port) { var decoded_var = (bytes[0] & 0x80 ? 0xFFFF<<16 : 0) | bytes[0]<<8 | bytes[1]; return { var: decoded_var } }
Als Beispiel für eine effiziente LoRa Payload eignet sich eine Wetterstation. Die Wetterstation verfügt über 5 Sensoren mit ganz unterschiedlichen Messbereichen. In 8 Bytes können alle Werte teilweise sogar mit zwei Nachkommastellen übertragen werden. Die Encodierung sieht wie folgt aus:
Temperatur: Die Temperatur sollte möglichst genau sein, weshalb hier mit zwei Kommastellen gearbeitet wird. In Mitteleuropa liegt die Temperatur in einem Bereich von etwa -30°C bis 60°C. Wie bereits vorgeführt, ist die Übertragung von negativen Zahlen aufwendiger. Man kann deshalb auch einfach eine Zahl zum Messwert addieren, sodass keine negativen Zahlen vorkommen können. Ein Integer unterstützt keine Kommastelle, daher wird zuerst die Temperatur um den Faktor 100 Multipliziert und anschließend mit 5000 addiert, um das Vorkommen negativer Zahlen zu verhindern.
Luftdruck: Für den Luftdruck ist in der Regel eine Kommastelle ausreichend. In einem Bereich von 300 bis 1070 Hektopascal kann der Luftdruck sowohl auf sehr hohen Bergen als auch unter dem Meeresspiegel gemessen und korrekt übertragen werden. Auch hierfür werden wieder zwei Bytes benötigt. Durch entsprechende Subtraktion des Messwertes kann der Messbereich für das jeweilige Höhenniveau der Station so angepasst werden, dass sogar zwei Nachkommastellen übertragen werden können.
Feuchtigkeit: Die Luftfeuchtigkeit wird von 0-100 % dargestellt. Mit einem Byte kann ein Zahlenwert von 0-255 dargestellt werden. Möchte man die Feuchtigkeit mit einer Genauigkeit von einem halben Prozent übertragen, so muss der Messwert um den Faktor zwei multipliziert werden.
Windgeschwindigkeit: Die Windgeschwindigkeit sollte schon eine Kommastelle haben, deshalb ist ein Integer erforderlich. Durch eine Multiplikation des Messwertes um den Faktor 10 oder Faktor 100 kann die Windgeschwindigkeit mit einer oder zwei Kommastellen übertragen werden.
Sonne: Bei der LoRa Wetterstation wird die Sonnenstrahlung in einem Prozentwert gemessen. Daher ist die Encodierung gleich wie bei der Feuchtigkeit.
Im folgenden Beispielcode werden statische Werte generiert. In der Regel liefern Sensoren die Messwerte als Float, weshalb zuerst die Werte in ein Integer umgewandelt werden müssen, bevor die Encodierung der Payload erzeugt werden kann:
float temperature = 27.83; float airpressure = 975.37; float humidity = 62.66; float windspeed = 11.82; float sunlight = 80.52; int tmp = ((int)(temperature * 100))+5000; int pre = (int)(airpressure * 10); byte hum = (int)(humidity * 2); int wnd = (int)(windspeed * 10); byte sun = (int)(sunlight * 2); uint8_t payload[8]; payload[0] = tmp >> 8; payload[1] = tmp; payload[2] = pre >> 8; payload[3] = pre; payload[4] = hum; payload[5] = wnd >> 8; payload[6] = wnd; payload[7] = sun;
Der Decoder funktioniert genau umgekehrt zum Encoder. Bei der Temperatur wird zuerst wieder eine Subtraktion von 5000 durchgeführt und anschließend um den Faktor 100 dividiert. Bei den restlichen Daten wird um den entsprechenden Faktor dividiert, der im Encoder multipliziert wurde. Dadurch werden alle Werte wieder in ihren ursprünglichen Wert inklusive Kommastelle zurückgewandelt.
Im Decoder werden alle Werte wieder zusammengesetzt und in die jeweilige Variable geschrieben. Im Return werden die entsprechenden Rechenoperationen durchgeführt, sodass am Ende wieder die ursprünglich encodieren Werte ausgegeben werden.
function Decoder(bytes, port) { var tmp = (bytes[0]<<8 | bytes[1]); var pre = (bytes[2]<<8 | bytes[3]); var hum = (bytes[4]); var wnd = (bytes[5]<<8 | bytes[6]); var sun = (bytes[7]); return { temperature: (tmp-5000)/100, airpressure: pre/10, humidity: hum/2, windspeed: wnd/10, sunlight: sun/2 } }
Einstieg in das LoRaWAN (TTN) mit dem Heltec LoRa32 V3 und Einrichtung vom Board in der Arduino IDE
WeiterlesenStarthilfe LoRaWAN - Diese Seite richtet sich an alle Einsteiger, die mit LoRaWAN starten wollen und ihre Sensoren in das IoT-Netzwerkt TTN integrieren wollen
WeiterlesenAEQ-WEB © 2015-2024 All Right Reserved