A Wi-Fi Switch for Domoticz using a XIAO ESP32C3 - Part 1
GNATS, a Tiny Basic ESP32 GPS Based NTP Server-> <-First Look at the Seeed Studio XIAO ESP32C3
 Part 1 - Demonstration Projects
Part 2 - Asynchronious Web Page Updates 
Part 3 - Better User Experience
Part 4 - Commands - version 0.0.8

Turning a LED on and off was the leitmotif of my first post about the XIAO ESP32C3. A connected LED was controlled with a physical push button connected to the development board, then with a button on a Web page served by the XIAO running MicroPython and finally with Bluetooth. The GitHub repository associated with that post also contained two further examples where the LED was controlled from a Web page using two different C++ libraries to run the web server. I now want to expand on this theme. In essence, the XIAO ESP32C3 will be used as the basis of a Wi-Fi switch integrated into my home automation system.

The source code for the PlatformIO projects / Arduino sketches presented in this post can be found in a GitHub Repository: sigmdel/xiao_esp32c3_wifi_switch.

Table of Content

  1. The Goal
  2. Hardware
  3. Hardware Abstraction
  4. Tiny Sketch, Big Footprint
  5. Magic Ingredients
  6. Compiling, Uploading, and Running the Firmware
    1. In the PlatformIO IDE
    2. In the Arduino IDE
    3. Execution of the Firmware
  7. Integration with Domoticz using HTML
  8. Useful Domoticz Additions
    1. Timed Light
    2. Auxiliary Light (updated 2023-06-24)
    3. Night Light
    4. Perceived Humidity
  9. Thoughts on the XIAO Starter Kit
  10. Alternate Hardware
  11. Critique and Future Developments

The Goal toc

The XIAO ESP32C3 Wi-Fi switch should resemble the venerable Sonoff Basic except that it has built-in sensors: one that measures temperature and humidity and another that measures the ambient brightness or light level. The Wi-Fi switch will run a Web server, specifically ESPAsyncWebServer in order to handle more than one connection, and it will be integrated in the Domoticz home automation system. The diagram below gives an overview of those components.

ESP32C3 Wi-Fi Switch in home automation environment

Web Interfaces Any resemblance between the Kitchen Light web interface and the (edited) Tasmota web interface next to it is not accidental. The objective is to try to reproduce the basic functionality of Theo Arends' powerful software which is used in many devices on our home automation system. That is an ambitious goal and if it's reached at all it will be with the culmination of many more posts. The immediate goal in this first step is getting together something that works acceptably.

It's not just a matter of toggling a LED on and off with a button on a Web page; numerous examples can be found on the Web. Whenever the state of the light is changed locally with the button, the light's status has to be updated on the Web page displayed by all clients connected to the Web server and in the home automation system. Similarly, if the toggle button on a client's Web page is clicked, then the hardware controlling the light must be activated accordingly and the light's status must be updated in the home automation system and on all connected clients' Web page simultaneously. Likewise, if the virtual light switch in the home automation system is toggled on or off, the actual relay on the Wi-Fi switch must be updated and the new status of the light must be shown on all connected clients' Web pages.

Hardware toc

Here is a description of the hardware used in this project.

In my first build of the project, I used discrete components which happened to be on hand. Then I remembered that Seeed Studio had kindly sent me a XIAO Starter Kit which contains an XIAO Expansion Base which is a carrier board for XIAO form factor developments boards. The Kit contained everything needed for this project and more. Given how easy it was to connect everything together, I decided to use the Kit build in this initial description of the hardware used. A description of the initial build with discrete components is relegated to the penultimate section of this post.

XIAO Expansion Board

Grove Components The base has an on-board user push button switch connected to the D1 pin of the XIAO dev board. Among many other things, the Starter kit included other devices that made it simple to build the hardware for the project.

Hardware Abstraction toc

Whenever the Web interface is updated, the data displayed is obtained from four Strings in main.cpp.

// Values to be displayed in Web page String ledStatus = "OFF"; String Temperature = "21.8"; String Humidity = "38.9"; String Light = "51";

Of course, the values of these strings depend on the sensor values. I decided to create a hardware abstraction layer to take care of the details of reading the sensor values and updating the corresponding strings. This is the pertinent part of the hardware header file (hardware.h).

// Hardware abstraction void initHardware(void); // Initialize the hardware (LED, button, temperature and light sensors) void checkHardware(void); // Read button and sensors and update readings strings in main.cpp void toggleLed(void); // Toggle the LED state and update ledStatus in main.cpp

That is pretty sparse and hopefully clear. The initHardware() function, called once in the setup() routine, does what one expects given its name. It initializes the two sensors and puts the LED in an initial off state. The checkHardware() function should be called in each iteration of the main loop() routine of the sketch. Every time called, it checks for a button press, and if one has occurred, it calls on toggleLed() to swith the state of the LED from on to off or vice versa. The toggleLed() function also updates the ledStatus String in main.cpp. The toggleLed() function is also used by the web server in an HTTP request that signals that the Toggle button has been clicked.

The details of the handling the hardware are hidden in the hardware.cpp implementation file. Here is how the LED (representing a relay) is handled.

// LED (i.e. relay) void setLed(int value) { digitalWrite(LED_PIN, value); ledStatus = (value ? "ON" : "OFF"); } void toggleLed(void) { setLed(1-digitalRead(LED_PIN)); } void initLed(void) { pinMode(LED_PIN, OUTPUT); setLed(0); }

The function setLed() does most of the work, and there's not much to it! It sets the digital I/O pin connected to the LED to 1 (HIGH) or 0 (LOW) according to its value parameter and then it updates the ledStatus string in main.cpp. Toggling the LED, is just a matter of reading the state of the I/O pin connected to the LED and then inverting its value. This is done in response to a user action, so there is no timing consideration involved. Initializing the LED, is just setting the mode of the I/O pin connected to the LED to OUTPUT and ensuring that the LED is off.

The push button is simpler to handle because the mdSimpleButton library is used. Initialization is implicit; it is done when the button instance of the mdSimpleButton class is created.

// Button mdSimpleButton button = mdSimpleButton(BUTTON_PIN); void checkButton(void) { if (button.update() >= BUTTON_RELEASED) { toggleLed(); } }

The state of the button is checked at every iteration of the loop() function, in order to have a best response rate as close to a wall light switch as possible.

An appropriate library takes care of the details of reading the temperature and humidity sensor. There is no need to read the sensor continuously, or at least I can't conceive of a reason to do that. Consequently, the reading is only done at specified intervals. The minimum time in milliseconds between readings of the sensor is defined in the SENSOR_DELAY macro defined in hardware.h.

// DHT Sensor unsigned long temptime; DFRobot_DHT20 dht20; void initSensor() { while (dht20.begin()) { delay(1000); } temptime = millis(); } void readTemp(void) { if (millis() - temptime > DHT_DELAY) { TempAndHumidity_t tah = dht20.getTempAndHumidity(); Temperature = String(tah.temperature, 1); Humidity = String(100*tah.humidity, 1); temptime = millis(); } }

That initSensor() function is a terrible bit of programming. What happens if the DHT20 is defective? The program could be cought in an endless while loop with no warning at all. The actual code does have logging functions and an error message will be printed to the serial monitor. That's not ideal, but it is better than nothing. Note that the DFRobot_DHT20::getTempAndHumidity method is not part of the original library.

Tiny Sketch, Big Footprint toc

While the main program is a mere 47 lines of actual code, yet compiled it does take a considerable portion of the Flash memory.

Checking size .pio/build/seeed_xiao_esp32c3/firmware.elf Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" RAM: [= ] 12.4% (used 40516 bytes from 327680 bytes) Flash: [====== ] 58.2% (used 763268 bytes from 1310720 bytes)

The result is slightly different if compiled with the Arduino IDE.

Sketch uses 755444 bytes (57%) of program storage space. Maximum is 1310720 bytes. Global variables use 40452 bytes (12%) of dynamic memory, leaving 287228 bytes for local variables. Maximum is 327680 bytes.

Lest one thinks something is wrong, the XIAO ESP32C3 does make use of its 4 MB of Flash memory. The latter is partitioned into three areas.

RegionSize (MB)
Program storage area1.2
Over-the-air buffer1.2
File system (spiffs)1.5
total: 3.9

So there is still plenty of room for a bigger program while ensuring easy over the air updates, but the program is nevertheless very big compared with what Tasmota achieves with the typical 1 MB of Flash memory as found on the Sonoff Basic switch.

Let's look at the source, which as typical starts with the list of libraries that are explicitly included.

#include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include "hardware.h" #include "html.h" #include "secrets.h"

Of course, some of these libraries include many more libraries. The next block of importance in the code has already been seen. It contains the String declarations for the data that will be displayed in the Web interface.

// Values to be displayed in Web page String ledStatus = "OFF"; String Temperature = "21.8"; String Humidity = "38.9"; String Light = "51";

Default values are specified just to ensure that something reasonable is displayed until the actual status of the sensors has been updated. Then the string substitution function is defined. Its role is central to the functioning of this program.

// Template substitution function String processor(const String& var){ if (var == "TITLE") return String("XIAO ESP32C3 WEB SERVER"); if (var == "DEVICENAME") return String("Kitchen Light"); if (var == "LEDSTATUS") return ledStatus; if (var == "TEMPERATURE") return Temperature; if (var == "HUMIDITY") return Humidity; if (var == "LIGHT") return Light; if (var == "INFO") return String("Using AsyncWebServer and a content refresh meta tag"); return String(); // empty string }

The HTML code served to the client contains placeholders which will be replaced by this function just as the HTML page is sent out to a client. Placeholders are "%" quoted strings. For example, the temperature read from the sensor is displayed in a cell of a table in the HTML code send to the client. The placeholder %TEMPERATURE% indicates where the numerical value is to be inserted.

<table> <tr><td>Temperature:</td><td><b>%TEMPERATURE%</b> °C</td></tr> ...

When the Web server is sending out the HTML file which contains that segment shown below, it recognizes the placeholder (because of the "%" quotes) and consequently it calls on the processor function with var = TEMPERATURE. The function will then return the Temperature String to the Web server that will substitute the content of Temperature, as last updated by the hardware, into the file being served to a client. This is called template processing.

In the next block of code, an instance of the web server is created, and then the setup() function initializes the Serial peripheral and the hardware. It then goes on to initialize the Wi-Fi radio and connects to the Wi-Fi network, because no Wi-Fi connection means no web server.

// Webserver instance using default HTTP port 80 AsyncWebServer server(80); void setup() { Serial.begin(); // ESP_LOGx use Serial, so a 2 second delay delay(2000); // should be sufficient for USB serial to be up initHardware(); // Connect to the Wi-Fi network, credentials in "secrets.h" WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(100); } // Print local IP address Serial.print("WiFi connected, IP address: "); Serial.println(WiFi.localIP()); // Setup async web browser server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", html_index, processor); }); server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request){ toggleLed(); request->send_P(200, "text/html", html_index, processor); // updates the client making the request only }); server.onNotFound([] (AsyncWebServerRequest *request) { request->send_P(404, "text/html", html_404, processor); }); // Start async web browser server.begin(); }

The end of the setup() function is all about configuring the web server and that amounts to defining three type of HTTP request handlers.

http://<IP>/ or http://<IP>Sends to the client the HTML page html_index defined in html.h
http://<IP>/ledToggles the state of the LED (using toggleLED which updates ledStatus)
Sends to the client the HTML page html_index defined in html.h
Anything elseSends to the client the error HTML page html_404 defined in html.h

The rest of the program is the repeating loop() which does nothing but check on the hardware to update the sensor data and the state of the LED.

void loop() { checkHardware(); }

The async web server is running in the background, handling client requests as they come in and does not need to be "pumped" in the loop() function.

Magic Ingredients toc

This is the char constant that holds the HTML that will be sent by the web server in response to a known request.

#include <Arduino.h> const char html_index[] PROGMEM = R"rawliteral( <!DOCTYPE HTML> <html> <head> <title>%TITLE%</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="refresh" content="5;url=/"> <link rel="icon" href="data:,"> <style> html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center; background-color: white;} h1{color: #0F3376; padding: 2vh;} table{margin-left: auto; margin-right: auto; margin-top:20px;} td{font-size: 1.5rem; text-align: left; padding: 8px;} .state{font-size: 2rem; font-weight: bold;margin-top:28px;} .button{display: inline-block; background-color: blue; border: none; border-radius: 6px; color: white; font-size: 1.5rem; width: 5em; height: 3em; text-decoration: none; margin: 2px; cursor: pointer;} .info{margin-top:48px;} </style> </head> <body> <h1>%DEVICENAME%</h1> <table> <tr><td>Temperature:</td><td><b>%TEMPERATURE%</b> °C</td></tr> <tr><td>Humidity:</td><td><b>%HUMIDITY%</b> %</td></tr> <tr><td>Brightness:</td><td><b>%LIGHT%</b></td></tr> </table> <div class="state">%LEDSTATUS%</div> <p><form action='toggle' method='get'><button class="button">Toggle</button></form></p> <div class="info">%INFO%</div> </body> </html>)rawliteral";

Don't forget that placeholders will be replaced as the HTML code is served. So this is the HTML code received by a web client.

<!DOCTYPE HTML> <html> <head> <title>XIAO ESP32C3 WEB SERVER</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="refresh" content="5;url=/"> <link rel="icon" href="data:,"> <style> html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center; background-color: white;} h1{color: #0F3376; padding: 2vh;} table{margin-left: auto; margin-right: auto; margin-top:20px;} td{font-size: 1.5rem; text-align: left; padding: 8px;} .state{font-size: 2rem; font-weight: bold;margin-top:28px;} .button{display: inline-block; background-color: blue; border: none; border-radius: 6px; color: white; font-size: 1.5rem; width: 5em; height: 3em; text-decoration: none; margin: 2px; cursor: pointer;} .info{margin-top:48px;} </style> </head> <body> <h1>Kitchen Light</h1> <table> <tr><td>Temperature:</td><td><b>20.7</b> °C</td></tr> <tr><td>Humidity:</td><td><b>36.0</b> &percnt;</td></tr> <tr><td>Brightness:</td><td><b>70</b></td></tr> </table> <div class="state">OFF</div> <p><form action='toggle' method='get'><button class="button">Toggle</button></form></p> <div class="info">Using AsyncWebServer and a content refresh meta tag</div> </body> </html>

There is not much to it. A few CSS styles to make the display pretty, a header, a table with three rows showing the current sensor values, a large ON or OFF showing the status of the LED and a form button labelled Toggle. Finally, there is an information line that will help distinguish various versions of this firmware. In the <head> section there are three meta tags that are of some importance.

Given the AsyncWebServer template substitution mechanism discussed above, this ensures that the page displayed by a web client is never more than 5 seconds out of date. It does not matter how many clients are connected to the server, each will request the page every five seconds on its own.

Compiling, Uploading, and Running the Firmware toc

Once the source code for this project has been downloaded and installed on a desktop machine or portable computer, the following directory structure will be in place. It is designed to make it possible to use either the Arduino IDE or the PlatformIO IDE.

wifi_switch ├── 01_simplified_hdw_version │   ├── includeDFRobot_DHT20 │   │   └── README │   ├── platformio.ini │   └── simple_wifi_switch │   ├── hardware.cpp │   ├── hardware.h │   ├── html.h │   ├── main.cpp │   ├── secrets.h.template │   └── simple_wifi_switch.ino ├── 02_basic_wifi_switch │   ... │   └── libraries ├── AsyncTCP ├── DFRobot_DHT20 ├── ESPAsyncWebServer ├── mdSimpleButton └── SimpleDHT

The project as described above is in the first subdirectory 01_simplified_hdw_version, but to make it as self-contained as possible third-party libraries are also included in the libraries directory. Actually, a couple of libraries found there are modified versions of the currently available repository.

Looking at the source code in simple_wifi_switch, three have already been covered main.cpp, hardware.h and html.h. The WiFi library handles connection with a Wi-Fi network but it needs the network's credentials which it expects to find in a file named secrets.h file not included in the source code. Instead there is a file named secrets.h.template.

// Replace with the correct network credentials const char* ssid = "***Wi-Fi***Network***Name***"; const char* password = "***Wi-Fi***Network***Password***";

As instructed edit the file to provide the Wi-Fi network credentials and save it as secrets.h.

The sketch, simple_wifi_switch.ino, is almost nothing but comments. It is there because the Arduino IDE expects a sketch file with the same name as the directory in which it is situated. The Arduino IDE treats .ino files differently from other source files, in particular, by generating a temporary header file. PlatformIO can work with .ino files so presumably, the content of main.cpp could be in simple_wifi_switch.ino and both IDE would be able to compile the source. I prefer avoiding this non standard behaviour which I find confusing given my limited experience with C/C++.

Instead of using Serial.print() statements throughout the code, I decided to use the logging facility built into Arduino-ESP32. Not only does that look very professional, but it has the advantage of removing all the associated code from the binary when the log level is set to 0 or none at a later date, without having to change the source code at all. You might want to look at the debug code in DFRobot_DHT20 which achieves the same thing in a universal fashion meaning it does not depend on a particular platform.

In the PlatformIO IDE toc

To open the project in PlatformIO, go to the PIO home page, click on the Open Project button and then browse to the 01_simplified_hdw_version directory that contains the platformio.ini configuration file for the project and then click on the Open "01_simplified_hdw_version" button to load the project.

[platformio] ; Make the Arduino IDE happy (.INO file must be in a directory of the same name) src_dir = simple_wifi_switch lib_dir = ../libraries [env:seeed_xiao_esp32c3] board = seeed_xiao_esp32c3 framework = arduino platform = espressif32 monitor_speed = 460800 ; Enable ArduinoEP32 logging build_flags = -DCORE_DEBUG_LEVEL=5 ; Clean the project after changing the level

There are a few things that are unusual about the content of the platformio.ini configuration shown above.

Make sure that a recent version of the Espressif 32 platform is used. It should be version 6.1.0 or newer, otherwise PlatformIO may have problems finding the XIAO ESP32C3 when uploading the firmware.

The project is compiled with the PlatformIO: Build command and then uploaded to the microcontroller with the PlatformIO: Upload command. Once the upload is completed successfully, the ESP32-C3 will start executing the new firmware. The serial output can be seen in a terminal. PlatformIO: Serial Monitor will start a terminal session and connect to the XIAO. Most times when the upload fails, it's because I have a serial monitor opened and connected with the XIAO. I just need to close all the open sessions and try the upload again.

PIO button bar

There are many ways to execute PIO commands, the easiest is to click on the appropriate button in the button bar found at the bottom of the window.

In the Arduino IDE toc

As far as I know, the Arduino IDE does not support configuration files for individual sketches. The setup is a manual thing. Installing the Espressinf Arduino-ESP32 platform is a two-step procedure.

  1. Add the URL of the JSON index file of the arduino-esp32 package, https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json in the Additional boards manager URLs: list in the Settings tab of the application Preferences found in the File menu. It is simpler than described as the image below attests.

    Platrom URL in the Arduino IDE Preferences

    Details can be found under Software setup in the Getting Started Seeed Wiki.
  2. Platrom URL in the Arduino IDE PreferencesMake sure that esp32 by Espressif is installed. This is done with the BOARDS MANAGER which is found under the Tools/Board:.../Boards manager... menu. There is an icon that can be clicked instead of going through the menu, it is the one highlighted in the column of icons on the left of the IDE window on the screen capture to the right.

There should be no need to repeat these steps, it is a one-time operation. The IDE needs to be told about the location of the sketch otherwise it will not find the libraries directory. This is done in the Preferences. Click on the BROWSE button and navigate to the code_switch directory so as to fill in the Sketchbook location: field as shown in the image below.

Sketchbook Location in Preferences

It would be good to note the previous value of the field in order to restore it when no longer working with this xiao_esp32c3_project2.

Board and Port Selection in the Arduino IDE There are two configuration steps that are required for each sketch as it is loaded into the IDE.

  1. Select the board in the Tools/Board menu. Be patient, there are many, many ESP32 based boards and they do not seem to be arranged in any meaningful way.
  2. Select the serial port of the board in the Tools/Port: menu. Of course the actual selected port can differ from what is shown on the right. Just pick the correct port from the list of all found ports displayed when the menu item is clicked.

Finally, the debug level for the logging done by the sketch is set in the simple_wifi_switch.ino file.

Debug Levels

#if (!PLATFORMIO) // Enable Arduino-ESP32 logging in Arduino IDE #ifdef CORE_DEBUG_LEVEL #undef CORE_DEBUG_LEVEL #endif #ifdef LOG_LOCAL_LEVEL #undef LOG_LOCAL_LEVEL #endif #define CORE_DEBUG_LEVEL 5 #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif

Just set the CORE_DEBUG_VALUE to the desired level. Once all the configuration is completed, compiling the code can be done by clicking on the check mark icon on the top tool bar. Uploading to the XIAO is done by clicking on the right arrow icon in the tool bar. Note that uploading will compile the code if need be. Starting the serial monitor once the code has been uploaded is done by clicking on the looking-glass icon at the right edge of the tool bar. There are keyboard short cuts and menu items to do these operations.

Execution of the Firmware toc

It is easier to capture the start of the program when the XIAO is reset in PlatformIO because it's serial monitor reconnects automatically. Each time the XIAO is reset, the serial connection is broken and the Arduino IDE disconnects the serial monitor and clears the Port setting. Before restarting the serial monitor the port has to be reset manually in the Tools manu and I am not nimble enough to do this in time to see the logging message unless a long delay were to be introduced after the Serial.begin() in the setup() function.

Here is a the logging output in from the firmware in the PlatformIO serial terminal along with comments in italic. First let's look at the start-up events.

Reconnecting to /dev/ttyACM1 . Connected! Function setup() started with Serial.begin() and then a 2 second delay. Meaning the internal setup time is approximately 0.16 seconds. [ 2162][I][main.cpp:44] setup(): [MAIN] Connecting to COROBRUN-2 [ 2169][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 0 - WIFI_READY [ 2201][V][WiFiGeneric.cpp:340] _arduino_event_cb(): STA Started [ 2202][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 2 - STA_START [ 2204][V][WiFiGeneric.cpp:97] set_esp_interface_ip(): Configuring Station static IP:, MASK:, GW: [ 2260][V][WiFiGeneric.cpp:355] _arduino_event_cb(): STA Connected: SSID: COROBRUN-2, BSSID: ec:be:dd:e6:ac:be, Channel: 1, Auth: WPA2_PSK [ 2261][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 4 - STA_CONNECTED [ 2316][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2416][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2516][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2616][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection ... [ 3316][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3416][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3516][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3616][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3716][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3789][V][WiFiGeneric.cpp:369] _arduino_event_cb(): STA Got New IP: [ 3790][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 7 - STA_GOT_IP [ 3793][D][WiFiGeneric.cpp:996] _eventCallback(): STA IP:, MASK:, GW: [ 3816][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection The only Serial.print() statement in the sketch provides an easy way to identify the URL of the Web server: WiFi connected, IP address: Note that it took a bit more than 1.5 second to establish to connect to the Wi-Fi network. This is by no means a constant. [ 3817][I][hardware.cpp:22] initLed(): [HDW] Initializing LED I/O pin. [ 3818][I][hardware.cpp:14] setLed(): [HDW] LED now OFF. [ 3823][I][hardware.cpp:44] initSensor(): [HDW] Initializing DHT20 temperature and humidity sensor. [ 3931][I][esp32-hal-i2c.c:75] i2cInit(): Initialising I2C Master: sda=6 scl=7 freq=100000 [ 3943][I][main.cpp:76] setup(): [MAIN] setup completed. The sensors are polled at regular intervals and their readings are updated. First reading of the temperature and humidity sensor 2 seconds after the hardware has been initialized. [ 5948][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 6009][I][hardware.cpp:55] readTemp(): [HDW] Temperature 21.8 --> 18.9 [ 6010][I][hardware.cpp:57] readTemp(): [HDW] Humidity 38.9 --> 40.6 First reading of the light sensor, about 5 (SENSOR_DELAY/2) seconds later. This is a first use of the analog digital converter, note how the later is calibrated. [ 10943][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [ 10943][D][esp32-hal-adc.c:190] __analogReadMilliVolts(): eFuse Two Point: Supported [ 10947][I][esp32-hal-adc.c:232] __analogReadMilliVolts(): ADC1: Characterized using Two Point Value: 0 [ 10956][I][hardware.cpp:76] readLight(): [HDW] Light 51 --> 70 Temperature and humidity sensor read again, (SENSOR_DELAY) 10 seconds after last reading. [ 16012][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 16073][I][hardware.cpp:55] readTemp(): [HDW] Temperature 18.9 --> 18.9 [ 16073][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.6 --> 40.6 No calibration of the ADC from now on. [ 20962][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [ 20962][I][hardware.cpp:76] readLight(): [HDW] Light 70 --> 71 [ 26076][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 26137][I][hardware.cpp:55] readTemp(): [HDW] Temperature 18.9 --> 18.9 [ 26137][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.6 --> 40.4

Now let's look at the use of the physical push-button and the Web interface to control the LED/relay.

The physical push button clicked and was released and the LED is turned on by the button handler. [1169852][I][hardware.cpp:33] checkButton(): [HDW] Push-button released [1169853][I][hardware.cpp:14] setLed(): [HDW] LED now ON. [1173372][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [1173433][I][hardware.cpp:55] readTemp(): [HDW] Temperature 19.2 --> 19.2 [1173433][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.1 --> 40.1 [1173435][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [1173443][I][hardware.cpp:76] readLight(): [HDW] Light 70 --> 73 LED turned off with the physical push button. [1174394][I][hardware.cpp:33] checkButton(): [HDW] Push-button released [1174394][I][hardware.cpp:14] setLed(): [HDW] LED now OFF. A Web browser requests the index (default) page from the Web server. [1333735][I][main.cpp:58] operator()(): [WEB] Index page requested. Reading of sensors continuing. [1334396][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [1334457][I][hardware.cpp:55] readTemp(): [HDW] Temperature 19.3 --> 19.3 [1334457][I][hardware.cpp:57] readTemp(): [HDW] Humidity 39.8 --> 39.9 [1334459][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [1334467][I][hardware.cpp:76] readLight(): [HDW] Light 71 --> 71 The Web browser continues to request the index page again every 5 seconds. With the next request, the changed value of the humidity sensor will be displayed in the refreshed Web page. [1338944][I][main.cpp:58] operator()(): [WEB] Index page requested. [1344062][I][main.cpp:58] operator()(): [WEB] Index page requested. The Web code toggle button is clicked and the Web browser sends an HTTP request. [1488544][I][main.cpp:62] operator()(): [WEB] Web button pressed. The Web server request handler toggles the state of the LED. [1488544][I][hardware.cpp:14] setLed(): [HDW] LED now ON. The web server then responds to the HTTP request by sending the index page back to the Web browser which will update the LED ON/OFF indicator on the Web page. Any other connected Web browser will show the updated status of the LED when they send an HTTP request to refresh their copy of the index page. [1493670][I][main.cpp:58] operator()(): [WEB] Index page requested.

At this point we are two thirds of the way through the first version of this project; adding the XIAO ESP32C3 Wi-Fi switch into the home automation system remains to be done.

Integration with Domoticz using HTML toc

It seems as if everyone is talking about Home Assistant. In just February and March there have been two articles on the Seeed Studio Wiki about using the XIAO ESP32C3 with HA: XIAO ESP32C3 accesses Home Assistant via ESPHome service and Connect Grove Modules to Home Assistant using ESPHome. However, there are other open source home automation systems, among them Domoticz which has been our home automation server for a number of years. I am not trying to convert anyone, but I do think Domoticz has some very good qualities. Among them, there is the simple installation and the very low system requirements. Initially, the system was built around Domoticz running on a single core Raspberry Pi Model B+ v1.2 and the performance was more than adequate. I even prepared a small system on a Raspberry Pi model B (Rev 2.0 211.12) with only 512 MB of memory for my sister. The Web interface might have been slow, but Domoticz itself had no problem controlling IoT devices quickly enough to be transparent.

Below I am assuming that Domoticz is already installed on a computer connected to the local area network. The Domoticz Wiki Main Page has links to installation instructions on the "big three" operating systems and specifif instructions for Raspberry Pis, Docker and a couple of NAS appliances. In Linux and any Raspberry Pi model the procedure is very simple. Enter a one line command at the prompt,

pi@tarte:~ $ sudo bash -c "$(curl -sSfL https://install.domoticz.com)"
and then answer the few questions asked. It is usually quite safe to accept the default values, but port assignments should be checked against any installed servers on the system. Of course there is no worry with a fresh install of the operating system on the host machine.

The only thing that is a prerequisite for this post is that the "Dummy" hardware interface be installed.

  1. Open the Domoticz Web interface in a Web browser. The address is http://:8080 where IP is the network address of the computer hosting Domoticz and 8080 is the default TCP port used by Domoticz. If you selected something else during the installation process, adjust accordingly. If you prefer using a secure protocol use the https:// address if the 443 default port was accepted, otherwise add the port after the IP address separated with a colon. A self-signed certificate is included in the Domoticz installation so it will probably be necessary to confirm that an exception can be made to access the site.
  2. Starting with the newly released 2023.1 stable version of Domoticz, a username (admin) and password (domoticz) will have to be given.
  3. Click on the Setup button and then the Hardware button. In the drop-down list select Dummy (Does Nothing... type and above give the hardware a name. I chose Virtual. Click on the Add button.
  4. Adding Dummy Hardware

Let's try to add the XIAO ESP32C3 Wi-Fi switch with its sensors into the Domoticz environment with the least number of changes to the code presented above. Frankly, there is not much to do. The first step is to create three virtual devices in Domoticz: a virtual switch, a virtual lux meter and a virtual temperature + humidity sensor. To add these virtual devices go back to the HARDWARE page.

Adding Virtual Sensors in Hardware

Click on theCreate Virtual Sensors button. This brings up the following dialog box.

Create Virtual Sensor Dialog Box

Select the sensor type, in the first case, Switch and enter a name for the switch. Do the same for the other two devices choosing the correct device type. These three devices will have consecutive identity numbers (idx) if constructed one after the other. In my case they were assigned id numbers 204, 205 and 206.

Domoticz Devices List

We are not truly measuring lux and there is a percent % virtual sensor, but I prefer the lux meter icon. Strictly speaking it does not really matter and in daily use the lux meter will be hidden (just by starting the name with a "$") or even replaced with a user variable. In the beginning, though, it will be useful to see the ersatz lux values in order to calibrate the sensor and determine which value represents dusk.

The values displayed on the virtual devices in the Web interface of the home automation system will be done with HTML requests. These must be JSON formatted HTTP requests as explained in the Wiki on Domoticz API/JSON URL's. It is not a bad idea to test with curl to verify that the HTTP request sent to the Domoticz web server does what is desired.

michel@hp:~$ curl "" -w "\n" { "status" : "OK", "title" : "SwitchLight" } michel@hp:~$ curl -d "type=command&param=udevice&idx=204&nvalue=0" "" -w "\n" { "status" : "OK", "title" : "SwitchLight" } michel@hp:~$ curl "" -w "\n" { "status" : "ERR" }

The first two requests will respectively set the virtual light switch state to on and off only. Domoticz will not take any action except for changing the displayed state of the virtual switch on the Web interface. While POST request can be used as seen in the second example above, GET requests will be used as they are simpler to make. The third request shows the typical respons when something is wrong with the query, such as an invalid index number.

As in the case of the hardware, I created a simple domoticz library to take care of updating the sensors in the home automation web interface. Here is the header file with the three functions to update each type of sensor.

#pragma once #include "domoticz_data.h" // Update the state of the virtual switch with given idx, value=0 for Off, value=1 for On. bool updateDomoticzSwitch(int idx, int value); // Update the "Lux" level to value in the light sensor with the given idx. bool updateDomoticzLightSensor(int idx, int value); // Update the Temperature (value1) and Humidity (value2) values in the Temp+Humidity // sensor with the given idx. Must set the humidity state to normal (0), comfortabe (1), // dry (2) or wet (3) explicitly, Domoticz does not calculate a value even though // it does calculate the dew point. // // Note value1 must be in °C even if °F are chosen as units in Domoticz settings // value2 must be a percent (from 0 to 100) such as 48.9 and will be displayed as 48.9% bool updateDomoticzTemperatureHumiditySensor(int idx, float value1, float value2, int state=0);

The state parameter will be discussed later under the heading Perceived Humidity.. As it is, the default 0 value will display "normal" in the Web interface no matter the relative humidity. The implementation found in domoticz.cpp is straight forward. The logic is simple, the function handling each type of sensor builds up a URL with the appropriate JSON formatted query. The function sendHttpRequest() takes care of sending that URL and logging the result obtained from the Domoticz web server.

#include <Arduino.h> #include "esp32-hal-log.h" #include <WiFi.h> #include <HTTPClient.h> #include "domoticz.h" #define TAG "DMZ" bool sendHttpRequest(String url) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGE(TAG, "Domoticiz not updated, Wi-Fi not connected."); return false; } ESP_LOGV(TAG, "HTTP request URL: %s", url.c_str()); HTTPClient http; http.begin(url.c_str()); int httpResponseCode = http.GET(); String payload; if (httpResponseCode > 0) payload = http.getString(); // Free resources http.end(); // Return true if OK with information log message, else return false with an error log message if ( (httpResponseCode == HTTP_CODE_OK) && (payload.indexOf("\"status\" : \"OK\"") > 0) ) { ESP_LOGI(TAG, "Domoticz updated."); return true; } else if (httpResponseCode == HTTP_CODE_OK) { ESP_LOGE(TAG, "Domoticz update failed with response: %s", payload.c_str()); return false; } ESP_LOGE(TAG, "Domoticz update failed with HTTP code: %d", httpResponseCode); return false; } bool updateDomoticzSwitch(int idx, int value) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGI(TAG, "Domoticiz not updated, Wi-Fi not connected."); return false; } String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command&param=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue="; url += value; return sendHttpRequest(url); } bool updateDomoticzLightSensor(int idx, int value) { String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command&param=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue=0&svalue="; url += value; return sendHttpRequest(url); } bool updateDomoticzTemperatureHumiditySensor(int idx, float value1, float value2, int state) { String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command&param=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue=0&svalue="; url += String(value1, 1); url += ";"; url += String(value2, 0); url += ";"; url += state; return sendHttpRequest(url); }

A little bit of care is needed when handling the response from the Domoticz web server. As seen with the curl commands, even when Domoticz receives an incorrectly formatted query, it will respond with a JSON formatted response with a return code of 200 (OK). Consequently the JSON response must be scanned to ensure that "status" : "OK" entry is present. The identity numbers of the virtual sensors and the IP address of the Domoticz web server are defined in the header file domoticz_data.h which is not included in the source code of this project. As with the Wi-Fi credential, there is a template file, domoticz_data.h.template instead.

#pragma once #define DOMOTICZ_URL "<ip address of domoticz server>:<tcp port>" #define SWITCH_IDX 204 #define LUX_IDX 205 #define TEMP_HUMI_IDX 206

Edit this file entering the correct values and save as domoticz_data.h in the same directory as the template and main.cpp.

It is rather obvious when the virtual sensors should be updated by the XIAO ESP32C3 firmware. It has to be done in the hardware handlers. So there are four extra lines in harware.cpp.

So now the Domoticz web interface will be updated each time the LED state is changed or a sensor is read. Indeed, the update on the Domoticz Web interface could occur more quickly than on a client Web browser connected to the XIAO ESP32C3 given that may require up to 5 seconds before that update is displayed. At this point clicking on the virtual switch in the Domoticz Web interface will cause its value to toggle between on and off, but nothing will happen on the XIAO side. Adding actions to be performed when the state of the virtual switch is changed within Domoticz will help solve this problem.

Edit a Domoticz Virtual Sensor Click on the Edit button of the virtual switch in teh Switches tab. Then add two URLs in the On Action and Off Action fields as shown below. Do not forget to click on the Save button otherwise the changes to fields will not be kept.

Domoticz Switch Actions

To complete this step, the XIAO Web server must include an additional two handlers for the requests that Domoticz may send to it. This is done in the setup() function in main.cpp.

server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){ ESP_LOGI(TAG2, "Domoticz command on"); setLed(1); request->send(200, "text/plain", "Domoticz device on"); }); server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){ ESP_LOGI(TAG2, "Domoticz command off"); setLed(0); request->send(200, "text/plain", "Domoticz device off"); });

Domoticz does not expect much of a response when it executes the On Action or Off Action. However to avoid polluting the Domoticz log, it is necessary to send a non-empty response with a 200 OK HTTP code. The mime type of the response probably does not matter much, but the text cannot be empty, so I chose to send back to the Domoticz server the same message as is logged to the serial monitor. A 202 Accepted code also satisfies Domoticz as long as the response is not empty.

Useful Domoticz Additions toc

Home automation devices become "intelligent" when decisions are made by the system independent of human intervention. Let's look at a few examples of things that can be done with our simple Wi-Fi device.

Timed Light toc

With help from Domoticz, the Wi-Fi light can become a timed light. Click on the Edit button of the XIAO ESP32C3 (virtual) Switch. Then put the desired number of seconds during which time the switch will remain on in the Off Delay field. Do not forget to click on the Save button before pressing the Back button.

Off Delay in Domoticz Switch Edit Screen

I have done this for a light in a staircase and something similar for a light in a wardrobe. In both cases the delay was longer than what is shown above but, unless one is really patient, 30 seconds seems like an eternity when testing.

Auxiliary Light (updated 2023-06-24) toc

Let's say that the XIAO ESP32C3 is controlling the light in a hallway that goes to a wardrobe which is also illuminated with a Wi-Fi controlled light. If the hallway has a window, then during the day it would be better to be able to turn the wardrobe light on without lighting up the hallway. However when the hallway light needs to be on, so will the wardrobe light. Instead of putting a timer on the latter, it makes more sense to let the hallway light control it. After two previous attempts, here is the actual dzVents script that I use.

--[[ Hallway light (idx 195) also controls wardrobe light (idx 87) but not vice-versa ]] return { on = { devices = { 195, }, }, logging = { level = domoticz.LOG_ERROR, -- change to LOG_ERROR when script is functioning as expected marker = 'Hallway', }, execute = function(dz, item) dz.log(item.name .. ' is now ' .. item.state , dz.LOG_DEBUG) if item.active then dz.devices(87).switchOn() else dz.devices(87).switchOff() end end }

This method works no matter how the state of the Domoticz virtual switch is updated and no matter the value of the Prevent loop setting in the MQTT Hardware definition in Domoticz. Save the script in the domoticz/scripts/dzVents/script directory and ensure that its file name end with the .lua extension.

2023-04-12 revision

This can be done with a Domoticz group.

Two virtual Domoticz switches Create Domoticz group

On the left we see the two virtual sensors which will be linked with a group. Start by creating a group. To do that click on the Scenes tab at the top of the Domoticz web interface and then name the group. Here it is named AuxSwitch. Set the type to Group in the drop-down type list and then click on the Add Scene button.

Start editing Domoticz group Add activation device

Once the group shows up in the Scenes tab, click on the Edit button. While barely visible above, click on the Add Manual Light/Switch Device under the Activation Devices list. Add the XIAO ESP32C3 Switch to the list by selecting it in the Device: drop-down list and then clicking on the Add Device button.

Add deviceSaving the changes

Add the Wardrobe virtual sensor to the list of Devices in the group. Do this by selecting the sensor in the Device: drop-down list and then clicking on the Add button. All that remains to be done is to save the group. I would suggest adding a "$" in front of the name of the group so that it becomes hidden in the Domoticz interface (it is still displayed in the list of devices) and adding a description of the group's purpose. It is easy to forget in a few months time why a hidden group or device is there. Don't forget to click on the Save button.

WARNING: This only works if HTTP request sent to Domoticz with changes to the status of the relay is


Furthermore, it might be necessary to set the Prevent Loop: to False in the Domoticz MQTT Hardware. More details here.

In the original version (2023-03-25), I argued that using the switch slave device facility would accomplish the desired task. I was wrong. Test of what I had proposed seemed to work, but I had forgotten to remove a Lua script which had taken care of one-way synchronization of the switches. I wanted to offer a solution that did not require a script and managed to fall into a trap, not for the first time I might add! Let me quote a short 2017 Domoticz forum reply by emontnemery on this topic.
[The p]urpose of [a] slave switch is to update [the] state of [a] main switch without sending a command to the [main] switch. This is useful for simple RF switches (meaning they don't report [their] state) with several remotes: If you press one of the remotes, [the state of the Domoticz virtual sensor will be] updated to try to be in sync with the real switch.
Then emontnemery goes on to provide the solution.
To do what you want to do you can instead create a group with only the [wardrobe], then you add [XIAO ESP32C3 Switch] as [the] "activation device" for the group.
With thanks, that is so much more succinct than my long-winded explanation.

This is not a theoretical discussion. Our house has outside lights controlled with a wall switch beside the front entrance and the attached garage has outside lights controlled by a switch in the garage. When it was time to illuminate the way for guests returning to their car in the driveway in the dark, I had to run to the garage to turn its outside lights and then come back to the front door to turn on the house outside lights. When no longer needed I had to retrace my steps to turn off both lights. Since the auxiliary light script has been in place, the switch by the front entrance turns all outside lights on or off. The switch in the garage controls the outside garage lights only.

While the attempt at using a Domoticz group instead of script was in place, my spouse asked me why the garage lights were broken. It had to be my fault of course. It wasn't hard to come to that conclusion, there are actually three outside lights along the garage with minuscule odds that all three would burn out at the same time. The script is back doing its job, and my status as chief geek is reestablished.

Night Light toc

The XIAO ESP32C3 could be the basis of a night light, because Domoticz has built-in functions to do that. Click on the Timers button of the virtual switch.

Setting an On timer based on sunset

There are many ways to set the time at which a IoT switch will be turned on or off. In this example, the light will be turned on at dusk every day, 40 minutes before sunset. Note that the action is enabled and that the Add button must be pressed to add the action to the list at the top.

On and Off timers based on sunset and sunrise

The screen capture above shows the result after adding an Off command to be performed 10 minutes after sunrise. Domoticz calculates the sunrise and sunset times for every day and shows them for the current date under its top banner in every tab sheet except for the settings page. Using the sunset and sunrise times to turn on and off a night light is certainly preferable to using fixed hours which would have to be adjusted over the course of a year. However, with the light sensor on the Wi-Fi switch it is possible to turn the light when needed implicitly taking into consideration the cloud cover and other possible determinants of the actual light level.

Here is a very simple dzVents Lua script to do that.

return { on = { devices = { 205 -- lux meter } }, execute = function(dz, device) if (device.lux < 60) then dz.devices(204).switchOn() else dz.devices(204).switchOff() end end }

I should point out that dzVents is just one of the many scripting methods available in Domoticz. There are two problems with that dzVents script. First the presence of the magic number 60. Remember that the output of the analog to digital converter connected to the light sensor was mapped to a value in the 0 to 100 range. I chose 60 as the threshold measure between light and dark, but that number is totally arbitrary. The threshold will crucially depend on the exact placement of the sensor. There needs to be some calibration. The other problem is that with such a hard decision rule between turning the light on and off, one has to expect that it will be flickering as the light level fluctuates around the threshold value. Here is a better version of the script.

--[[ Night light with some hysteresis Device with idx 205 is the "lux" meter Device with idx 204 is the XIAO ESP32C3 Wi-Fi light switch ]]-- return { on = { devices = { 205 -- lux meter } }, execute = function(dz, device) local isOn = dz.devices(204).active local lux = device.lux if ((lux < dz.variables('dark_lo_threshold').value) and not isOn) then dz.devices(204).switchOn() elseif ((lux > dz.variables('dark_hi_threshold').value) and isOn) then dz.devices(204).switchOff() --else --do nothing in zone between dark_lo_threshold and dark_hi_threshold end end }

Instead of a magic value, the script depends on two thresholds that are set as user variables and therefore easily changed while calibrating the light sensor without any need to edit the script.

User variables in Domoticz

User variables are managed in the Setup/More Options/User variables pop-up window. The above shows five user variables still present on my system after deleting the very first one ever created and adding the threshold variables. To add a user variable just enter a new name in the Variable name: field, chose the variable type in the drop-down list, assign a value to the variable and then press on the Add button. To change the value of a user variable, click on its name in the list of entries, enter the new value in the Variable value: field and then click on the Update button under the list of variables.

High and low thresholds

The high and low thresholds establish a band of lux values in which the LED/relay is neither turned on nor turned off. If the lux level goes above the high threshold then the LED/relay is turned off, and if the light level is inferior to the low threshold then the LED/relay is turned on.

Perceived Humidity toc

The temperature and humidity sensors in the Domoticz calculate the dew point when the temperature and humidity are set. One of four strings, 'Normal', 'Comfortable', 'Dry', or 'Wet' called the humidity status is also displayed. I presume it is meant to indicate how we perceive the relative humidity. It is important to note that Domoticz does not set this string, it is specified in the bool updateDomoticzTemperatureHumiditySensor(int idx, float value1, float value2, int state=0); helper function. Since version 3.0.15 the dzVents scripting system does provide a means of updating the humidity status. Here is a dzVents script that exploits this added functionality. One needs to add the idx of all Temp + Humidity and Temp + Humidity + Barometer sensors i the trigger devices list.

--[[ Fix up humidity status of "Temp + Humidity" and "Temp + Humidity + Baro" type sensors Add their idx in the devices list below Tested with temp+humidity sensors with idx 206 and 208 and temp+humidity+Baro sensor with idx 207 Change "units = 'C'"" to "units = 'F'"" if the °Fahrenheit are used to display temperature in the Domoticz Web interface. This assignment is the first line of execute = function(dz, item) below. User variable 'TemperatureUnit' must be 'C' or 'F' indicating unit used to display temperature values Tested in Domoticz 2023.1, Build f9b9ac774, and dzVents Version: 3.1.8 ]] return { on = { devices = { 206, 207, 208 }, }, data = { updating = { initial = 0 } }, logging = { level = domoticz.LOG_ERROR, -- change LOG_ERROR to LOG_DEBUG to see log messages in Domoticz log marker = 'fix_humid', }, execute = function(dz, item) units = 'C' -- set to 'F' if the display units set to °Faherenheit in Domoticz Settings dz.log('item '..item.name..', type: '..item.deviceType..', idx: '..item.id) if dz.data.updating == 0 then dz.data.updating = 1 dz.log('updating item') local temp = item.temperature if units == 'F' then dz.log('Converting '..temp..'°F') temp = dz.utils.toCelsius(temp) dz.log(' to '..temp..'°C') end if string.match(item.deviceType, 'Baro') then dz.log('item.updateTempHumBaro') item.updateTempHumBaro(temp, item.humidity, dz.HUM_COMPUTE, item.pressure, item.forecast) else dz.log('item.updateTempHum') item.updateTempHum(temp, item.humidity, dz.HUM_COMPUTE) end elseif dz.data.updating == 1 then dz.log('not updating item') dz.data.updating = 0 end end

By default this script works correctly if the temperature units are displayed in the Celsius/Centigrade scale (°C) in the Domoticz interface. If temperatures are displayed in the ° Fahrenheit, the a conversion is necessary. As indicated in the top comment of the script, it must be edited and the line units = 'C' must be changed to units = 'F'. This "hardwired" definition entails problems when the temperature units are changed Setup/Settings/Meters/Counters, until the units = '?' line is updated. I suspect that changing units will not be done often so it is not too onerous. The table summarizes how the dzVents will chose the humidity status.

Relative Humidity

I am not convinced that this is the appropriate way to go. Back in July 2017, I had chosen to use the dew point instead. I may come back to that approach in a future installement.

Thoughts on the XIAO Starter Kit toc

It was surprising that Seeed Studio had sent a XIAO Starter Kit, I had never expressed an interest in it nor even mentioned it in the few e-mails we have exchanged. Do not misunderstand me, I am not against starter kits as such, I just thought I had outgrown them.

When dipping my toes in the Arduino universe, I did purchase a starter kit from Sparkfun built around their RedBoard, an "official" Arduino clone. At the time, it was comforting to get a development board and a few components that I could trust would work together. However, when working through a few of the examples in the included project book, I was underwhelmed by the experience. Again, I do not want this last statement to be misunderstood. I had experience in programming with Pascal, Modula, Delphi and some Java and I already had a decent collection of parts such as I²C and SPI displays, sensors and so on.

After first building the project with discrete components, I remembered the XIAO Starter Kit and build a second project with it as explained above. This experience has changed my opinion. I can now see the value of the kit for those that have a bit more experience. First of all, the XIAO Expansion Base by itself does transform the XIAO boards into true development boards. There is a built-in I²C display, a buzzer, a "user" push button, a reset switch, a micro SD card reader and a lithium coin cell battery holder. Having all these components so readily available often makes it possible to start developing software without delay. The ability to easily add two other I²C devices and a serial port using Grove compatible peripherals only enhances this. And there's more. As shown above, there is a Grove connector for the D0 I/O pin and female headers for all the pins of the XIAO so that it is easy to connect the base to a breadboard.

There is a downside to using the XIAO Expansion Base. It is necessary to solder male headers on the XIAO board. Sometimes one would not want to do this in case the XIAO is to be soldered to a carrier board using the castellated pads or if connections will be made with wires. Of course, I did solder the header pin on one of my XIAO ESP32C3 and decided to leave it in the XIAO Expansion Base declaring that to be my development test bed for all members of the XIAO family. Given that the XIAO costs a tenth as much as the Starter Kit and a third of the Expansion Base, it is not much of an investment. To use accounting terminology, there is no real sunk cost as the XIAO with pin headers could still be used elsewhere.

In conclusion, I can see two use cases for the XIAO Starter Kit. Beginners will enjoy using it. They will be able to concentrate on learning how to use the Arduino IDE (or better yet the PlatformIO IDE) which is daunting enough at the start without having to worry much about the hardware. For those that are already at ease working with microcontrollers, sensors and so on, then there is a gain in productivity to be had with the kit. The kit is not cheap compared to the XIAO boards, so if that is a problem, perhaps the XIAO Expansion Base would make more sense. In that case, individual Grove sensors and other peripherals could be purchased as needed, although one has to be careful because shipping charges can be proportionally high when making little purchases. No matter, I no longer think that I have outgrown useful devices such as the XIAO Expansion Base or Starter kits.

Alternate Hardware toc

As made amply clear that the XIAO Expansion Base and the Grove components are not needed. At first, I used discrete components on a small breadboard and connections to the XIAO with flying leads. The light sensor was a voltage divider made with a light dependant resistor and a fixed resistor and the temperature and humidity sensor was a DHT11 which are notoriously inaccurate. I must admit that the humidity measures of the DHT11 were grossly inaccurate but that was expected because the sensor is one of two rejects from another project. So in actual use, I would recommend the DHT22 or the DHT20 which is more accurate than the DHT11. At the very least get more than one DHT11 and test their accuracy. Here is a schematic of the four components as connected to the XIAO ESP32C3 board.

Hardware schematic 1/2Hardware schematic 2/2

I used a DHT11 mounted on a small printed circuit board which takes care of the pull up resistor. Be careful the pinout is different on different boards. The value of the current limiting resistor connected to the LED is not critical, 200 to 500 ohms would be acceptable depending on the LED's colour. I know that sounds odd, but the power requirement of LEDs typically depends on their colour; look it up on the Web. I am not sure which LDR I used; the package says "type 5516 5-10K" but I remember that I wrote that months after buying the photoresistor and having a hard time identifying it. A 4.3 kΩ seems well matched to the LDR.

Of course the sensor drivers used previously will not work with this hardware. I rewrote the header and implementation files to accommodate the two sets of peripherals presented above and even other types of sensors. I have even made it possible to substitute emulated sensors instead of physical devices. Here is the modified header file. I also renamed the files hardware2.h and hardware2.cpp to avoid overwriting the original versions.

#pragma once #include <Arduino.h> // pin definitions // The sensor data in main.cpp that the hardware will update - all strings extern String ledStatus; extern String Temperature; extern String Humidity; extern String Light; // LED (or relay) #define LED_PIN D10 // Push button #define BUTTON_PIN D1 // Light sensor #define LS_PIN D0 // has to be one of D0 - D3 (A0 - A3) // Temperature and humidity sensor #define DHT_PIN D8 // not used if DHT = DHT20 or DHT_NONE #define DHT_NONE 0 // emulate a temperature/humidity sensor #define DHT11 1 // 1-wire DHT11 sensor #define DHT22 2 // 1-wire DHT22 sensor #define DHT20 3 // I²C DHT20 sensor Grove temperature/humidity sensor #define DHT DHT11 // Installed temperature/humidiy sensor type // light sensor #define LS_NONE 0 // emulate light sensor #define LS_LDR 1 // light dependant resistor #define LS_DIODE 2 // photodiode sensor Grove Light Sensor 1.2 #define LS LS_LDR // installed light sensor type #define LS_FIFO 10 // size of FIFO queue when calculating rolling average if < 2, then no averaging done #define LS_READ 10000 // minimum time (in ms) between readings of the sensor data if averaging #define SENSOR_DELAY 60000 // minimum time (in ms) between updates of the sensor data // Hardware abstraction void initHardware(void); // Initialize the hardware (LED, button, temperature and light sensors) void checkHardware(void); // Read button and sensors and update readings strings in main.cpp void toggleLed(void); // Toggle the LED state and update ledStatus in main.cpp void setLed(int value); // Set the LED on (value = 1) or off (value = 0)

As can be seen all this is handled with macros, making the code messier, especially in the implementation part so I will not go into details. I will point out that I added a First-In-First-Out (FIFO) queue to calculate a rolling average for the light level measurements. It seemed appropriate to average the data from the light sensor because a cloud could cover the sun for a short interval, a person could cast a shadow over the sensor for a few seconds, or a temporary light might push up the reading artificially. The light level is read at a faster frequency, specified with the LS_READ millisecond time interval between readings. On each reading, the latest value is added to the queue while the oldest reading is removed and the average value is recalculated and saved. When the SENSOR_DELAY has expired, it is the latest calculated average value which is used to update String Light; displayed in Web clients and in the Domoticz Web interface. The size of the queue is 10 by default but that can be changed with the LS_FIFO. Obviously if LS_FIFO < 2, there will not be much averaging going on!

Critique and Future Developments toc

While the Wi-Fi switch does work, at this stage, this is just a demonstration project. It is much too brittle. Case in point, when I rebuilt the project with the XIAO starter kit, it did not work properly. The DHCP assigned IP address of the second ESP32-C3 was different because it has a different MAC address. Using fixed IP addresses was not a good idea, akin to using unnamed numerical constants or so-called magic numbers. There are ways around this. The ESP32 does support multicast DNS (mDNS) so that the switch could be assigned a host name which it can advertise. This is not foolproof but it does work, some of the time at least. I find that using an MQTT broker much more resilient and dependable. Like most other home automation systems, Domoticz does support MQTT.

There is no provision for things going wrong in the firmware. One problem has already been identified: the firmware will hang if the DHT20 cannot be initialized for some reason. Getting temperature readings is not the main function of the Wi-Fi switch so it is inexcusable to disable the device if an auxiliary function cannot be done for whatever reason. What happens if the Wi-Fi connection is lost? What happens if the Wi-Fi connection is never established in the first place because the network credentials are incorrect or had to be changed at the router? There is no provision for updating the firmware, where really, over-the-air firmware should be supported.

Using the template substitution engine of ESPAsyncWebServer along with the content refresh meta tag does work, but it is not perfect by any means. For one thing, the web interface flashes each time the page is reloaded. In order to mitigate that problem, a longer delay between reloads might be chosen, with the consequence that the web interface is less responsive. Even a relatively short five second delay before the Web interface is updated seems long when the LED is toggled with the physical switch.

The list of things to do to approach what Tasmota does is long, but I think the next bit to do will be to find a better way to update the data displayed on client Web browsers.

GNATS, a Tiny Basic ESP32 GPS Based NTP Server-> <-First Look at the Seeed Studio XIAO ESP32C3