Outils pour utilisateurs

Outils du site


wiki:projets:open-air:proto

Contexte du projet


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).

Premier prototype sur breadboard

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.

Second prototype sur breadboard (mobile)

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é.

Développement d'un prototype "en dur" v1

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.

Retour à la page principale du projet

wiki/projets/open-air/proto.txt · Dernière modification: 2017/09/17 17:08 de Vincent Dupuis