ESP32 and touch display - part 6, RGB lamp control, RGB picker

Today we are creating another mini project - this time it will be a touchscreen RGB lamp controller. The controller itself will be based on the ESP32 board + ESP32-2432S028R touch display, while it will control any Tasmota/OpenBeken device via Tasmota's HTTP interface. Commands will be sent as GET requests and responses will be parsed from the received JSON format, which the Tasmota documentation discusses in detail:
https://tasmota.github.io/docs/Commands/
https://tasmota.github.io/docs/Commands/#with-web-requests
This topic continues the series on the ESP32-2432S028R board:
ESP32 and touchscreen display - tutorial - part 1 - how to program? Basics .
ESP32 and touch display - part 2 - how to draw pixels, lines, shapes, performance issues
ESP32 and touch display - tutorial part 3 - interactions, games and fun .
ESP32 and touch display - tutorial part 4 - web weather, APIs, JSON .
ESP32 and touch display - part 5 - LVGL in SquareLine Studio
I've taken the liberty of using the trial version of SquareLine Studio in this topic, but it's essentially redundant here, the RGB controller can be placed from within the code (which SquareLine Studio de facto does), I'll post the code for creating it too.
Step 1 - the UI itself .
The whole project is very attractive and beginner-friendly. We have the necessary components ready - even the colour selection widget is already in LVGL, which by the way you can read about in their documentation:
https://docs.lvgl.io/7.11/widgets/cpicker.html
In addition, a slider to control the brightness level and maybe an on/off button of some kind is useful.
We add objects in SquareLine Studio:


As in the previous section, we also add events:



Now it's time to implement the events, i.e. button press, slider move and colour selection separately.
So far without sending data to the lamp.
Button - colour change when pressed (lamp state is either on or off):
Code: C / C++
Colour selection - here you need to swap the colour from the packed 16 bit mode to something closer to us, RGB in a more convenient form:
Code: C / C++
The result:
[ 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
And then there's the slider, which for now just displays the change in the log:
Code: C / C++
Result:
[ 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
Step 2 - putting everything together .
This step is optional, as with the Tasmota we can send the colour value, brightness level and on/off status separately, but for controlling the LEDs directly from the ESP (e.g. the WS2812 strip) this could still be useful.
So, we have three values (colour, brightness and on/off status) and we want to combine them. The easiest way is to multiply them:
Code: C / C++
Of course, this function has to be called after each change.
Step 3 - Tasmota/OpenBeken control interface .
However, let us return to the control of the Tasmota device. We will implement communication based on HTTP, this is probably the simplest option, although MQTT could also be considered in the future. Whatever the method, it's worth reading the Tasmota command documentation:
https://tasmota.github.io/docs/Commands/
Regarding HTTP, it is worth reading the related topic, it is about the interface we are using:
OpenBeken as a mini HTTP host - writing pages in Javascript, Tasmota's REST API etc .
That is, we will send commands in the format:
http://192.168.0.201/cm?cmnd=POWER%20ON
Yes, the same can easily be tested in a web browser.
Rather, you should start by connecting to WiFi:
Code: C / C++
Now you need to send the command somehow. This is accomplished by the following code snippet:
Code: C / C++
I implement the sending of the command in the thread. Otherwise, I would block the refresh of the user interface while waiting for a response from the device. This could take an extremely long time, especially if the target device was offline.
The code above creates a thread using xTaskCreate:
Code: C / C++
Documentation: https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/system/freertos.html
The URL is passed as an argument to the thread.
Using our function is very simple. We just call (for example):
Code: C / C++
Now we need to put it to use. Let's start with the on/off. We plug these directly into the button, as our manual colour counting is no longer needed:
Code: C / C++
Similarly, the slider:
Code: C / C++
And the colour:
Code: C / C++
Step 4 - Bilateral status synchronisation .
So far we have only done the sending of data to the lamp. What we lack is communication in the other direction. That is, if we externally change the state of our lamp, the state of the touch interface will not change. Simply put, the ESP will not know that something has changed.
In an ideal world, the lamp itself would be able to send us information about changes, but here we are relying on Tasmota's capabilities, so we will have to query the lamp itself about its state.
In Tasmota, the Status command is used for this.
We send:
http://192.168.0.212/cm?cmnd=Status
An example response is:
Code: JSON
This will need to be parsed using ArduinoJSON, but we've already done a similar thing in the section on getting weather information from the internet.
What's worse is that we need to think about what to do with this response afterwards.
We shouldn't edit the UI from another thread, at least not while it's being refreshed.
In that case, we need to pick up the response from the thread somehow, but it's not as easy as it might seem. How about using the thread-safe queue from FreeRTOS?
https://www.freertos.org/a00018.html
Queues from FreeRTOS, like other mechanisms, take care of thread-safety and allow data to be passed safely between threads. We can't just share resources between threads without safeguards, because it can go to errors that are difficult to reproduce and fix.
We define a queue:
Code: C / C++
Create a queue (arguments are maximum number and size of element):
Code: C / C++
Now the addition to the queue needs to be done, but a moment. First, I also had to change the GET sending code so that it uses HttpClient. This is because the previous code had a problem with a missing Content-Length in the GET response:
Code: C / C++
The code above also includes an add to queue - xQueueSend call. In the argument, I specify a long time to wait for the queue to be available, and still release the created buffer if the add fails. Otherwise we would have a memory leak that would quickly run out of memory....
Receive response - called from loop:
Code: C / C++
Time to compile and upload - just to check that the code works and that we get a response in the main thread.
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"}
It works! Now it's time for the next step, which is parsing. We need to load this JSON into the appropriate structures. ArduinoJSON will come in handy:
Code: C / C++
Similarly, as in the example with the weather. We deserialise the JSON:
Code: C / C++
Then we need to apply a trick. We want to support both reading from the full state, where we need to search for the element StatusSTS , and from the truncated state (where the document root is StatusSTS ). To do this, we first look for StatusSTS, and if we do not find it, we assume that the root itself is this object. Whether we get a full or truncated status from Tasmota in response depends on which command we use.
Code: C / C++
Once we have extracted the statusSTS, the brightness level (Dimmer), on/off status (POWER) and colour in HSB format (HSBColor) can be selected from it:
Code: C / C++
Then we need to save the received information to our internal variables.
Code: C / C++
The biggest fun is with the colour, because we need to convert it to RGB. We have a ready-made function for this from the internet. It is worth mentioning here that this colour is not multiplied by the brightness level, etc:
Code: C / C++
Now you still need the helper function SetColorFromBytes, which essentially just sets the colourWheel from the given RGB:
Code: C / C++
Similarly, now a helper function that sets the enabled state on the GUI:
Code: C / C++
For convenience, I also added a bSend argument to specify whether the state change should be sent to the target device or not. If we are receiving the state from that particular device, there is no reason to send it back to it.
We check, everything works:



Final test on video:
The video shows control of the lamp from its native OBK panel and from the touchscreen display control programme developed here. The communication is two-way, although the states refresh with some small delay. The video shows that, for example, when I move the brightness level bar on the OBK panel, it also moves itself on the touch display.
Summary .
Our program works, the lamp can be controlled, and we got a taste of the basics of linking the user interface to web actions and were again exposed to the JSON format. By the way, we can appreciate LVGL again, because the colour selection widget was for like.
But of course this is not the end of the adventure, now the code could still be significantly improved and optimised. I would consider, for example, abandoning the creation of a new thread separately each time, in favour of a single "worker thread" that would simply perform network operations and collect new "network commands" from some queue. But this was only supposed to be a short demo so this is a secondary issue.
In the next part I will try either to optimise this controller shown here, or I will be tempted to use a slightly different panel, maybe some form of sensor reading? .
Comments