W Zimie głośno było o smogu: pyłach i ogólnie o jakości powietrza, którym oddychamy na co dzień. W większych miastach są stacje, które mierzą stopień zanieczyszczenia powietrza ale jest ich niewiele więc o ile nie mieszkamy blisko takiej stacji to te informacje mogą jedynie pomóc zorientować się w jakim stopniu powietrze jest zanieczyszczone w danym regionie. Bieżące dane z całej Polski można znaleźć tutaj. Ponieważ chciałem mieć dokładne dane jaki jest stan powietrza w mieszkaniu postanowiłem samemu sklecić układ, który mierzyłby jego jakość.
Dla dociekliwych i wybrednych zadających sobie pytanie co to ma wspólnego z chmurą obliczeniową podaję hasło po którym wszystko powinno stać się jasne Internet Of Things 🙂
Wykorzystane moduły
Wybór padł na Arduino. Nie potrzebuję układu z systemem operacyjnym do wykonywania bardziej złożonych zadań (np. Raspberry Pi). To na czym mi zależy to jedynie pomiar jakości powietrza wraz z jego temperaturą i wilgotnością. No i fajnie by było odkładać te dane gdzieś online tak żeby można było je sprawdzać jak nie ma mnie w domu. Do tego wszystkiego Arduino spokojnie wystarczy.
Poniżej podaję nazwy układów wraz z krótkim opisem ich funkcji:
- Arduino Uno WiFi – główny mikrokontroler. To do niego wszystko trzeba podłączyć i to on zapewnia komunikację zarówno modułów jak i z siecią Internet za pomocą wbudowanego WiFi
- Base Shield v2 Grove – nakładka na główny mikrokontroler. Zapewnia łatwy sposób na podłączanie modułów do mikrokontrolera
- MP503 v1.3 – mierzy ogólny poziom zanieczyszczenia powietrza czyli stężenie szkodliwych gazów. UWAGA: nie dostarcza danych na temat stężenia konkretnego gazu, raczej informuje o ogólnym poziomie jakości powietrza
- Shinyei PPD42NS – mierzy zawartość pyłu PM2,5 oraz PM10 w powietrzu. To właśnie te cząsteczki tworzą smog. Te o średnicy mniejszej niż 2,5 µm są bardziej szkodliwe ponieważ są mniejsze i mogą dostawać się do krwioobiegu i ogólnie są gorsze dla organizmu
- TH02 – mierzy temperaturę i wilgotność powietrza. Niestety z tym modułem jest trochę kłopotów ale o tym za chwilę
Teraz to wszystko trzeba jeszcze ze sobą połączyć. Dzięki nakładce Grove jest to proste – wystarczy wpiąć odpowiedni kabelek do wyjścia. Poniżej zamieszczam zdjęcia jak to połączyłem.
Jedna ważna uwaga – to że czujnik cząstek stałych wisi sobie pionowo a reszta luźno lata to nie przypadek. Tylko pionowo umieszczony czujnik zapewnia prawidłowe pomiary. W dodatku warto zakleić/zakryć otwór na wierzchniej stronie czujnika – dzięki temu pomiary będą bardziej wiarygodne. Czujnik mierzy cząstki przez niego przefruwające laserem więc jakiekolwiek dodatkowe źródło światła może zakłamać pomiary.
Pewnie zauważyliście że czujnik Shinyei do płytki Grove jest podłączony przez „przejściówkę” 😉 Sposób połączenia jest następujący (skrajny lewy kabelek czujnika cząstek stałych zostaje nie podłączony):
Jeśli chcemy podłączyć się do sieci należy jeszcze wykonać czynności opisane w tym miejscu. A jak już podłączymy się do sieci to możemy pokusić się o przesyłanie danych pomiarowych do jakiegoś serwisu, który ładnie je wyświetli i pokaże w przeglądarce 🙂
Zbieranie danych i ich dalsza obróbka
Wybrałem Thingspeak, ponieważ zapewnia darmowy interfejs i wizualizację przesłanych danych na wykresie. Nic więcej mi do szczęścia nie potrzeba. Brałem pod uwagę jeszcze Xively ale coś się pozmieniało w ich polityce i zrobili się bardziej komercyjni więc ostatecznie padło na Thingspeak. Po założeniu konta należy utworzyć kanał do którego będziemy pisać (nie ma znaczenia czy kanał jest publiczny lub prywatny) i skopiować jego klucz API (write API key). Ten klucz będzie nam potrzebny przy programowaniu układu do zdefiniowania połączenia czujnik – Thingspeak.
Programowanie układu
Czas ubrudzić sobie ręce 🙂 Do programowania Arduino wykorzystujemy Arduino IDE oraz język C/C++. IDE należy pobrać oraz zainstalować z oficjalnej strony Arduino. Mikrokontroler można połączyćz IDE na dwa sposoby: WiFi lub USB. Wystarczy podpiąć kabel lub sieć i z poziomu aplikacji wybrać rodzaj mikrokontrolera, który będziemy programować (menu Tools). Następnie w tym samym menu rozwijamy Port i tam wybieramy odpowiednio port COM po którym będziemy się łączyć (jeśli przez USB) lub adres IP urządzenia (jeśli przez sieć). Teraz do mikrokontrolera można wgrywać programy i dać mu coś do roboty.
Przykładowe mniej lub bardziej skomplikowane programy znajdują się w menu File – Examples. Możemy z nich skorzystać żeby zorientować się jak to działa i zdiagnozować czy układ programuje się poprawnie (np. czy diody świecą poprawnie). Program ładuje się do układu przyciskiem ze strzałeczką skierowaną w prawo zaraz pod menu (File, Edit, itd.) aplikacji lub skrótem klawiaturowym Ctrl + U. Najpierw następuje kompilacja a następnie próba załadowania programu do pamięci mikrokontrolera. W razie jakichś problemów zostaniecie o tym poinformowani przez IDE. Po załadowaniu programu do pamięci, natychmiast rozpoczyna on swoją pracę.
Po tym wprowadzeniu omówię kod składający się na program wykonywujący się w moim układzie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
#include <UnoWiFiDevEd.h> #include <TH02_dev.h> #include "AirQuality.h" #include "Arduino.h" #define CONNECTOR "rest" #define SERVER_ADDR "api.thingspeak.com" #define APIKEY_THINGSPEAK "xxxxxxxxxxxxxxx"; unsigned long starttime; unsigned long sampletime_ms = 3600000; // TIME BETWEEN MEASURES AND UPDATES unsigned long triggerOnP2; unsigned long triggerOffP2; unsigned long pulseLengthP2; unsigned long durationP2; boolean valP2 = HIGH; boolean triggerP2 = false; float ratioP2 = 0; float countP2; float concLarge; unsigned long triggerOnP1; unsigned long triggerOffP1; unsigned long pulseLengthP1; unsigned long durationP1; boolean valP1 = HIGH; boolean triggerP1 = false; float ratioP1 = 0; float countP1; float concSmall; AirQuality airqualitysensor; int current_quality =-1; void setup() { pinMode(8, INPUT); // > 1um starttime = millis(); // Power up,delay 150ms,until voltage is stable for TH module to work delay(150); // Reset HP20x_dev //TH02.begin(); delay(100); Ciao.begin(); Ciao.println("Ciao is ready"); airqualitysensor.init(14); } void measure(){ valP1 = digitalRead(8); // > 1um valP2 = digitalRead(9); // > 2.5um if(valP1 == LOW && triggerP1 == false){ triggerP1 = true; triggerOnP1 = micros(); } if (valP1 == HIGH && triggerP1 == true){ triggerOffP1 = micros(); pulseLengthP1 = triggerOffP1 - triggerOnP1; durationP1 = durationP1 + pulseLengthP1; triggerP1 = false; } if(valP2 == LOW && triggerP2 == false){ triggerP2 = true; triggerOnP2 = micros(); } if (valP2 == HIGH && triggerP2 == true){ triggerOffP2 = micros(); pulseLengthP2 = triggerOffP2 - triggerOnP2; durationP2 = durationP2 + pulseLengthP2; triggerP2 = false; } if ((millis() - starttime) > sampletime_ms) { // Integer percentage 0=>100 ratioP1 = durationP1/(sampletime_ms*10.0); // Integer percentage 0=>100 ratioP2 = durationP2/(sampletime_ms*10.0); countP1 = 1.1*pow(ratioP1,3)-3.8*pow(ratioP1,2)+520*ratioP1+0.62; countP2 = 1.1*pow(ratioP2,3)-3.8*pow(ratioP2,2)+520*ratioP2+0.62; float PM10count = countP1; float PM25count = countP1 - countP2; //PM10 count to mass concentration conversion double r10 = 2.6*pow(10,-6); double pi = 3.14159; double vol10 = (4/3)*pi*pow(r10,3); double density = 1.65*pow(10,12); double mass10 = density*vol10; double K = 3531.5; concLarge = (PM10count)*K*mass10; //PM2.5 count to mass concentration conversion double r25 = 0.44*pow(10,-6); double vol25 = (4/3)*pi*pow(r25,3); double mass25 = density*vol25; concSmall = (PM25count)*K*mass25; // Measure the temperature //float temper = TH02.ReadTemperature(); // Measure the humidity //float humidity = TH02.ReadHumidity(); // Check the air pollution (harmful gases) current_quality=airqualitysensor.slope(); String uri = "/update?api_key="; uri += APIKEY_THINGSPEAK; uri +="&field1="; uri += String(concLarge); uri +="&field2="; uri += String(concSmall); uri +="&field3="; uri += String(temper); uri +="&field4="; uri += String(humidity); uri +="&field5="; uri += String(current_quality); Ciao.println("Send data on ThingSpeak Channel"); CiaoData data = Ciao.write(CONNECTOR, SERVER_ADDR, uri); if (!data.isEmpty()){ Ciao.println("PM10: " + String(concLarge)); Ciao.println("PM2.5: " + String(concSmall)); Ciao.println( "State: " + String (data.get(1)) ); Ciao.println( "Response: " + String (data.get(2)) ); } else{ Ciao.println("Write Error"); } //Reset Values durationP1 = 0; durationP2 = 0; starttime = millis(); } } void loop() { measure(); // Recall of the measure program } ISR(TIMER1_OVF_vect) { if(airqualitysensor.counter==61)//set 2 seconds as a detected duty { airqualitysensor.last_vol=airqualitysensor.first_vol; airqualitysensor.first_vol=analogRead(A0); airqualitysensor.counter=0; airqualitysensor.timer_index=1; PORTB=PORTB^0x20; } else { airqualitysensor.counter++; } } |
Na samym początku definiuję stałe z namiarem na moje konto do Thingspeak – najważniejszy jest klucz API – można go znaleźć w ustawieniu swojego kanału w Thingspeak. Sam skrypt jest podzielony na 3 główne funkcje: setup, loop i measure:
- Setup: inicjuje moduły i piny z których będą odczytywane dane
- Loop: funkcja która w pętli wykonuje swoje zadania. W moim przypadku w pętli wykonuje się funkcja measure, o której poniżej
- Measure: to tutaj są wykonywane pomiary i obliczenia
Jak się przyjrzycie to zauważycie że moduł pomiaru temperatury i wilgotności jest nieużywany. Testowałem tą konfigurację długo i wyszło na to że ten czujnik który jest podłączany za pomocą interfejsu I2C gryzie się z biblioteką Ciao, którą wykorzystuję do łączenia się z WiFi i wyświetlania informacji diagnostycznych w konsoli WiFi (na stronce urządzenia). Pytałem na różnych forach ale nigdzie nie uzyskałem żadnej odpowiedzi jak ten konflikt rozwiązać, więc póki co zbieram dane tylko na temat cząstek PM2,5 i PM10 w powietrzu jak i o ogólnej jego jakości. Odstępy czasu co ile zbieram dane ustalam w zmiennej sampletime_ms. Jest to wartość w milisekundach więc 3600000 oznacza że dane będą szły do Thingspeak co godzinę.
Ogólna jakość powietrza jest wyliczana z wartości napięcia czujnika i w efekcie będzie on zwracał wartości liczbowe oznaczające poziom zanieczyszczenia powietrza. Dokładny sposób działania jest wyjaśniony tutaj.
Poniżej wykresy które generuje dla mnie Thingspeak na podstawie danych mierzonych i wysyłanych przez Arduino:
I to w zasadzie wszystko. Jeśli uda się wam pogodzić bibliotekę Ciao z czujnikiem TH02 to dajcie proszę znać 🙂 A ja w tym czasie pomyślę jak te dane wykorzystać w chmurze.
Poniżej jeszcze pomocne linki: