Tomas Urbanavičius
Published © GPL3+

Advanced environmental and precipitation monitoring station

Advanced environmental and precipitation monitoring station with LoRa

IntermediateWork in progress5 hours18
Advanced environmental and precipitation monitoring station

Things used in this project

Hardware components

WisBlock Base Board 2nd Gen RAK19007
×1
RAKwireless - RAK11300
×1
PCB Antenna for LoRa 863-870MHz
×1
RAK7268V2 WisGate Edge Lite 2
×1
Environment Sensor BOSCH BME680 RAK1906
×1
Rain Sensor Microchip MCP606 RAK12005+RAK12030
×1

Software apps and online services

Arduino IDE
Arduino IDE
The Things Stack
The Things Industries The Things Stack

Hand tools and fabrication machines

Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Code

Environmental and precipitation monitoring station with LoRa

Arduino
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
#include "LoRaWan-Arduino.h"

// ============================
// LoRaWAN Settings
// ============================
bool doOTAA = true;
#define LORAWAN_APP_PORT 2
#define LORAWAN_APP_INTERVAL 10000  // ms

DeviceClass_t g_CurrentClass = CLASS_A;
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_EU868;
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_MSG;

// OTAA keys
uint8_t nodeDeviceEUI[8] = {0xAC,0x1F,0x09,0xFF,0xFE,0x06,0xB5,0xFB};
uint8_t nodeAppEUI[8]    = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
uint8_t nodeAppKey[16]   = {0xA6,0x7D,0x91,0xEA,0xD1,0xB8,0x6B,0x66,0x61,0x91,0xB9,0xDC,0xDD,0x4A,0x53,0xDD};

// ============================
// LoRaWAN Buffers
// ============================
#define LORAWAN_APP_DATA_BUFF_SIZE 16
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0};

bool send_now = false;
mbed::Ticker appTimer;

// ============================
// Forward Declarations
// ============================
static void lorawan_has_joined_handler(void);
static void lorawan_join_failed_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
void tx_lora_periodic_handler(void);
void send_lora_frame(uint8_t* payload, uint8_t len);

// ============================
// LoRa callbacks
// ============================
static lmh_callback_t g_lora_callbacks = {
    BoardGetBatteryLevel,
    BoardGetUniqueId,
    BoardGetRandomSeed,
    lorawan_rx_handler,
    lorawan_has_joined_handler,
    lorawan_confirm_class_handler,
    lorawan_join_failed_handler,
    nullptr,
    nullptr
};

// ============================
// BME680 + Rain sensor
// ============================
Adafruit_BME680 bme;
#define SENSOR_PIN  WB_IO6   // Water sensor pin
bool isRaining = false;

// ============================
// Helpers
// ============================
uint8_t getAirQuality(float gasKOhms){
    if(gasKOhms > 10.0) return 0;     // Good
    else if(gasKOhms > 5.0) return 1; // Moderate
    else return 2;                     // Poor
}

void checkRain(){
    isRaining = (digitalRead(SENSOR_PIN) == HIGH);
}

void floatToBytes(float value, uint8_t* bytes) {
    union { float f; uint8_t b[4]; } u;
    u.f = value;
    for(int i=0;i<4;i++) bytes[i] = u.b[i];
}

// ============================
// Send payload via LoRa
// ============================
void send_lora_frame(uint8_t* payload, uint8_t len){
    if(lmh_join_status_get() != LMH_SET) return;

    if(len > LORAWAN_APP_DATA_BUFF_SIZE) len = LORAWAN_APP_DATA_BUFF_SIZE;

    memcpy(m_lora_app_data.buffer, payload, len);
    m_lora_app_data.port = LORAWAN_APP_PORT;
    m_lora_app_data.buffsize = len;

    if(lmh_send(&m_lora_app_data, g_CurrentConfirm) == LMH_SUCCESS){
        Serial.println("Data sent via LoRaWAN");
    } else {
        Serial.println("Send failed!");
    }
}

// ============================
// Setup
// ============================
void setup() {
    Serial.begin(115200);
    while(!Serial){}

    // Init BME680
    Wire.begin();
    if(!bme.begin(0x76)){
        Serial.println("Could not find BME680 sensor!");
        while(1) delay(1000);
    }
    bme.setTemperatureOversampling(BME680_OS_8X);
    bme.setHumidityOversampling(BME680_OS_2X);
    bme.setPressureOversampling(BME680_OS_4X);
    bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
    bme.setGasHeater(320, 150);

    // Rain sensor
    pinMode(SENSOR_PIN, INPUT);

    // Init LoRa
    lora_rak11300_init();
    Serial.println("RAK11300 Environment Monitor");

    if(doOTAA){
        lmh_setDevEui(nodeDeviceEUI);
        lmh_setAppEui(nodeAppEUI);
        lmh_setAppKey(nodeAppKey);
    }

    lmh_param_t g_lora_param_init = {LORAWAN_ADR_ON, DR_0, LORAWAN_PUBLIC_NETWORK, 3, TX_POWER_5, LORAWAN_DUTYCYCLE_OFF};
    if(lmh_init(&g_lora_callbacks, g_lora_param_init, doOTAA, g_CurrentClass, g_CurrentRegion) != 0){
        Serial.println("lmh_init failed");
        return;
    }
    lmh_join();
}

// ============================
// Loop
// ============================
void loop() {
    if(!bme.performReading()){
        Serial.println("Failed BME680 reading");
        delay(1000);
        return;
    }

    float temperature = bme.temperature;
    float humidity = bme.humidity;
    float pressure_hPa = bme.pressure / 100.0;
    float gas = bme.gas_resistance / 1000.0;

    checkRain();
    uint8_t airQuality = getAirQuality(gas);

    // Payload: temp (4), humidity (4), pressure (4), airQuality (1), rain (1)
    uint8_t payload[14];
    floatToBytes(temperature, &payload[0]);
    floatToBytes(humidity, &payload[4]);
    floatToBytes(pressure_hPa, &payload[8]);
    payload[12] = airQuality;
    payload[13] = isRaining ? 1 : 0;

    send_lora_frame(payload, 14);

    // Serial monitor output
    Serial.println("Weather Data:");
    Serial.print("Temperature: "); Serial.print(temperature,2); Serial.println(" °C");
    Serial.print("Humidity: "); Serial.print(humidity,2); Serial.println(" %");
    Serial.print("Pressure: "); Serial.print(pressure_hPa,2); Serial.println(" hPa");
    Serial.print("Air Quality: "); 
    if(airQuality==0) Serial.println("Good");
    else if(airQuality==1) Serial.println("Moderate");
    else Serial.println("Poor");
    Serial.print("Raining: "); Serial.println(isRaining ? "Yes" : "No");
    Serial.println("-------------------------------");

    delay(LORAWAN_APP_INTERVAL);
}

// ============================
// LoRa Handlers
// ============================
void lorawan_has_joined_handler(void){
    Serial.println("OTAA Network Joined!");
    lmh_class_request(g_CurrentClass);
    appTimer.attach(tx_lora_periodic_handler, std::chrono::milliseconds(LORAWAN_APP_INTERVAL));
}

void lorawan_join_failed_handler(void){ Serial.println("OTAA join failed!"); }
void lorawan_rx_handler(lmh_app_data_t *app_data){ Serial.printf("RX on port %d, size %d\n", app_data->port, app_data->buffsize); }
void lorawan_confirm_class_handler(DeviceClass_t Class){ Serial.printf("Switched to class %c\n", "ABC"[Class]); }

void tx_lora_periodic_handler(void){
    send_now = true;
    appTimer.attach(tx_lora_periodic_handler, std::chrono::milliseconds(LORAWAN_APP_INTERVAL));
}

Decoder

JavaScript
function decodeUplink(input) {
    var bytes = input.bytes;

    // Temperature
    var temperature = new DataView(new Uint8Array(bytes.slice(0,4)).buffer).getFloat32(0,true);
    // Humidity
    var humidity = new DataView(new Uint8Array(bytes.slice(4,8)).buffer).getFloat32(0,true);
    // Pressure
    var pressure = new DataView(new Uint8Array(bytes.slice(8,12)).buffer).getFloat32(0,true);
    // Air Quality
    var airQualityEnum = bytes[12];
    var airQuality = "Unknown";
    if(airQualityEnum === 0) airQuality = "Good";
    else if(airQualityEnum === 1) airQuality = "Moderate";
    else if(airQualityEnum === 2) airQuality = "Poor";
    // Rain
    var raining = bytes[13] === 1;

    // Round floats
    temperature = Math.round(temperature*100)/100;
    humidity = Math.round(humidity*100)/100;
    pressure = Math.round(pressure*100)/100;

    return {
        data: {
            temperature: temperature,
            humidity: humidity,
            pressure: pressure,
            airQuality: airQuality,
            raining: raining
        }
    };
}

Credits

Tomas Urbanavičius
3 projects • 6 followers
Teacher at Kaunas Education Center of Technologies (Kautech) & Erasmus+ project member: "DSIoT" (2022-1-PT01-KA220-VET-000090202)

Comments