To create a fully autonomous, solar-powered station that not only records when it rains, but also measures key atmospheric conditions before, during and after the rain. This allows us to see how precipitation affects the local environment and air quality.
How does it work?The two sensors work together to create a complete picture:
- RAK12030 (Rain Sensor): Acts as the main trigger for the event. It accurately determines the start and end of the rain, allowing us to measure the duration of the rainfall.
- RAK1906 (BME680 Environmental Sensor): Provides context. It continuously measures, for example, every 5 minutes:
Temperature and Humidity: Allows us to see how the humidity in the air reaches 100% and how the temperature drops as a rain front approaches.
Atmospheric Pressure: This is a classic weather forecasting tool. A sudden drop in pressure is often a harbinger of an approaching storm or rain.
Air quality (VOC): The BME680 measures the amount of volatile organic compounds in the air. This is a particularly interesting "green" part, because rain literally "washes" the air of pollutants. You will be able to see how the air quality indicators improve after rain.
History of collected data:In your data platform (e.g. Datacake or Thingspeak), you should be able to see the following graph:
Before rain: Atmospheric pressure starts to fall and air humidity rises.
Start of rain: RAK12030 sends a signal "it's raining". Humidity reaches 100%.
After rain: RAK12030 sends a signal "it's not raining". The pressure starts to rise again and the air quality indicator (VOC) improves significantly - the air becomes cleaner.
"Green" benefits:Air quality monitoring: The project becomes a real tool that shows the benefits of natural processes (rain) for air cleanliness. It is a great educational tool.
Hyperlocal weather data: You collect extremely accurate data about the microclimate of your yard, garden or district, which is much more valuable than general weather forecasts.
Simple weather forecast: By observing pressure drop trends, you can predict upcoming rain yourself.
Understanding the water cycle: You visually see how pressure, temperature, humidity and precipitation are interconnected.
Story (Step-by-Step Guide)Step 0: Gateway: Setup and ConfigurationTo avoid damage to the gateway, make sure to connect the antenna before turning it on!
This step is thoroughly explained in the guide - IoT Education Kit - Setup the Gateway RAK7268V2 - that can be found on https://www.hackster.io/520073/iot-education-kit-setup-the-gateway-rak7268v2-6b222f
Step 1: Mounting the WisBlock Components- Place the microprocessor (RAK11300) in the dedicated slot on the motherboard, aligning the pins and holes correctly.
- Press lightly until you hear a click, then secure it using the screws and screwdriver in the kit.
- Carefully connect the LoRa 863-870MHz antenna to its designated location.
- Assemble the RAK12030 (Rain Sensor) module and RAK1906 (BME680 Environmental Sensor) by mounting on the mainboard.
Fully assembled hardware.
Install Arduino IDE/PlatformIO and support for the RAK4631 board. Install the necessary libraries for the sensors.
Insert this code:
#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));
}
Write code to initialize and read data from sensors.
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
}
};
}
Decoded mesage:
Comments