fab-lab.eu
Published

Environmental Sensing with LoRaWAN (TTN)

This is plug and play: particulate matter (PM2.5) and volatile organic compounds (VOC) plus air pressure, temp and humidity.

BeginnerFull instructions provided1 hour2,073
Environmental Sensing with LoRaWAN (TTN)

Things used in this project

Hardware components

#IoT Octopus
or any ESP8266 plus and BME680 and wires ;-)
×1
Seeed Studio Grove PM 2.5 Laser
×1

Story

Read more

Code

The PM2.5, BME680, LoRaWAN (TTN) code

Arduino
using the Arduino(TM) IDE, with ESP8266 (Adafruit ESP8266 Feather) is a great starting point, also the BME680 and and Grove PM2.5 libs are required
#include <lmic.h>
#include <hal/hal.h>
#include <Adafruit_BME680.h>
#include <Wire.h>

// Grove HM330X - Particulate Matters Sensor (PM2.5)
#include "Seeed_HM330X.h"

HM330X sensor;
u8 buf[30];
u16 value25=0;

const char *str[]={"sensor num: ","PM1.0 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
                    "PM2.5 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
                    "PM10 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
                    "PM1.0 concentration(Atmospheric environment,unit:ug/m3): ",
                    "PM2.5 concentration(Atmospheric environment,unit:ug/m3): ",
                    "PM10 concentration(Atmospheric environment,unit:ug/m3): ",
                    };

err_t print_result(const char* str,u16 value)
{
    if(NULL==str)
        return ERROR_PARAM;
    Serial.print(str);
    Serial.println(value);
    return NO_ERROR;
}

/*parse buf with 29 u8-data*/
err_t parse_result(u8 *data)
{
    u16 value=0;
    err_t NO_ERROR;
    if(NULL==data)
        return ERROR_PARAM;
    for(int i=1;i<8;i++)
    {
         value = (u16)data[i*2]<<8|data[i*2+1];
         print_result(str[i-1],value);
    }
    int i = 6; // PM2.5 concentration(Atmospheric environment,unit:ug/m3)
    value25 = value = (u16)data[i*2]<<8|data[i*2+1];
    Serial.println(value25);      
}

err_t parse_result_value(u8 *data)
{
    if(NULL==data)
        return ERROR_PARAM;
    for(int i=0;i<28;i++)
    {
        Serial.print(data[i],HEX);
        Serial.print("  ");
        if((0==(i)%5)||(0==i))
        {
            Serial.println(" ");
        }
    }
    u8 sum=0;
    for(int i=0;i<28;i++)
    {
        sum+=data[i];
    }
    if(sum!=data[28])
    {
        Serial.println("wrong checkSum!!!!");
    }
    Serial.println(" ");
    Serial.println(" ");
    return NO_ERROR;
}

// LoraWAN Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
// https://github.com/matthijskooijman/arduino-lmic
// -------- LoRa PinMapping FeatherWing Octopus
const lmic_pinmap lmic_pins = {  
  .nss = 2,                            // Connected to pin D
  .rxtx = LMIC_UNUSED_PIN,             // For placeholder only, Do not connected on RFM92/RFM95
  .rst = LMIC_UNUSED_PIN,              // Needed on RFM92/RFM95? (probably not) D0/GPIO16 
  .dio = {
    15, 15, LMIC_UNUSED_PIN         }
};

static const u1_t PROGMEM DEVEUI[8]={
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; // <------- change here
void os_getDevEui (u1_t* buf) { 
  memcpy_P(buf, DEVEUI, 8);
}

static const u1_t PROGMEM APPEUI[8]={
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; // <------- change here
void os_getArtEui (u1_t* buf) { 
  memcpy_P(buf, APPEUI, 8);
}

static const u1_t PROGMEM APPKEY[16]={
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; // <------- change here
void os_getDevKey (u1_t* buf) {  
  memcpy_P(buf, APPKEY, 16);
};

volatile int LoRaWAN_Tx_Ready      = 0; // Merker für ACK 

int LoRaWAN_Rx_Payload = 0 ;
// -------- LoRa Event 
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_JOINED:
    Serial.println(F("EV_JOINED"));
    // Disable link check validation (automatically enabled
    // during join, but not supported by TTN at this time).
    LMIC_setLinkCheckMode(0);
    break;
  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"));
      LoRaWAN_Rx_Payload = 0; 
      for (int i = 0;i<LMIC.dataLen;i++) { 
        Serial.println(LMIC.frame[i+ LMIC.dataBeg],HEX);
        LoRaWAN_Rx_Payload = 256*LoRaWAN_Rx_Payload+LMIC.frame[i+ LMIC.dataBeg];
      }
    }
    LoRaWAN_Tx_Ready = 1;
    // Schedule next transmission
    //os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
    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;
  default:
    Serial.println(F("Unknown event"));
    break;
  }
}

// BME680 Lib written by Limor Fried & Kevin Townsend for Adafruit Industries, http://www.adafruit.com/products/3660
Adafruit_BME680 boschBME680; // Objekt Bosch Umweltsensor

void setup(){ // Einmalige Initialisierung
  Serial.begin(115200);
  // -- Initialisiere LoraWAN 
  os_init();             // LMIC LoraWAN
  LMIC_reset();          // Reset the MAC state 
  LMIC.txpow = 27;       // Maximum TX power 
  LMIC.datarate=DR_SF12; // Long Range
  LMIC.rps = updr2rps(LMIC.datarate);

  Wire.begin(); // ---- Initialisiere den I2C-Bus 
  if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C");

  if (!boschBME680.begin(118)) { 
    Serial.println("Failed to communicate BME680");
    while (1) {
      delay(1);
    };
  }

  // Set up Bosch BME 680
  boschBME680.setTemperatureOversampling(BME680_OS_8X);
  boschBME680.setHumidityOversampling(BME680_OS_2X);
  boschBME680.setPressureOversampling(BME680_OS_4X);
  boschBME680.setIIRFilterSize(BME680_FILTER_SIZE_3);
  boschBME680.setGasHeater(320, 150); // 320*C for 150 ms

  // Init HM330X on I2C
  if(sensor.init())
    {
        Serial.println("HM330X init failed!!!");
        while(1);
    }
}

void loop() { // Kontinuierliche Wiederholung 

  if(sensor.read_sensor_value(buf,29))
  {
    Serial.println("HM330X read result failed!!!");
  }
  //parse_result_value(buf);
  parse_result(buf);

  { //Block------------------------------ sende Daten an TTN  
    int port = 10;
    static uint8_t mydata[10];
    int wert=round(boschBME680.readTemperature()*10);
    mydata[0] = wert >> 8; 
    mydata[1] = wert & 0xFF;
    wert=round(boschBME680.readHumidity()*10);
    mydata[2] = wert >> 8; 
    mydata[3] = wert & 0xFF;
    wert=round(boschBME680.readPressure()/100.*10);
    mydata[4] = wert >> 8; 
    mydata[5] = wert & 0xFF;
    wert=round(boschBME680.readGas()/1000.*10);
    mydata[6] = wert >> 8; 
    mydata[7] = wert & 0xFF;
    wert=round(value25*10);
    mydata[8] = wert >> 8; 
    mydata[9] = wert & 0xFF;
    // Check if there is not a current TX/RX job running
    //if (LMIC.opmode & OP_TXRXPEND) {
    if (LMIC.opmode & (1 << 7)) { 
      Serial.println(F("OP_TXRXPEND, not sending"));
    } 
    else {
      // Prepare upstream data transmission at the next possible time.
      LoRaWAN_Tx_Ready = 0;                                 // Merker für ACK
      LMIC_setTxData2(port, mydata, sizeof(mydata), 0);     // Sende         
      Serial.println(F("Packet queued"));
      while(LoRaWAN_Tx_Ready==0) {
        yield();
        os_runloop_once();
      };  // Warte bis gesendet
    }
  } // Blockende
  delay( 30000 );
}

The TTN payload decoder

JavaScript
function Decoder(bytes, port) { // Decode an uplink message from a buffer (array) of bytes to an object of fields.
var decoded = {}; //
decoded.port = port;
decoded.humidity = 0; decoded.temp = 0; decoded.gas=0;
if (port === 10) {  // Port selection
  decoded.temp = (parseFloat((bytes[0] << 8) | bytes[1]).toFixed(2))/10;
  decoded.humidity = (parseFloat((bytes[2] << 8) | bytes[3]).toFixed(2))/10;
  decoded.pressure = (parseFloat((bytes[4] << 8) | bytes[5]).toFixed(2))/10;
  decoded.gas = (parseFloat((bytes[6] << 8) | bytes[7]).toFixed(2))/10;
  decoded.pm25 = (parseFloat((bytes[8] << 8) | bytes[9]).toFixed(2))/10;
}
return decoded;
}

Credits

fab-lab.eu

fab-lab.eu

22 projects • 275 followers
Maker!

Comments