Air Quality Monitoring

Based on the Kaa v1.3.

Time to complete: 35 min.

Overview

Welcome to the Air Quality solution tutorial! We assume that you have basic knowledge about the Kaa platform. If any of the tutorial parts seems difficult to you, you can always refer to the Getting Started tutorial cycle where you will grasp the basic platform concepts.

At the end of the tutorial, you’ll have the fully operable air quality monitoring solution. The solution will include an IoT-enabled device that measures PM2.5, PM10, Indoor Air Quality (IAQ), CO2, temperature, humidity, and reports the readings into the Kaa platform. Also, the solution will have three dashboards on Kaa UI - one for listing devices; one for measurements visualization on charts and gauges; and another one for device administration like viewing device logs and metadata.

Prerequisites

For the solution completion you will need the below components:

  • Kaa Cloud account - create it here if you don’t have it yet
  • ESP8266 - order link
  • SDS011 (PM2.5, PM10) - order link
  • BME680 (IAQ, temperature, humidity, pressure measurements) - order link
  • SenseAir S8 (CO2 measurements) - order link

You can order the above hardware on Amazon using the provided links or anywhere you want. The links were provided for your convenience.

Playbook

Kaa Arduino SDK

To seamlessly communicate with the Kaa platform using Arduino or ESP-base boards (ESP8266, ESP32), there is [Kaa Arduino SDK][Kaa Arduino SDK] that one can install right from the Arduino IDE Library Manager. The library encapsulates all Kaa Protocol communication complexities and provides users with a neat interface.

In that tutorial, we will use SDK to initialize connection with Kaa, push telemetry data (air quality measurements), and handle user device configuration updates from Kaa UI. Study [Kaa Arduino SDK][Kaa Arduino SDK] and its examples to see what you can do with that.

The installation of the Kaa Arduino SDK is the same as for any other Arduino libraries - go to Tools > Manage Libraries…, type Kaa in a search field, and install the library. In this tutorial, we are using the 0.1.7 library version.

Connecting and testing BME680

By default, the BME680 sensor measures temperature, humidity, pressure, and gas level as a resistance value in Ohm. These measurements are not really useful to understand real air quality. But using the Bosch Sensortec Environmental Cluster (BSEC) algorithm we can obtain Indoor Air Quality index or simply IAQ-index from the above measurements.

The algorithm calculates IAQ-index based on the sensor’s current and historical measurements. You don’t need to implement the algorithm by yourself since it is already implemented in the BSEC library that can be easily installed using Arduino IDE Library Manager. Open the Arduino IDE and go to Tools > Manage Libraries…, search and install the BSEC Software library. In this tutorial, we are using the 1.6.1.1480 library version.

Before being able to use the BSEC library we need to make some modifications to the platform.txt file for the ESP8266 board on your computer.

Find platform.txt of the ESP8266 board. You can find it using the below command:

sudo find / -name 'platform.txt'

For example, on my Mac the file has the next path:

/System/Volumes/Data/Users/apasika/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/platform.txt

NOTE: your computer may have several platform.txt files but we need one under the esp8266 folder.

Open the file and find the below block

# These can be overridden in platform.local.txt
compiler.c.extra_flags=
compiler.c.elf.extra_flags=
compiler.S.extra_flags=
compiler.cpp.extra_flags=
compiler.ar.extra_flags=
compiler.objcopy.eep.extra_flags=
compiler.elf2hex.extra_flags=

add the next line to the end of the block

compiler.libraries.ldflags=

So you will end up with the modified block below.

# These can be overridden in platform.local.txt
compiler.c.extra_flags=
compiler.c.elf.extra_flags=
compiler.S.extra_flags=
compiler.cpp.extra_flags=
compiler.ar.extra_flags=
compiler.objcopy.eep.extra_flags=
compiler.elf2hex.extra_flags=
compiler.libraries.ldflags=

Next, search for the below block

## Combine gc-sections, archives, and objects
recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {build.exception_flags} -Wl,-Map "-Wl,{build.path}/{build.project_name}.map" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{archive_file_path}" {compiler.c.elf.libs} -Wl,--end-group  "-L{build.path}"

and add this snippet

{compiler.libraries.ldflags}

So you will end up with the below block

## Combine gc-sections, archives, and objects
recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {build.exception_flags} -Wl,-Map "-Wl,{build.path}/{build.project_name}.map" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{archive_file_path}" {compiler.c.elf.libs} {compiler.libraries.ldflags} -Wl,--end-group  "-L{build.path}"

Close and save the platform.txt.

You can find more information about the BSEC library installation here.

Now we can wire the BME680 sensor to ESP8266 and obtain an IAQ index together with other measurements such as temperature, humidity, pressure, etc.

Wire BME680 to the ESP8266 board using the below table.

ESP8266 BME680
3.3V VCC
GND GND
SCL D1
SDA D2

Paste the below demo code for the BME6680 sensor into Arduino IDE.

#include "bsec.h"

// Helper functions declarations
void checkIaqSensorStatus(void);

// Create an object of the class Bsec
Bsec iaqSensor;

String output;

// Entry point for the example
void setup(void) {
  Serial.begin(115200);
  while (!Serial) delay(10);
  Wire.begin();

  iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire);
  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();

  // Print the header
  output = "Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent";
  Serial.println(output);
}

// Function that is looped forever
void loop(void) {
  unsigned long time_trigger = millis();
  if (iaqSensor.run()) { // If new data is available
    output = String(time_trigger);
    output += ", " + String(iaqSensor.rawTemperature);
    output += ", " + String(iaqSensor.pressure);
    output += ", " + String(iaqSensor.rawHumidity);
    output += ", " + String(iaqSensor.gasResistance);
    output += ", " + String(iaqSensor.iaq);
    output += ", " + String(iaqSensor.iaqAccuracy);
    output += ", " + String(iaqSensor.temperature);
    output += ", " + String(iaqSensor.humidity);
    output += ", " + String(iaqSensor.staticIaq);
    output += ", " + String(iaqSensor.co2Equivalent);
    output += ", " + String(iaqSensor.breathVocEquivalent);
    Serial.println(output);
  } else {
    checkIaqSensorStatus();
  }
  delay(500);
}

// Helper function definitions
void checkIaqSensorStatus(void) {
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      output = "BSEC error code : " + String(iaqSensor.status);
      Serial.println(output);
    } 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);
      Serial.println(output);
    } else {
      output = "BME680 warning code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    }
  }
}

Connect ESP8266 to your computer, select the right serial port and upload the sketch to ESP8266.

Open the serial monitor. If everything is correct, you will see the output as below.

NOTE: in order to see the right IAQ values, BME680 should work up to 15 minutes to heat up.

BSEC library version 1.4.8.0
Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent
96, 25.89, 99737.00, 39.54, 1665.00, 25.00, 0, 25.89, 39.54, 25.00, 500.00, 0.50
3352, 25.87, 99732.00, 39.50, 1980.00, 25.00, 0, 25.81, 39.67, 25.00, 500.00, 0.50
6608, 25.90, 99728.00, 39.48, 2478.00, 25.00, 0, 25.84, 39.59, 25.00, 500.00, 0.50
9864, 25.94, 99726.00, 39.43, 3061.00, 25.00, 0, 25.88, 39.47, 25.00, 500.00, 0.50
13119, 25.94, 99730.00, 39.35, 3569.00, 25.00, 0, 25.88, 39.41, 25.00, 500.00, 0.50
16375, 25.95, 99730.00, 39.28, 4071.00, 25.00, 0, 25.89, 39.33, 25.00, 500.00, 0.50
19631, 25.98, 99734.00, 39.24, 4559.00, 25.00, 0, 25.92, 39.26, 25.00, 500.00, 0.50
22886, 25.98, 99743.00, 39.23, 5040.00, 25.00, 0, 25.92, 39.27, 25.00, 500.00, 0.50

Now we are sure that everything is OK with the BME680 sensor and we can move on to wiring and testing the next sensor.

Connecting and testing SDS011

Nova Fitness SDS011 is a laser dust sensor with a mounted fan inside that automatically sucks air. The sensor uses the laser light scattering principle to measure the value of dust particles suspended in the air. It provides high precision and reliable readings of PM2.5 and PM10 values. Any change in environment can be observed almost instantaneously - a short response time below 10 seconds. The sensor in standard mode reports reading with a 1-second interval.

Let’s wire SDS011 to ESP8266 and test it.

Wire SDS011 to ESP8266 board using the below table.

ESP8266 SDS011
Vin 5V
GND GND
D3 TXD
D4 RXD

We will need to install Nova Fitness SDS dust sensors library to communicate with the SDS011 sensor. Open the Arduino IDE and go to Tools > Manage Libraries…, search and install the Nova Fitness Sds dust sensors library.

Nova Fitness SDS dust sensors library installation

Paste the below demo code for the SDS011 sensor into Arduino IDE.

#include "SdsDustSensor.h"

#define RX_PIN 0
#define TX_PIN 2
SdsDustSensor sds(RX_PIN, TX_PIN);

void setup() {
  Serial.begin(115200);
  sds.begin();

  Serial.println(sds.queryFirmwareVersion().toString()); // prints firmware version
  Serial.println(sds.setActiveReportingMode().toString()); // ensures sensor is in 'active' reporting mode
  Serial.println(sds.setContinuousWorkingPeriod().toString()); // ensures sensor has continuous working period - default but not recommended
}

void loop() {
  PmResult pm = sds.readPm();
  if (pm.isOk()) {
    Serial.printf("PM2.5 = %.2f; PM10 = %.2f\n", pm.pm25, pm.pm10);
  } else {
    Serial.print("Could not read values from sensor, reason: ");
    Serial.println(pm.statusToString());
  }

  delay(5000);
}

Connect ESP8266 to your computer, select the right serial port and upload the sketch to ESP8266.

NOTE: unwire TX or RX SDS011’s pin from ESP during the sketch upload, otherwise you will get an error.

Open the serial monitor. If everything is correct, you will see the output as below.

Mode: active
Working period: continuous
PM2.5 = 3.90; PM10 = 16.40
PM2.5 = 3.70; PM10 = 12.20
PM2.5 = 4.10; PM10 = 11.20
PM2.5 = 4.10; PM10 = 9.70
PM2.5 = 3.90; PM10 = 9.10
PM2.5 = 5.00; PM10 = 9.60
PM2.5 = 4.70; PM10 = 8.60
PM2.5 = 4.40; PM10 = 7.90

Now we are sure that everything is OK with the SDS011 sensor and we can move on to wiring and testing the next sensor.

Connecting and testing SenseAir S8

SenseAir S8 is the CO2 monitoring sensor for residential areas. This sensor is based on modern nondispersive infrared technology (NDIR).

Let’s wire SenseAir S8 to ESP8266 and test it.

Wire SenseAir S8 to ESP8266 board using the below table.

ESP8266 SenseAir S8
Vin 5V
GND GND
D7 RX
D8 TX

Connect ESP8266 to your computer, select the right serial port and upload the below sketch to ESP8266.

#include "SoftwareSerial.h"

#define RX_PIN 13
#define TX_PIN 15

SoftwareSerial co2SensorSerial(RX_PIN, TX_PIN);
byte co2Response[] = {0, 0, 0, 0, 0, 0, 0};

void setup() {
  Serial.begin(115200);
  co2SensorSerial.begin(9600);
}

void loop() {
  int co2 = requestCO2();
  Serial.printf("CO2 %d ppm\n", co2);
  delay(2000);
}

int requestCO2() {
  static byte readCommand[] = {0xFE, 0x44, 0x00, 0x08, 0x02, 0x9F, 0x25};

  int readAttempt = 0;
  while (!co2SensorSerial.available()) {
    co2SensorSerial.write(readCommand, 7);
    readAttempt++;
    delay(1000);
    if (readAttempt >= 5) {
      Serial.println(F("Failed to request CO2. Skipping it..."));
      return 0;
    }
  }

  int timeout = 0;
  while (co2SensorSerial.available() < 7) {
    timeout++;
    if (timeout > 10) {
      while (co2SensorSerial.available()) {
        co2SensorSerial.read();
      }
      break;
    }
    delay(50);
  }

  for (int i = 0; i < 7; i++) {
    co2Response[i] = co2SensorSerial.read();
  }

  int high = co2Response[3];
  int low = co2Response[4];

  int val = high * 256 + low;
  return val * 1;
}

Open the serial monitor. If everything is correct, you will see the output as below.

CO2 0 ppm   <-- the sensor is heating up
CO2 0 ppm   <-- the sensor is heating up
CO2 810 ppm
CO2 810 ppm
CO2 782 ppm
CO2 762 ppm
CO2 762 ppm
CO2 762 ppm
CO2 747 ppm
CO2 729 ppm

Data visualization

Now we are ready to visualize air quality measurements on Kaa UI. For that, we’ll need to upload the final sketch to ESP8266 and run a pre-configured solution template with all needed charts, gauges, and other widgets on Kaa UI.

Let’s start with running the solution template. Log in into your Kaa Cloud account, then go to Solutions page, click Add solution from template in the top right corner, select the Air Quality Monitoring template and click Create.

All right, the template is ready and is waiting to visualize data for us. The last thing we should do is connect assembled device previously uploading the final sketch to it to start observing real data in the template.

Copy the following sketch to your ESP8266. Set ssid and password to your WiFi credentials. To find out the application version, go to the Home dashboard in the template and copy the application version defined on the right information panel (ends with the -v1 suffix). Paste it as a value for the KAA_APP_VERSION macros in the below sketch.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "kaa.h"
#include "bsec.h"
#include "SdsDustSensor.h"

#define KAA_SERVER "mqtt.cloud.kaaiot.com"
#define KAA_PORT 1883
#define KAA_TOKEN "air-station-1"
#define KAA_APP_VERSION ""

#define RECONNECT_TIME  5000 // ms

#define RX_PIN 0
#define TX_PIN 2

const char* ssid = "";
const char* password = "";

char mqtt_host[] = KAA_SERVER;
unsigned int mqtt_port = KAA_PORT;

unsigned long now = 0;
unsigned long lastReconnect = 0;
unsigned long lastMsg = 0;

WiFiClient espClient;
PubSubClient client(espClient);
Kaa kaa(&client, KAA_TOKEN, KAA_APP_VERSION);

SdsDustSensor sds(RX_PIN, TX_PIN);

Bsec iaqSensor;
bool bsecInitialized = false;
bool bme680Initialized = false;

int reportingFrequencyMillis = 60 * 1000; // one minute

SoftwareSerial co2SensorSerial(13, 15);
byte co2Response[] = {0, 0, 0, 0, 0, 0, 0};

#define ERROR_LOG_LEVEL "Error"
#define WARN_LOG_LEVEL "Warn"

#define PRINT_DBG(...) printMsg(__VA_ARGS__)

void printMsg(const char * msg, ...) {
  char buff[256];
  va_list args;
  va_start(args, msg);
  vsnprintf(buff, sizeof(buff) - 2, msg, args);
  buff[sizeof(buff) - 1] = '\0';
  Serial.print(buff);
}

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  // WiFi and Kaa connection setup
  setupWiFi();
  client.setServer(mqtt_host, mqtt_port);
  client.setCallback(callback);
  bool success = client.setBufferSize(1000);
  if (!success) {
    PRINT_DBG("Failed to set buffer size for MQTT client\n");
  }

  // SDS011 setup
  sds.begin();
  Serial.println(sds.queryFirmwareVersion().toString());
  Serial.println(sds.setActiveReportingMode().toString());
  Serial.println(sds.setContinuousWorkingPeriod().toString());

  // SenseAir S8 setup
  co2SensorSerial.begin(9600);

  // BME680 and BSEC library setup
  Wire.begin();

  iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire);
  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();
}

void loop() {
  // Checking connection
  if (!client.connected()) {
    now = millis();
    if (((now - lastReconnect) > RECONNECT_TIME) || (now < lastReconnect)) {
      lastReconnect = now;
      reconnect();
    }
    return;
  }
  client.loop();

  // Sending telemetry
  now = millis();
  if (((now - lastMsg) > reportingFrequencyMillis) || (now < lastMsg)) {
    lastMsg = now;
    sendTelemetry();
  }
}

void checkIaqSensorStatus() {
  if (iaqSensor.status == BSEC_OK) {
    bsecInitialized = true;
  } else {
    if (iaqSensor.status < BSEC_OK) {
      String err = "BSEC error code: " + String(iaqSensor.status);
      PRINT_DBG("%s\n", err.c_str());
      sendLogToKaa(ERROR_LOG_LEVEL, err);
    } else {
      String warning = "BSEC warning code : " + String(iaqSensor.status);
      PRINT_DBG("%s\n", warning.c_str());
      sendLogToKaa(WARN_LOG_LEVEL, warning);
    }
  }

  if (iaqSensor.bme680Status == BME680_OK) {
    bme680Initialized = true;
  } else {
    if (iaqSensor.bme680Status < BME680_OK) {
      String err = "BME680 error code : " + String(iaqSensor.bme680Status);
      PRINT_DBG("%s\n", err.c_str());
      sendLogToKaa(ERROR_LOG_LEVEL, err);
    } else {
      String warning = "BME680 warning code : " + String(iaqSensor.bme680Status);
      PRINT_DBG("%s\n", warning.c_str());
      sendLogToKaa(WARN_LOG_LEVEL, warning);
    }
  }
}

void composeAndSendMetadata() {
  String ip = (
                String(WiFi.localIP()[0]) + "." +
                String(WiFi.localIP()[1]) + "." +
                String(WiFi.localIP()[2]) + "." +
                String(WiFi.localIP()[3])
              );

  StaticJsonDocument<255> metadata;
  metadata["ip"] = ip;
  metadata["mac"] = String(WiFi.macAddress());
  metadata["serial"] = String(ESP.getChipId());
  metadata["microchip"] = "ESP8266";
  metadata["model"] = "Node MCU";

  kaa.sendMetadata(metadata.as<String>().c_str());
}

void fillWithSDS011Data(StaticJsonDocument<255> &data) {
  PmResult pm = sds.readPm();
  if (pm.isOk()) {
    data[0]["pm25"] = pm.pm25;
    data[0]["pm10"] = pm.pm10;
  } else {
    String err = "Failed to read measurements from SDS011 sensor. Reason: " + pm.statusToString();
    PRINT_DBG("%s\n", err.c_str());
    sendLogToKaa(ERROR_LOG_LEVEL, err);
  }
}

void fillWithBME680Data(StaticJsonDocument<255> &data) {
  if (iaqSensor.run()) {
    data[0]["temperature"] = iaqSensor.temperature;
    data[0]["pressure"] = iaqSensor.pressure / 130; // converting Pa to mmHg
    data[0]["humidity"] = iaqSensor.humidity;
    data[0]["aqi"] = iaqSensor.staticIaq;
    data[0]["co2Equivalent"] = iaqSensor.co2Equivalent;
    data[0]["vocEquivalent"] = iaqSensor.breathVocEquivalent;
  } else {
    String err = "Failed to read measurements from BME680 sensor";
    PRINT_DBG("%s\n", err.c_str());
    sendLogToKaa(ERROR_LOG_LEVEL, err);
    checkIaqSensorStatus();
  }
}

void fillWithSenseAirS8Data(StaticJsonDocument<255> &data) {
  int co2 = requestCO2();
  if (co2 != -1) {
    data[0]["co2"] = co2;
  } else {
    String err = "Failed to read CO2 from SenseAir S8 sensor";
    PRINT_DBG("%s\n", err.c_str());
    sendLogToKaa(ERROR_LOG_LEVEL, err);
  }
}

void sendTelemetry() {
  StaticJsonDocument<255> data;
  data.createNestedObject();

  fillWithBME680Data(data);
  fillWithSDS011Data(data);
  fillWithSenseAirS8Data(data);

  kaa.sendDataRawUnreliably(data.as<String>().c_str());
}

int requestCO2() {
  static byte readCommand[] = {0xFE, 0x44, 0x00, 0x08, 0x02, 0x9F, 0x25};

  int readAttempt = 0;
  while (!co2SensorSerial.available()) {
    co2SensorSerial.write(readCommand, 7);
    readAttempt++;
    delay(1000);
    if (readAttempt >= 5) {
      PRINT_DBG("Failed to request CO2. Skipping it...");
      return -1;
    }
  }

  int timeout = 0;
  while (co2SensorSerial.available() < 7) {
    timeout++;
    if (timeout > 10) {
      while (co2SensorSerial.available()) {
        co2SensorSerial.read();
      }
      break;
    }
    delay(50);
  }

  for (int i = 0; i < 7; i++) {
    co2Response[i] = co2SensorSerial.read();
  }

  int high = co2Response[3];
  int low = co2Response[4];

  int val = high * 256 + low;
  return val * 1;
}

void sendLogToKaa(String level, String log) {
  StaticJsonDocument<255> data;
  data.createNestedObject();

  String withLevel = level + ": " + log;
  data[0]["log"] = withLevel;
  kaa.sendDataRawUnreliably(data.as<String>().c_str());
}

void setupWiFi() {
  delay(10);
  PRINT_DBG("Connecting to %s\n", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    PRINT_DBG(".");
  }
  String ipstring = (
               String(WiFi.localIP()[0]) + "." +
               String(WiFi.localIP()[1]) + "." +
               String(WiFi.localIP()[2]) + "." +
               String(WiFi.localIP()[3])
             );
  PRINT_DBG("WiFi connected\n");
  PRINT_DBG("IP address: %s\n", ipstring.c_str());
}

void configResponseCallback(char* requestStatus, char* payload, unsigned int len) {
  if (!strcmp(requestStatus, "status")) {
    updateReportingFrequencyFromConfig(payload, len);
  } else {
    PRINT_DBG("Error config response was received: %s; %s\n", requestStatus, payload);
  }
}

void configPushCallback(char* payload, unsigned int len) {
  updateReportingFrequencyFromConfig(payload, len);
}

void updateReportingFrequencyFromConfig(char* payload, unsigned int len) {
  DynamicJsonDocument doc(192);
  deserializeJson(doc, payload, len);
  JsonVariant json_var = doc.as<JsonVariant>();

  if (json_var.containsKey("config") && json_var["config"].containsKey("reportingFrequencyMin")) {
    int reportingFrequencyMin = json_var["config"]["reportingFrequencyMin"].as<int>();
    reportingFrequencyMillis = reportingFrequencyMin * 60 * 1000;
    PRINT_DBG("New reporting frequency: %d millis\n", reportingFrequencyMillis);
  } else {
    PRINT_DBG("Config payload without 'reportingFrequencyMin' key was received. Ignoring it...\n");
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  PRINT_DBG("Message arrived [%s] ", topic);
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  kaa.messageArrivedCallback(topic, (char*)payload, length);
}

void reconnect() {
  PRINT_DBG("Attempting MQTT connection to %s:%u ...", mqtt_host, mqtt_port);
  String clientId = "AQ-tutorial-device-" + String(ESP.getChipId());
  // Attempt to connect
  if (client.connect(clientId.c_str())) {
    PRINT_DBG("Connected to WiFi\n");
    kaa.connect();
    kaa.setConfigResponseCallback(&configResponseCallback);
    kaa.setConfigPushCallback(&configPushCallback);
    kaa.requestConfig();
    composeAndSendMetadata();
  } else {
    PRINT_DBG("failed, rc=%d try again in %d milliseconds\n", client.state(), RECONNECT_TIME);
  }
}

Connect ESP8266 to your computer, select the right serial port and upload the sketch to ESP8266.

Now we can go to Kaa UI and view dashboards and widgets with real device data. Regarding the template it has three dashboards:

  • Home dashboard - with the map and device list widgets
  • Monitor’s dashboard - with the current measurements values widget, reference table, and gauges and charts displaying average values for the selected time range.
  • Administration dashboard - with device’s metadata, raw configuration, and device logs.

Resources

All the tutorial resources are located on GitHub.

Feedback

This tutorial is based on Kaa 1.3 released on February 17-th, 2021. If you, our reader from the future, spot some major discrepancies with your current version of the Kaa platform, or if anything does not work for you, please give us a shout and we will help!

And if the tutorial served you well, we’d still love to hear your feedback, so join the community!