Karl Schleicher
Published © CC BY

Detecting Natural Gas Emissions

Mobile Gas Detector, public Helium LoraWan network, and Internet dashboard monitor air quality near industry and in gas fields.

IntermediateFull instructions provided20 hours974
Detecting Natural Gas Emissions

Things used in this project

Hardware components

STMicroelectronics B-L072CZ-LRWAN1
×1
ELEGOO Mega 2560 R3 Board Black ATmega2560 ATMEGA16U2
ELEGOO Mega 2560 R3 Board Black ATmega2560 ATMEGA16U2
×1
Dragino LoRa Long Range Transceiver Shield
×1
Gravity: Analog CH4 Gas Sensor (MQ4) For Arduino
DFRobot Gravity: Analog CH4 Gas Sensor (MQ4) For Arduino
×1
Gravity: DHT11 Temperature Humidity Sensor For Arduino
DFRobot Gravity: DHT11 Temperature Humidity Sensor For Arduino
×1
Helium LoRaWan Hotspot
×1

Software apps and online services

Arduino IDE
Arduino IDE
helium console
mydevices cayenne console

Story

Read more

Schematics

Figure 8:

Fritzing diagram for the ELEGOO/Arduino MEGA 2560 microcontroller with Dragino LoRa Long Range Transceiver Shield gas sensor. The DHT11 (digital Humidity and Temperature) is on the upper right. The Gas Sensor is on the middle right, the small breadboard is on the lower right. The gas sensor is attached to analog pin 0 and the DHT11 (Humidity and Temperature) is attached to digital pin 4. This diagram can be compared to the hardware photograph, Figure 2.

Figure 9

Fritzing diagram for STMicroelectronics B-L072Z-LRWAN1 gas sensor. Parts and wiring is the same as the ELEGOO/Arduino microcontroller with Dragino shield (Figure 8) except the DHT11 is connected to digital pin 7. This diagram can be compared to the hardware photograph, Figure 2.

Code

Sketch for the ELEGOO/Arduino MEGA microcontroller

C/C++
Sketch for the ELEGOO/Arduino MEGA microcontroller Dragino LoRa Shield gas sensor
#include <CayenneLPP.h>
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <dht.h>
dht DHT;
#define DHT11_PIN 4

// This is the "App EUI" in Helium. Make sure it is little-endian (lsb).
static const u1_t PROGMEM APPEUI[8] = {0x50, 0x13, 0x4C, 0xFB, 0x06, 0x22, 0x42, 0x04};
void os_getArtEui(u1_t *buf) { memcpy_P(buf, APPEUI, 8); }

// This should also be in little endian format
// These are user configurable values and Helium console permits anything
static const u1_t PROGMEM DEVEUI[8] = {0x02, 0x6A, 0xAF, 0xDB, 0x44, 0x1A, 0x6C, 0xCC};
void os_getDevEui(u1_t *buf) { memcpy_P(buf, DEVEUI, 8); }

// This is the "App Key" in Helium. It is big-endian (msb).
static const u1_t PROGMEM APPKEY[16] = {0x1B, 0x88, 0xFD, 0xCA, 0xE6, 0x23, 0xF0, 0xE3, 0xC6, 0xD6, 0xB0, 0x78, 0xF1, 0x4A, 0xD0, 0x57};
void os_getDevKey(u1_t *buf) { memcpy_P(buf, APPKEY, 16); }

static uint8_t mydata[] = "Hello, world!";
float sensorValueFloat; //variable to store sensor value

// Init CayenneLPP Payload
CayenneLPP lpp(51); // lpp(uint8_t size) size is max payload size

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 10;

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = 10,//RFM Chip Select 
    .rxtx = LMIC_UNUSED_PIN, 
    .rst = 9,//RFM Reset
    .dio = {2, 6, 7}, //RFM Interrupt, RFM LoRa pin, RFM Lo  
}; 

void onEvent(ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
  case EV_SCAN_TIMEOUT:
    Serial.println(F("EV_SCAN_TIMEOUT"));
    break;
  case EV_BEACON_FOUND:
    Serial.println(F("EV_BEACON_FOUND"));
    break;
  case EV_BEACON_MISSED:
    Serial.println(F("EV_BEACON_MISSED"));
    break;
  case EV_BEACON_TRACKED:
    Serial.println(F("EV_BEACON_TRACKED"));
    break;
  case EV_JOINING:
    Serial.println(F("EV_JOINING"));
    break;
  case EV_JOIN_TXCOMPLETE:
    Serial.println(F("EV_JOIN_TXCOMPLETE"));
    break;
  case EV_JOINED:
    Serial.println(F("EV_JOINED"));
    {
      u4_t netid = 0;
      devaddr_t devaddr = 0;
      u1_t nwkKey[16];
      u1_t artKey[16];
      LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
      Serial.print("netid: ");
      Serial.println(netid, DEC);
      Serial.print("devaddr: ");
      Serial.println(devaddr, HEX);
      Serial.print("artKey: ");
      for (size_t i = 0; i < sizeof(artKey); ++i) {
        if (i != 0)
          Serial.print("-");
        Serial.print(artKey[i], HEX);
      }
      Serial.println("");
      Serial.print("nwkKey: ");
      for (size_t i = 0; i < sizeof(nwkKey); ++i) {
        if (i != 0)
          Serial.print("-");
        Serial.print(nwkKey[i], HEX);
      }
      Serial.println("");
    }
    // Disable link check validation (automatically enabled
    // during join, but because slow data rates change max TX
    // size, we don't use it in this example.
    LMIC_setLinkCheckMode(0);
    break;
  /*
  || This event is defined but not used in the code. No
  || point in wasting codespace on it.
  ||
  || case EV_RFU1:
  ||     Serial.println(F("EV_RFU1"));
  ||     break;
  */
  case EV_JOIN_FAILED:
    Serial.println(F("EV_JOIN_FAILED"));
    break;
  case EV_REJOIN_FAILED:
    Serial.println(F("EV_REJOIN_FAILED"));
    break;
    break;
  case EV_TXCOMPLETE:
    Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
    if (LMIC.txrxFlags & TXRX_ACK)
      Serial.println(F("Received ack"));
    if (LMIC.dataLen) {
      Serial.println(F("Received "));
      Serial.println(LMIC.dataLen);
      Serial.println(F(" bytes of payload"));
    }
    // Schedule next transmission
    Serial.println(F("schedule next transmission"));
    os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL),
                        do_send);
    Serial.println(F("after os_setTimedCallback"));
    break;
  case EV_LOST_TSYNC:
    Serial.println(F("EV_LOST_TSYNC"));
    break;
  case EV_RESET:
    Serial.println(F("EV_RESET"));
    break;
  case EV_RXCOMPLETE:
    // data received in ping slot
    Serial.println(F("EV_RXCOMPLETE"));
    break;
  case EV_LINK_DEAD:
    Serial.println(F("EV_LINK_DEAD"));
    break;
  case EV_LINK_ALIVE:
    Serial.println(F("EV_LINK_ALIVE"));
    break;
  /*
  || This event is defined but not used in the code. No
  || point in wasting codespace on it.
  ||
  || case EV_SCAN_FOUND:
  ||    Serial.println(F("EV_SCAN_FOUND"));
  ||    break;
  */
  case EV_TXSTART:
    Serial.println(F("EV_TXSTART"));
    break;
  default:
    Serial.print(F("Unknown event: "));
    Serial.println((unsigned)ev);
    break;
  }
}

void do_send(osjob_t *j) {
  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    int chk=DHT.read11(DHT11_PIN);
    // Prepare upstream data transmission at the next possible time.
    Serial.println(F("call LMIC_setTxData2"));
    sensorValueFloat = analogRead(0); // read GPIO a0, the gas sensor
    Serial.print("%gas_concentration="); Serial.println(sensorValueFloat *100.0/1024.0);
    Serial.print("DHT.temperature="); Serial.println(DHT.temperature);
    Serial.print("DHT.humidity="); Serial.println(DHT.humidity);
    lpp.reset();
    // Pack Packload
    lpp.addRelativeHumidity(1,sensorValueFloat*100.0/1024.0);
    lpp.addTemperature     (2,DHT.temperature);
    lpp.addRelativeHumidity(3,DHT.humidity);

    Serial.print("lpp.getSize()="); Serial.println(lpp.getSize());
    // it would be good to add lat/long
    
    // Send Packet
    LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(),0);

    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  delay(5000);
  while (!Serial);
  Serial.begin(9600);
  Serial.println(F("Starting  arduino_mega_dragino_gas"));

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  // allow much more clock error than the X/1000 default. See:
  // https://github.com/mcci-catena/arduino-lorawan/issues/74#issuecomment-462171974
  // https://github.com/mcci-catena/arduino-lmic/commit/42da75b56#diff-16d75524a9920f5d043fe731a27cf85aL633
  // the X/1000 means an error rate of 0.1%; the above issue discusses using
  // values up to 10%. so, values from 10 (10% error, the most lax) to 1000
  // (0.1% error, the most strict) can be used.
  LMIC_setClockError(1 * MAX_CLOCK_ERROR / 40);

  LMIC_setLinkCheckMode(0);
  LMIC_setDrTxpow(DR_SF8, 20);
  LMIC_selectSubBand(1);

  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
}

void loop() { os_runloop_once(); }

Sketch for STMicroelectronics sensor

C/C++
Code for STMicroelectronics B-L072Z-LRWAN1 gas sensor
#include "LoRaWAN.h"
#include <CayenneLPP.h>
#include <dht.h>
dht DHT;
#define DHT11_PIN 7

const char *devEui = "847153B5781E4FBB";
const char *appEui = "3E980DB8FED35584";
const char *appKey = "A4FC202C0AA316D0F17868C0900F39E2";
// Max Payload 53 Bytes for DR 1
const uint8_t payload[] = "Hello, World!";

float sensorValueFloat; //variable to store sensor value
// Init CayenneLPP Payload
CayenneLPP lpp(51); // lpp(uint8_t size) size is max payload size

void setup( void )
{
    Serial.begin(9600);
    
    while (!Serial) { }

    // US Region
    LoRaWAN.begin(US915);
    // Helium SubBand
    LoRaWAN.setSubBand(2);
    // Disable Adaptive Data Rate
    LoRaWAN.setADR(false);
    // Set Data Rate 1 - Max Payload 53 Bytes
    LoRaWAN.setDataRate(1);
    // Device IDs and Key
    LoRaWAN.joinOTAA(appEui, appKey, devEui);

    Serial.println("JOIN( )");
}

void loop( void )
{
    if (LoRaWAN.joined() && !LoRaWAN.busy())
    {
        Serial.print("TRANSMIT( ");
        Serial.print("TimeOnAir: ");
        Serial.print(LoRaWAN.getTimeOnAir());
        Serial.print(", NextTxTime: ");
        Serial.print(LoRaWAN.getNextTxTime());
        Serial.print(", MaxPayloadSize: ");
        Serial.print(LoRaWAN.getMaxPayloadSize());
        Serial.print(", DR: ");
        Serial.print(LoRaWAN.getDataRate());
        Serial.print(", TxPower: ");
        Serial.print(LoRaWAN.getTxPower(), 1);
        Serial.print("dbm, UpLinkCounter: ");
        Serial.print(LoRaWAN.getUpLinkCounter());
        Serial.print(", DownLinkCounter: ");
        Serial.print(LoRaWAN.getDownLinkCounter());
        Serial.println(" )");

        // Send Packet
        {
          // make block for the chk variable
          int chk=DHT.read11(DHT11_PIN);
        }
        sensorValueFloat = analogRead(0); // read GPIO a0, the gas sensor
        Serial.print("%gas_concentration:"); Serial.println(sensorValueFloat*100.0/1024.0);
        Serial.print("DHT.temperature="); Serial.println(DHT.temperature);
        Serial.print("DHT.humidity="); Serial.println(DHT.humidity);
        lpp.reset();
        // Pack Packload
        lpp.addRelativeHumidity(1,sensorValueFloat*100.0/1024.0);
        lpp.addTemperature     (2,DHT.temperature);
        lpp.addRelativeHumidity(3,DHT.humidity);
        
        Serial.print("lpp.getSize()="); Serial.println(lpp.getSize());
        // it would be good to add lat/long

        // Send Packet
        LoRaWAN.sendPacket(1, lpp.getBuffer(), lpp.getSize());
        // old hello world packet send LoRaWAN.sendPacket(1, payload, sizeof(payload));
    }

    delay(10000); //10 Seconds
}

Credits

Karl Schleicher

Karl Schleicher

1 project • 1 follower
Exploration Research Geophysicist for 35 years. BA Math, U Houston; M.S. Management, Operations Research, U. Texas at Dallas.

Comments