Skip to content

LoRaWAN temperature & humidity with DHT sensor

Using XIAO Seeed ESP32S3 & Wio-SX1262 https://www.seeedstudio.com/Wio-SX1262-with-XIAO-ESP32S3-p-5982.html

And The Things Network LoRaWAN network and platform. https://console.cloud.thethings.network/

They are already many accessible LoRaWAN gateways freely shared through The Things Network .

Screenshot of 22/04/2025

There is already a wide collective coverage.

https://ttnmapper.org/heatmap/

Coding a device

Original code from Seeed : https://files.seeedstudio.com/wiki/XIAO_ESP32S3_for_Meshtastic_LoRa/DHT.zip

Needing the following libraries with Arduino IDE, be sure to have the exact library version…

//RadioLib == 6.6.0
//LoRaWAN ESP32 version == 1.1.0

And using a DHT sensor on pin 4.

AgriLab code below

// Requirements :
//RadioLib == 6.6.0
//LoRaWAN ESP32 version == 1.1.0


#include "config.h"
#include "EEPROM.h"
#include <Wire.h>

#include <OneWire.h>
#include <DallasTemperature.h>
const int oneWireBus = 4;  
OneWire oneWire(oneWireBus);
DallasTemperature sensors(&oneWire);

// regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500
const LoRaWANBand_t Region = EU868;
const uint8_t subBand = 0; // For US915 and AU915

// SX1262 pin order: Module(NSS/CS, DIO1, RESET, BUSY);
SX1262 radio = new Module(41, 39, 42, 40);

// create the LoRaWAN node
LoRaWANNode node(&radio, &Region, subBand);

uint64_t joinEUI =   RADIOLIB_LORAWAN_JOIN_EUI;
uint64_t devEUI  =   RADIOLIB_LORAWAN_DEV_EUI;
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY };

#define LORAWAN_DEV_INFO_SIZE 36
uint8_t deviceInfo[LORAWAN_DEV_INFO_SIZE] = {0};

#define SERIAL_DATA_BUF_LEN  64
uint8_t serialDataBuf[SERIAL_DATA_BUF_LEN] = {0};
uint8_t serialIndex = 0;

#define UPLINK_PAYLOAD_MAX_LEN  256
uint8_t uplinkPayload[UPLINK_PAYLOAD_MAX_LEN] = {0};
uint16_t uplinkPayloadLen = 0;

uint32_t previousMillis = 0;

void setup() {
  Serial.begin(115200);
  Serial.println(F("start"));  
  sensors.begin(); // DS18B20


  if(!EEPROM.begin(LORAWAN_DEV_INFO_SIZE))
  {
    Serial.println("Failed to initialize EEPROM");
    while(1);
  }

  uint32_t now = millis();
  while(1)
  {
    deviceInfoSet();
    if(millis() - now >= 5000) break;
  }

  deviceInfoLoad();
  Serial.println(F("\nSetup... "));  
  Serial.println(F("Initialise the radio"));
  int16_t state = radio.begin();
  debug(state!= RADIOLIB_ERR_NONE, F("Initialise radio failed"), state, true);

  // SX1262 rf switch order: setRfSwitchPins(rxEn, txEn);
  radio.setRfSwitchPins(38, RADIOLIB_NC);

  // Setup the OTAA session information
  node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
  Serial.println(F("Join ('login') the LoRaWAN Network"));

  while(1)
  {
    state = node.activateOTAA(LORAWAN_UPLINK_DATA_RATE);
    if(state == RADIOLIB_LORAWAN_NEW_SESSION) break;
    debug(state!= RADIOLIB_LORAWAN_NEW_SESSION, F("Join failed"), state, true);
    delay(15000);
  }

  // Disable the ADR algorithm (on by default which is preferable)
  node.setADR(false);

  // Set a fixed datarate
  node.setDatarate(LORAWAN_UPLINK_DATA_RATE);

  // Manages uplink intervals to the TTN Fair Use Policy
  node.setDutyCycle(false);

  Serial.println(F("Ready!\n"));

  //Wire.begin();
  //dht.begin();
}

void loop() {
  float temp_hum_val[2] = {0};
  sensors.requestTemperatures();//DS18B20

  //if (!dht.readTempAndHumidity(temp_hum_val)) {
    if (1) {  
    uplinkPayloadLen = 0;
    memset(uplinkPayload, sizeof(uplinkPayload), 0);

    // Convert temperature and humidity to bytes with decimal precision
    uint16_t tempDecimal = (sensors.getTempCByIndex(0) * 100);//DS18B20
    Serial.print(tempDecimal);//DS18B20
    Serial.println("ºC");//DS18B20
    //uint16_t humDecimal = (temp_hum_val[0] * 100);
    //uint16_t tempDecimal = (16 * 100);
    uint16_t humDecimal = (80 * 100);
    uplinkPayload[uplinkPayloadLen++] = (tempDecimal >> 8);
    uplinkPayload[uplinkPayloadLen++] = tempDecimal & 0xFF;
    uplinkPayload[uplinkPayloadLen++] = (humDecimal >> 8);
    uplinkPayload[uplinkPayloadLen++] = humDecimal & 0xFF;

    Serial.print("Temperature: ");
    Serial.print(temp_hum_val[1]);
    Serial.print(" *C, Humidity: ");
    Serial.println(temp_hum_val[0]);
    Serial.print("Uplink payload length: ");
    Serial.println(uplinkPayloadLen);
    // Output the uplink payload for debugging
    Serial.print("Uplink payload: ");
    for (int i = 0; i < uplinkPayloadLen; i++) {
      Serial.print(uplinkPayload[i], HEX);
      Serial.print(" ");
    }
    Serial.println();

    int16_t state = node.sendReceive(uplinkPayload, uplinkPayloadLen, LORAWAN_UPLINK_USER_PORT);
    if (state!= RADIOLIB_LORAWAN_NO_DOWNLINK && state!= RADIOLIB_ERR_NONE) {
      Serial.println("Error in sendReceive:");
      Serial.println(state);
    } else {
      Serial.println("Sending uplink successful!");
    }
  } else {
    Serial.println("Failed to get temprature and humidity value.");
    uplinkPayloadLen = 0;
    memset(uplinkPayload, sizeof(uplinkPayload), 0);
  }

  uint32_t currentMillis = millis();
  if(currentMillis - previousMillis >= LORAWAN_UPLINK_PERIOD)
  {
    previousMillis = currentMillis;
    if(uplinkPayloadLen)
    {
      Serial.println(F("Sending uplink"));
      int16_t state = node.sendReceive(uplinkPayload, uplinkPayloadLen, LORAWAN_UPLINK_USER_PORT);
      debug((state!= RADIOLIB_LORAWAN_NO_DOWNLINK) && (state!= RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false);
      uplinkPayloadLen = 0;
    }
  }
  delay(1000);
}

void deviceInfoLoad() {
  uint16_t checkSum = 0, checkSum_ = 0;
  for(int i = 0; i < LORAWAN_DEV_INFO_SIZE; i++) deviceInfo[i] = EEPROM.read(i);
  for(int i = 0; i < 32; i++) checkSum += deviceInfo[i];
  memcpy((uint8_t *)(&checkSum_), deviceInfo + 32, 2);

  if(checkSum == checkSum_)
  {
    memcpyr((uint8_t *)(&joinEUI), deviceInfo, 8);
    memcpyr((uint8_t *)(&devEUI), deviceInfo + 8, 8);
    memcpy(appKey, deviceInfo + 16, 16);

    Serial.println("Load device info:");
    Serial.print("JoinEUI:");
    Serial.println(joinEUI, HEX);
    Serial.print("DevEUI:");
    Serial.println(devEUI, HEX);
    Serial.print("AppKey:");
    arrayDump(appKey, 16);
    Serial.print("nwkKey:");
    arrayDump(nwkKey, 16);
  }
  else
  {
    Serial.println("Use the default device info as LoRaWAN param");
  }
}

void deviceInfoSet() {
  if(Serial.available())
  {
    serialDataBuf[serialIndex++] = Serial.read();
    if(serialIndex >= SERIAL_DATA_BUF_LEN) serialIndex = 0;
    if(serialIndex > 2 && serialDataBuf[serialIndex - 2] == '\r' && serialDataBuf[serialIndex-1] == '\n')
    {
      Serial.println("Get serial data:");
      arrayDump(serialDataBuf, serialIndex);
      if(serialIndex == 34) // 8 + 8 + 16 + 2
      {
        uint16_t checkSum = 0;
        for(int i = 0; i < 32; i++) checkSum += serialDataBuf[i];
        memcpy(deviceInfo, serialDataBuf, 32);
        memcpy(deviceInfo + 32, (uint8_t *)(&checkSum), 2);
        for(int i = 0; i < 34; i++) EEPROM.write(i, deviceInfo[i]);
        EEPROM.commit();
        Serial.println("Save serial data, please reboot...");
      }
      else
      {
        Serial.println("Error serial data length");
      }

      serialIndex = 0;
      memset(serialDataBuf, sizeof(serialDataBuf), 0);
    }
  }
}

With the following file including your keys (see things network below to get your keys)

File: config.h

#ifndef _CONFIG_H_
#define _CONFIG_H_

#include <RadioLib.h>

#ifndef RADIOLIB_LORAWAN_JOIN_EUI
#define RADIOLIB_LORAWAN_JOIN_EUI  0x0000000000000000
#endif


#ifndef RADIOLIB_LORAWAN_DEV_EUI
#define RADIOLIB_LORAWAN_DEV_EUI   0x70B3D57ED00701A4
#endif

#ifndef RADIOLIB_LORAWAN_APP_KEY
#define RADIOLIB_LORAWAN_APP_KEY   0x6C, 0x2C, 0x77, 0x85, 0xE9, 0x7A, 0xD2, 0x64, 0x4F, 0xBD, 0xF6, 0x75, 0x67, 0x1D, 0x93, 0xEA
#endif


#ifndef RADIOLIB_LORAWAN_NWK_KEY   // Put your Nwk Key here
#define RADIOLIB_LORAWAN_NWK_KEY   0x40, 0xED, 0xE5, 0x13, 0x93, 0x62, 0xB2, 0xD6, 0x51, 0xED, 0x6B, 0x69, 0x4A, 0xE2, 0xDF, 0x08
#endif

#define LORAWAN_UPLINK_USER_PORT  2
#define LORAWAN_UPLINK_DATA_RATE  3

#define LORAWAN_UPLINK_PERIOD     10000 // ms

#define LORAWAN_UPLINK_DATA_MAX   115 // byte


// result code to text ...
String stateDecode(const int16_t result) {
  switch (result) {
  case RADIOLIB_ERR_NONE:
    return "ERR_NONE";
  case RADIOLIB_ERR_CHIP_NOT_FOUND:
    return "ERR_CHIP_NOT_FOUND";
  case RADIOLIB_ERR_PACKET_TOO_LONG:
    return "ERR_PACKET_TOO_LONG";
  case RADIOLIB_ERR_RX_TIMEOUT:
    return "ERR_RX_TIMEOUT";
  case RADIOLIB_ERR_CRC_MISMATCH:
    return "ERR_CRC_MISMATCH";
  case RADIOLIB_ERR_INVALID_BANDWIDTH:
    return "ERR_INVALID_BANDWIDTH";
  case RADIOLIB_ERR_INVALID_SPREADING_FACTOR:
    return "ERR_INVALID_SPREADING_FACTOR";
  case RADIOLIB_ERR_INVALID_CODING_RATE:
    return "ERR_INVALID_CODING_RATE";
  case RADIOLIB_ERR_INVALID_FREQUENCY:
    return "ERR_INVALID_FREQUENCY";
  case RADIOLIB_ERR_INVALID_OUTPUT_POWER:
    return "ERR_INVALID_OUTPUT_POWER";
  case RADIOLIB_ERR_NETWORK_NOT_JOINED:
      return "RADIOLIB_ERR_NETWORK_NOT_JOINED";

  case RADIOLIB_ERR_DOWNLINK_MALFORMED:
    return "RADIOLIB_ERR_DOWNLINK_MALFORMED";
  case RADIOLIB_ERR_INVALID_REVISION:
    return "RADIOLIB_ERR_INVALID_REVISION";
  case RADIOLIB_ERR_INVALID_PORT:
    return "RADIOLIB_ERR_INVALID_PORT";
  case RADIOLIB_ERR_NO_RX_WINDOW:
    return "RADIOLIB_ERR_NO_RX_WINDOW";
  case RADIOLIB_ERR_INVALID_CID:
    return "RADIOLIB_ERR_INVALID_CID";
  case RADIOLIB_ERR_UPLINK_UNAVAILABLE:
    return "RADIOLIB_ERR_UPLINK_UNAVAILABLE";
  case RADIOLIB_ERR_COMMAND_QUEUE_FULL:
    return "RADIOLIB_ERR_COMMAND_QUEUE_FULL";
  case RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND:
    return "RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND";
  case RADIOLIB_ERR_JOIN_NONCE_INVALID:
    return "RADIOLIB_ERR_JOIN_NONCE_INVALID";
  case RADIOLIB_ERR_N_FCNT_DOWN_INVALID:
    return "RADIOLIB_ERR_N_FCNT_DOWN_INVALID";
  case RADIOLIB_ERR_A_FCNT_DOWN_INVALID:
    return "RADIOLIB_ERR_A_FCNT_DOWN_INVALID";
  case RADIOLIB_ERR_DWELL_TIME_EXCEEDED:
    return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED";
  case RADIOLIB_ERR_CHECKSUM_MISMATCH:
    return "RADIOLIB_ERR_CHECKSUM_MISMATCH";
  case RADIOLIB_LORAWAN_NO_DOWNLINK:
    return "RADIOLIB_LORAWAN_NO_DOWNLINK";
  case RADIOLIB_LORAWAN_SESSION_RESTORED:
    return "RADIOLIB_LORAWAN_SESSION_RESTORED";
  case RADIOLIB_LORAWAN_NEW_SESSION:
    return "RADIOLIB_LORAWAN_NEW_SESSION";
  case RADIOLIB_LORAWAN_NONCES_DISCARDED:
    return "RADIOLIB_LORAWAN_NONCES_DISCARDED";
  case RADIOLIB_LORAWAN_SESSION_DISCARDED:
    return "RADIOLIB_LORAWAN_SESSION_DISCARDED";
  }
  return "See TypeDef.h";
}

// helper function to display any issues
void debug(bool isFail, const __FlashStringHelper* message, int state, bool Freeze) {
  if (isFail) {
    Serial.print(message);
    Serial.print(" - ");
    Serial.print(stateDecode(state));
    Serial.print(" (");
    Serial.print(state);
    Serial.println(")");
    while (Freeze);
  }
}

// helper function to display a byte array
void arrayDump(uint8_t *buffer, uint16_t len) {
  for(uint16_t c = 0; c < len; c++) {
    char b = buffer[c];
    if(b < 0x10) { Serial.print('0'); }
    Serial.print(b, HEX);
  }
  Serial.println();
}

void memcpyr(uint8_t *dst, const uint8_t *src, uint16_t size)
{
    dst = dst + ( size - 1 );
    while( size-- )
    {
        *dst-- = *src++;
    }
}

#endif

The Things Network

You’ll need to create an account on https://console.cloud.thethings.network/ Then to create “an application” to decode the packet.

Inside your application you have to create an “uplink payload formatter” with this cutoms javascript formater

function decodeUplink(input) {
  var bytes = input.bytes;
  var temperatureDecimal = (bytes[0] << 8) | bytes[1];
  var humidityDecimal = (bytes[2] << 8) | bytes[3];

  var temperature = temperatureDecimal / 100;
  var humidity = humidityDecimal / 100;

  return {
    data: {
      temperature: temperature,
      humidity: humidity
    }
  };
}

This will decode for example the following “06401F40” in 16°C temperature , and 80% of humidity

In your application you have to “Register end device”. That’s where you’ll have some keys.

Those keys are needed in the config.h :

RADIOLIB_LORAWAN_JOIN_EUI (related to your application)

RADIOLIB_LORAWAN_DEV_EUI (prelated to end device)

RADIOLIB_LORAWAN_APP_KEY (in a specific format)

RADIOLIB_LORAWAN_NWK_KEY (in a specific format)

If you have access to a TTN gateway (if you own the gateway and can see messages going through), you’ll be able to see the join process. In the gateway tab you have a console like interface .

You’ll see also the join in the serial communication with your end node

Be aware this is for prototyping. You’ll do many mistake. For security reason and cryptography there is a counter in LoRaWAN protocol. We have to disable that feature for testing. On your end device configuration page please check “Resets join nonces enable” by clicking on “Reset used DevNonces” .

Otherwise all your messages will be discarded.

If Everything works correctly you should see the message and the decoded value in the application console.

MQTT data

TheThingsNetwork can give you back the data in the different form; One of them is by subscribing to their MQTT borker with the differents keys. https://www.thethingsindustries.com/docs/integrations/other-integrations/mqtt/

Files

modif002.ino

config.h

DHT.cpp

DHT.h

DHT.cpp