Stazione comunitaria statica per polveri sottili PM10 + WiFi da 30 euro
Seguite il tutorial : questo post
1) togliete il sensore di temperatura e inserite il Dust Sensor PM10 da 18 euro..
2) Rispetto al post “Rilevazione PM10 con #arduino e stazione wifi mobile da 50euro” non ho necessità del GPS e quindi risparmio anche sull’Arduino UNo
Praticamente tutta la stazione si basa su alimentazione, scheda ESP8266, un led e il sensore Dust Grove
Il sensore deve essere rivolto verso l’alto “in piedi” quindi e non vanno coperte le due finestrelle come da figura mentre il fori centrale va chiuso con nastro:
Al solito vi fate un account su data.sparkfun.com e la scheda WiFi si collega in hotspot tramite il vostro cellurare oppure se presente, con un Wifi permanente.
Eccovi il codice. Attenzione usate Arduino IDE ma dovete mettere ESP8266 come scheda!! (leggete il post che vi ho indicato all’inizio):
/*
* This sketch sends data via HTTP GET requests to data.sparkfun.com service.
*
* You need to get streamId and privateKey at data.sparkfun.com and paste them
* below. Or just customize this script to talk to other HTTP servers.
*
*/
#define LOCATIONID "6542124" // 6542124 location id for Lecce, 3165185 Trieste on openweathermap.org
#define APIID "" // free api key on api.openweathermap.org
#include < ESP8266WiFi.h>
bool dry=false;
bool rain=false;
float c=1.0;
int pin = 0; // GPIO 0 pin of Dust Sensor
// #define ESP8266_LED 2
int ESP8266_LED = 2;
const char* ssid = "Piersoft"; //inserire SID e chiave WEP
const char* password = "12345678";
const char* hostw="178.62.133.61"; //api.openweathermap.org
const char* host = "54.86.132.254"; //data.sparkfun.com
const char* publicKey = ""; //inserire public stream on data.sparkfun.com
const char* privateKey = ""; //inserire private key on data.sparkfun.com
const char* host1 = "184.106.153.149"; //api.thinspeak.com (for twitter)
const char* privateKey1=""; //inserire privatekey thinkspeak twitter
unsigned long duration;
unsigned long starttime;
unsigned long sampletime_ms = 30000; //30000 for 30 sec, 300000 for 5 minutes
unsigned long lowpulseoccupancy = 0;
float ratio = 0;
float concentration = 0;
float concentration25 = 0;
float concentrationmg = 0;
float concentrationmg25 = 0;
int humidit=0.0;
String main="";
float pm10corretto=0;
float pm25corretto=0;
void setup() {
Serial.begin(9600);
pinMode(pin,INPUT);
starttime = millis();
pinMode(ESP8266_LED, OUTPUT);
delay(2000);
pinMode(ESP8266_LED, LOW);
// We start by connecting to a WiFi network
Serial.println();
Serial.println();
Serial.print("ESP8266 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());
Serial.print("connecting to ");
Serial.println(hostw);
// Use WiFiClient class to create TCP connections
WiFiClient client;
const int httpPort = 80;
if (!client.connect(hostw, httpPort)) {
Serial.println("connection failed");
setup();
//return;
}
// We now create a URI for the request
String url="/data/2.5/weather";
url+="?id=";
url += LOCATIONID;
url +="&APPID=";
url +=APIID;
Serial.print("Requesting URL: ");
Serial.println(url);
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: "+hostw+"\r\nConnection: keep-alive\r\n\r\n");
delay(500);
// read response
String response = "";
while(client.available()){
String line = client.readStringUntil('\r');
// Serial.print(line);
if (line.indexOf("{") >=0){
response += (line);
}
if (line.indexOf("Clear") >=0 || line.indexOf("clear") >=0){
dry=true;
rain=false;
}
else if (line.indexOf("rain") >=0 || line.indexOf("Rain") >=0){
rain=true;
dry=false;
}
delay(1);
}
Serial.println();
Serial.print("Main: ");
Serial.println(getValuesFromKey(response, "main"));
main=getValuesFromKey(response, "main");
Serial.print("Temp: ");
Serial.print(getValuesFromKey(response,"temp").toInt()-272.15); // kelvin to celsius
Serial.println(" C°");
Serial.print("Humidity: ");
Serial.print(getValuesFromKey(response, "humidity"));
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(getValuesFromKey(response, "pressure"));
Serial.println(" hPa");
Serial.println();
humidit=(getValuesFromKey(response, "humidity").toInt());
correction(humidit);
}
void(* resetFunc) (void) = 0;
int value=0;
void loop() {
value++;
pinMode(ESP8266_LED, LOW);
if (value<=30)
{
duration = pulseIn(pin, LOW);
lowpulseoccupancy = lowpulseoccupancy+duration;
if ((millis()-starttime) > sampletime_ms)
{
ratio = lowpulseoccupancy/(sampletime_ms*10.0); // Integer percentage 0=>100
concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // using spec sheet curve
// secondo me nella formula seguente / 10 secondo le formule sui siti no..ma i valori sono troppo alti anche se coerenti
concentrationmg= concentration * 0.427 /10;
concentrationmg25= concentration * 0.0020800535;
// 0.427 proviene da :
// http://www.fijnstofmeter.com/documentatie/Data-Validation.pdf
// assumendo PM10 "The mass of a particle in the PM10 channel is 1.21E-4 μg"
// PM concentration μg/m3 --> concentration × 3531.5 × Particles Mass
// 3531.5 è la conversione tra feet3 e m3
// ergo 3531.5 x 1.21 / 10000 = 0.4273 per il PM10
// The mass of a particle in the PM2.5 channel is 5.89E-7 μg
// analogamente 3531.5 x 5.89 / 10000000 = 0.0020800535
// tabelle di correzione --> fattore c
// PM10 corretto = PM10 non corretto x humidit/100 x c
pm10corretto=concentrationmg;
pm25corretto=concentrationmg25;
if (dry==true || rain == true) {
pm10corretto=concentrationmg*c*humidit/100;
pm25corretto=concentrationmg25*c*humidit/100;
}
if (pm10corretto <= 0.1 ) resetFunc();
lowpulseoccupancy = 0;
starttime = millis();
}
}else{
WiFiClient client;
const int httpPort = 80;
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
//setup();
return;
}
Serial.print(lowpulseoccupancy);
Serial.print(",");
Serial.print(ratio);
Serial.print(", concentrazione particelle per 0.0.1 feet3: ");
Serial.println(concentration);
Serial.print(",PM10 ug/m3 non corretto: ");
Serial.println(concentrationmg);
Serial.print("PM10 corretto: ");
Serial.println(pm10corretto);
Serial.print(", PM2.5 ug/m3 non corretto: ");
Serial.println(concentrationmg25);
Serial.print("PM25 corretto: ");
Serial.println(pm25corretto);
// We now create a URI for the request
String url2 = "/input/";
url2 += publicKey;
url2 += "?private_key=";
url2 += privateKey;
url2 += "&pm10=";
url2 += pm10corretto;
url2 += "&pm2_5=";
url2 += pm25corretto;
url2 += "&hum=";
url2 += humidit;
url2 += "&ic=";
url2 += c;
url2 += "&status=";
url2 += main;
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
// resetFunc();
return;
}
Serial.print("Requesting UR2L: ");
Serial.println(url2);
// This will send the request to the server
client.print(String("GET ") + url2 + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
pinMode(ESP8266_LED, HIGH);
delay(200);
pinMode(ESP8266_LED, LOW);
delay(200);
while(client.available()){
String line = client.readStringUntil('\r');
Serial.print(line);
}
Serial.println();
Serial.println("closing connection Data");
if (pm10corretto > 50 ) { // la normativa prevede il max 50ug/m3 medie giornaliere da superare max 35 volte in una anno
WiFiClient client;
const int httpPort = 80;
Serial.print("connecting to ");
Serial.println(host1);
if (!client.connect(host1, httpPort)) {
Serial.println("connection Twitter failed");
// resetFunc();
return;
}
String url1 = "/apps/thingtweet/1/statuses/update?";
url1 += "api_key=";
url1 += privateKey1;
//cambiare @piersoft con proprio account se si vuole essere taggati
url1 += "&status=@piersoft Concentrazione PM10: ";
url1 += pm10corretto;
url1 += " qualcuno perlomeno sta fumando.. ";
delay(10);
Serial.print("Requesting URL Twitter: ");
Serial.println(url1);
// This will send the request to the server
client.print(String("GET ") + url1 + " HTTP/1.1\r\n" +
"Host: " + host1 + "\r\n" +
"Connection: close\r\n\r\n");
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
pinMode(ESP8266_LED, HIGH);
delay(100);
pinMode(ESP8266_LED, LOW);
delay(100);
//setup();
// Read all the lines of the reply from server and print them to Serial
while(client.available()){
String line = client.readStringUntil('\r');
Serial.print(line);
}
Serial.println();
Serial.println("closing connection Twitter");
}
delay(15*60000); // restart ogni 5 minuti
resetFunc();
}
}
String getValuesFromKey(const String response, const String sKey)
{
String sKey_ = sKey;
sKey_ = "\"" + sKey + "\":";
char key[sKey_.length()];
sKey_.toCharArray(key, sizeof(key));
int keySize = sizeof(key)-1;
String result = "";
int n = response.length();
for(int i=0; i < (n-keySize-1); i++)
{
char c[keySize];
for(int k=0; k
// PM10 corretto = PM10 non corretto x humidit/100 x c
Serial.print("humidity intera: ");
Serial.println(hum);
if (dry==true){
if (0<=hum && hum<=19) c=10.1;
else if (20<=hum && hum<=24) c=8.75;
else if (25<=hum && hum<=29) c=8;
else if (30<=hum && hum<=34) c=8;
else if (35<=hum && hum<=39) c=8;
else if (40<=hum && hum<=44) c=7;
else if (45<=hum && hum<=49) c=6;
else if (50<=hum && hum<=54) c=5.75;
else if (55<=hum && hum<=59) c=5.5;
else if (60<=hum && hum<=64) c=5.5;
else if (65<=hum && hum<=69) c=3.5;
else if (70<=hum && hum <=74) c=3.5;
else if (75<=hum && hum<=79) c=3.75;
else if (80<=hum && hum <=84) c=2.25;
else if (85<=hum && hum <=89) c=1.5;
else if (90<=hum && hum<=94) c=0.825;
else if (95<=hum && hum<=100) c=0.525;
}
else if (rain==true){
if (0<=hum && hum<=19) c=6.4;
else if (20<=hum && hum<=24) c=6.4;
else if (25<=hum && hum<=29) c=6.4;
else if (30<=hum && hum<=34) c=6.4;
else if (35<=hum && hum<=39) c=6.4;
else if (40<=hum && hum<=44) c=6.3;
else if (45<=hum && hum<=49) c=6.3;
else if (50<=hum && hum<=54) c=5.7;
else if (55<=hum && hum<=59) c=5.5;
else if (60<=hum && hum<=64) c=4.2;
else if (65<=hum && hum<=69) c=4.1;
else if (70<=hum && hum<=74) c=3.2;
else if (75<=hum && hum<=79) c=3.2;
else if (80<=hum && hum<=84) c=2.1;
else if (85<=hum && hum<=89) c=2.1;
else if (90<=hum && hum<=94) c=0.8;
else if (95<=hum&& hum<=100) c=0.5;
}
Serial.print("indice correzione: ");
Serial.println(c);
//float pm10=5; //prova correzione pm10 con valore fittizio
//float pm10corretto=pm10*c*hum/100;
//Serial.print("PM10 corretto: ");
//Serial.println(pm10corretto);
}
Nota metodologica:
1) il sensore è al buio (messo nastro isolante nel foro centrale) e inserito in una scatola con prese d'aria.
2) la formula di conversione tra particelle e microgrammi e feet al cubo e m3 è un'approssimazione come descritto su http://www.fijnstofmeter.com/documentatie/Data-Validation.pdf ed è relativa al PM10 --> 1,21E-4 (PM10) x 3531.5 x concentrazione particelle /100 --> 0.427 x concentrazione particelle / 100
Quindi il codice usa le API di api.openweathermap.org per prendere le rilevazioni dell'umidità e del fatto che ci sia pioggia o sereno e viene applicata questa tabella correttiva:
e quindi applicata la formula finale:
nel codice le parti da personalizzare sono SSID e Password WEP, la key pubblica e privata di data.sparkfun.com, la key privata dell'app twitter collegata con proprio account thinkspeak, l'api key su openweathermap.org