LoRa Sensor mittels Arduino und LoRa Shield

Über einen Freund bin ich vor ein paar Wochen auf das Thema LoRa bzw. LoRaWAN aufmerksam geworden.

Vor allem seit ich The Things Network (https://www.thethingsnetwork.org) kenne und somit über die Community eine Möglichkeit besteht, die Technologie sinnvoll zu nutzen, bin ich interessiert mich damit mehr zu beschäftigen.

Nachtrag Juli 2017: wir haben mit dem Aufbau einer Community bei The Things Network in Wien begonnen. Das Ziel ist die Schaffung eines freien und offenen Netzes für IoT. Nachdem ich mehrfach auf meinen Blog hin angeschrieben wurde, es den Personen aber nicht bewusst war, dass sich hier was tut, möchte ich auf folgende Links verweisen: folgt uns auf Twitter (@TTN_Vienna), für Updates und Infos zu den nächsten Treffen oder besucht die Wiener Community Seite!

Ein LoRa Sensor ist ein Endgerät, das über das LoRa-Protokoll in ein Netzwerk Informationen funkt. Empfangen werden die Pakete üblicherweise von einem Gateway. Bis zur Auswertung der Pakete sind noch Network Server und Application Server nötig, die in meinem Fall über TTN (The Things Network) bereitgestellt werden.

Da ich auch mit Freunden einen Gateway bauen möchte, brauchen wir natürlich einen LoRa Sensor, um unseren Gateway zu testen. Als günstige Variante habe ich Arduino + LoRa Shield für Arduino gefunden.

Nachtrag Juli 2017: wir haben mit dem Aufbau einer Community bei The Things Network in Wien begonnen. Das Ziel ist die Schaffung eines freien und offenen Netzes für IoT. Nachdem ich mehrfach auf meinen Blog hin angeschrieben wurde, es den Personen aber nicht bewusst war, dass sich hier was tut, möchte ich auf folgende Links verweisen: folgt und auf Twitter (@TTN_Vienna) für Updates und Infos zu den nächsten Treffen oder besucht die Wiener Community Seite!

Zutaten

Falls jemand entspannt an das Projekt herangeht und mit 5-6 Wochen Lieferzeit aus Shenzhen (China) kein Problem hat, gibt es das LoRa Shield auch kostengünstiger (€ 18,70 am 1.4.2017) über Tindie zu kaufen. Hier beachtet bitte, dass Shipping (+ € 6,12 nach Österreich) und ggf. Zoll dazukommen.

Bitte beachtet bei Bestellungen immer, dass ihr die 868 MHz-Variante auswählt! Nur diese darf in der EU betrieben werden bzw. wird hier funktionieren!

Von der Vorgehensweise halte ich mich an diese Anleitungen:

  1. ein tolles Youtube-Video, das genau den hier beschriebenen Aufbau erklärt: LoRa Node with Arduino and Dragino Shield connected to TTN LoRaWAN von Andreas Spiess
  2. Software (Arduino Sketch) “Hello World” von https://github.com/SensorsIot/LoRa
  3. LMIC library von IBM, angepasst für Arduino: wird vom Arduino Sketch benötigt

Vielen Dank an die Kollegen von TTN Zürich, die diese Anleitungen und Programme zur Verfügung stellen!

Zusammenbau

Das LoRa Shield muss nun nur mehr auf den Arduino gesteckt werden. Die Antenne wird an den SMA-Anschluss geschraubt.

Konfiguration

Nun lädt man den Arduino Sketch (“Hello World”, siehe Link oben) ins Arduino IDE und muss ein paar Werte anpassen. Dazu erstellt man eine “Application” in der Console von TTN und legt ein Gerät (“Device”) an. Eine Over-The-Air-Activation (OTAA) ist nicht möglich, daher muss man manuell ABP wählen und die Werte vom Webinterface abschreiben. Diese werden von TTN automatisch generiert.

  1. NWKSKEY: der Network Session Key muss im korrekten Format in die geschwungenden Klammern { } eingefügt werden.
  2. APPSKEY: ebenso der Application Session Key und zum Schluss die
  3. DEVADDR, also die Geräteadresse im korrekten Format.

TTN bietet im Webinterface übrigens die Werte bereits im richtigen Format an. Im Zweifelsfall muss man auf “< >” klicken, dann werden die Werte in anderen Formaten dargestellt und können mit copy & paste übernommen werden. Das gewünschte Format ist “msb”.

Ansonsten musste ich keine weiteren Änderungen am Code durchführen.

Trotzdem habe ich den zu übertragenden Text von “HI” auf “stefan test” geändert: dazu habe ich die Payload in der Variable “message[]” in Zeile 57 angepasst:

  // Payload to send (uplink)
  static uint8_t message[] = "stefan test";

Nun habe ich den Sketch kompiliert und auf den Arduino übertragen. Unmittelbar darauf hat er begonnen, alle 20 Sekunden kurz zu blinken, wodurch ich mich bestätigt gefühlt habe, dass es funktioniert und nun alle 20 Sekunden die Meldung “stefan test” mittels LoRa übertragen wird.

Überprüfen am Gateway

Einen Gateway hatten wir parat und er hat sofort die Pakete empfangen. Über die “Traffic” Funktion in der TTN Console kann man die ankommenden Pakete gleich sehen.

Man sieht hier mehrere Pakete, die im Abstand von ca. 25 Sekunden ankommen. Die Frequenz wechselt bei jedem Paket, weil mehrere Kanäle genutzt werden: 868,1 – 868,5 – 868,3 usw.

Folgendes JSON Objekt mit allen Details erhält man aus der TTN Console beim Gateway Traffic:

{
  "gw_id": "eui-b827ebfffe6f377d",
  "payload": "QI4cASaAaAABhVJosx+GBwUwHBqp4DGG",
  "f_cnt": 104,
  "lora": {
    "spreading_factor": 9,
    "bandwidth": 125,
    "air_time": 205824000
  },
  "coding_rate": "4/5",
  "timestamp": "2017-03-29T05:57:06.352Z",
  "rssi": -25,
  "snr": 11.2,
  "dev_addr": "26011C8E",
  "frequency": 868100000
}

Es enthält sämtliche Details zur Übertragung. Ein paar Werte möchte ich kurz hervorheben:

  • gw_id: zeigt die ID des Gateways, der das Paket empfangen hat
  • payload: sind die übertragenen Daten in verschlüsselter Form
  • f_cnt: ist der Counter und gibt die Anzahl der Pakete wider
  • lora.spreading_factor: zeigt hier den Spreading Factor 9, der im Sketch eingestellt ist
  • rssi: zeigt die Signalstärke des empfangenen Pakets am Gateway an (hier: -25 dbm)
  • snr: das Signal/Rausch-Verhältnis
  • dev_addr: die Geräteadresse, die von TTN vergeben wurde und ich als DEVADDR im Sketch hinterlegt habe.
  • frequency gibt die Frequenz in Hertz an

Wir sehen, dass das Paket einwandfrei übertragen wurde und sogar sehr gut (-25 dbm) empfangen wurde. Bei diesem Test war der Abstand vom Sensor zum Gateway aber auch im Bereich von 5 Metern.

Optimierungen

Als Spreading Factor ist 9 eingestellt. Um die Reichweite zu erhöhen, kann man auch zB. SF12 einstellen. Das geht im Arduino Sketch auf Zeile 90:

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF9, 14);

Falls man die Pakete weniger oft übertragen möchte (alle 20 Sekunden ist zum Testen super, aber dauerhaft verbraucht es zu viel Airtime), kann das in Zeile 39 anpassen:

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 20;

16 Gedanken zu „LoRa Sensor mittels Arduino und LoRa Shield“

  1. Hallo Stefan.

    Ich habe heute deine Anleitung durchgespielt und den Sketch installiert. Das hat auch alles super geklappt.
    Leider bekomme ich aber keine Verbindung zu TTN.
    Hast du eine Idee wo ich nachschauen könnte?
    Ist der Sketch noch aktuell und funktional?

    Laut dem SerialMonitor sah erstmal alles soweit gut aus.

      1. Hallo.

        Wo das Gateway ist weiß ich. Bin eben extra noch da vorbeigefahren.

        ABP ist aktiviert. Aber den Haken hatte ich nicht entfernt. Das muss mir wohl durchgegangen sein.

        Ich werde es morgen auf dem Weg zur Arbeit nochmal testen.

        Erstmal danke 😀

        Gruß
        Jens

  2. Hallo Stefan,

    ich bin ehrlich gesagt etwas ratlos momentan. In meinen Augen ist mein Programmcode richtig, ich empfange aber trotzdem keine Nachrichten bei TTN. Vielleicht findest du ja einen Fehler.

    #include
    #include
    #include
    #include
    #include

    #define DHTPIN 6 //Der Sensor wird an PIN 0 angeschlossen

    #define DHTTYPE DHT11 // Es handelt sich um den DHT11 Sensor

    DHT dht(DHTPIN, DHTTYPE);

    static const u1_t NWKSKEY[16] = { 0x6C, 0xA9, 0x3C, 0x3A, 0xA7, 0x51, 0x25, 0xA7, 0xE0, 0x9A, 0x02, 0x2A, 0x6D, 0x79, 0x82, 0xE9 };
    static const u1_t APPSKEY[16] = { 0x7A, 0xB4, 0xD5, 0x57, 0x06, 0x5C, 0x77, 0xD0, 0x33, 0x67, 0x36, 0xD7, 0x0A, 0x0C, 0xAB, 0x30 };
    static const u4_t DEVADDR = 0x260116AC;

    // These callbacks are only used in over-the-air activation, so they are
    // left empty here (we cannot leave them out completely unless
    // DISABLE_JOIN is set in config.h, otherwise the linker will complain).
    void os_getArtEui (u1_t* buf) { }
    void os_getDevEui (u1_t* buf) { }
    void os_getDevKey (u1_t* buf) { }

    static osjob_t sendjob;

    // Schedule TX every this many seconds (might become longer due to duty
    // cycle limitations).
    const unsigned TX_INTERVAL = 20;

    // Pin mapping Dragino Shield
    const lmic_pinmap lmic_pins = {
    .nss = 10,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 9,
    .dio = {2, 6, 7},
    };

    void onEvent (ev_t ev) {
    Serial.print(os_getTime());
    Serial.print(“: “);
    switch(ev) {
    case EV_SCAN_TIMEOUT:
    Serial.println(F(“EV_SCAN_TIMEOUT”));
    break;
    case EV_BEACON_FOUND:
    Serial.println(F(“EV_BEACON_FOUND”));
    break;
    case EV_BEACON_MISSED:
    Serial.println(F(“EV_BEACON_MISSED”));
    break;
    case EV_BEACON_TRACKED:
    Serial.println(F(“EV_BEACON_TRACKED”));
    break;
    case EV_JOINING:
    Serial.println(F(“EV_JOINING”));
    break;
    case EV_JOINED:
    Serial.println(F(“EV_JOINED”));

    // Disable link check validation (automatically enabled
    // during join, but not supported by TTN at this time).
    LMIC_setLinkCheckMode(0);
    break;
    case EV_RFU1:
    Serial.println(F(“EV_RFU1”));
    break;
    case EV_JOIN_FAILED:
    Serial.println(F(“EV_JOIN_FAILED”));
    break;
    case EV_REJOIN_FAILED:
    Serial.println(F(“EV_REJOIN_FAILED”));
    break;
    break;
    case EV_TXCOMPLETE:
    Serial.println(F(“EV_TXCOMPLETE (includes waiting for RX windows)”));
    if (LMIC.txrxFlags & TXRX_ACK)
    Serial.println(F(“Received ack”));
    if (LMIC.dataLen) {
    Serial.println(F(“Received “));
    Serial.println(LMIC.dataLen);
    Serial.println(F(” bytes of payload”));
    }
    // Schedule next transmission
    os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
    break;
    case EV_LOST_TSYNC:
    Serial.println(F(“EV_LOST_TSYNC”));
    break;
    case EV_RESET:
    Serial.println(F(“EV_RESET”));
    break;
    case EV_RXCOMPLETE:
    // data received in ping slot
    Serial.println(F(“EV_RXCOMPLETE”));
    break;
    case EV_LINK_DEAD:
    Serial.println(F(“EV_LINK_DEAD”));
    break;
    case EV_LINK_ALIVE:
    Serial.println(F(“EV_LINK_ALIVE”));
    break;
    default:
    Serial.println(F(“Unknown event”));
    break;
    }
    }

    void do_send(osjob_t* j){
    // Check if there is not a current TX/RX job running
    if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F(“OP_TXRXPEND, not sending”));
    } else {
    // Prepare upstream data transmission at the next possible time.
    uint32_t humidity = dht.readHumidity(false) * 100;
    uint32_t temperature = dht.readTemperature(false) * 100;

    Serial.println(“Humidity: ” + String(humidity));
    Serial.println(“Temperature: ” + String(temperature));

    byte payload[4];
    payload[0] = highByte(humidity);
    payload[1] = lowByte(humidity);
    payload[2] = highByte(temperature);
    payload[3] = lowByte(temperature);

    LMIC_setTxData2(1, (uint8_t*)payload, sizeof(payload), 0);

    }
    // Next TX is scheduled after TX_COMPLETE event.
    }

    void setup() {
    Serial.begin(9600);
    Serial.println(F(“Starting”));

    dht.begin();

    #ifdef VCC_ENABLE
    // For Pinoccio Scout boards
    pinMode(VCC_ENABLE, OUTPUT);
    digitalWrite(VCC_ENABLE, HIGH);
    delay(1000);
    #endif

    // LMIC init
    os_init();
    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();

    // Start job
    do_send(&sendjob);

    }

    void loop() {
    os_runloop_once();
    }

    1. Hallo,

      verwendest du das Dragino Shield? Sonst würden die PINs angepasst gehören.

      Hast du die NetworkSessionKey & AppSessionKey im richtigen Format (MSB vs. LSB) übertragen?

      Gibt es eine Fehlermeldung oder einen Hinweis auf ein Problem? (beim Kompilieren, ev. über die serielle Schnittstelle)?

      LgS

  3. Hallo Stefan,

    ich habe meinen Code ein wenig verändert und mittlerweile empfange ich Daten, allerdings immer nur B8 B8 B8 B8 als Payload. Also leider noch nicht die richtigen Temperatur- und Luftfeuchtigkeitsdaten.

    Vielleicht siehst du ja den Fehler:

    #include
    #include
    #include
    #include

    #define DHTPIN 0 //Der Sensor wird an PIN 0 angeschlossen

    #define DHTTYPE DHT11 // Es handelt sich um den DHT11 Sensor

    DHT dht(DHTPIN, DHTTYPE);

    #ifdef CREDENTIALS
    static const u1_t NWKSKEY[16] = NWKSKEY1;
    static const u1_t APPSKEY[16] = APPSKEY1;
    static const u4_t DEVADDR = DEVADDR1;
    #else
    static const u1_t NWKSKEY[16] = { 0xC3, 0x0F, 0x8B, 0x35, 0x2C, 0xDE, 0xFD, 0x35, 0x89, 0xEE, 0xDB, 0x22, 0x06, 0x9A, 0xFE, 0x44 };
    static const u1_t APPSKEY[16] = { 0xBD, 0x12, 0x4E, 0x7F, 0x20, 0x18, 0xEC, 0x22, 0x55, 0x2E, 0xCF, 0x3E, 0xEA, 0x79, 0x19, 0xA2 };
    static const u4_t DEVADDR = 0x26011F31;
    #endif

    // These callbacks are only used in over-the-air activation, so they are
    // left empty here (we cannot leave them out completely unless
    // DISABLE_JOIN is set in config.h, otherwise the linker will complain).
    void os_getArtEui (u1_t* buf) { }
    void os_getDevEui (u1_t* buf) { }
    void os_getDevKey (u1_t* buf) { }

    static osjob_t sendjob;

    // Schedule TX every this many seconds (might become longer due to duty
    // cycle limitations).
    const unsigned TX_INTERVAL = 20;

    // Pin mapping Dragino Shield
    const lmic_pinmap lmic_pins = {
    .nss = 10,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 9,
    .dio = {2, 6, 7},
    };

    void onEvent (ev_t ev) {
    if (ev == EV_TXCOMPLETE) {
    Serial.println(F(“EV_TXCOMPLETE (includes waiting for RX windows)”));
    // Schedule next transmission
    os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
    }
    }
    void do_send(osjob_t* j){
    // Check if there is not a current TX/RX job running
    if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F(“OP_TXRXPEND, not sending”));
    } else {
    // Prepare upstream data transmission at the next possible time.
    uint32_t humidity = dht.readHumidity(false);
    uint32_t temperature = dht.readTemperature(true);

    Serial.println(“Humidity: ” + String(humidity));
    Serial.println(“Temperature: ” + String(temperature));

    byte payload[4];
    payload[0] = highByte(humidity);
    payload[1] = lowByte(humidity);
    payload[2] = highByte(temperature);
    payload[3] = lowByte(temperature);

    LMIC_setTxData2(1, uint8_t(payload), sizeof(payload), 0);

    }
    // Next TX is scheduled after TX_COMPLETE event.
    }

    void setup() {
    Serial.begin(115200);
    Serial.println(F(“Starting…”));

    // LMIC init
    os_init();

    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();

    // Set static session parameters.
    LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);

    // Disable link check validation
    LMIC_setLinkCheckMode(0);

    // TTN uses SF9 for its RX2 window.
    LMIC.dn2Dr = DR_SF9;

    // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
    LMIC_setDrTxpow(DR_SF7,14);

    // Start job
    do_send(&sendjob);
    }

    void loop() {
    os_runloop_once();
    }

  4. Hallo, beim Versuch den Sketch für das Node zu kompilieren erscheint die Fehlermeldung: ‘DR_SF12’ was not declared in this scope. Ich würde mich sehr freuen, wenn man mir die richtige Deklaration verraten könnte.
    MfG Alexander Kluge

  5. Hallo,
    ich wollte den Sketch kompilieren, habe jedoch folgende Fehlermeldung erhalten: ‘DR_SF12’ was not declared in this scope.
    Ich würde mich freuen, wenn man mir die korrekte Deklaration verraten könnte.
    MfG Alexander

    1. Hallo,

      der Artikel ist bald 4 Jahr alt, da gibt es viele neuere Informationen zu dem Thema. Auch Sensoren mit den von dir genannten gibt es zB. auf ESP32 Basis (oder zumindest mit ESP 8266) mit Deep Sleep Funktionen etc.; zB. mit den TTGO T-Beam habe ich da gute Erfahrungen gemacht. Schau’ mal im Internet, ich würde dir aber raten, in Richtung ESP32 mit LoRa(WAN) zu suchen.

      LgS

Schreibe einen Kommentar