fab-lab.eu
Published © CC BY-NC-SA

Citizen Science Box

Installing your #IoT project in the wild is not that easy: weather protection, solar power and network connection... we did it all!

AdvancedWork in progress7,660
Citizen Science Box

Things used in this project

Hardware components

Adafruit solar charger
×1
Adafruit Feather HUZZAH with ESP8266 WiFi
Adafruit Feather HUZZAH with ESP8266 WiFi
×1
SparkFun LiPo Fuel Gauge
SparkFun LiPo Fuel Gauge
×1
Solar Panel
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

oshpark - pcb layout

you can get your pcb from oshpark

Code

Arduino Code

Arduino
sends data every 15 min (900.000 millis)
replace "YOUR xxx from TTN CONSOLE" with your TTN device setting
#include <lmic.h>
#include <hal/hal.h>
#include <bsec.h>
#include <Wire.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>

#define fuel_on 1

#ifdef fuel_on
  #include "MAX17043.h"
#endif

int fuel = 0 ;
int battery = 0 ;
// LoraWAN Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
// (c) 2018 Terry Moore, MCCI
// https://github.com/mcci-catena/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         }
};

void os_getArtEui (u1_t* buf) { 
}
void os_getDevEui (u1_t* buf) { 
}
void os_getDevKey (u1_t* buf) { 
}

// bach-03-001-abp // fuel on

static const u4_t DEVADDR = YOUR DEV ADDR FROM TTN CONSOLE;

static const u1_t PROGMEM APPSKEY[16]={
  YOUR APP KEY FROM TTN CONSOLE };

static const u1_t PROGMEM NWKSKEY[16]={
  YOUR NETWORK KEY FROM TTN CONSOLE };


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;
  case EV_TXSTART:
    Serial.println(F("EV_TXSTART"));
    break;
  case EV_JOIN_TXCOMPLETE:
    Serial.println(F("EV_JOIN_TXCOMPLETE"));
    break;
  default:
    Serial.println(F("Unknown event"));
    break;
  }
}

/* 
 Bosch BSEC Lib, https://github.com/BoschSensortec/BSEC-Arduino-library
 The BSEC software is only available for download or use after accepting the software license agreement.
 By using this library, you have agreed to the terms of the license agreement: 
 https://ae-bst.resource.bosch.com/media/_tech/media/bsec/2017-07-17_ClickThrough_License_Terms_Environmentalib_SW_CLEAN.pdf */
Bsec iaqSensor;     // Create an object of the class Bsec 
Ticker Bsec_Ticker; // schedule cyclic update via Ticker 

// ------------------------   Helper functions Bosch Bsec - Lib 
void checkIaqSensorStatus(void)
{ 
  String output; 
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      output = "BSEC error code : " + String(iaqSensor.status);
      for (;;) {
        Serial.println(output);
        delay(500);
      } // Halt in case of failure 
    } 
    else {
      output = "BSEC warning code : " + String(iaqSensor.status);
      Serial.println(output);
    }
  }

  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      output = "BME680 error code : " + String(iaqSensor.bme680Status);
      for (;;){
        Serial.println(output);
        delay(500);
      }  // Halt in case of failure 
    } 
    else {
      output = "BME680 warning code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    }
  }
}

// Housekeeping: scheduled update using ticker-lib
void iaqSensor_Housekeeping(){  // get new data 
  iaqSensor.run();
}

extern "C" {  // zur Nutzung der speziellen ESP-Befehle wie Deep Sleep
#include "user_interface.h"
}


void setup(){ // Einmalige Initialisierung
  pinMode( 0 , OUTPUT);
  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);

  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);
  // Set up the channels used by the Things Network, which corresponds 
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used
  LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
  LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);      // g2-band
  LMIC_setLinkCheckMode(0);   // Disable link check validation
  LMIC.dn2Dr = DR_SF9;	       // TTN uses SF9 for its RX2 window.
  LMIC_setDrTxpow(DR_SF7,14); // Set data rate and transmit power for uplink

  Wire.begin(); // ---- Initialisiere den I2C-Bus 


#ifdef fuel_on
  FuelGauge.begin();
#endif
  

  iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire);
  String output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
  Serial.println(output);
  checkIaqSensorStatus();

  bsec_virtual_sensor_t sensorList[10] = {
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
  };

  iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();  
  iaqSensor_Housekeeping();
  Bsec_Ticker.attach_ms(2000, iaqSensor_Housekeeping);

  digitalWrite( 0 , LOW );

#ifdef fuel_on
  FuelGauge.quickstart();
#endif

  delay( 10000 );

  fuel = 0 ;

  battery = 0 ;

#ifdef fuel_on

  Serial.print("Version:       "); Serial.println(FuelGauge.version());
  Serial.print("ADC:           "); Serial.println(FuelGauge.adc());
  Serial.print("Voltage:       "); Serial.print(FuelGauge.voltage()); Serial.println(" v");
  Serial.print("Percent:       "); Serial.print(FuelGauge.percent()); Serial.println("%");
  Serial.print("Is Sleeping:   "); Serial.println(FuelGauge.isSleeping() ? "Yes" : "No");
  Serial.print("Alert:         "); Serial.println(FuelGauge.alertIsActive() ? "Yes" : "No");
  Serial.print("Threshold:     "); Serial.println(FuelGauge.getThreshold());
  Serial.print("Compensation:  0x"); Serial.println(FuelGauge.compensation(), HEX);
  // FuelGauge.sleep();
  // FuelGauge.quickstart();
  // FuelGauge.wake();

  fuel = FuelGauge.percent();
  battery = FuelGauge.voltage();
#endif


  { //Block------------------------------ sende Daten an TTN  
    int port = 10;
    static uint8_t mydata[18];
    int wert=round(analogRead(0)*1000);
    mydata[0] = wert >> 16; 
    mydata[1] = wert >> 8; 
    mydata[2] = wert ;
    wert=round(iaqSensor.temperature*1000);
    mydata[3] = wert >> 16; 
    mydata[4] = wert >> 8; 
    mydata[5] = wert ;
    wert=round(iaqSensor.humidity*1000);
    mydata[6] = wert >> 16; 
    mydata[7] = wert >> 8; 
    mydata[8] = wert ;
    wert=round((iaqSensor.pressure/100.)*1000);
    mydata[9] = wert >> 16; 
    mydata[10] = wert >> 8; 
    mydata[11] = wert ;
    wert=round(battery*1000);
    mydata[12] = wert >> 16; 
    mydata[13] = wert >> 8; 
    mydata[14] = wert ;
    wert=round(fuel*1000);
    mydata[15] = wert >> 16; 
    mydata[16] = wert >> 8; 
    mydata[17] = wert ;
    // 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( 500 );

  WiFi.disconnect( true );
  delay(2); // Wifi Off 
  ESP.deepSleep( (long)900000*1000UL,WAKE_RF_DISABLED);//Tiefschlaf, danach Reset und von vorn

}

void loop() { // Kontinuierliche Wiederholung 
}

Credits

fab-lab.eu

fab-lab.eu

22 projects • 275 followers
Maker!

Comments