[Ostatnie zmiany]:
07-11-2017: Przykłady pobierania danych z zaawansowanych stacji, Integracja ze Open-Smog, promocja na czujnik
20-12-2017: Kontrola zasilania USB na Raspberry Pi 3, dzięki czytelnikowi
03-02-2018: Nowe API od GIOŚ Poland
08-02-2018: Odnajdywanie ID
SMOG stał się nieodłączną częścią życia w dużych miastach. Postarajmy się więc zbudować wiarygodny, własny czujnik SMOGu – na początek cząsteczek o wielkości 2,5 oraz 10 μm. Pewnym problemem staje się wybór czujnika, ponieważ niełatwo znaleźć taki, który łączy w sobie długą żywotność i precyzję pomiaru z przystępną ceną. W końcu – po zasięgnięciu opinii – zdecydowałem się na czujnik cząsteczek – Nova Fitness SDS011:
- zadowalająca precyzja pomiaru
- wentylator
- możliwość zamontowania wężyka
- duża trwałość
- wbudowany interfejs UART, wraz z konwerterem USB
- Niska cena całego rozwiązania: od około $40 przy użyciu Orange Pi Zero – do $60 jeśli używamy Raspberry Pi
Czego będziemy potrzebować?
Hardware:
- Raspberry Pi lub klon (Orange Pi) z wolnym portem USB
- czujnik Nova Fitness SDS011 w wersji z konwerterem USB-UART, możesz znaleźć najtaniej na Aliexpress.
Software:
- ulubiona dystrybucja Raspbian lub Armbian dla klonów
- podstawowe programy występujące jako opcje w dystrybucji Linuksa
Budowa, konfiguracja oraz wykresy
Urządzenie podłączamy możliwie krótkim (kilka cm) wężykiem, tak aby wlot pobierał z zewnątrz powietrze. Długi wężyk – 100cm jak na pierwszym zdjęciu – znacznie obniża rezultaty – zaniża pomiar.

Pamiętajmy także, że czujnik pracuje cały czas, więc być może warto spróbować załączać go tylko na czas pomiaru. W wersji pracującej u mnie – wybrałem dość proste rozwiązanie – wyłączam zasilanie po pomiarze, a przed pomiarem – załączam i czekam 60 sekund.

Kolejna ważna kwestia – czujnik staje się niewiarygodny, gdy wilgotność przekracza 70%, pamiętajmy o tym!
Najpierw zainstalujmy bc – potrzebny do obliczeń:
1 2 3 |
apt update apt upgrade apt install bc git |
Nie zapominajmy, że to pierwsza wersja naszego czujnika – i jak się domyślacie – będziemy modyfikować go pod kątem pomiarów wilgotności.
Komunikacja z urządzeniem jest bardzo prosta – po umieszczeniu w porcie USB otrzymujemy /dev/ttyUSB0 – z którego czytamy tak samo jak z innego interfejsu szeregowego:
1 2 |
/bin/stty -F /dev/ttyUSB0 9600 raw /usr/bin/od --endian=big -x -N10 < /dev/ttyUSB0 |
Po odczytaniu – sprawdzamy czy linia zaczyna się od „aac0”. Dane podawane szesnastkowo, little endian – a więc finalną wartość należy wyliczyć. Cały kod, łącznie z aktualizowaniem bazy Influx do rysowania w Grafanie przedstawia się następująco, mam nadzieję, że jest niezwykle przejrzysty:
Wersja dla dowolnego komputera, testowana na Orange Pi:
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 |
#!/bin/bash # Czytnik z SDS011, zakładamy port szeregowy na /dev/ttyUSB0 # Bazuje na: http://kuehnast.com/s9y/archives/633-Feinstaubmessung_mit_dem_Raspberry_Pi.html # lukasz.jokiel@gmail.com, 2017, NO WARRANTY, GPL v2 # # Zmienne - jeśli trzeba należy zmienić serial_port="/dev/ttyUSB0" #Włączamy port USB - zasilanie - dotyczy WYŁĄCZNIE Orange Pi (A20) /usr/bin/sunxi-pio -m PH26'' #Czekamy aż załączy się wentylator i ustablizuje przepływ powietrza sleep 60 #Główny program #Ustawiamy tryb portu szeregowego /bin/stty -F $serial_port 9600 raw #Czytamy dane - big-endian, szenastkowe RAW_DATA=`/usr/bin/od --endian=big -x -N10 < /dev/ttyUSB0 | /usr/bin/head -n 1 | /usr/bin/cut -f2-10 -d" "` HEADER=`echo $RAW_DATA | /usr/bin/awk '{print $1}'` #Probe for propper header if [ "$HEADER" = "aac0" ]; then #Let us cut the RAW DATA and put it into variables - data is in hexadecimals HEX_PPM25_L=$(echo $RAW_DATA|cut -f2 -d " "|cut -b1-2); HEX_PPM25_H=$(echo $RAW_DATA|cut -f2 -d " "|cut -b3-4); HEX_PPM10_L=$(echo $RAW_DATA|cut -f3 -d " "|cut -b1-2); HEX_PPM10_H=$(echo $RAW_DATA|cut -f3 -d " "|cut -b3-4); #Convert variables to decimals PPM25_L=$(echo $((0x$HEX_PPM25_L))); PPM25_H=$(echo $((0x$HEX_PPM25_H))); PPM10_L=$(echo $((0x$HEX_PPM10_L))); PPM10_H=$(echo $((0x$HEX_PPM10_H))); #More simple math PPM25=`echo "((${PPM25_H}*256)+${PPM25_L})/10" | bc` PPM10=`echo "((${PPM10_H}*256)+${PPM10_L})/10" | bc` #Update the local InfluxDB /usr/bin/curl -i -XPOST 'http://127.0.0.1:8086/write?db=smogdb' --data-binary "ppm25sds011 value=${PPM25}" /usr/bin/curl -i -XPOST 'http://127.0.0.1:8086/write?db=smogdb' --data-binary "ppm10sds011 value=${PPM10}" else echo HEADER ERROR fi #Turining OFF the USB ports for Orange Pi /usr/bin/sunxi-pio -m PH26'' |
Wersja dla Raspberry Pi 3
Dzięki czytelnikowi bloga – Piotrek Pilek, pozdrowienia, mamy sposób na identyczną kontrolę zasilania USB! (przy okazji – Piotr pracuje w Lantech w Szczecinie – popatrzcie na to niedocenione miasto – https://lantech.com.pl/internet_szczecin_oferta/transmisje-live/)
Kontrolę zasilania USB możemy uzyskać dzięki programowi Vadim Mikhailov, który stworzył oprogramowanie dla kilkunastu róznych hubów USB wraz z obsługą Raspberry Pi. Program instalujemy:
1 2 3 4 5 6 |
cd ~ sudo apt install libusb-1.0 libusb-dev git clone https://github.com/mvp/uhubctl cd uhubctl make sudo make install |
Następnie modyfikujemy linie w powyższym przykładzie:
1 |
/usr/bin/sunxi-pio -m PH26'' |
na
1 |
/usr/sbin/uhubctl -a off -p 2 |
A następnie analogicznie na końcu pliku:
1 |
/usr/bin/sunxi-pio -m PH26'' |
na
1 |
/usr/sbin/uhubctl -a on -p 2 |
Uwaga – wyłącza to wszystkie porty USB, ale nie wyłącza portu eth0 ani też wlan0
Rysujemy!
Teraz – podobnie jak wcześniej – rysujemy dane w Grafanie, definicje wyglądają następująco:
1 2 |
SELECT last("value") FROM "ppm25sds011" WHERE $timeFilter GROUP BY time(1m) fill(none) SELECT last("value") FROM "ppm10sds011" WHERE $timeFilter GROUP BY time(1m) fill(none) |
To wszystko!
Dzielenie się danymi, wysyłanie do innych systemów. Czy masz własny projekt?
Nasze dane od smogu są dostępne i bardzo łatwo możemy się nim podzielić. Jeśli prowadzisz własny projekt agregujacy dane – napisz proszę komenatarz lub emaila – dopiszę sposób w jaki można wysłać do konkretnych projektów dane.
Integracja z OpenSmog
Open-Smog to projekt zapoczątkowany jako realizacja pomysłu Artura Kurasińskiego. Po szczegóły zapraszam na Slacka: https://open-smog.slack.com/
Integracja sprowadza się do dodania do skryptu na końcu:
1 |
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d "[ { \"data\": { \"pm2_5\": ${PPM25}, \"pm10\": ${PPM10}, \"temp\": ${temperature}, \"hum\": ${humidity} } } ]" 'http://ADRES_WWW_OPENSMOG/v1/Sensors/TWOJEID/data' |
Obiecywałem, że będzie prosto, prawda ?
Pomiary, aktualizowane co 60 minut, możecie śledzić pod adresem http://pogoda.jokielowie.com/
Pobieranie danych i weryfikacja ze stacjami WIOŚ
Z pewnością każdego interesuje jak dokładny jest taki czujnik. Możemy bardzo szybko dodać do naszego skryptu dane z oficjalnych stacji WIOŚ: mapa znajduje się pod adresem: http://powietrze.gios.gov.pl/pjp/current. Wybieramy interesującą nas stację – w przykładzie będzie to stacja na ul. Koszyka w Opolu, która ma adres: http://powietrze.gios.gov.pl/pjp/current/station_details/chart/10374. Jak zauważył Krzysztof Styc na grupie Domoticz – WIOŚie podają aktualne dane (ostatni pomiar) w formacie JSON.
Pod koniec 2017 uległo to jednak zmianie i udostępniono nowe API pod nowym adresem, także w formacie JSON: http://powietrze.gios.gov.pl/pjp/content/api
To pozwala nam szybko wyciągnąć nową zmienną i wpisać ją do bazy InfluxDB – dodając kilka linijek do naszego skryptu. Najpierw musimy jednak znaleźć ID naszej stacji:
1 |
http://api.gios.gov.pl/pjp-api/rest/station/findAll |
Szukamy np: Opola i znajdujemy StationID:

Mając StationID – wpisujemy je w URL, który pokaże konkretne pomiary:
1 |
http://api.gios.gov.pl/pjp-api/rest/station/sensors/10374 |
Mamy nasze ID do pomiarów – oto i one:
1 |
http://api.gios.gov.pl/pjp-api/rest/data/getData/16147 |
Modyfikujemy skrypt:
1 2 3 4 5 |
city_wios_station=`curl -s http://api.gios.gov.pl/pjp-api/rest/data/getData/16147 | awk -F, '{print $3}' | sed -e 's/}/:/g' | awk -F: '{print $2}'` if [ $city_wios_station = "null" ] ; then city_wios_station=`curl -s http://api.gios.gov.pl/pjp-api/rest/data/getData/16147 | awk -F, '{print $5}' | sed -e 's/}/:/g' | awk -F: '{print $2}'` fi /usr/bin/curl -i -XPOST 'http://127.0.0.1:8086/write?db=smogdb' --data-binary "ppm10_city_wios_station value=${city_wios_station}" |
Oczywiście dla stacji podających więcej danych – należy zmodyfikować nieco kod. Najpierw trzeba znaleźć ID jak powyżej.
Po modyfikacji (przykłady bez optymizacji) możemy pobrać więcej zmiennych, co wymaga jak zwykle małej zabawy z parametrami.
Na wykresie pojawi się więc dodatkowa zmienna – lub zmienne, dzięki której zweryfikujemy nasze wskazania. Dla gazów warto zrobić osobny wykres.
Wskaźniki – zegary – aktualne wartości na stronie WWW
Informację o aktualnych wartościach ilości cząsteczek w metrze sześciennym możemy także prezentować w formie wskaźników lub zegarów. Z pomocą przychodzi gotowa bilblioteka w JavaScript, która współpracuje bezproblemowo z większością przeglądarek: http://justgage.com/:

Pobieramy ZIP „justgauge”, rozpakowujemy i przygotowujemy naszą surową stronę, w przykładzie będziemy jeszcze pobierać dane z bazy InfluxDB – temperaturę i wilgotność, tak abyśmy wiedzieli czy nasz pomiar jest wiarygodny. Surowa wersja strony:
1 2 3 4 |
<!doctype html> Stacja pogodowa/Weather station </head> Stacja pogodowa SMOG w ... /SMOG Weather Station in ... |
Zapiszmy plik jako raw-index.html
W głównym skrypcie mamy już zmienną która odpowiada na 2,5 i 10. Pobierzmy więc – dodają te line do skryptu – wilgotność i temperaturę. W InfluxDB są to w moim przykładzie zmienne „temperatura_out” oraz „wilgotnosc_out” – zmieniamy je na własne, podobnie jak adres IP i nazwę bazy danych – oczywiście dołączamy do głównego skryptu:
1 2 |
temperature=`/usr/bin/curl -s -G 'http://127.0.0.1:8086/query' --data-urlencode "db=ZMIEN_MNIE" --data-urlencode "q=SELECT last(\"value\") FROM \"temperatura_out\"" | /bin/sed -e 's/[{}]/''/g' | /usr//bin/awk -v k="text" '{n=split($0,a,","); print a[n]}' | tr -d "[\]]"` humidity=`/usr/bin/curl -s -G 'http://127.0.0.1:8086/query' --data-urlencode "db=ZMIEN_MNIE" --data-urlencode "q=SELECT last(\"value\") FROM \"wilgotnosc_out\"" | /bin/sed -e 's/[{}]/''/g' | /usr//bin/awk -v k="text" '{n=split($0,a,","); print a[n]}' | tr -d "[\]]"` |
Gotowe – zatem wymieńmy odpowiednie wartości w naszym pliku i stwórzmy nowy:
1 |
sed -e "s/__temperature__/${temperature}/g" -e "s/__humidity__/${humidity}/g" -e "s/__ppm25__/${PPM25}/g" -e "s/__ppm10__/${PPM10}/g" < raw-index.html > index.html |
Plik index.html wysyłamy do naszego hostingu na przykład tak, oczywiście wcześniej wykonując „ssh keygen” oraz „ssh-copy-id”
1 |
scp index.html user@host.com:/var/www/pogoda.host.com/html/index.html |
Każde uruchomienie skryptu zaktualizuje nam plik!