Project tutorial
Arduino Environmental Monitoring

Arduino Environmental Monitoring © GPL3+

Periodically measure environmental conditions and send them to WolkAbout IoT Platform to monitor the environment remotely.

  • 3,487 views
  • 11 comments
  • 19 respects

Components and supplies

Necessary tools and machines

Apps and online services

About this project

Introduction

We had some Environment click sensors handy, so we decided to hook them up to the Arduino MKR1000 and visualise them on WolkAbout IoT Platform. The idea was to do a measurement every minute and publish the results once every 15 minutes. If the publishing of sensor readings fails (due to a busy network or some other issue), then the results should be persisted in the Flash memory of the device. With a potential maximum of 96 writes a day, we minimise the chances of getting the Flash memory into a wrong state (Arduino guarantees 10,000 write cycles).

Hardware setup

The Environment click sensor is hooked up to the I2C communication pins (namely 11 and 12 on the MKR1000), Vcc and ground.

Software setup

We used a couple of libraries to make this project possible, all of which can be installed from Arduino IDE's library manager, or by downloading.zip archives from GitHub repositories listed in the attachments and then adding them to Arduino IDE.The required libraries are:

  • WiFi101
  • Adafruit Unified Sensor Driver
  • Adafruit BME680 Library
  • RTCZero
  • FlashStorage
  • WolkConnect

Once all the required libraries are installed, go ahead and copy the content of Environment monitoring sketch from the attachments into Arduino IDE.

It implements a structure of a circular buffer and uses it to store the measurement results. There is also an implementation of how to store that structure in Flash memory using the FlashStorage library. Timed reading and publishing of the data are achieved by using the RTCZero library. Initially, the HTTP request is made after connecting to WiFi to get the current epoch that will be fed into the RTC library so the sensor readings can be properly timestamped.

An alarm interrupt changes flags for tasks that need to be done (read and publish) and the rest of the work is handled in the loop function. Here we check if we need to read or publish. For minimal power consumption, WiFi is set to be in a low power mode as well as the MKR1000. The sensor on the Environment click goes low powered way automatically until a reading is requested.

Changes that have to be made to the sketch include entering WiFi credentials on line 33 and 34:

const char* ssid = "<*YOUR SSID*>";
const char* wifi_pass = "<*YOUR WIFI PASSWORD*>";

as well as entering device credentials from WolkAbout IoT Platform.

To get these credentials, you need to create a device first by using a device template. The device template for this project is available in the attachments, so create a copy of it. Log in to or create your account on WolkAbout IoT Platform and navigate to the Devices section.

Select the Device templates tab and then press the + sign and select the Upload option, navigating to your local copy of the device template.

Now that a device template is available, you can create a device from it by clicking Create device.

The following form has the Environment click device template pre-selected, so simply press Next step.

Here, you need to give your device a name and tick the checkbox for Create semantic group for this device (necessary for creating widgets on your dashboard later on).

Press Save and the form with device credentials will be displayed. Store this information however you want and then enter it into your sketch in Arduino IDE:

const char *device_key = "device_key";
const char *device_password = "device_password";

Now you can go ahead, verify and upload this sketch to your board, but you still need to create a way to display the received sensor data.

To display this data, switch over to Dashboards tab on WolkAbout IoT Platform and create a new dashboard by pressing + and entering a name for the dashboard. Then you can start adding widgets to your dashboard by pressing Add widget.

Select Control

and then select Temperature from your device.

Repeat the process for Pressure and Humidity. Create another widget of the type Card, select Altitude and create another card widget for Gas Resistance. You can rearrange the widgets as you like it.

After adding widgets for the whole individual sensor reading you expect to receive from your device, the following dashboard will be ready to receive the data from your device.

So, go ahead and deploy the device to the environment you have chosen and monitor the conditions remotely.

Conclusion

By connecting Arduino MKR1000 and Environment Click to the WolkAbout IoT Platform, you created an energy-efficient way to report the environmental conditions of the place you want to monitor remotely. This solution can be extended through the use of WolkAbout IoT Platform's rule engine, which allows you to receive different types of notifications if a certain value passes a threshold you defined.

For more information about the rule engine and other WolkAbout IoT Platform's features, you can visit our User Guides page.

Code

Environment monitoring sketchArduino
Import this sketch into Arduino IDE and edit the WiFi credentials and device credentials
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
#include <bme680_defs.h>
#include <bme680.h>

#include <WiFi101.h>
#include <RTCZero.h>
#include <FlashStorage.h>

#include "WolkConn.h"
#include "MQTTClient.h"
/*Number of outbound_message_t to store*/
#define STORAGE_SIZE 32
#define SEALEVELPRESSURE_HPA (1013.25)

/*Circular buffer to store outbound messages to persist*/
typedef struct{

  boolean valid;

  outbound_message_t outbound_messages[STORAGE_SIZE];

  uint32_t head;
  uint32_t tail;

  boolean empty;
  boolean full;

} Messages;

static Messages data;
/*Connection details*/
const char* ssid = "ssid";
const char* wifi_pass = "wifi_pass";

const char *device_key = "device_key";
const char *device_password = "device_key";
const char* hostname = "api-demo.wolkabout.com";
int portno = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

WiFiClient httpClient;

/* WolkConnect-Arduino Connector context */
static wolk_ctx_t wolk;
/* Init flash storage */
FlashStorage(flash_store, Messages);
/*Init i2c sensor communication*/
Adafruit_BME680 bme;

RTCZero rtc;

bool read;
/*Read sensor every minute. If you change this parameter
make sure that it's <60*/
const byte readEvery = 1;
bool publish;
/*Publish every 10 minutes. If you change this parameter
make sure that it's <60*/
const byte publishEvery = 10;
byte publishMin;

/*Flash storage and custom persistence implementation*/
void _flash_store()
{
  data.valid = true;
  flash_store.write(data);
}
void increase_pointer(uint32_t* pointer)
{
    if ((*pointer) == (STORAGE_SIZE - 1))
    {
        (*pointer) = 0;
    }
    else
    {
        (*pointer)++;
    }
}

void _init()
{
    data = flash_store.read();

    if (data.valid == false)
    {
      data.head = 0;
      data.tail = 0;

      data.empty = true;
      data.full = false;

    }
}

bool _push(outbound_message_t* outbound_message)
{
    if(data.full)
    {
        increase_pointer(&data.head);
    }

    memcpy(&data.outbound_messages[data.tail], outbound_message, sizeof(outbound_message_t));

    increase_pointer(&data.tail);
    
    data.empty = false;
    data.full = (data.tail == data.head);

    return true;
}

bool _peek(outbound_message_t* outbound_message)
{
    memcpy(outbound_message, &data.outbound_messages[data.head], sizeof(outbound_message_t));
    return true;
}

bool _pop(outbound_message_t* outbound_message)
{
    memcpy(outbound_message, &data.outbound_messages[data.head], sizeof(outbound_message_t));
    
    increase_pointer(&data.head);
    
    data.full = false;
    data.empty = (data.tail == data.head);

    return true;
}

bool _is_empty()
{
    return data.empty;
}
void init_wifi()
{
  if ( WiFi.status() != WL_CONNECTED) {
    while (WiFi.begin(ssid, wifi_pass) != WL_CONNECTED) {
      delay(1000);
    }
  }
}
void setup_wifi() 
{

  delay(10);

  if ( WiFi.status() != WL_CONNECTED) {
    int numAttempts = 0;
    while (WiFi.begin(ssid, wifi_pass) != WL_CONNECTED) {
      numAttempts++;
      if(numAttempts == 10){
        Serial.println("Couldn't reach WiFi!");
        break;
      }
      delay(1000);
    }
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  /*Initialize the circular buffer structure*/
  _init();
  
  init_wifi();
  
  /*Get current epoch from server*/
  httpClient.connect("now.httpbin.org", 80);

  httpClient.println("GET / HTTP/1.1");
  httpClient.println("Host: now.httpbin.org");
  httpClient.println("Connection: close");
  httpClient.println();

  httpClient.find("\"epoch\": ");
  char epochChar[11];
  char c = httpClient.read();
  int i = 0;
  while((httpClient.available()) && c != '.')
  {
    epochChar[i] = c;
    i++;
    c = httpClient.read();
  }
  int epoch = atoi(epochChar);

  rtc.begin();

  rtc.setEpoch(epoch);

  wolk_init(&wolk, NULL, NULL, NULL, NULL,
            device_key, device_password, &client, hostname, portno, PROTOCOL_JSON_SINGLE, NULL, NULL);

  wolk_init_custom_persistence(&wolk, _push, _peek, _pop, _is_empty);
  
  /*The on board LED will turn on if something went wrong*/
  if(!bme.begin())
  {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  /*Sensor init*/
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms

  delay(200);

  read = true;
  publish = true;

  rtc.setAlarmTime(rtc.getHours(), (rtc.getMinutes() + readEvery) % 60, rtc.getSeconds());
  rtc.enableAlarm(rtc.MATCH_MMSS);

  rtc.attachInterrupt(alarmMatch);
  publishMin = (rtc.getMinutes() + publishEvery) % 60;
  
  WiFi.lowPowerMode();

}

void loop() {
  /*In order to keep the interrupt routine as short as possible
  routine only sets the tasks to be done
  read = true means that the sensor reading should be done
  publish = true means that the readings should be published to platform
  or persisted in flash if the connection is not available
  */
  if(read)
  {
    read = false;
    Serial.println("Read!");
    
    if (!bme.performReading()) {
    digitalWrite(LED_BUILTIN, HIGH);
    }
    
    wolk_add_numeric_sensor_reading(&wolk, "T", bme.temperature, rtc.getEpoch());
    wolk_add_numeric_sensor_reading(&wolk, "H", bme.humidity, rtc.getEpoch());
    wolk_add_numeric_sensor_reading(&wolk, "P", bme.pressure / 100.0, rtc.getEpoch());
    wolk_add_numeric_sensor_reading(&wolk, "GR", bme.gas_resistance, rtc.getEpoch());
    wolk_add_numeric_sensor_reading(&wolk, "A", bme.readAltitude(SEALEVELPRESSURE_HPA), rtc.getEpoch());
    
    /*set new alarm*/
    int alarmMin = (rtc.getMinutes() + readEvery) % 60;
    rtc.setAlarmMinutes(alarmMin);
    delay(100);
  }
  
  if(publish)
  {
    publish = false;
    setup_wifi();
    wolk_connect(&wolk);
    if(!wolk.is_connected)
    {
      _flash_store();
    }
    delay(100);
    if(wolk_publish(&wolk) == W_TRUE)
    {
    _flash_store();
    }
    /*set new publish time*/
    publishMin = (rtc.getMinutes() + publishEvery) % 60;
    delay(100);
    wolk_disconnect(&wolk);
    delay(100);
  }
  delay(100);
  
}
/*Timed interrupt routine*/
void alarmMatch()
{
  read = true;
  if(publishMin == rtc.getMinutes())
  {
    publish = true;
  }
}
Environment click-manifest.jsonJSON
Used to create a device template on WolkAbout IoT Platform
{
  "id": 833,
  "name": "Environment click",
  "protocol": "JsonSingleReferenceProtocol",
  "description": "Device template for the BME680 sensor on MikroElektronika's board Environment click",
  "deviceType": "STANDARD",
  "connectivityType": "MQTT_BROKER",
  "published": false,
  "feeds": [
    {
      "id": 1381,
      "name": "Temperature",
      "reference": "T",
      "description": "",
      "unit": {
        "id": 31,
        "name": "CELSIUS",
        "symbol": "℃",
        "readingTypeId": 2,
        "system": "SI",
        "context": null,
        "inUse": true,
        "readingTypeName": "TEMPERATURE"
      },
      "minimum": -40,
      "maximum": 80,
      "readingType": {
        "id": 2,
        "name": "TEMPERATURE",
        "dataType": "NUMERIC",
        "size": 1,
        "precision": 1,
        "labels": null,
        "iconName": "ico_temperature"
      }
    },
    {
      "id": 1382,
      "name": "Humidity",
      "reference": "H",
      "description": "",
      "unit": {
        "id": 124,
        "name": "HUMIDITY_PERCENT",
        "symbol": "%",
        "readingTypeId": 4,
        "system": "NON_SI",
        "context": null,
        "inUse": true,
        "readingTypeName": "HUMIDITY"
      },
      "minimum": 0,
      "maximum": 100,
      "readingType": {
        "id": 4,
        "name": "HUMIDITY",
        "dataType": "NUMERIC",
        "size": 1,
        "precision": 1,
        "labels": null,
        "iconName": "ico_humidity"
      }
    },
    {
      "id": 1383,
      "name": "Pressure",
      "reference": "P",
      "description": "",
      "unit": {
        "id": 112,
        "name": "MILLIBAR",
        "symbol": "mb",
        "readingTypeId": 3,
        "system": "NON_SI",
        "context": null,
        "inUse": true,
        "readingTypeName": "PRESSURE"
      },
      "minimum": 300,
      "maximum": 1100,
      "readingType": {
        "id": 3,
        "name": "PRESSURE",
        "dataType": "NUMERIC",
        "size": 1,
        "precision": 1,
        "labels": null,
        "iconName": "ico_pressure"
      }
    },
    {
      "id": 1384,
      "name": "Gas Resistance",
      "reference": "GR",
      "description": "",
      "unit": {
        "id": 23,
        "name": "OHM",
        "symbol": "Ω",
        "readingTypeId": 43,
        "system": "SI",
        "context": null,
        "inUse": true,
        "readingTypeName": "ELECTRIC_RESISTANCE"
      },
      "minimum": 0,
      "maximum": 100000,
      "readingType": {
        "id": 43,
        "name": "ELECTRIC_RESISTANCE",
        "dataType": "NUMERIC",
        "size": 1,
        "precision": 1,
        "labels": null,
        "iconName": null
      }
    },
    {
      "id": 1385,
      "name": "Altitude",
      "reference": "A",
      "description": "",
      "unit": {
        "id": 26,
        "name": "METRE",
        "symbol": "m",
        "readingTypeId": 22,
        "system": "SI",
        "context": null,
        "inUse": true,
        "readingTypeName": "LENGHT"
      },
      "minimum": -1000,
      "maximum": 3000,
      "readingType": {
        "id": 22,
        "name": "LENGHT",
        "dataType": "NUMERIC",
        "size": 1,
        "precision": 1,
        "labels": null,
        "iconName": "ico_length"
      }
    }
  ],
  "actuators": [],
  "alarms": [],
  "configs": [],
  "generallyAvailable": false
}
WolkAbout Environment Monitoring
This repository contains the Arduino sketch used in this project
Arduino WiFi 101 Shield
Wifi library for the Arduino WiFi 101 Shield
Adafruit Unified Sensor Driver
Unified sensor library required for all sensors
Adafruit BME680 Library
Library used for the Environment click to get sensor readings
RTC Library for Arduino
RTC Library for SAMD21 based boards
FlashStorage library for Arduino
A convenient way to store data into Flash memory on the ATSAMD21
WolkConnect-Arduino
Arduino library which provides easy connectivity to WolkAbout IoT Platform.

Schematics

Arduino-MKR1000 and Environment click
22 01    connected   hackster 6r1gdbyrrt

Comments

Similar projects you might like

Arduino Environmental Monitoring

Project showcase by Prajay Basu

  • 14,382 views
  • 1 comment
  • 28 respects

Lake turbidity and environmental monitoring with BLE

Project tutorial by Dimiter Kendri

  • 1,553 views
  • 0 comments
  • 11 respects

An Environmental Monitoring Tool

Project in progress by Pyre Mage

  • 2,968 views
  • 1 comment
  • 16 respects

Plant Monitoring System using AWS IoT

Project tutorial by CJA3D

  • 30,849 views
  • 6 comments
  • 77 respects

Smart Garbage Monitoring System Using Arduino 101

Project tutorial by Technovation

  • 22,707 views
  • 7 comments
  • 37 respects

Multiple mode Environmental Sensor Deck with MKR1000

Project tutorial by ConsoleTeam

  • 11,864 views
  • 14 comments
  • 48 respects
Add projectSign up / Login