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 fablabs, « 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).
Le tout début de l'histoire commence avec un capteur de particule, un arduino et un montage sur breadboard. Ca fonctionne mais ce n'est absolument pas mobile puisque qu'il faut un ordinateur relié à la carte arduino pour pouvoir affichée les mesures du capteur.
Pour le second prototype, nous nous sommes largement inspiré du projet OpenGeiger qui était en cours de réalisation au même moment au fablab et nous avons utilisé un RFduino, “sorte d'arduino” capable d'envoyer des données en Bluetooth à un smartphone. Nous avons réalisés des tests avec plusieurs capteurs pour tester leur variabilité.
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.
La conception a été réalisé sous le logiciel Eagle. Le schéma sous Eagle est le suivant
Et le PCB
Le premier proto a été réalisé entièrement au fablab (le PCB a été réalisé par gravure chimique et les vias ont été réalisés “à la main”).
Le Code Source du firmware téléversé dans le RFduino est le suivant:
//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(); }
La station transmet environ toute les secondes la concentration en particules PM2.5 (en particules per cubic feet), la temperature (en degrés Celcius), l'humidité relative (pourcentage) et le niveau de charge de la batterie.