Zum Inhalt springen

Sensornode für TTN

    1. Einführung

    TTN: Das globale Netzwerk für IoT

    • The Things Network (TTN) ist ein globales, offenes LoRaWAN-Netzwerk, das es dir ermöglicht, IoT-Geräte wie Sensoren oder Aktoren drahtlos zu verbinden und Daten an zentrale Systeme zu übermitteln. LoRaWAN zeichnet sich durch eine sehr geringe Energieaufnahme und hohe Reichweite aus, weshalb es ideal für IoT-Projekte ist, bei denen Geräte oft in abgelegenen Bereichen betrieben werden.
    • Mit TTN kannst du Daten von deinen Nodes über LoRa-Gateways an einen Cloud-Server senden. Dort können die Daten weiterverarbeitet, gespeichert oder an andere Systeme weitergeleitet werden. TTN stellt dafür eine einfache und kostenfreie Plattform bereit, die sowohl für Hobbyisten als auch professionelle IoT-Anwendungen geeignet ist.

    Warum Sensornodes für TTN?

    TTN bietet mit seiner Kombination aus Reichweite, Energieeffizienz, Kostenfreiheit und Community-Support die perfekte Grundlage für deine Projekte. Egal, ob du deine Umgebung überwachen, smarte Anwendungen entwickeln oder einfach nur erste Erfahrungen mit IoT sammeln möchtest – TTN eröffnet dir eine Welt voller Möglichkeiten.

    Ziel dieses Beitrages

    • Sensornode für TTN erstellen und integrieren
    • Überblick über notwendige Schritte

    2. Vorbereitung

    Für die Realisierung unseres Vorhabens benötigen wir eine kleine Anzahl von Komponenten, die in hier kurz beschreiben will.

    2.1 Benötigte Hardware

    • Lora-fähiges Board
      Es wird ein Board benötigt, dass einen Lora-Chip verbaut hat. Dazu gehören Heltec Lora Board, oder die TTGo Boards.
    • Eine zur Region geeignete angepasste Antenne. (868 Mhz für Europa)
    • Temperatur/Luftfeuchte Sensor
      Ich selbst habe einen SHT31 für dieses Projekt gewählt. Es kann aber auch ein BME-280 o.ä. sein.
      Dabei wird der hier vorgestellte Code entsprechend deiner Wahl angepasst werden müssen.

    2.2 Software und Accounts

    • Arduino-IDE oder VS Code (ich in diesem Projekt VS Code)
    • Library
      Für mein Board TTGO Lora ESP32 habe ich die lmic-Library gewählt. Um ganz genau zu sein, diese hier:
      mcci-catena/MCCI LoRaWAN LMIC library@^5.0.1
    • TTN-Account erstellen und vorbereiten

    3. Sensornodes für TTN aufbauen

    3.1 Hardware-Aufbau

    Für die Erstellung der Sensornode habe ich ein TTGO Lora Board mit OLED Display ausgewählt.
    Dieses wurde per PIN Header auf eine Lochrasterplatte gesteckt. Damit kann ich mich frei entscheiden, was ich letztlich an das Board stecke, bzw. welche Sensoren ich betreiben möchte. Hier habe ich einen Sensor SHT31, der per I2C – Schnittstelle angeschaltet ist, verwendet. Information zu diesem Sensor habe ich im Beitrag Temperaturmessung mit dem SH3x bereits gegeben.

    TTGO Board vorne
    TTGO Bord hinten

    Um mit Sensor und Stromversorgung flexibel zu sein, habe ich mir 2 Connectoren aufgelötet.
    Hier besteht die Möglichkeit über den USB Anschluss mit 5 V zu versorgen oder direkt 5V auf das Board zu legen.

    Zur Beschaltung: siehe auch hier im Beispiel bei randomnerdtutorials.com

    SHT31
    SDA
    SCL
    VCC
    GND

    TTGO Board
    SDA – PIN 21
    SCL – PIN 22
    3.3V
    GND

    Stromversorgung
    5 V
    GND

    TTGO Board
    5V
    GND


    3.2 Software-Konfiguration

    Zur Vorbereitung des Programms benötigen wir in VS Code noch die Angabe von Board und Library.
    Lege in VS Code ein neues Projekt an. Über die Ameise oder aus der Symbolleiste das Haus.

    Vorbereitung - neues Projekt
    VSC neues Projekt über die Ameise
    Vorbereitung Platform
    PlatformIO neues Projekt über die Symbolleiste, das Haus
    Projektdaten in Platform vergeben
    Projekteinstellungen

    Mit Klick auf Boards bitte das Board auswählen, welches du in Verwendung hast. Danach mit ok bestätigen.


    Button Boards
    Board Konfiguration in VSC

    Mit Klick auf Librarys die LMIC library (aktuell in der Version 5.0.1 auswählen und auch bestätigen.


    Button Libraries
    Library in VSC konfigurieren

    Die Platform.ini sollte dann so aussehen:
    [env:ttgo-lora32-v2]
    platform = espressif32
    board = ttgo-lora32-v2
    framework = arduino
    lib_deps =
    adafruit/Adafruit SHT31 Library@^2.2.2
    mcci-catena/MCCI LoRaWAN LMIC library@^5.0.1
    monitor_speed = 115200


    Als nächstes muss in der hier gezeigten Datei (lmic_project_config.h) die projektspezifische Definition erfolgen. Kommentiere bitte die Zeilen, so wie hier dargestellt. Der Frequenzplan für Europa ist CFG_eu868 und der Lora_Cip ist der SX1276.

    Screenshot Regionseinstellung

    Account für TTN erstellen

    Falls noch kein Account bei TheThingsNetwork (TTN) vorhanden ist, bitte unter https://www.thethingsnetwork.org/ registrieren. Es wird nicht zwingend ein eigenes Gateway benötigt. Hier reicht es aus, wenn in der Nähe ein Gateway existiert was die vom SensorNode gesendeten Daten empfängt und an das TTN weiterleitet.


    Sensornode in TTN erstellen

    In TTN gibt es Applicationen und End devices. Eine Application kann ein oder mehrere End devices enthalten.
    Du kannst eine neue Application erstellen, um dein End device darin zu erstellen oder du hast bereits eine Application und nutzt diese für ein End device.


    Erstellen einer Application:

    Mit Klick auf das „Plus“ öffnet sich nachfolgende Auswahl. Klicke hier auf Add application.

    Sensornode für TTN erstellen - Application in TTN einrichten

    Hier ist nicht allzu viel auszufüllen. Vergib unter Application ID einen aussagekräftigen Namen, bestehend aus Kleinbuchstaben und Bindestrich. Dieser kann später nicht mehr geändert werden. Vergib dann einen sprechenden Namen für deine Application nach freier Wahl und klicke dann auf Create application. Fertig!

    Sensornode für TTN erstellen - Application in TTN anlegen

    Wenn du vorher noch kein End device hattest, ist die Application erst einmal leer.

    Sensornode für TTN erstellen - End device anlegen in TTN

    Mit Klick auf „+ Register end device“ oder mit dem Plus oben rechts, kannst du ein neues End device registrieren.

    Sensornode für TTN erstellen - Butten Register end Device

    Selektiere wie unten stehend „Enter end device specifics manually“.
    Wähle den Frequenzplan, LoraWan Spezification und Regional Parameters wie angezeigt aus.

    Sensornode für TTN erstellen - Ansicht eines Endgerätes

    Scrolle weiter runter und vergib die Schlüssel, wie hier angezeigt. Die JoinEUI musst du selbst eintippen.
    In der Hilfe steht, entweder du hast vom Hersteller eine eindeutige Nummer oder du vergibst selbst eine hexadezimal, also Zahlen von 0 – 9 und A – F. Die DEVEUI und AppKey generierst du mit Klick auf „Generate“.
    Jetzt vergibst du noch eine End device ID und mit Klick auf Register end device ist der Sensornode eingerichtet.

    Sensornode für TTN erstellen - Daten für End device festlegen

    Das Ergebnis sieht dann so aus. Im Screenshot habe ich einen anderen End devices Namen vergeben. „test-device“ aus dem oberen Bild und „test-blogpost“ unten sollten gleich heißen.

    Sensornode für TTN erstellen - Konfiguration der Schlüssel

    Damit ist das End device oder der SensorNode für TTN registriert. Was jetzt noch zu tun ist – die markierten Schlüssel sind in den Code zu übernehmen. Achte dabei darauf, dass die lmic-Bibliothek den AppEUI und die DevEUI LSB formatiert und den APPKey MSB formatiert haben möchte. Das erreichst du, indem du auf die Pfeile klickst und dann kopierst.

    // OTAA-Schlüssel aus dem angelegten End device in LSB/MSB-Format einfügen
    static const u1_t PROGMEM DEVEUI[8] = { 0xE1, 0xCD, 0x06, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 }; //LSB
    static const u1_t PROGMEM APPEUI[8] = { 0x05, 0xFB, 0xBF, 0x34, 0x12, 0x03, 0x02, 0x01 }; //LSB
    static const u1_t PROGMEM APPKEY[16] = { 0xA2, 0x96, 0xFC, 0x20, 0x62, 0xE4, 0xF3, 0xC2, 0x58, 0x63, 0xEA, 0x00, 0x78, 0xBC, 0xB1, 0xBE }; //MSB


    Payload-Decoder in TTN einrichten

    Da wir die Daten im Code codieren werden, müssen wir in TTN auch einen Decoder einrichten, damit wir die übertragene Temperatur und Luftfeuchte wieder im Klartext vorliegen haben. Informiere dich gerne auch in meinem Blogbeitrag zur Payload für TTN

    function decodeUplink(input) {
      // Dekodierung der 4 Bytes aus dem Payload
      var temperature = (input.bytes[0] << 8) | input.bytes[1]; // 16-Bit Temperatur
      var humidity = (input.bytes[2] << 8) | input.bytes[3];    // 16-Bit Luftfeuchtigkeit
    
      // Rückgabe der dekodierten Daten als JSON
      return {
        data: {
          temperature: (temperature/ 100.0) -30, // Falls die Temperatur um Faktor 100 skaliert wurde
          humidity: humidity / 10.0        // Falls die Luftfeuchtigkeit um Faktor 10 skaliert wurde
        }
      };
    }

    Oben gezeigter Decoder muss, wie unten gezeigt im Uplink eingetragen werden. Dabei hast du 2 Stellen. Links trägst du den Decoder für die Application ein. Heißt, wenn du 2 Nodes in einer Application hast, wird der Decoder für beide Nodes gleichermaßen genutzt. Willst du für jeden Node einen Decoder verwenden, trage ihn bitte rechts mit Klick auf Payload formatters ein.

    Sensornode für TTN erstellen - Payload einstellne

    Das Programm

    // VS Code Programm zur Übermittlung von Sensormesswerten an das TheTingsNetwork
    // Programm wurde mit Unterstützung ChatGPT optimiert
    // dieser Code steht jedem zur freien Verfügung
    
    //Librarys einbinden
    #include <Arduino.h>
    #include <lmic.h>
    #include <hal/hal.h>
    #include <SPI.h>
    #include <Wire.h>
    #include <Adafruit_SHT31.h>
    
    //globales Ein-/Ausschalten der Ausgaben über die serielle Schnittstelle
    #define DEBUG 1
    #if DEBUG == 1
    #define debug(x) Serial.print(x)
    #define debugln(x) Serial.println(x)
    #else
    #define debug(x)
    #define debugln(x)
    #endif
    
    //Variablen für die Zeitmesessung (Heater)
    unsigned long heaterStartMillis = millis();
    const unsigned long heaterDuration = 10000;      // Heizerdauer: 10 Sekunden
    bool heater_status = false; // Status ob Heater im SHT31 ein- oder ausgeschaltet ist
    
    //Sendeintervall der Sensorwerte an das TTN
    uint16_t sendInterval = 900; // wie oft werden Sensorwerte gemessen und gesendet in Sekunden
    
    // Variablen für Temperatur und Luftfeuchtigkeit
    float t;
    int t1;
    float h;
    int h1;
    
    //Sensor- und Sendejob anlegen
    Adafruit_SHT31 sht31 = Adafruit_SHT31(); // Sensorobjekt anlegen
    osjob_t sendjob; // Lora Sendobjekt anlegen
    
    // OTAA-Schlüssel aus dem angelegten End device in LSB/MSB-Format einfügen
    static const u1_t PROGMEM DEVEUI[8] = { 0xE1, 0xCD, 0x06, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 }; //LSB
    static const u1_t PROGMEM APPEUI[8] = { 0x05, 0xFB, 0xBF, 0x34, 0x12, 0x03, 0x02, 0x01 }; //LSB
    static const u1_t PROGMEM APPKEY[16] = { 0xA2, 0x96, 0xFC, 0x20, 0x62, 0xE4, 0xF3, 0xC2, 0x58, 0x63, 0xEA, 0x00, 0x78, 0xBC, 0xB1, 0xBE }; //MSB
    
    // Pinbelegung für TTGO LoRa32 Chip V2.0
    const lmic_pinmap lmic_pins = {
        .nss = 18,
        .rxtx = LMIC_UNUSED_PIN,
        .rst = 14,
        .dio = {26, 33, 32},
        //.spi = {SCK, MISO, MOSI},
    };
    
    // Deklaration für Sensor-Abfrageroutine
    void get_sht3x_data();
    
    // die OTAA-Schlüssel in die Variablem kopieren
    void os_getArtEui(u1_t *buf) { memcpy_P(buf, APPEUI, 8); }
    void os_getDevEui(u1_t *buf) { memcpy_P(buf, DEVEUI, 8); }
    void os_getDevKey(u1_t *buf) { memcpy_P(buf, APPKEY, 16); }
    
    //Funktion zum Senden der Daten an das TTN
    void sendeDaten(osjob_t* j) {
      if (LMIC.opmode & OP_TXRXPEND) {
        debugln(F("Übertragung läuft, warte..."));
      } 
      else {
        // Messung der Sensordaten
        debugln("Frage Sensor ab");
        get_sht3x_data();
    
        // Daten für die Übertragung vorbereiten
        debugln("Daten für Payload packen");
        uint8_t daten[4];
        daten[0] = t1 >> 8;
        daten[1] = t1 & 0xFF;
        daten[2] = h1 >> 8;
        daten[3] = h1 & 0xFF;
    
        // Daten senden
        LMIC_setTxData2(1, daten, sizeof(daten), 0);
        debugln(F("Paket in Warteschlange"));
      }
    }
    
    void setup() {
      Serial.begin(115200);
      while (!Serial);
    
    // Initialisiere SHT31
      if (!sht31.begin(0x44)) {
        debugln(F("SHT31 nicht gefunden!"));
        while (true);
      }
    
      debugln(F("Starte OTAA..."));
    
      // LMIC-Initialisierung
      os_init();
      LMIC_reset();
      LMIC_setAdrMode(0); // Deaktiviert ADR
      LMIC_setDrTxpow(DR_SF12, 14); // Bsp: SF12 für maximale Reichweite, 14 dBm Sendeleistung
      LMIC_startJoining(); // OTAA-Join starten
    }
    
    void loop() {
      os_runloop_once();
    
      // Prüfen, ob der Heizer aktiv ist und ob die Heizzeit abgelaufen ist
      if (heater_status && (millis() - heaterStartMillis >= heaterDuration)) {
        sht31.heater(false);      // Heizer ausschalten
        heater_status = false;    // Status des Heizers zurücksetzen
        debugln("Heater deaktiviert!");
      }      
    }
    
    // Routine zur Abfrage der Sensorwerte des SHT31 und Einschalten des Heaters
    void get_sht3x_data() {
      t = sht31.readTemperature();
      h = sht31.readHumidity();
        if (! isnan(t)) {  // check if 'is not a number'
        debug("Temp *C = "); debug(t); debug("\t\t"); // Formatierte Ausgabe
      } else { 
        debugln("Failed to read temperature");
      }
      
      if (! isnan(h)) {  // check if 'is not a number'
        debug("Hum. % = "); debugln(h);
      } else { 
        debugln("Failed to read humidity");
      }
    t1 = (t + 30) * 100; // negative Werte vermeiden und ganzzahlig machen
    h1 = h * 10; // Luftfeuchtigkeit ganzzahlig machen
    
      // Heizer aktivieren für genauere Messwerte Luftfeuchtigkeit
      if (!heater_status) { // Nur aktivieren, wenn er nicht schon läuft
        sht31.heater(true); // Heater einschalten
        heater_status = true;
        heaterStartMillis = millis(); // Startzeit des Heizers speichern
        debugln("Heater eingeschaltet!");
      }
    }
    
    //Events prüfen und Aktionen starten
    void onEvent(ev_t ev) {
      debug(os_getTime());
      debugln(": ");
      
      switch (ev) {
        case EV_JOINING:
          debugln(F("EV_JOINING: Versuche, dem Netzwerk beizutreten"));
          break;
    
        case EV_JOINED:
          debugln(F("EV_JOINED: Erfolgreich dem Netzwerk beigetreten"));
          LMIC_setLinkCheckMode(0);
          os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(1), sendeDaten); //erstes senden
          break;
    
        case EV_TXSTART:
          debugln(F("EV_TXSTART: Übertragung gestartet"));
          break;
    
        case EV_TXCOMPLETE:
          debugln(F("EV_TXCOMPLETE: Übertragung abgeschlossen"));
          if (LMIC.txrxFlags & TXRX_ACK) {
            debugln(F("ACK erhalten"));
          }
          if (LMIC.dataLen) {
            debug(F("Empfangene Daten: "));
            debug(LMIC.dataLen);
            debugln(F(" Bytes"));
          }
    
          // hier wird die eigentliche Sendung abhängig von sendinterval gestartet
          os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(sendInterval), sendeDaten);
          break;
    
        case EV_JOIN_FAILED:
          debugln(F("EV_JOIN_FAILED: Join-Versuch fehlgeschlagen"));
          break;
    
        case EV_REJOIN_FAILED:
          debugln(F("EV_REJOIN_FAILED: Rejoin-Versuch fehlgeschlagen"));
          break;
    
    default:
        debug(F("Unbekanntes Ereignis: "));
        debug(ev);
        debug(F(" (Hex: 0x"));
       Serial.print(ev, HEX);
        debugln(F(")"));
        break;
      }
    }

    Kompiliere den vollständigen Code und lade ihn auf dein Board. Achte bitte darauf, dass wir einen Sender vor uns haben. Sender bitte immer mit angeschlossener Antenne einschalten. Wenn alles geklappt hat, solltest du im TTN einige Sekunden später den ersten Join Versuch sehen können.


    So sieht beispielhaft ein Join ins TTN und die Übertragung der Daten aus. Dazu musst du in die serielle Schnittstelle von VSC wechseln.

    Starte OTAA…
    4056: EV_JOINING: Versuche, dem Netzwerk beizutreten
    246809: EV_TXSTART: Übertragung gestartet
    567875: EV_JOINED: Erfolgreich dem Netzwerk beigetreten
    Frage Sensor ab
    Temp *C = 20.30 Hum. % = 50.43
    Heater eingeschaltet!
    Daten für Payload packen
    633242: EV_TXSTART: Übertragung gestartet
    Paket in Warteschlange
    1013251: EV_TXCOMPLETE: Übertragung abgeschlossen
    Heater deaktiviert!


    Im TTN siehst du mit Klick auf eine Zeile unter Live Data beispielhaft den vollständigen Request im JSON-Format.

    "received_at": "2024-12-08T17:39:11.572418999Z",
    "uplink_message": {
      "session_key_id": "xxxxxxxxxx7t4bt2sZriMA==",
      "f_port": 1,
      "frm_payload": "E6gB+Q==",
      "decoded_payload": {
        "humidity": 50.5,
        "temperature": 20.32
      },
      "rx_metadata": [
        {
          "gateway_ids": {
            "gateway_id": "gateway-xxxxxxxxx01",
            "eui": "41374E4B30xxxxxxx"
          },
          "time": "2024-12-08T17:39:11.270569Z",
          "timestamp": 2628052980,
          "rssi": -89,
          "channel_rssi": -89,
          "snr": 8,
          "location": {
            "latitude": xx.6853717939413,
            "longitude": xx.3375034765693,
            "altitude": 54,
            "source": "SOURCE_REGISTRY"
          },
          "uplink_token": "Ch4KHAoQZ2F0ZXdheS1wbmdoZ24wMRIIQTdOSzBGSjYQ9M+T5QkaDAi/s9e6BhDVzImuASCgoqihvkw=",
          "channel_index": 1,
          "received_at": "2024-12-08T17:39:11.342307728Z"
        }

    Wie solche JSON Daten dekodiert werden und wie du die Nutzlast, also deine Temperatur und andere Sensorwerte weiterverarbeiten kannst, zeige ich dir im Beitrag – TTN Node Integration per Webhook.


    Damit sind wir wieder am Ende eines DIYTechAdventures – Sensornode für TTN angekommen. Ich gebe zu, es war umfangreich. Aber nimm dir die Zeit alles in Ruhe zu testen. Falls Fragen sind, ich bin jederzeit per Kommentar erreichbar.


    0 Kommentare
    Inline Feedbacks
    View all comments
    Schlagwörter: