Tomas Urbanavičius
Published © GPL3+

Using gas detector RAK12004 - MQ-2

Monitoring the composting process using gas detector RAK12004 - MQ-2

BeginnerWork in progress3 hours16
Using gas detector RAK12004 - MQ-2

Things used in this project

Hardware components

RAKwireless - RAK19001_VB
×1
RAKwireless - RAK11300
×1
Gas Sensor Winsen Electronics MQ2 RAK12004
×1
PCB Antenna for LoRa
×1
WisGate Edge Lite 2
Arduino WisGate Edge Lite 2
×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

Gas detector

C/C++
Arduino IDE
#include <Wire.h>
#include "ADC121C021.h"
#include <Arduino.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 (pakeisk savo)
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};

mbed::Ticker appTimer;

// ============================
// MQ2 Settings
// ============================
#define EN_PIN WB_IO6  // Power pin
ADC121C021 MQ2;
#define MQ2_ADDRESS 0x51
#define MQ2_RL 10.0
#define RatioMQ2CleanAir 1.0

float calcR0;

// ============================
// 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 ppm, float percent);

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

// ============================
// 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){}

    // Power on MQ2
    pinMode(EN_PIN, OUTPUT);
    digitalWrite(EN_PIN, HIGH);
    delay(300);

    // Init MQ2
    while(!(MQ2.begin(MQ2_ADDRESS, Wire))){
        Serial.println("MQ2 not found! Check wiring.");
        delay(200);
    }
    MQ2.setRL(MQ2_RL);
    MQ2.setA(-0.890);
    MQ2.setB(1.125);
    MQ2.setRegressionMethod(0);

    calcR0 = 0;
    for(int i = 0; i < 100; i++){
        calcR0 += MQ2.calibrateR0(RatioMQ2CleanAir);
    }
    MQ2.setR0(calcR0/100);
    Serial.printf("R0 Value: %3.2f\r\n", MQ2.getR0());

    // 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 sensorPPM = MQ2.readSensor();
    float PPMpercentage = sensorPPM / 10000.0;

    Serial.printf("MQ2 Gas Sensor PPM: %3.2f\n", sensorPPM);
    Serial.printf("PPM Percentage: %3.3f%%\n", PPMpercentage);

    // Send via LoRa
    send_lora_frame(sensorPPM, PPMpercentage);

    delay(LORAWAN_APP_INTERVAL);
}

// ============================
// LoRa Send
// ============================
void send_lora_frame(float ppm, float percent){
    if(lmh_join_status_get() != LMH_SET) return;

    uint8_t i = 0;
    floatToBytes(ppm, &m_lora_app_data.buffer[i]); i += 4;
    floatToBytes(percent, &m_lora_app_data.buffer[i]); i += 4;

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

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

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

JavaScript dekoder

JavaScript
function decodeUplink(input) {
    var bytes = input.bytes;
    
    // PPM (float32, 4 bytes, little-endian)
    var ppmArray = bytes.slice(0, 4);
    var ppm = new DataView(new Uint8Array(ppmArray).buffer).getFloat32(0, true);

    // PPM percentage (float32, 4 bytes, little-endian)
    var percentArray = bytes.slice(4, 8);
    var percent = new DataView(new Uint8Array(percentArray).buffer).getFloat32(0, true);

    // Optional: round values
    ppm = Math.round(ppm * 100) / 100;
    percent = Math.round(percent * 1000) / 1000;

    return {
        data: {
            ppm: ppm,
            percent: percent
        }
    };
}

Credits

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

Comments