logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

ESP32 i wyświetlacz dotykowy - część 6, kontrola lampy RGB, RGB picker

p.kaczmarek2 04 Wrz 2024 09:23 1581 0

TL;DR

  • Powstał dotykowy kontroler lampy RGB na ESP32 z wyświetlaczem ESP32-2432S028R, sterujący urządzeniami Tasmota/OpenBeken przez HTTP.
  • Interfejs zbudowano w LVGL/SquareLine Studio: picker koloru, suwak jasności i przycisk on/off, a komendy wysyłano w osobnym wątku FreeRTOS przez GET.
  • W przykładach użyto komendy http://192.168.0.212/cm?cmnd=Status, a odpowiedź zwracała m.in. Dimmer:59 i POWER:ON.
  • Stan odczytywano z JSON, parsując StatusSTS w ArduinoJSON i konwertując HSBColor do RGB oraz synchronizując GUI z urządzeniem.
  • Testy pokazały dwustronną synchronizację z panelem OBK; zmiany jasności i koloru odświeżały się po obu stronach, choć z niewielkim opóźnieniem.
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA
📢 Słuchaj (Głos AI):
  • Lampka RGB z kontrolerem dotykowym ESP32
    Dzisiaj tworzymy kolejny miniprojekt - tym razem będzie to dotykowy kontroler lampy RGB. Sam kontroler będzie bazować na płytce ESP32 + wyświetlacz dotykowy ESP32-2432S028R, natomiast sterować on będzie dowolnym urządzeniem Tasmota/OpenBeken poprzez interfejs HTTP Tasmoty. Komendy będą wysyłane w postaci żądań GET a odpowiedzi będą parsowane z odebranego formatu JSON, który dokumentacja Tasmoty szczegółowo omawia:
    https://tasmota.github.io/docs/Commands/
    https://tasmota.github.io/docs/Commands/#with-web-requests

    Ten temat stanowi kontynuację serii o płytce ESP32-2432S028R:
    ESP32 i wyświetlacz dotykowy - tutorial - część 1 - jak programować? Podstawy
    ESP32 i wyświetlacz dotykowy - część 2 - jak rysować piksele, linie, kształty, kwestia wydajności
    ESP32 i wyświetlacz dotykowy - tutorial cz. 3 - interakcje, gry i zabawy
    ESP32 i wyświetlacz dotykowy - tutorial cz. 4 - pogoda z internetu, API, JSON
    ESP32 i wyświetlacz dotykowy - część 5 - LVGL w SquareLine Studio

    W tym temacie pozwoliłem użyć sobie próbnej wersji SquareLine Studio, ale zasadniczo jest tutaj to zbędne, kontroler RGB można umieścić z poziomu kodu (co zresztą de facto SquareLine Studio robi), kod jego tworzenia też umieszczę.

    Krok 1 - sam interfejs UI
    Cały projekt jest bardzo atrakcyjny i przyjazny dla początkujących. Potrzebne komponenty mamy gotowe - nawet widget wyboru koloru jest już w LVGL, o czym zresztą można poczytać w ich dokumentacji:
    https://docs.lvgl.io/7.11/widgets/cpicker.html
    Oprócz tego przyda się slider do kontroli poziomu jasności i może jakiś przycisk on/off.
    Dodajemy obiekty w SquareLine Studio:
    Interfejs dotykowego kontrolera lampy RGB
    Ekran dotykowy z wyborem koloru i suwakiem na kontrolerze RGB
    Tak jak w poprzedniej części, też dodajemy zdarzenia:
    Zrzut ekranu z oprogramowania do projektowania interfejsu z kolorowym suwakiem i przyciskiem. Interfejs wizualny do kontrolera lampy RGB z przyciskiem on/off. Interfejs projektu kontrolera lampy RGB w SquareLine Studio
    Teraz pora zaimplementować zdarzenia, czyli osobno wciśnięcie przycisku, przesunięcie slidera oraz wybór koloru.
    Jak na razie bez wysyłania danych do lampy.
    Button - zmiana koloru w momencie wciśnięcia (stan lampy jest albo włączony, albo wyłączony):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Wybór koloru - tu trzeba kolor zamienić z upakowanego trybu 16 bitowego na coś nam bliższego, RGB w wygodniejszej postaci:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rezultat:
    
    [ 7382][I][main.cpp:39] MyColorChange(): Selected color: R=248, G=76, B=0
    
    [ 9884][I][main.cpp:39] MyColorChange(): Selected color: R=128, G=252, B=0
    
    [ 10375][I][main.cpp:39] MyColorChange(): Selected color: R=8, G=252, B=0
    
    


    No i suwak, który na razie tylko wyświetla w logu zmianę:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rezultat:
    
    [ 17024][I][main.cpp:19] MySliderChange(): Selected brightness=55
    
    [ 17084][I][main.cpp:19] MySliderChange(): Selected brightness=54
    
    [ 19754][I][main.cpp:19] MySliderChange(): Selected brightness=91
    



    Krok 2 - łączenie wszystkiego w całość
    Ten krok jest opcjonalny, gdyż w przypadku Tasmoty możemy wysyłać osobno wartość koloru, poziom jasności oraz stan on/off, ale w przypadku sterowania LEDami bezpośrednio z ESP (np. pasek WS2812) to jednak może się przydać.
    A zatem, mamy trzy wartości (kolor, jasność oraz stan on/off) i chcemy je połączyć. Najłatwiej jest je przemnożyć:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Oczywiście tę funkcję trzeba wywoływać po każdej zmianie.

    Krok 3 - interfejsu kontroli Tasmoty/OpenBeken
    Wróćmy jednak do sterowania urządzeniem Tasmoty. Komunikację zrealizujemy w oparciu o HTTP, to chyba najprostsza opcja, choć w przyszłości można by rozważyć też MQTT. Bez względu na sposób, warto zapoznać się z dokumentacją komend Tasmoty:
    https://tasmota.github.io/docs/Commands/
    W kwestii HTTP warto przeczytać pokrewny temat, dotyczy on właśnie interfejsu z którego korzystamy:
    OpenBeken jako mini hosting HTTP - pisanie stron w Javascript, REST API Tasmota itd
    Czyli wysyłać będziemy komendy w formacie:
    
    http://192.168.0.201/cm?cmnd=POWER%20ON
    

    Tak, to samo można łatwo testować w przeglądarce internetowej.
    Zacząć raczej trzeba od podłączenia do WiFi:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Teraz trzeba jakoś wysłać komendę. Realizuje to poniższy fragment kodu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Wysyłanie komendy realizuję w wątku. W przeciwnym razie blokowałbym odświeżanie interfejsu użytkownika na czas oczekiwania na odpowiedź od urządzenia. Mogłoby to trwać to wyjątkowo długo, zwłaszcza jeśli docelowe urządzenie byłoby offline.
    Powyższy kod tworzy wątek za pomocą xTaskCreate:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Dokumentacja: https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/system/freertos.html
    Adres URL jest przekazywany jako argument do wątku.
    Użycie naszej funkcji jest bardzo proste. Wywołujemy tylko (przykładowo):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Teraz trzeba to wykorzystać. Zacznijmy od on/off. Podpinamy je bezpośrednio pod przycisk, bo nasze ręczne liczenie kolorów nie jest już potrzebne:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Analogicznie, suwak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    No i kolor:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod



    Krok 4 - Dwustronna synchronizacja stanu
    Jak na razie zrobiliśmy tylko wysyłanie danych do lampy. Brakuje nam natomiast komunikacji w drugą stronę. To znaczy, że jeśli z zewnątrz zmienimy stan naszej lampy, to stan interfejsu dotykowego się nie zmieni. Po prostu ESP nie będzie wiedział, że coś się zmieniło.
    W idealnym świecie sama lampa mogłaby wysyłać do nas informacje o zmianach, ale tutaj opieramy się na możliwościach Tasmoty, więc będziemy musieli sami odpytywać lampę o jej stan.
    W Tasmocie służy do tego komenda Status.
    Wysyłamy:
    
    http://192.168.0.212/cm?cmnd=Status
    

    Przykładowa odpowiedź to:
    Kod: JSON
    Zaloguj się, aby zobaczyć kod

    Trzeba to będzie parsować przy użyciu ArduinoJSON, ale podobną rzecz już wykonywaliśmy w części o pobieraniu informacji pogodowych z Internetu.
    Gorzej, że musimy zastanowić się co potem z tą odpowiedzią zrobić.
    Nie powinniśmy edytować interfejsu użytkownika z innego wątku, a przynajmniej nie w trakcie jego odświeżania.
    Trzeba w takim razie w jakiś sposób odebrać odpowiedź z wątku, ale nie jest to takie proste jakby się mogło wydawać. Może użyjemy kolejki thread-safe z FreeRTOS?
    https://www.freertos.org/a00018.html
    Kolejki z FreeRTOS, jak podobnie inne mechanizmy, dbają o bezpieczeństwo wątków i pozwalają bezpiecznie przekazywać dane między wątkami. Nie możemy tak po prostu współdzielić zasobów między wątkami bez zabezpieczeń, bo może przejść do trudnych w odtworzeniu i naprawieniu błędów.
    Definiujemy kolejkę:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Tworzenie kolejki (argumenty to maksymalna ilość i rozmiar elementu):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Teraz trzeba zrobić dodawanie do kolejki, ale moment. Najpierw musiałem też zmienić kod wysyłania GET, tak aby korzystał z HttpClient. Wynika to stąd, że poprzedni kod miał problem z brakiem Content-Length w odpowiedzi na GET:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Powyższy kod zawiera też dodawanie do kolejki - wywołanie xQueueSend. W argumencie określam długi czas oczekiwania na dostępność kolejki oraz mimo to, jeśli dodawanie się nie powiedzie, to zwalniam utworzony bufor. W przeciwnym razie byśmy mieli wyciek pamięci, która szybko by się skończyła..

    Odbieranie odpowiedzi - wywoływane z loop:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Pora skompilować i wgrać - tylko po to, by sprawdzić czy kod działa i czy otrzymujemy odpowiedź w głównym wątku.
    
    Connecting to WiFi...
    Connected to WiFi
    [ 6344][I][esp32-hal-adc.c:235] __analogReadMilliVolts(): ADC1: Characterized using eFuse Vref: 1072
    
    HTTP GET Status = 200
    Received 139 bytes: {"Dimmer":59,"Fade":"OFF","Speed":1,"LedTable":"ON","Color":"77,37,0,0,0","HSBColor":"29,100,97","Channel":[30,14,0],"CT":500,"POWER":"ON"}
    [ 7253][I][main.cpp:266] checkForReplies(): [MainThread] Received response: {"Dimmer":59,"Fade":"OFF","Speed":1,"LedTable":"ON","Color":"77,37,0,0,0","HSBColor":"29,100,97","Channel":[30,14,0],"CT":500,"POWER":"ON"}

    Działa! Teraz pora na następny etap, czyli na parsing. Musimy wczytać ten JSON w odpowiednie struktury. Przyda nam się ArduinoJSON:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Podobnie tak jak w przykładzie z pogodą. Deserializujemy JSON:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Potem musimy zastosować pewien trik. Chcemy wspierać zarówno odczyt z pełnego stanu, gdzie trzeba wyszukać element StatusSTS, oraz z okrojonego stanu (gdzie root dokumentu to StatusSTS). W tym celu najpierw szukamy StatusSTS, a jeśli go nie znajdujemy, to zakładamy że sam root jest tym obiektem. To, czy w odpowiedzi otrzymamy od Tasmoty pełny czy okrojony stan zależy od tego jakiej komendy użyjemy.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Jak już wydobędziemy statusSTS, to można wybrać z niego poziom jasności (Dimmer), stan on/off (POWER) i kolor w formacie HSB (HSBColor):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Potem musimy zapisać odebrane informacje do naszych wewnętrznych zmiennych.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Największa zabawa jest z kolorem, bo trzeba go zamienić na RGB. Mamy do tego gotową funkcję z internetu. Warto tu wspomnieć, że ten kolor nie jest przemnożony przez poziom jasności, itd:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Teraz jeszcze potrzeba pomocniczej funkcji SetColorFromBytes, która zasadniczo po prostu ustawia kolor ColorWheel z danego RGB:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Analogicznie, teraz funkcja pomocnicza ustawiająca stan enabled na GUI:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Dla wygody dodałem też argument bSend by określać czy zmiana stanu ma zostać wysłana do docelowego urządzenia czy nie. Jeśli odbieramy stan z tego właśnie urządzenia, to nie ma po co go mu odsyłać.
    Sprawdzamy, wszystko działa:
    Wyświetlacz dotykowy z interfejsem sterowania RGB obok lampy Lampa RGB świecąca na czerwono oraz dotykowy wyświetlacz z interfejsem do sterowania. ESP32 z wyświetlaczem dotykowym do kontrolera lampy RGB
    Finalny test na video:



    Filmik pokazuje sterowanie lampą z jej natywnego panelu OBK oraz z opracowanego tutaj programu obsługi wyświetlacza dotykowego. Komunikacja jest obustronna, chociaż stany odświeżają się z pewnym małym opóźnieniem. Na filmie widać, że np. jak przesunę pasek poziomu jasności na panelu OBK to przesuwa się on też sam na wyświetlaczu dotykowym.

    Podsumowanie
    Nasz program działa, lampą da się sterować, a my zasmakowaliśmy podstaw łączenia interfejsu użytkownika z akcjami sieciowymi i znów mieliśmy styczność z formatem JSON. Przy okazji możemy znów docenić LVGL, bo widget wyboru koloru był dla jak znalazł.
    Oczywiście jednak to nie koniec przygody, teraz można by jeszcze kod znacznie ulepszyć i zoptymalizować. Zastanawiałbym się np. nad rezygnacją z tworzenia osobno za każdym razem nowego wątku na rzecz pojedynczego wątku "worker thread" który by po prostu realizował operacje sieciowe i zbierał nowe "polecenia sieciowe" z jakiejś kolejki. Ale to miało być tylko krótkie demko więc to sprawa już drugorzędna.
    W kolejnej części spróbuję albo ten pokazany tu kontroler zoptymalizować, albo skuszę się już na nieco inny panel, może jakaś forma odczytu z czujników?

    Fajne? Ranking DIY
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    Inżynier programista z wieloletnim doświadczeniem embedded i full stack developer.
    Specjalizuje się w: embedded, Full-Stack Developer
    p.kaczmarek2 napisał 14725 postów o ocenie 12810, pomógł 659 razy. Jest z nami od 2014 roku.
  • REKLAMA
📢 Słuchaj (Głos AI):
REKLAMA