Ceci est une ancienne révision du document !
Chaque jour, l’air que nous respirons est plus ou moins pollué, chaud et humide. Il varie selon l’heure, selon la météo et les sources d’émission de polluants, selon vos activités (à l’intérieur ou à l’extérieur, à proximité du trafic, dans un parc, …). Quel est l’air que vous respirez ? Il nous entoure, on le respire… mais il est invisible, sauf en cas de forte pollution. L’évaluation de la qualité de l’air (confort thermique et pollution) est possible par la mesure de paramètres météorologiques et de polluants atmosphériques.
Au quotidien, nous sommes informé(e)s sur la qualité de l’air et la météo, via les médias, sites internet et panneaux d’affichage municipaux. Pour l’Île-de-France, cette information est principalement issue des mesures de l’air par MétéoFrance et AirParif, mesures réalisées ponctuellement dans des stations et complétée par des modèles. Elles ne sont pas forcément représentatives de notre propre exposition à la pollution de l'air selon nos activités et des changements de température et d'humidité selon les espaces fréquentés.
Les fablab, « laboratoire de fabrication numérique », mettent à disposition des outils permettant à tous de créer des objets, électroniques ou non, tels que des drones, des figurines ou encore des capteurs environnementaux. Ils font partie d’un mouvement de démocratisation de la science où chaque citoyen peut créer des capteurs et collecter des données grâce à des tutoriaux ouverts à tous, disponibles généralement sur internet, et à de l’électronique à bas prix. La collecte de données citoyennes permet à chacun de créer sa propre information sur ses expositions environnementales par exemple, voire d’enrichir une base de données scientifiques (sciences citoyennes).
Nous avons développé une première version du capteur en Septembre 2015. Cette version se présente sous la forme d'un kit et comprend deux capteurs: un capteur de température et d'humidité relative (DHT22) et un capteur de particules fines (SHARP GP2Y1010AU0F).
La communication vers le smartphone (Android ou iOS) se fait via Bluetooth Low Energy (4.0) grâce à un RFDuino.
Le schéma sous Eagle
Et le PCB
Code Source :
//Bibliothèques utilisées
#include "DHT.h"
#include <RFduinoBLE.h>
//A commenter pour désactiver les messages de debug sur le port série
#define DEBUG
//Déclaration du pinout
#define BATT_SENS_PIN 2
#define DUST_SENSOR_PIN 3
#define LED_POWER_PIN 4
#define DHT_PIN 5
#define LEDS_PIN 6 // Indicateurs à leds
#define LED_AIR 0
#define LED_HUMIDEX 1
#define LED_BLUETOOTH 2
#define NB_PIXELS 3
//RGB pour traversants, GRB pour CMS
//#define LED_MODE_GRB
const int nb_leds = NB_PIXELS*3;
uint8_t leds[nb_leds];
//Initialisation de la bibliothèque DHT
DHT dht(DHT_PIN, DHT22);
// Variables globales
int i;
float ppm;
long last_time;
int sample_rate = 1000; // en millisecondes
boolean ble_connected = false;
union _floatToChar { // Utilitaire de conversion
float f;
char c[4];
} floatToChar;
void setup() {
#ifdef DEBUG
Serial.begin(9600); // Initialisation du port série de debug
Serial.println("OpenAir !");
#endif
pinMode(LEDS_PIN, OUTPUT);
setRGB(LED_BLUETOOTH, 0, 55, 200);
setRGB(LED_AIR, 0, 55, 200);
setRGB(LED_HUMIDEX, 0, 55, 200);
showLeds();
analogReference(VBG); // Référence de 1.2V interne
dht.begin(); // Initialisation du DHT22
pinMode(BATT_SENS_PIN, INPUT); // Initialisation du moniteur de batterie
pinMode(LED_POWER_PIN, OUTPUT); // Initialisation du capteur de particules fines
pinMode(DUST_SENSOR_PIN, INPUT);
i = 0;
ppm = 0;
last_time = millis(); // Initialisation de la base de temps
RFduinoBLE.deviceName = "OpenAir"; // Initialisation de la connexion bluetooth
RFduinoBLE.advertisementData = "FDS06"; // Numéro de série
RFduinoBLE.begin();
setRGB(LED_BLUETOOTH, 0, 0, 0);
setRGB(LED_AIR, 0, 0, 0);
setRGB(LED_HUMIDEX, 0, 0, 0);
showLeds();
}
void loop() {
// On vérifie que la radio n'utilise pas les ressources
while (!RFduinoBLE.radioActive);
while (RFduinoBLE.radioActive);
// Lecture du capteur de particules fines
i++;
digitalWrite(LED_POWER_PIN, LOW);
delayMicroseconds(280);
ppm += analogRead(DUST_SENSOR_PIN)*3.6 / 1023.0;
delayMicroseconds(40);
digitalWrite(LED_POWER_PIN, HIGH);
delayMicroseconds(9680);
if (millis() - last_time> sample_rate) {
float voltage = ppm / i;
float ppmpcf = (voltage - 0.0356) * 120000 / 100;
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
float batterie = (float) analogRead(BATT_SENS_PIN) * 3.6 * 2.0 / 1023.0 ;
#ifdef DEBUG
if (isnan(temperature) || isnan(humidity)) {
Serial.println("Erreur lors de la lecture du DHT22");
}
#endif
sendData(ppmpcf, humidity, temperature, batterie);
setLedAirQuality(ppmpcf);
setLedHumidex(humidity, temperature);
setRGB(LED_BLUETOOTH, 0, 0, (ble_connected) ? 255 : 0);
showLeds();
i = 0;
ppm = 0;
last_time = millis();
}
}
void RFduinoBLE_onConnect() {
ble_connected = true;
// Selection d'un interval de connection plus lent, pour économiser la batterie et
// laisser le temps aux zones de code critiques de s'executer sans que la radio reprenne
// la main.
RFduinoBLE_update_conn_interval(900, 1000);
}
void RFduinoBLE_onDisconnect() {
ble_connected = false;
}
void RFduinoBLE_onReceive(char *data, int len) {
if (len> 0) {
Serial.println(data);
}
}
void sendData(float ppmpcf, float humidity, float temperature, float batterie) {
#ifdef DEBUG
Serial.print("ppmpcf: ");
Serial.print(ppmpcf);
Serial.print("\tHumidity: ");
Serial.print(humidity);
Serial.print(" %\tTemperature: ");
Serial.print(temperature);
Serial.print(" *C\t Batterie :");
Serial.print(batterie);
Serial.println(" V");
#endif
char data[16];
floatToChar.f = ppmpcf;
data[0] = floatToChar.c[0];data[1] = floatToChar.c[1];
data[2] = floatToChar.c[2];data[3] = floatToChar.c[3];
floatToChar.f = humidity;
data[4] = floatToChar.c[0];data[5] = floatToChar.c[1];
data[6] = floatToChar.c[2];data[7] = floatToChar.c[3];
floatToChar.f = temperature;
data[8] = floatToChar.c[0];data[9] = floatToChar.c[1];
data[10] = floatToChar.c[2];data[11] = floatToChar.c[3];
floatToChar.f = batterie;
data[12] = floatToChar.c[0];data[13] = floatToChar.c[1];
data[14] = floatToChar.c[2];data[15] = floatToChar.c[3];
RFduinoBLE.send(data, 16);
}
void setLedAirQuality(float ppmpcf) {
if (ppmpcf <75) { // Excellent
setRGB(LED_AIR, 0, 255, 0);
} else if (ppmpcf <150) { // Very good
setRGB(LED_AIR, 55, 200, 0);
} else if (ppmpcf <300) { // Good
setRGB(LED_AIR, 110, 145, 0);
} else if (ppmpcf <1050) { // Fair
setRGB(LED_AIR, 145, 110, 0);
} else if (ppmpcf <3000) { // Poor
setRGB(LED_AIR, 200, 55, 0);
} else { // Very poor
setRGB(LED_AIR, 255, 0, 0);
}
}
// https://fr.wikipedia.org/wiki/Indice_humidex
// http://www.physlink.com/Education/AskExperts/ae287.cfm
void setLedHumidex(float H, float T) {
float e = 6.112 * pow(10, 7.5*T / (237.7+T)) * H/100.0;
float humidex = T + 5.0/0.9 * (e - 10.0);
#ifdef DEBUG
Serial.print("Humidex : ");
Serial.println(humidex);
#endif
if (humidex <29) { // Aucun inconfort
setRGB(LED_HUMIDEX, 0, 255, 0);
} else if (humidex <150) { // Un certain inconfort
setRGB(LED_HUMIDEX, 55, 200, 0);
} else if (humidex <300) { // Beaucoup d'inconfort : évitez les efforts
setRGB(LED_HUMIDEX, 110, 145, 0);
} else if (humidex <1050) { // Danger
setRGB(LED_HUMIDEX, 145, 110, 0);
} else { // Coup de chaleur imminent
setRGB(LED_HUMIDEX, 255, 0, 0);
}
}
void setRGB(int led, uint8_t r, uint8_t g, uint8_t b) {
#ifdef LED_MODE_GRB
leds[led*3] = g*0.3;
leds[led*3+1] = r*0.3;
#else
leds[led*3] = r*0.3;
leds[led*3+1] = g*0.3;
#endif
leds[led*3+2] = b*0.3;
}
void showLeds() {
noInterrupts();
for (int wsOut = 0; wsOut <nb_leds; wsOut++) {
for (int x=7; x>=0; x--) {
NRF_GPIO->OUTSET = (1UL <<LEDS_PIN);
if (leds[wsOut] & (0x01 <<x)) {
__ASM ( \
" NOP\n\t" \
" NOP\n\t" \
" NOP\n\t" \
" NOP\n\t" \
" NOP\n\t" \
);
NRF_GPIO->OUTCLR = (1UL <<LEDS_PIN);
} else {
NRF_GPIO->OUTCLR = (1UL <<LEDS_PIN);
__ASM ( \
" NOP\n\t" \
" NOP\n\t" \
" NOP\n\t" \
);
}
}
}
delayMicroseconds(50); // latch and reset WS2812.
interrupts();
}