Mój poprzedni temat o tworzeniu wykresów na Arduino R4 uzyskał większy odzew niż się spodziewałem, więc teraz dla odmiany wykonuję podobny projekt ale w bardziej kompletnej formie i to na platformie ESP8266 w PlatformIO. Standardowo całość składana będzie z gotowych bibliotek, a proces opiszę w formie samouczka. Najpierw uruchomię tutaj czujnik temperatury i wilgotności DHT11, następnie pokażę jak można pobrać bieżący czas z Internetu. Potem zademonstruję jak można zbierać pomiary w pamięci w buforze kołowym, a na koniec wyświetlę je na prostej stronie WWW z wykresem, całość dostępna w obrębie naszego WiFi.
Pierwsze kroki
Jakiś czas temu publikowałem temat o ArduinoOTA i ESP8266 w PlatformIO, polecam się z nim zapoznać. Tutaj zakładam, że mamy uruchomione OTA i będę korzystać z tego, co wcześniej opracowałem. Dla przypomnienia - oto mój "hello world":
Kod: C / C++
W tym temacie staramy się składać wszystko z gotowych elementów, więc użyjemy też gotowej biblioteki do czujnika DHT11. Musimy ją tylko zainstalować.
Otwieramy zakładkę Libraries:
Wyszukujemy DHT. Biblioteka DHT wymaga też dodania Adafruit Unified Sensor Library, więc dodajemy obie biblioteki. Najpierw Unified Sensor:
Dodajemy do projektu:
Trzeba będzie wskazać, do którego naszego projektu tę bibliotekę dodajemy:
Dodane:
W ten sposób dodajemy obie biblioteki. Czujne oko zauważy, że dodały się one też do naszego platformio.ini:
Również warto zauważyć, że na stronie biblioteki jest też przykład jej użycia:
Zintegrujmy go z naszym kodem. Pomiar wykonuję w momencie przełączenia stanu diody:
Kod: C / C++
Na ten moment wyniki wysyłam tylko przez UART. Potem zrobimy stronkę internetową. Sprawdźmy najpierw, czy DHT w ogóle działa:
Dodajemy stronę internetową
Wystawienie naszych pomiarów na świat jest naprawdę bardzo proste. Do utworzenia serwera służy klasa ESP8266WebServer. Najpierw załączamy jej nagłówek:
Kod: C / C++
Potem tworzymy jej instancję, argumentem jest port na którym powstanie serwer (HTTP to z reguły port 80):
Kod: C / C++
Potem tworzymy funkcje obsługujące dane strony:
Kod: C / C++
W głównej pętli obsługujemy klientów:
Kod: C / C++
No i na koniec funkcja obsługująca główną stronę:
Kod: C / C++
Rezultat:
To naprawdę bardzo proste. Teraz wyświetlimy temperaturę.
Wyświetlamy temperaturę
Aby wyświetlić temperaturę na stronie musimy dokonać dwóch rzeczy:
- trzeba te pomiary zapisać, dla uproszczenia damy je do zmiennych globalnych
- trzeba je potem umieścić w tekście strony, do formatowania napisu użyję mojego ulubionego sposobu, czyli bufora w stylu C i funkcji sprintf.
Same zmienne globalne:
Kod: C / C++
Zapis do zmiennych globalnych:
Kod: C / C++
Zmodyfikowana funkcja tworząca stronę:
Kod: C / C++
W ten sposób na naszym ekranie pojawi się temperatura i wilgotność odczytana z DHT.
Czas z sieci (NTP)
NTP to Network Time Protocol, czyli sposób na pobranie czasu z sieci. Tutaj też mamy gotowca, skorzystamy z klasy NTPClient. Dodajemy odpowiednią bibliotekę, tak jak wcześniej:
Ta biblioteka też oczywiście ma gotowy przykład użycia:
Kod: C / C++
Trzeba jeszcze uzyskać jakoś czas w postaci napisu. Jest do tego gotowa funkcja - getFormattedTime. Po integracji z naszą stroną otrzymujemy:
Kod: C / C++
Rezultat:
Jeśli jesteśmy ambitni to możemy samodzielnie formatować czas wedle własnego uznania. Mamy do tego funkcję strftime, która działa podobnie jak spritnf. W niej też formatujemy czas za pomocą specjalnych znaczników. Poniżej nieco rozwinięty przykład:
Kod: C / C++
Lepsza organizacja
Naszym celem jest jednak zbieranie pomiarów. Zanim go zrealizujemy, proponuję lepiej zorganizować sobie zapis pomiaru. Wprowadźmy strukturę reprezentującą pomiar. Tylko co taka struktura powinna zawierać? Zawrzemy w niej:
- temperaturę (float)
- wilgotność (też float)
- czas (tutaj najlepiej użyć typu time_t, który de facto też jest liczbą, nie radzę podążać drogą zapisu czasu jako string, byłoby to niewydajne i niewygodne w dalszej obróbce)
Kod: C / C++
Potem można utworzyć globalnie instancję tej struktury:
Kod: C / C++
i nasz program będzie działać tak jak wcześniej, ważne tylko by zapisywać czas w momencie pomiaru a nie tworzyć go w momencie odczytania strony.
Historia pomiarów - bufor kołowy - cz. 1
Teraz trzeba wymyśleć jak przechowywać pomiary. Chciałoby się użyć zwykłej tablicy, ale co z usuwaniem starych pomiarów? Pamięć RAM nie jest nieskończona.
Jest na to prosty sposób - przyda się nam tutaj bufor kołowy.
Bufor kołowy to struktura danych oparta o tablicę, która pozwala na przechowywanie pewnej ilości elementów danych, a gdy bufor jest pełny, nowe dane zaczynają zastępować najstarsze. Jak sama nazwa wskazuje - indeks elementu do którego zapisujemy zatacza tutaj koło. Odpowiednia manipulacja indeksem tablicy (wraz z odpowiednim sposobem odczytu i zapisu) sprawia, że staje się ona buforem kołowym.
Zdecydujmy się najpierw na rozmiar tablicy - jeśli chcemy mieć dzień pomiarów, gdzie pomiar jest co 10 minut, to starczy 24 * 6.
Kod: C / C++
Potrzebujemy też indeks ostatnie pomiaru (ten indeks będzie "zataczał koła" po tablicy, będzie się zapętlać):
Kod: C / C++
Funkcja dodająca pomiar (i zapętlająca indeks za pomocą operatora modulo):
Kod: C / C++
No i funkcja czyszcząca tablicę:
Kod: C / C++
Oczywiście trzeba to jeszcze wpiąć w pobieranie pomiarów z DHT:
Kod: C / C++
oraz w ich wyświetlanie (na ten moment bez wykresu):
Kod: C / C++
Nareszcie wykres
Teraz pora utworzyć wykres. Wygenerujemy go podobnie jak w pokrewnym temacie: Rysowanie wykresów w HTML na Arduino R4 - statystyki, pomiary na mini stronie WWW.
Zdecydowałem się na użycie do tego biblioteki chart.js
Na stronie W3schools można znaleźć przykłady jej użycia:
https://www.w3schools.com/js/js_graphics_chartjs.asp
Cytując przykład z powyższej strony - wszystko sprowadza się do wpięcia naszych zebranych wartości do tablic:
Powyższy kod wygeneruje taki wykres:
Tablice cechuje to, że wszystkie elementy za wyjątkiem pierwszego są poprzedzone przecinkiem, co również musimy obsłużyć, pewnie za pomocą jakiegoś ifa.
Tworzymy trzy tablice:
- labele (oznaczenia, tutaj daty/czasy)
- wartości temperatury
- wartości wilgotności
Przykładowe rozwiązanie:
Kod: C / C++
Powyższe rozwiązanie jak na razie ignoruje bufor kołowy!
Dodatkowo przerywam pętle wcześnie by pominąć "puste" pomiary.
Potem zostaje umieścić tak wygenerowane tablice w reszcie kodu generującego wykres, który możemy wziąć chociażby z przykładów użycia tej biblioteki:
Kod: C / C++
Oto rezultat, dla efektu podgrzałem czujnik mocną latarką:
Poprawki i ulepszenia
Kod z poprzedniego akapitu nie jest poprawny. Musimy respektować bufor kołowy oraz pomijać puste pomiary. Pomiar jest pusty, jeśli czas jest równy 0, natomiast iterację bufore zaczynamy od najstarszego wpisu, który identyfikujemy jako:
(lastSample+1)%SAMPLES_COUNT
operator % to modulo, reszta z dzielenia przez liczbę całkowitą. W ten sposób zaczynamy od najstarszego wpisu oraz kończymy na lastSample.
Kod: C / C++
Można tu pokazać jeszcze rezultat, ale od strony wyglądu raczej nic się nie zmieni (no, chyba, że byśmy poczekali dłużej, wtedy bez poprawki na bufor kołowy wykres mógłby się psuć):
Częstotliwość pomiaru, sprzątanie
Teraz jeszcze warto wydzielić te 1000 ms okresu próbkowania temperatury do osobnej zmiennej. Powiedzmy:
Kod: C / C++
Tak naprawdę cały ten fragment kodu powinien być do przepisania, może by dało się użyć np. klasy Ticker, ale to może w kolejnej części.
Wgląd w pełny kod:
Kod: C / C++
Zrzut ekranu ze zbierania próbek przez dłuższy czas w lekko zmodyfikowanej wersji programu:
Podsumowanie
Bardzo prosty i przyjemny projekt. W sam raz na początek z ESP. Po odpowiednim dobraniu ilości próbek (ogranicza nas pamięć RAM) i częstotliwości próbkowania można mieć ładne wykresy pokazujące temperaturę i wilgotność z ostatnich kilku dni.
Oczywiście to tylko mały przykład i można tu wiele poprawić, np:
- estetyka (podział wykresu na dwa, itd, jednostki)
- kod (wspomniany Ticker by się przydał)
- konfigurowalność (można by podpiąć WiFiManager by uniknąć potrzeby wpisywania na sztywno do kodu naszego SSID i hasła)
- przydałoby się też sprawdzać przed pierwszym pomiarem czy czas z NTP jest już pobrany, jeśli nie, to pomijać pomiar
Być może zajmę się tym w kolejnej części, a na razie zapraszam do komentowania. Czy realizowaliście tego typu bądź podobny projekt na ESP? A może ktoś pokusi się o rozwinięcie podanego tu przeze mnie zalążka i przeróbkę go w pełnoprawny system?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.