Project showcase
IoT@School: An IoT Implementation for Monitoring Air Quality

IoT@School: An IoT Implementation for Monitoring Air Quality © GPL3+

A classroom, in winter times is ill ventilated leading to poor air quality causing fatigue, headaches and in some instances nausea.

  • 6,724 views
  • 1 comment
  • 27 respects

Components and supplies

Apps and online services

About this project

A classroom in the wintertime is ill ventilated, leading to poor air quality causing fatigue and in sometimes nausea.

Well, that's what is commonly known. But is there actual and precise data for every classroom in your school ? Can the problem at hand be solved by just placing plants to purify the air? Should the classroom be equipped with a type 'D' ventilation system? Can the heating be controlled from a database containing occupancy?

And after all that ... Who will water the plants during summer holiday?

This project is a prototype that will allow to acquire data to get all of these questions answered.  

Why this is Done

As with all in education, this is an attempt to keep up with developing technologies while motivating students to learn ever more.

The Idea

Every teacher sees students doze off during lectures, and since this has nothing to do with my obviously impeccable teaching ability an other cause must be identified. We read somewhere that air quality in classrooms around the world is poor. But how bad is it in our actual case?  

Also, our team was last year asked to find newer strategies to control and monitor the heating system in our school. For the moment it isn't monitored but merely controlled using thermostatic valves that are turned off and on by an outdated Windows XP computer system on a local closed network.

That 's when the Flemish Education Minister came up with the idea to offer a €5.000 grant for Science Technology Engineering and Mathematics projects in Schools for Science and Technology (SWeTe).

We took this opportunity, and wrote our group up for a project.   

How it is Done

The first steps

We were very anxious to get things started. So waiting for the AllThingsTalk RDK's we did not waste any time. Using the hardware we had lying around we started with the first experiments. No, not that difficult, actually it was quite easy to get things started with the maker.smartliving.io portal. Just create a profile and follow the instructions, we did our first experiments with Raspberry Pi's ... piece of cake or should that be piece of pi?

Using the tutorials we had the example app up on our Raspberry Pi's up and running in no time. The whole process and the accompanying Python code is well documented

Taking it a step further

Then ... When the Rapid Development Kits we received for pitching the idea on portal.iotopia.be were in the mail, we dove right in. The new Genuino 101 was the first obstacle. Getting a software upgrade on our schools computers required the system administrators intervention. Nevermind, the small delay didn't keep us from trying the darn thing on our personal laptops.

A second obstacle showed up when the Genuino 101 Curie module's programming software generated a list of errors caused by spaces in file names and paths. A quick search on the Arduino website learned us that this is a known problem. To solve it the script ought to be replaced by the one found at github.  

Okay, let's move on ... We had no trouble what so ever to get the example application up and running on the Genuino 101. Great.

The time has come to start fiddling with sensors and what not.   

We ordered Grove CO2 and O2 sensors Seeedstudio, but those keep us waiting so we started with a Grove TPH sensor with a Sensirion SH2x relative humidity sensor and a Bosch Sensortec BMP180 pressure sensor. It took us a while to get that up and running on the Genuino 101, right until the moment where we were ABSOLUTELY convinced the SH2x sensor had died on us and wrote an email to Peter at SmartLiving.com stating that we suspected the sensor to be broken.  

Not a day later the reply to our email said: "There is an incompatibility between the Grove TPH sensor and the Genuino 101, we suspect that there is an I2C address conflict with the Genuino's on board peripherals". We were able to concur with that find in no time when we tried the TPH sensor with an Arduino Ethernet.

The next item on our list ... We wanted to acquire, monitor and store data, the later are unfortunately features that aren't offered by the Smartliving Broker, so we explored using are own broker using Mosquitto on a Raspberry Pi.

So we wrote a small Python script that subscribes with a wildcard to all topics posted on our broker "RPi-KK" on our local network. 

#!/usr/bin/env python

import os
import paho.mqtt.client as mqtt

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

client = mqtt.Client()
client.on_message = on_message
Theclient.connect("RPi-KK", 1883, 60)
client.subscribe("#")
# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
os.system("clear")
print "MQTT message viewer"
rc = 0
while(rc == 0):
    rc = client.loop()
print "Exit"

This script ran on Raspberry Pi and produced the below output when messages were posted using various publishers.

Among the publishers various Arduino boards and the new received Genuino 101 using MQTT PubSubClient library.

That is when we also turned the Grove ESP8266 WiFi module inside out. Trying various downloaded firmware files ending up with the  mqtt_esp8266 example in the PubSubClient library.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid =     "************";
const char* password = "************";
const char* mqtt_server = "RPi-KK";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi();
void callback(char* topic, byte* payload, unsigned int length);
void reconnect();
void setup() {
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}
void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is acive low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
  }
}
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

This software was used to post the messages in the above screenshot. Building on this example we should be able to piece everything together while waiting for the extra sensors and kits we bought.

OMG, we are running late

My oh my ... Time flies when you're having fun. March 13, and we still have a lot to do ... time for a contingency plan if we want to keep up with IOTopia's schedule.

What about MATLAB

MATLAB is a piece of software everybody knows of, but few use it ... It is primarily focused at scientific computing in the academic world. Reckoning not all sensors scientists read find themselves just around the corner or even on the same planet, we thought MATLAB or The MathWorks Inc. might have something that could be of use.

Surfing the web we came across ThingSpeak an IoT portal supplied by The MathWorks Inc. A quick review lead us to believe this IoT portal for collecting, visualizing and analyzing data must be able to do all that we require right from the shelf.  

Piecing it all Together

There was minimal change needed to get the example cope found at ThingSpeak up and running. 

#include <SPI.h>            // Needed for the Ethernet chip
#include <Ethernet.h>       // The Ethernet library
#include <Wire.h>           // Needed by the sensors
#include <Sodaq_TPH.h>      // TPH sensor library

// Local Network Settings
// Must be unique on local network
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xDE, 0x3B }; 

// ThingSpeak Settings
byte server[]  = { 184, 106, 153, 149 };
String writeAPIKey = "****************";

// Time interval in milliseconds to update ThingSpeak 
// (number of seconds * 1000 = interval)
const long updateThingSpeakInterval = 120 * 1000L;      

// Variable Setup
long lastConnectionTime = 0L; 
boolean lastConnected = false;
int failedCounter = 0;

// Initialize Arduino Ethernet Client
EthernetClient client;

void setup() {
  // Start Serial for debugging on the Serial Monitor
  Serial.begin(9600);
  Wire.begin();
  // Start Ethernet on Arduino
  startEthernet();
}

void loop() {
  // Read value from Analog Input Pin 0
  String humidity = String(tph.readHumidity());
  String pressure = String(tph.readPressure());
  String tempSHT = String(tph.readTemperatureSHT());
  String tempBMP = String(tph.readTemperatureBMP());
  // Print Update Response to Serial Monitor
  while (client.available()) {
    char c = client.read();
    Serial.print(c);
  }
  
  // Disconnect from ThingSpeak
  if (!client.connected() && lastConnected) {
    Serial.println();
    Serial.println("... disconnected");
    client.stop();
  }
  
  // Update ThingSpeak
  if(!client.connected() && 
    (millis() - lastConnectionTime > updateThingSpeakInterval)) {
    String tmp = "&field1="+humidity+
                 "&field2="+tempSHT+
                 "&field3="+pressure+
                 "&field4="+tempBMP;
    Serial.println(tmp);
    updateThingSpeak(tmp);
  }

  // Check if Arduino Ethernet needs to be restarted
  if (failedCounter > 3 ) {startEthernet();}
  lastConnected = client.connected();
}
void updateThingSpeak(String tsData) {
  if (client.connect(server, 80)) {         
    client.println("POST /update HTTP/1.1");
    client.println("Host: api.thingspeak.com");
    client.println("Connection: close");
    client.println("X-THINGSPEAKAPIKEY: "+writeAPIKey);
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.print("Content-Length: ");
    client.println(tsData.length());
    client.println();
    client.println();
    client.print(tsData);
    lastConnectionTime = millis();
    if (client.connected()) {
      Serial.println("Connecting to ThingSpeak...");     
      failedCounter = 0;
    } else {
      failedCounter++;
      Serial.println
        ("Connection to ThingSpeak failed ("+String(failedCounter, DEC)+")");   
    }   
  } else {
    failedCounter++;
    Serial.println
        ("Connection to ThingSpeak Failed ("+String(failedCounter, DEC)+")");   
    lastConnectionTime = millis(); 
  }
}

void startEthernet() {
  client.stop();
  Serial.println("Connecting Arduino to network ...");
  while(!Serial);
  // Connect to network amd obtain an IP address using DHCP
  if (Ethernet.begin(mac) == 0)  {
    Serial.println("DHCP Failed, reset Arduino to try again");
  } else {
    Serial.print("Arduino connected to network using DHCP, IP: ");
    Serial.println(Ethernet.localIP());
  }  
  while(!Serial);
}


What is Next

Well, we did not get all that equipment to leave it just lying there. We plan to continue the project and build about ten devices and gather data over our schools WiFi network from various classrooms. We 'll monitor CO2 concentration (when the sensors arrive from mainland China) Relative Humidity, Temperature and various other polluting agents. With the acquired data we will device a plan to better our inner climate ... So that when students doze off, it is not caused by bad air quality.   

For the time being, guests can monitor the humidity and temperature in my home office using this link.

Code

Arduino softwareC/C++
#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include <Sodaq_TPH.h>

// Local Network Settings
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xDE, 0x3B }; // Must be unique on local network

// ThingSpeak Settings
byte server[]  = { 184, 106, 153, 149 };
String writeAPIKey = "****************";
const long updateThingSpeakInterval = 120 * 1000L;      // Time interval in milliseconds to update ThingSpeak (number of seconds * 1000 = interval)

// Variable Setup
long lastConnectionTime = 0L; 
boolean lastConnected = false;
int failedCounter = 0;

// Initialize Arduino Ethernet Client
EthernetClient client;

void setup()
{
  // Start Serial for debugging on the Serial Monitor
  Serial.begin(9600);
  Wire.begin();
  
  // Start Ethernet on Arduino
  startEthernet();
}

void loop()
{
  // Read value from Analog Input Pin 0
  String humidity = String(tph.readHumidity());
  String pressure = String(tph.readPressure());
  String tempSHT = String(tph.readTemperatureSHT());
  String tempBMP = String(tph.readTemperatureBMP());
  
  // Print Update Response to Serial Monitor
  while (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  // Disconnect from ThingSpeak
  if (!client.connected() && lastConnected)
  {
    Serial.println();
    Serial.println("... disconnected");
    
    client.stop();
  }
  
  // Update ThingSpeak
  if(!client.connected() && (millis() - lastConnectionTime > updateThingSpeakInterval)) {
    String tmp = "&field1="+humidity+"&field2="+tempSHT+"&field3="+pressure+"&field4="+tempBMP;
    Serial.println(tmp);
    updateThingSpeak(tmp);
  }
  
  // Check if Arduino Ethernet needs to be restarted
  if (failedCounter > 3 ) {startEthernet();}
  
  lastConnected = client.connected();
}

void updateThingSpeak(String tsData)
{
  if (client.connect(server, 80))
  {         
    client.println("POST /update HTTP/1.1");
    client.println("Host: api.thingspeak.com");
    client.println("Connection: close");
    client.println("X-THINGSPEAKAPIKEY: "+writeAPIKey);
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.print("Content-Length: ");
    client.println(tsData.length());
    client.println();
    client.println();

    client.print(tsData);
    lastConnectionTime = millis();
    
    if (client.connected()) {
      Serial.println("Connecting to ThingSpeak...");     
      failedCounter = 0;
    } else {
      failedCounter++;
      Serial.println("Connection to ThingSpeak failed ("+String(failedCounter, DEC)+")");   
    }   
  } else {
    failedCounter++;
    
    Serial.println("Connection to ThingSpeak Failed ("+String(failedCounter, DEC)+")");   
    lastConnectionTime = millis(); 
  }
}

void startEthernet()
{
  client.stop();
  Serial.println("Connecting Arduino to network ...");
  while(!Serial);
  
  // Connect to network amd obtain an IP address using DHCP
  if (Ethernet.begin(mac) == 0)  {
    Serial.println("DHCP Failed, reset Arduino to try again");
  } else {
    Serial.print("Arduino connected to network using DHCP, IP: ");
    Serial.println(Ethernet.localIP());
  }  
  while(!Serial);
}

Schematics

Schematic
Iotopia bb

Comments

Similar projects you might like

The Inventive Toothbrush

Project showcase by Ruben Mortier

  • 9,802 views
  • 4 comments
  • 12 respects

A.S.C.H. (Automatic Safe Chicken House)

Project showcase by Team A.S.C.H.

  • 5,908 views
  • 2 comments
  • 24 respects

Water Quality Monitoring and Notification System

Project showcase by emmanuel ani

  • 50,597 views
  • 38 comments
  • 112 respects

MultiFunctional Clock

Project in progress by 3 developers

  • 15,853 views
  • 2 comments
  • 28 respects

The Train of the Future

Project showcase by Team STT

  • 14,074 views
  • 5 comments
  • 19 respects

Indoor Air Quality Monitoring System

Project tutorial by Team East West University

  • 10,717 views
  • 3 comments
  • 45 respects
Add projectSign up / Login