Tomas Urbanavičius
Published © GPL3+

Monitoring UV index, ground temperature and humidity

UMonitoring UV index, ground temperature and humidity readings using RAK12035 (LTR390-UV), Rak12023/12035 sensors and LoRa

BeginnerWork in progress4 hours18
Monitoring UV index, ground temperature and humidity

Things used in this project

Hardware components

RAKwireless - RAK11300
×1
RAKwireless - RAK19001_VB
×1
PCB Antenna for LoRa 863-870MHz
×1
WisGate Edge Lite 2 RAK7268\RAK7268C  Gateway for LoRaWAN
RAKwireless WisGate Edge Lite 2 RAK7268\RAK7268C Gateway for LoRaWAN
×1
UV Light Sensor Lite-On LTR-390UV-01 RAK12019
×1
Rak12023/12035 soil sensor
×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

UVlight_LTR390 RAK12035 soilSensor

Arduino
#include <Arduino.h>
#include "LoRaWan-Arduino.h"
#include <SPI.h>
#include <Wire.h>
#include "UVlight_LTR390.h"
#include "RAK12035_SoilMoisture.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 8
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};

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(float uvi, uint8_t uvEmergency, uint8_t soilHumidity, int16_t soilTemp);

// ============================
// 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
};

// ============================
// Sensors
// ============================
UVlight_LTR390 ltr;
RAK12035 soilSensor;

// Calibration values for soil sensor
uint16_t zero_val = 73;
uint16_t hundred_val = 250;

// Soil readings
uint8_t soilHumidity = 0;
int16_t soilTemperature = 0;

// ============================
// Helpers
// ============================
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];
}

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

    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(WB_IO2, OUTPUT);
    digitalWrite(WB_IO2, HIGH);
    delay(300);

    Wire.begin();

    // Init LTR390
    if(!ltr.init()){
        Serial.println("Couldn't find LTR390 sensor!");
        while(1) delay(1000);
    }
    ltr.setMode(LTR390_MODE_UVS);
    ltr.setGain(LTR390_GAIN_3);
    ltr.setResolution(LTR390_RESOLUTION_16BIT);

    // Init Soil Sensor
    soilSensor.begin();
    soilSensor.get_dry_cal(&zero_val);
    soilSensor.get_wet_cal(&hundred_val);

    // Init LoRa
    lora_rak11300_init();
    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() {
    float uvi = 0;
    uint8_t uvEmergency = 0;

    if(ltr.newDataAvailable()){
        uvi = ltr.getUVI();
        if(uvi >= 5.0){
            uvEmergency = 1;
        }
    }

    soilSensor.get_sensor_moisture(&soilHumidity);
    uint16_t tempRaw = 0;
    soilSensor.get_sensor_temperature(&tempRaw);
    soilTemperature = tempRaw / 10;

    send_lora_frame(uvi, uvEmergency, soilHumidity, soilTemperature);

    delay(LORAWAN_APP_INTERVAL);
}

// ============================
// Send LoRa Frame
// ============================
void send_lora_frame(float uvi, uint8_t uvEmergency, uint8_t soilHumidity, int16_t soilTemp){
    if(lmh_join_status_get() != LMH_SET) return;

    uint8_t i = 0;
    floatToBytes(uvi, &m_lora_app_data.buffer[i]); i+=4;
    m_lora_app_data.buffer[i++] = uvEmergency;
    m_lora_app_data.buffer[i++] = soilHumidity;
    m_lora_app_data.buffer[i++] = (soilTemp >> 8) & 0xFF;
    m_lora_app_data.buffer[i++] = soilTemp & 0xFF;

    m_lora_app_data.port = LORAWAN_APP_PORT;
    m_lora_app_data.buffsize = i;

    lmh_send(&m_lora_app_data, g_CurrentConfirm);
}

// ============================
// 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){}
void lorawan_confirm_class_handler(DeviceClass_t Class){}
void tx_lora_periodic_handler(void){ appTimer.attach(tx_lora_periodic_handler, std::chrono::milliseconds(LORAWAN_APP_INTERVAL)); }

TTN decoder

JavaScript
Decodes the readings sent by sensors and interprets the values.
function decodeUplink(input) {
    var bytes = input.bytes;

    // UV
    var uvi = ((bytes[0] | (bytes[1]<<8) | (bytes[2]<<16) | (bytes[3]<<24)) >>> 0);
    uvi = new DataView(new Uint32Array([uvi]).buffer).getFloat32(0, true);

    // Apvalinam iki 2 skaitmenų po kablelio
    uvi = parseFloat(uvi.toFixed(2));

    var uvEmergency = bytes[4];

    // Soil
    var soilHumidity = bytes[5];
    var soilTemp = (bytes[6]<<8) | bytes[7];

    return {
        data: {
            uv: uvi,
            uvEmergency: uvEmergency,
            soilHumidity: soilHumidity,
            soilTemp: soilTemp
        }
    };
}

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