LoRa-Node-DHT22

First LoRa node in the real world

Batterie + Solar + 3D Gehäuse + Arduino Pro Mini + RFM95W + DHT22

Finally a real application

Hello friends,

apologize for the rare posts, I’ve been employed in a makerspace for a few weeks now and I’m not coming to write something. Great fun, but documenting here in the blog…

So, as mentioned in the headline: The first node is running 🙂 Sending every 10 Minutes.

Livedata

Temperature:

Humidity:

Battery:

 

Construction

LoRa-Node-DHT22-Solar

The board from the post  “My first own TTN node with self designed PCB” certainly known to you, otherwise read the article;)

The structure is thus very simple. The board has everything we need, the DHT22 temperature sensor is simply soldered to the Arduino and ready.

The solar cell is very small, the battery also only in AA (14500) format. No problem with sunshine. We will see how the combination works in winter! I’m curious! However, the battery should last long enough so that winter should not be a problem.

Data processing

The Things Network -> Payload -> NodeRed -> Thingspeak / own Database / …

LoRa-PCB

Technical specifications

  • Arduino Pro Mini 3.3V (LowPower mods)
  • RFM95W
  • 0.15W 5V solar cell
  • 900mAh 3.7V LiPo
  • Running time about 2 years without sun?
  • 5 days sun to fully charge

Mini-Solar-Cell-0.1W

Runtime Bill

As we learned in the LowPower article here we can expect self-discharge, now and then, deep sleep to consume about 0.04mA.

At 900mAh battery and 0.04mA consumption we come to 900 days. Therefore, winter should not be a problem! Top!

Here’s how the real consumption showed up after 5 months.

LoRa-Node

Code

// Default LoRa libs
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

// For this Sketch
#include <Wire.h>
#include "DHT.h"
#include <LowPower.h>

// DHT Sensor
#define DHTPIN 8
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Enable debug prints to serial monitor
#define MY_DEBUG

// For Mario PCB
int BATTERY_SENSE_PIN = A0;
int BATTERY_FULL = 4.2;

// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8] = { ToDo };
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8] = { ToDo };
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = { ToDo };
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

static uint8_t mydata[] = "Hello, world!"; // Default
static osjob_t sendjob;

// 10min -> 12:10 -> 130s zuviel -> pro minute 13s zuviel
#define SECPROMIN 13

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL_MINUTES = 10;
const unsigned TX_INTERVAL = (60 - SECPROMIN) * TX_INTERVAL_MINUTES;

// Pin mapping for Mario PCB
const lmic_pinmap lmic_pins = {
  .nss = 10,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 9,
  .dio = {4, 5, 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
      for (int i = 0; i < int(TX_INTERVAL / 8); i++) {
        // Use library from https://github.com/rocketscream/Low-Power
        LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
      }
      do_send(&sendjob);
      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) {
  // Read sensor values and multiply by 100 to effectively keep 2 decimals
  // Signed 16 bits integer, -32767 up to +32767
  int16_t t = dht.readTemperature() * 100;
  // Unsigned 16 bits integer, 0 up to 65535
  uint16_t h = dht.readHumidity() * 100;
  // Unsigned 16 bits integer, 0 up to 65535
  uint16_t b = readBat() * 100;
  byte buffer[6];
  buffer[0] = t >> 8;
  buffer[1] = t;
  buffer[2] = h >> 8;
  buffer[3] = h;
  buffer[4] = b >> 8;
  buffer[5] = b;
  LMIC_setTxData2(1, buffer, sizeof(buffer), 0);

#ifdef MY_DEBUG
  Serial.println("");
  Serial.print("temperature: ");
  Serial.print(t);
  Serial.print(", humidity: ");
  Serial.print(h);
  Serial.print(", battery: ");
  Serial.print(b);
  Serial.println("");
#endif

  // 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.
    LMIC_setTxData2(1, buffer, sizeof(buffer), 0);
    // LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Starting"));

  // Fuer A0 Battery:
  analogReference(INTERNAL);

  dht.begin();
  delay(2000);

#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 (sending automatically starts OTAA too)
  do_send(&sendjob);
}

float readBat() {
  int sensorValue = analogRead(BATTERY_SENSE_PIN);
#ifdef MY_DEBUG
  Serial.println(sensorValue);
#endif
  // 1M, 470K divider across battery and using internal ADC ref of 1.1V
  // Sense point is bypassed with 0.1 uF cap to reduce noise at that point
  // ((1e6+470e3)/470e3)*1.1 = Vmax = 3.44 Volts
  // 3.44/1023 = Volts per bit = 0.003363075

  // 2M, 470K 1.1V ref => 5,78V Max!
  // 5,78/1023 = 0.00565
  float batteryV  = sensorValue * 0.00565;

#ifdef MY_DEBUG
  Serial.print("Battery Voltage: ");
  Serial.print(batteryV);
  Serial.println(" V");
#endif
  return batteryV;
}

void loop() {
  os_runloop_once();
}

Affiliate links:

If you order through any of the following AliExpress links, I get about 8% commission 😉

Soshine 4pc 3.7V ICR 14500 900mAh Li-ion
Mini 0.15W 5V Solar Panel
DHT22 single-bus digital temperature and humidity sensor

16 thoughts on “First LoRa node in the real world”

  1. Mario, el código que propones para Arduino no compila. Tienes que tener en cuenta los pines del SPI

    1. Hola, funcionó para mí en ese momento. Puede ser que algo haya cambiado en la biblioteca.

  2. Hey Mario,
    mega interessantes Projekt und genau das, was ich gerade suche! Allerdings habe ich das Problem, wenn ich deinen Code per Copy and Paste ausprobiere, dass mir der Fehler für den Arduino Pro Mini angezeigt wird: “Der Sketch verwendet 25280 Bytes (82%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
    Globale Variablen verwenden 2329 Bytes (113%) des dynamischen Speichers, -281 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.”

    Ich hab mit dem RFM95 noch überhaupt keine Erfahrung gesammelt und keine Ahnung, wie ich libraries ausdünnen kann. Wie hat das bei dir geklappt? Wäre dir wirklich sehr für eine Idee dankbar, was ich falsch mache 😀
    LG
    Johannes

  3. Hallo Mario,
    hat sich erledigt, ich habe den Fehler gefunden! Die Libraries haben sich nicht korrekt installiert oder so..

    1. function Decoder(bytes, port) {
      var t = (bytes[0] & 0x80 ? 0xFFFF<<16 : 0) | bytes[0]<<8 | bytes[1]; var h = bytes[2]<<8 | bytes[3]; var b = bytes[4]<<8 | bytes[5]; var p = (bytes[6]<<8 | bytes[7]) * 100 + (bytes[8]<<8 | bytes[9]) / 100; return { temperature: t / 100, humidity: h / 100, battery: b / 100, pressure: p } }

  4. Tolle Idee!
    Habe mir zwei Boards nachgebaut, aber anscheinend sendet keines von beiden…
    Weshalb hast du DIO9 des Arduino nochmal verkabelt?

    Gruß – Sven

    1. Komisch. D9 ist der Reset Pin zum Arduino. Ich habe inzwischen das 10 Board oder so verbaut. I.d.R. klappt alles. Kommst du aus der Nähe Oberhausen?

  5. Hello,
    this seems to be a very nice project. Currently I to try to rebuild this since some days, always when I have a bit of time and I use a breadboard for the first experiments. Connections are fine and a “hello world” example is also working. However, extending the code and implementing a sensor like you show yields already some problems.

    May I ask which versions of the libraries did you install? The current ones seem to be too big such that stability problems occur and the program does not do what is should (I use the serial monitor for debugging).
    – MCCI LoRaWAN LMIC Library – version ???
    – DHT sensor library – version ???
    – Arduino Low Power – version ???
    – Wire – version ???

    Best regards!
    Felix

      1. Hi,
        thanks a lot. I could succeed with IBM LMIC framework (matthijskooijman/IBM LMIC framework 1.5.1). Is this the one you used? It takes between 5-10 min until the µC can join the network. Once it joined, it runs stable. Is this normal?
        I rebuild the project with an ESP32 – just out of curiosity, and the ESP32 setup joins the network almost immediately. I did multiple tries for a conclusive result.
        I will try with STM32 in the near future.

        Best regards!
        Felix

    1. Hello,

      I hope it is not too late.

      PCB:
      https://oshwlab.com/mariozw/TTN-dd5f957581974c02a573633d15e9e668
      https://easyeda.com/mariozw/TTN-dd5f957581974c02a573633d15e9e668

      I still don’t have all the stuff on Thingiverse unfortunately, but I’m happy to share my Fusion360 project. I think you can export whole bodies as STL.
      Not all of the designs I have printed out. Many were just concepts. But they can still fit. I just do not know it anymore 😀

      https://a360.co/35BukKb
      https://a360.co/3bkoQHk
      https://a360.co/3or7ThQ

      Greetings

  6. Super Projekt 😉 Lora ist wirklich ein gutes Thema.

    Ich hab mal versucht dein Projekt nachzubauen, ich bekomme im SerialMonitor aber einen Fehler:

    Packet queued
    128271: EV_JOINING
    238230: Unknown event

    Im Gateway unter Traffic kommt alles an aber unter der Application des Sensor keine Daten? Hast du einen Tipp für mich ?

    grüße Tobi

    1. Hei Tobi,

      ja, du hast die falschen Keys eingetragen. Wenn das Paket nur auf dem Gateway zu erkennen ist, ist irgendetwas falsch.
      Falsche AppKey -> kein entschlüsseln. Falsche DevAdrr -> keine Zuordnung, ..
      Achte darauf, dass die meisten Bibliotheken die Keys in Hex Form haben möchten. Manche in LSB manche in MSB (welches Bit kommt zuerst..)
      Manchmal steht es dabei, manchmal muss man ausprobieren.

      Grüße!

Comments are closed.