Bastiaan Slee
Published © GPL3+

Chiiiiiirp! Indoor Air Quality Measurement and Alarm

Monitor and advise about indoor air quality (IAQ) with the help of a MAX32620FTHR, BME680, LoRaWAN, Cayenne, and IFTTT.

IntermediateFull instructions provided5 hours3,106

Things used in this project

Hardware components

MAX32620FTHR
Maxim Integrated MAX32620FTHR
×1
BME680 Breakout - Air Quality, Temperature, Pressure, Humidity Sensor
×1
RFM95W 868/915Mhz RF Transceiver Module
×1
LiPo Battery 500 mAh
×1
RGB Diffused Common Cathode
RGB Diffused Common Cathode
×1
Speaker: 0.25W, 8 ohms
Speaker: 0.25W, 8 ohms
×1

Software apps and online services

MBED online compiler

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Custom parts and enclosures

Eagle files for the LoRaWAN FeatherWing

Open these files in Eagle, or upload the BRD file to OSH Park to order the boards

Schematics

Chiiiiiirp schematics

Fritzing schematics

Code

main.cpp

Arduino
Same main.cpp as it the archive file, but to give an insight in the code right here!
/**********************************************************************************************************************/
/* header files */
/**********************************************************************************************************************/

#include <mbed.h>
#include <stdio.h>

// For the PWM RGB LED and Speaker
#include "MAX326XXFTHR_PwmOut.h"

// For the RFM95W LoRaWAN module
#include "lorawan/LoRaWANInterface.h"
#include "lorawan/system/lorawan_data_structures.h"
#include "events/EventQueue.h"
#include "trace_helper.h"
#include "lora_radio_helper.h"
#include "CayenneLPP.h"

// For the BME680 sensor
#include "bsec_integration.h"






/**********************************************************************************************************************/
/* various local and global variable declarations */
/**********************************************************************************************************************/
// Keep device ON when only a battery is connected
// See https://maximintegratedsupport.force.com/support/s/article/After-modifying-code-and-adding-a-Li-battery-the-MAX32620FTHR-board-shuts-down-What-am-I-missing
#define POWER_HOLD_OFF      0
#define POWER_HOLD_ON       1
DigitalOut pw_Hold(P2_2, POWER_HOLD_ON);



// If DEBUG is needed, uncomment this line
//#define I2C_DEBUG_MODE
//#define BME680_DEBUG_MODE
#define LORA_DEBUG_MODE

// Output DEBUG to Serial (through MAX32625PICO )
Serial pc(USBTX, USBRX);






// Timer for getting the micro seconds since board start
Timer t_time;


// Init I2C and serial communication to PORT1 of the MAX32620FTHR
I2C i2c(
    I2C0_SDA, /*I2CM0_SDA*/
    I2C0_SCL  /*I2CM0_SCL*/
);



// function used to write output logs from the I2C function
void log (const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
}



/*!
 * @brief           Write operation in either I2C or SPI
 * param[in]        dev_addr        I2C or SPI device address
 * param[in]        reg_addr        register address
 * param[in]        reg_data_ptr    pointer to the data to be written
 * param[in]        data_len        number of bytes to be written
 * @return          result of the bus communication function
 */
int8_t bus_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
    int8_t result;
    char data[data_len + 1];

    data[0] = (char) reg_addr;

    for (uint8_t i = 1; i < data_len + 1; i++) {
        data[i] = (char) reg_data_ptr[i - 1];
    }

#ifdef I2C_DEBUG_MODE
    log("[0x%X] I2C $%X <= ", dev_addr >> 1, data[0]);
#endif

    result = i2c.write(dev_addr, data, data_len + 1);

#ifdef I2C_DEBUG_MODE
    for (uint8_t i = 1; i < data_len + 1; i++)
        log("0x%X ", data[i]);

    log("[W: %d, L: %d] \r\n", result, data_len);
#endif

    return result;
}



/*!
 * @brief           Read operation in either I2C or SPI
 * param[in]        dev_addr        I2C or SPI device address
 * param[in]        reg_addr        register address
 * param[out]       reg_data_ptr    pointer to the memory to be used to store the read data
 * param[in]        data_len        number of bytes to be read
 * @return          result of the bus communication function
 */
int8_t bus_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
    int8_t result;
    char data[1];

    data[0] = (char) reg_addr;

#ifdef I2C_DEBUG_MODE
    log("[0x%X] I2C $%X => ", dev_addr >> 1, data[0]);
#endif

    result = i2c.write(dev_addr, data, 1);

#ifdef I2C_DEBUG_MODE
    log("[W: %d] ", result);
#endif

    result = i2c.read(dev_addr, (char *) reg_data_ptr, data_len);

#ifdef I2C_DEBUG_MODE
    for (uint8_t i = 0; i < data_len; i++)
        log("0x%X ", reg_data_ptr[i]);

    log("[R: %d, L: %d] \r\n", result, data_len);
#endif

    return result;
}




/*!
 * @brief           System specific implementation of sleep function
 * @param[in]       t_ms    time in milliseconds
 * @return          none
 */
void sleep(uint32_t t_ms)
{
    wait_ms(t_ms);
}




/*!
 * @brief           Capture the system time in microseconds
 * @return          system_current_time    current system timestamp in microseconds
 */
int64_t get_timestamp_us()
{
    int64_t system_current_time = t_time.read_high_resolution_us();
    return (int64_t) (system_current_time);
}











/**********************************************************************************************************************/
/* BME680 setup */
/**********************************************************************************************************************/

Thread t_bme680;

float   bme680_temp_current = 0.00f;
float   bme680_press_current = 0.00f;
float   bme680_hum_current = 0.00f;
float   bme680_gas_current = 0.00f;
float   bme680_iaq_score = 0.00f;
uint8_t bme680_iaq_rating_nr = 0;
char *  bme680_iaq_rating_txt;
uint8_t bme680_iaq_rating_prev = 0;
uint8_t bme680_iaq_accuracy = 0;
uint8_t ifttt_trigger = 0;


/*!
 * @brief           Load previous library state from non-volatile memory
 * @param[in,out]   state_buffer    buffer to hold the loaded state string
 * @param[in]       n_buffer        size of the allocated state buffer
 * @return          number of bytes copied to state_buffer
 */
uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
{
    // Load a previous library state from non-volatile memory, if available.
    //
    // Return zero if loading was unsuccessful or no state was available,
    // otherwise return length of loaded state string.
    //
    // Not implemented...
    return 0;
}

/*!
 * @brief           Save library state to non-volatile memory
 * @param[in]       state_buffer    buffer holding the state to be stored
 * @param[in]       length          length of the state string to be stored
 * @return          none
 */
void state_save(const uint8_t *state_buffer, uint32_t length)
{
    // Save the string some form of non-volatile memory, if possible.
    //
    // Not implemented...
}

/*!
 * @brief           Load library config from non-volatile memory
 * @param[in,out]   config_buffer    buffer to hold the loaded state string
 * @param[in]       n_buffer        size of the allocated state buffer
 * @return          number of bytes copied to config_buffer
 */
uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer)
{
    // Load a library config from non-volatile memory, if available.
    //
    // Return zero if loading was unsuccessful or no config was available,
    // otherwise return length of loaded config string.
    //
    // Not implemented...
    return 0;
}








/**********************************************************************************************************************/
/* RGB LED setup */
/**********************************************************************************************************************/

// PWM for OPEN_DRAIN_LEDS (second parameter is for the Open Drain)
MAX326XXFTHR_PwmOut rgbLED[] = {
    MAX326XXFTHR_PwmOut(P0_4,1), /* RED */
    MAX326XXFTHR_PwmOut(P0_7,1), /* GREEN */
    MAX326XXFTHR_PwmOut(P0_5,1)  /* BLUE */
};

// Each LED has its own charactaristics, see https://www.fasttech.com/p/2207301 for the LED used here
// RGB Characteristic   PinV / LedV / MixRatio
float rgbLED_R_char =   3.3f / 2.1f /    3;
float rgbLED_G_char =   3.3f / 3.3f /    6;
float rgbLED_B_char =   3.3f / 3.3f /    1;

// The actual color mixing function
void set_rgbLED (unsigned int rgbLED_R, unsigned int rgbLED_G, unsigned int rgbLED_B, unsigned int rgbLED_Lum)
{
    float rgbLED_fact = ((rgbLED_R + rgbLED_G + rgbLED_B) / 3.00f);

    rgbLED[0].write( ( ((rgbLED_R/256.00f) * rgbLED_R_char * (rgbLED_Lum/100.00f)) * (256.00f/3.00f/rgbLED_fact) ) );
    rgbLED[1].write( ( ((rgbLED_G/256.00f) * rgbLED_G_char * (rgbLED_Lum/100.00f)) * (256.00f/3.00f/rgbLED_fact) ) );
    rgbLED[2].write( ( ((rgbLED_B/256.00f) * rgbLED_B_char * (rgbLED_Lum/100.00f)) * (256.00f/3.00f/rgbLED_fact) ) );
}












/**********************************************************************************************************************/
/* Speaker setup */
/**********************************************************************************************************************/

// EVENTQUEUE and THREAD for playChirp function
/**
 * Maximum number of events for the event queue.
 * 10 is the safe number for the stack events, however, if application
 * also uses the queue for whatever purposes, this number should be increased.
 */
#define MAX_NUMBER_OF_EVENTS_CHIRP            10

static EventQueue ev_queue_speaker  (MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_x(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_6(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_5(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_4(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_3(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);
static EventQueue ev_queue_speaker_0(MAX_NUMBER_OF_EVENTS_CHIRP * EVENTS_EVENT_SIZE);

Thread t_speaker;


// Original idea from https://learn.sparkfun.com/tutorials/mbed-starter-kit-experiment-guide/experiment-9-pwm-sounds
// Adapted PwmOut for easier usage, and got anoter set of (more secure) tone frequencies

/*
 *
 * Program to play a simple chirp
 *
 * Tones are created by quickly pulsing a speaker on and off
 *   using PWM, to create signature frequencies.
 *
 * Each note has a frequency, created by varying the period of
 *  vibration, measured in microseconds. We'll use pulse-width
 *  modulation (PWM) to create that vibration.
 */
 
// TONES  ==========================================
// https://en.wikipedia.org/wiki/Piano_key_frequencies
// Start by defining the relationship between
//        note,   frequency.
#define     C      261.6256
#define     Cx     277.1826
#define     D      293.6648
#define     Dx     311.1270
#define     E      329.6276
#define     F      349.2282
#define     Fx     369.9944
#define     G      391.9954
#define     Gx     415.3047
#define     A      440.0000
#define     Ax     466.1638
#define     B      493.8833
#define     R        0.0000   // Define a special note, 'R', to represent a rest

// PWM for SPEAKER
MAX326XXFTHR_PwmOut speaker[] = {
    MAX326XXFTHR_PwmOut(P0_3,1)
};

// CHIRP and TIMING: chirp_tones[] is an array of notes, accompanied by chirp_beats[], which sets each note's relative length (higher #, longer note)
float chirp_tones[] = { Cx,  R,  Cx,  Dx,  F,  R,  C,  R,  Cx,  R,  D,  E };
int   chirp_beats[] = { 10, 10,  31,  21, 10, 97, 21, 10,  10, 19, 73, 21 };

// Chirp length, for looping.
int MAX_COUNT = sizeof(chirp_tones) / 4;

// Calculate duration of a quarter note from bpm (100)
float chirp_tempo = 600.00f / 100.00f;

// Initialize core variables
int chirp_tone_ = 0;
int chirp_beat = 0;
float chirp_duration = 0;
float chirp_volume = 0.00001f;

// Is there currently a chirp playing?
int chirp_playing = 0;

// PLAY CHIRP: Pulse the speaker to play a tone for a particular duration
void playChirp()
{
    if (chirp_playing == 0) {
        chirp_playing = 1;
        for (int i=0; i<MAX_COUNT; i++) {
            chirp_tone_ = chirp_tones[i];
            chirp_beat = chirp_beats[i];
            chirp_duration = (float)(chirp_beat * chirp_tempo); // Set up timing
            if (chirp_tone_ != 0) {
                speaker[0].period(1.0f/chirp_tone_);
                speaker[0].write(chirp_volume/2.0f);
            } else {
                speaker[0].write(0.00f);
            }
            wait_ms(chirp_duration);
            speaker[0].write(0.0f);
            wait_ms(0.1);
        }
        speaker[0].write(0.0f);
        chirp_playing = 0;
    }
}

void doNothing()
{
    // Well, do nothing... ;)
}

// Remove (unchain) the current chirp from the eventqueue, by attaching them to the ghost queue
void ev_queue_speaker_unchain()
{
    ev_queue_speaker_6.chain(&ev_queue_speaker_x);
    ev_queue_speaker_5.chain(&ev_queue_speaker_x);
    ev_queue_speaker_4.chain(&ev_queue_speaker_x);
    ev_queue_speaker_3.chain(&ev_queue_speaker_x);
}











/**********************************************************************************************************************/
/* LoRaWAN setup */
/**********************************************************************************************************************/

// EVENTQUEUE and THREAD for Lora function
/**
 * Maximum number of events for the event queue.
 * 10 is the safe number for the stack events, however, if application
 * also uses the queue for whatever purposes, this number should be increased.
 */
#define MAX_NUMBER_OF_EVENTS_LORA            10

/**
* This event queue is the global event queue for both the
* application and stack. To conserve memory, the stack is designed to run
* in the same thread as the application and the application is responsible for
* providing an event queue to the stack that will be used for ISR deferment as
* well as application information event queuing.
*/
static EventQueue ev_queue_lora(MAX_NUMBER_OF_EVENTS_LORA * EVENTS_EVENT_SIZE);

Thread t_lora;


using namespace events;

// Max payload size can be LORAMAC_PHY_MAXPAYLOAD.
// This example only communicates with much shorter messages (<30 bytes).
// If longer messages are used, these buffers must be changed accordingly.
uint8_t tx_buffer[100];
uint8_t rx_buffer[100];


// Sets up an application dependent transmission timer in ms. Used only when Duty Cycling is off for testing
#define TX_TIMER                        60000
#define LORA_DUTY_CYCLE_ON              false

// Maximum number of retries for CONFIRMED messages before giving up
#define CONFIRMED_MSG_RETRY_COUNTER     3

// On which port do you receive your messages (standard is 15)
#define LORA_APP_PORT                   1

// Is sending in progress - default to 1 until successful connection
int lorawan_sending = 1;

// Some setup for Cayenne Low Power Packets, depends on spreading factor and frequency used
#define CAYENNELPP_MAX_SIZE             100 

CayenneLPP Payload(CAYENNELPP_MAX_SIZE);



// Event handler: This will be passed to the LoRaWAN stack to queue events for the application which in turn drive the application.
static void lora_event_handler(lorawan_event_t event);

// Constructing Mbed LoRaWANInterface and passing it down the radio object.
static LoRaWANInterface lorawan(radio);

// Application specific callbacks
static lorawan_app_callbacks_t callbacks;



// Function for the repeating call to sends a message to the LoRa Network Server
static void send_message()
{
    int16_t retcode;

    if (lorawan_sending == 0) {
    
        // set trigger to currently sending
        lorawan_sending = 1;

        // Clear the CayenneLPP buffer
        Payload.reset();
    
        // Fill the CayenneLPP buffer
        Payload.addTemperature(0, bme680_temp_current / 1.00f);
        Payload.addRelativeHumidity(1, bme680_hum_current / 1.00f);
        Payload.addBarometricPressure(2, bme680_press_current / 100.00f);
        Payload.addBarometricPressure(3, bme680_gas_current / 1000.00f);
        Payload.addBarometricPressure(4, bme680_iaq_score / 1.00f);
        Payload.addPresence(5, bme680_iaq_accuracy);
        Payload.addPresence(6, ifttt_trigger);
        
        // Print the results
    #ifdef LORA_DEBUG_MODE
        pc.printf("LoRaWan: Temperature = %.2f *C | Humidity = %.2f %% | Pressure = %.2f hPa | Gas = %.2f KOhms | IAQ Score = %.2f | IAQ Accuracy = %d | IFTTT trigger = %d \r\n", bme680_temp_current, bme680_hum_current, bme680_press_current / 100.00f, bme680_gas_current / 1000.00f, bme680_iaq_score, bme680_iaq_accuracy, ifttt_trigger);
    
        pc.printf("LoRaWAN: Payload = ");
        for (uint8_t i = 0; i < Payload.getSize(); i++) {
            pc.printf("%02x", Payload.getBuffer()[i]);
        }
        pc.printf(" | Bytes = %d \r\n", Payload.getSize());
    #endif
    
        // reset IFTTT trigger
        ifttt_trigger = 0; 
        
        // Send the LoRaWAN message and get the status returned
        retcode = lorawan.send(LORA_APP_PORT, Payload.getBuffer(), Payload.getSize(), MSG_UNCONFIRMED_FLAG);
        if (retcode < 0) {
            retcode == LORAWAN_STATUS_WOULD_BLOCK ? pc.printf("LoRaWAN: send() - WOULD BLOCK \r\n")
            : pc.printf("LoRaWAN: send() - Error code %d \r\n", retcode);
    
            if (retcode == LORAWAN_STATUS_WOULD_BLOCK) {
                //retry in 3 seconds
                if (LORA_DUTY_CYCLE_ON) {
                    ev_queue_lora.call_in(3000, send_message);
                }
            }
            return;
        }
        pc.printf("LoRaWAN: %d bytes scheduled for transmission \r\n", retcode);
    }
}



// Receive a message from the LoRa Network Server
static void receive_message()
{
    int16_t retcode;
    retcode = lorawan.receive(LORA_APP_PORT, rx_buffer, sizeof(rx_buffer), MSG_CONFIRMED_FLAG|MSG_UNCONFIRMED_FLAG);

    if (retcode < 0) {
        pc.printf("LoRaWAN: receive() - Error code %d \r\n", retcode);
        return;
    }

    pc.printf("LoRaWAN: Data: ");
    for (uint8_t i = 0; i < retcode; i++) {
        pc.printf("%x", rx_buffer[i]);
    }
    pc.printf("\r\n");

    pc.printf("LoRaWAN: Data Length: %d \r\n", retcode);
    memset(rx_buffer, 0, sizeof(rx_buffer));
}


// LoRa Event handler
static void lora_event_handler(lorawan_event_t event)
{
    switch (event) {
        case CONNECTED:
            pc.printf("LoRaWAN: Connection - Successful \r\n");

            // set trigger to not sending
            lorawan_sending = 0;

            // Send first message
            send_message();
            
            if (LORA_DUTY_CYCLE_ON) {
            } else {
                ev_queue_lora.call_every(TX_TIMER, send_message);
            }
            break;
        case DISCONNECTED:
            ev_queue_lora.break_dispatch();
            pc.printf("LoRaWAN: Disconnected Successfully \r\n");
            break;
        case TX_DONE:
            pc.printf("LoRaWAN: Message Sent to Network Server \r\n");

            // set trigger to not sending
            lorawan_sending = 0;

            if (LORA_DUTY_CYCLE_ON) {
                send_message();
            }
            break;
        case TX_TIMEOUT:
        case TX_ERROR:
        case TX_CRYPTO_ERROR:
        case TX_SCHEDULING_ERROR:
            pc.printf("LoRaWAN: Transmission Error - EventCode = %d \r\n", event);

            // set trigger to not sending
            lorawan_sending = 0;

            // try again
            if (LORA_DUTY_CYCLE_ON) {
                send_message();
            }
            break;
        case RX_DONE:
            pc.printf("LoRaWAN: Received message from Network Server \r\n");
            receive_message();
            break;
        case RX_TIMEOUT:
        case RX_ERROR:
            pc.printf("LoRaWAN: Error in reception - Code = %d \r\n", event);
            break;
        case JOIN_FAILURE:
            pc.printf("LoRaWAN: OTAA Failed - Check Keys \r\n");
            break;
        case UPLINK_REQUIRED:
            pc.printf("LoRaWAN: Uplink required by NS \r\n");
            if (LORA_DUTY_CYCLE_ON) {
                send_message();
            }
            break;
        default:
            MBED_ASSERT("LoRaWAN: Unknown Event");
    }
}











// This function is the repeating call from the BME680 sensor and BSEC library to fill the sensor values into the system variables and calculate the IAQ score

/*!
 * @brief           Handling of the ready outputs
 * @param[in]       timestamp       time in nanoseconds
 * @param[in]       iaq             IAQ signal
 * @param[in]       iaq_accuracy    accuracy of IAQ signal
 * @param[in]       temperature     temperature signal
 * @param[in]       humidity        humidity signal
 * @param[in]       pressure        pressure signal
 * @param[in]       raw_temperature raw temperature signal
 * @param[in]       raw_humidity    raw humidity signal
 * @param[in]       raw_gas         raw gas sensor signal
 * @param[in]       bsec_status     value returned by the bsec_do_steps() call
 * @return          none
 */
void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, float temperature, float humidity,
                  float pressure, float raw_temperature, float raw_humidity, float gas,
                  bsec_library_return_t bsec_status)
{
    // Set the system variables
    bme680_temp_current = temperature;
    bme680_press_current = pressure;
    bme680_hum_current = humidity;
    bme680_gas_current = gas;
    bme680_iaq_score = iaq;
    bme680_iaq_accuracy = iaq_accuracy;

    // Define the IAQ index
    if (iaq_accuracy == 0) {
        bme680_iaq_score = 0.00f;
        bme680_iaq_rating_nr = 0;
        bme680_iaq_rating_txt = "???";
    } else if (bme680_iaq_score >= 300.00f) {
        bme680_iaq_rating_nr = 6;
        bme680_iaq_rating_txt = "Hazardous";
    } else if (bme680_iaq_score >= 200.00f) {
        bme680_iaq_rating_nr = 5;
        bme680_iaq_rating_txt = "Very Unhealthy";
    } else if (bme680_iaq_score >= 150.00f) {
        bme680_iaq_rating_nr = 4;
        bme680_iaq_rating_txt = "Unhealthy";
    } else if (bme680_iaq_score >= 100.00f) {
        bme680_iaq_rating_nr = 3;
        bme680_iaq_rating_txt = "Little bad";
    } else if (bme680_iaq_score >=  50.00f) {
        bme680_iaq_rating_nr = 2;
        bme680_iaq_rating_txt = "Average";
    } else if (bme680_iaq_score >=   0.00f) {
        bme680_iaq_rating_nr = 1;
        bme680_iaq_rating_txt = "Good";
    } else {
        bme680_iaq_rating_nr = 0;
        bme680_iaq_rating_txt = "???";
    }
    
    // Print the results
#ifdef BME680_DEBUG_MODE
    pc.printf("BME680 sensor data: Temperature = %.2f *C | Pressure = %.2f hPa | Humidity = %.2f %% | Gas = %.2f KOhms \r\n", bme680_temp_current, bme680_press_current/100.00f, bme680_hum_current, bme680_gas_current/1000.00f);
    pc.printf("BME680 air quality: Score = %.2f | Accuracy = %d | Rating = %s \r\n", bme680_iaq_score, bme680_iaq_accuracy, bme680_iaq_rating_txt);
#endif

    // If there were changes, do change the LED color and set the chirps
    if (bme680_iaq_rating_prev == bme680_iaq_rating_nr) {
        // do nothing, as there was no change
    } else if (bme680_iaq_rating_nr == 6) {
        ev_queue_speaker_unchain();
        set_rgbLED(255,0,0,100);
        chirp_volume = 1;
        ev_queue_speaker_6.chain(&ev_queue_speaker);
    } else if (bme680_iaq_rating_nr == 5) {
        ev_queue_speaker_unchain();
        set_rgbLED(255,85,0,50);
        chirp_volume = 0.8;
        ev_queue_speaker_5.chain(&ev_queue_speaker);
    } else if (bme680_iaq_rating_nr == 4) {
        ev_queue_speaker_unchain();
        set_rgbLED(255,170,0,25);
        chirp_volume = 0.1;
        ev_queue_speaker_4.chain(&ev_queue_speaker);
    } else if (bme680_iaq_rating_nr == 3) {
        ev_queue_speaker_unchain();
        set_rgbLED(255,255,0,12);
        chirp_volume = 0.05;
        ev_queue_speaker_3.chain(&ev_queue_speaker);
    } else if (bme680_iaq_rating_nr == 2) {
        ev_queue_speaker_unchain();
        set_rgbLED(170,255,0,6);
        chirp_volume = 0.00001f;
    } else if (bme680_iaq_rating_nr == 1) {
        ev_queue_speaker_unchain();
        set_rgbLED(0,255,0,3);
        chirp_volume = 0.00001f;
    } else {
        ev_queue_speaker_unchain();
        set_rgbLED(5,5,5,2);
        chirp_volume = 0.00001f;
    }


    if (bme680_iaq_rating_prev == 0) {
        // Previous rating was unknown, no message to show on ifttt
        ifttt_trigger = 0;
    } else if (bme680_iaq_rating_prev > bme680_iaq_rating_nr) {
        // Previous rating was worse, so it's getting better!
        ifttt_trigger = 1;
    } else if (bme680_iaq_rating_prev < bme680_iaq_rating_nr) {
        // Previous rating was better, so it's getting worse...
        ifttt_trigger = 2;
    }
    bme680_iaq_rating_prev = bme680_iaq_rating_nr;
    
    if (ifttt_trigger != 0) {
        // Send the message to the LoRaWAN network right now
        ev_queue_lora.call(send_message);
    }
}



/*!
 * @brief       Call to endless loop BSEC function which reads and processes data based on sensor settings
 * @return          none
 */
void bsec_loop_start()
{
    /* Call to endless loop function which reads and processes data based on sensor settings */
    /* State is saved every 10.000 samples, which means every 10.000 * 3 secs = 500 minutes  */
    bsec_iot_loop(sleep, get_timestamp_us, output_ready, state_save, 10000);
}









/**
 * Entry point for application, takes care of starting the needed functionality and threads
 */
int main()
{
    t_time.start();

    // setup tracing
    setup_trace();

    pc.printf(" \r\n");
    pc.printf(" \r\n");
    pc.printf("Starting Chiiiiiirp! \r\n");
    pc.printf(" \r\n");
    set_rgbLED(1,1,1,100);



    pc.printf("Speaker: Loading Chirp eventqueues \r\n");
    chirp_volume = 0.02;
    ev_queue_speaker_6.call_every(5000, playChirp);
    ev_queue_speaker_5.call_every(10000, playChirp);
    ev_queue_speaker_4.call_every(60000, playChirp);
    ev_queue_speaker_3.call_every(300000, playChirp);
    
    ev_queue_speaker_0.call_every(100000000, doNothing);
    ev_queue_speaker_0.chain(&ev_queue_speaker);
    
    t_speaker.start(callback(&ev_queue_speaker, &EventQueue::dispatch_forever));
    ev_queue_speaker.call(playChirp);






    /* Call to the function which initializes the BSEC library
     * Switch on low-power mode and provide no temperature offset */
    pc.printf("BME680: Load BME680 / BSEC library \r\n");
    return_values_init ret;
    ret = bsec_iot_init(BSEC_SAMPLE_RATE_LP, 0.0f, bus_write, bus_read, sleep, state_load, config_load);
    if (ret.bme680_status) {
        /* Could not intialize BME680 */
        pc.printf("BME680: Could not intialize BME680 (%d) \r\n",(int)ret.bme680_status);
        return (int)ret.bme680_status;
    } else if (ret.bsec_status) {
        /* Could not intialize BSEC library */
        pc.printf("BME680: Could not intialize BSEC library (%d) \r\n",(int)ret.bsec_status);
        return (int)ret.bsec_status;
    } else {
        // Start a thread with the BME680/BSEC loop
        pc.printf("BME680: BME680 / BSEC library loaded successfull \r\n");
        t_bme680.start(callback(&bsec_loop_start));
    }






    pc.printf("LoRaWAN: Load LoRaWAN stack \r\n");
    // stores the status of a call to LoRaWAN protocol
    lorawan_status_t retcode;

    // Initialize LoRaWAN stack
    if (lorawan.initialize(&ev_queue_lora) != LORAWAN_STATUS_OK) {
        pc.printf("LoRaWAN: Mbed LoRaWANStack initialization failed! \r\n");
    } else {
        pc.printf("LoRaWAN: Mbed LoRaWANStack initialized \r\n");
    }

    // prepare application callbacks
    callbacks.events = mbed::callback(lora_event_handler);
    lorawan.add_app_callbacks(&callbacks);

    // Set number of retries in case of CONFIRMED messages
    if (lorawan.set_confirmed_msg_retries(CONFIRMED_MSG_RETRY_COUNTER) != LORAWAN_STATUS_OK) {
        pc.printf("LoRaWAN: CONFIRMED message retries - set_confirmed_msg_retries failed! \r\n");
    } else {
        pc.printf("LoRaWAN: CONFIRMED message retries - set_confirmed_msg_retries = %d \r\n", CONFIRMED_MSG_RETRY_COUNTER);
    }

    // Enable adaptive data rate
    if (lorawan.enable_adaptive_datarate() != LORAWAN_STATUS_OK) {
        pc.printf("LoRaWAN: Adaptive data rate (ADR) - enable_adaptive_datarate failed! \r\n");
    } else {
        pc.printf("LoRaWAN: Adaptive data rate (ADR) - enable_adaptive_datarate enabled \r\n");
    }

    pc.printf("LoRaWAN: Connection - In Progress... \r\n");
    retcode = lorawan.connect();
    if (retcode == LORAWAN_STATUS_OK || retcode == LORAWAN_STATUS_CONNECT_IN_PROGRESS) {
    } else {
        pc.printf("LoRaWAN: Connection - error, code = %d \r\n", retcode);
    }

    // make your event queue dispatching events forever within a thread
    t_lora.start(callback(&ev_queue_lora, &EventQueue::dispatch_forever));




}

// EOF

MBED Compiler code

Arduino
Import the file in MBED, it will automatically import some unchanged libraries. You have to add the BSEC files yourself as these are copyright protected.
No preview (download only).

Credits

Bastiaan Slee

Bastiaan Slee

4 projects • 26 followers
Tinkerer with RaspberryPi, Python, EmonCMS, meter reading, OpenTherm, Weather Station, IoT, cloud-server, Pebble Time, LoRaWAN, WeMos

Comments