DYI ESP32-based weather station for home usage (the initial goal is Home Assistant integration). It measures temperature and humidity with access over Matter/Thread.
Disclaimer: This is my first project of such kind (hardware + firmware), so it might follow decisions which are not typical or based on wrong assumptions or expectations.
- Read an SHT41 sensor every N minutes
- Report the values as a Matter sensor over Thread
- Use as little battery as possible
- The chip is an ESP32-C6. It runs Rust with ESP-IDF,
rs-matterand OpenThread. - The device is a Thread sleepy end device. It sleeps (light sleep) almost all the time and polls its parent every 15 seconds.
- BLE is on only while a commissioning window is open.
- After a start the device stays awake for 10 minutes. The time is used for pairing or debugging. RESET is for a new awake window.
- A bad sensor reading is never sent as a real value. The device still goes back to sleep.
- Each board has its own serial number, setup passcode and discriminator, all taken from the chip MAC. Nothing is stored.
- macOS or Linux, with
miseanddirenv. git,curl,patch,tar, a C compiler for host tools- Internet access for the first build (Rust, ESP-IDF v5.5.5, crates)
- About 8 GB of disk space. Everything is installed to the project's folder. Nothing is installed on the system
- One of these boards + an SHT41 with I2C pull-ups:
| Board | Cargo feature | SDA / SCL | Flash | Notes |
|---|---|---|---|---|
| Seeed XIAO ESP32-C6 | board-xiao (default) |
GPIO22 / GPIO23 | 4 MB | |
| Unexpected Maker TinyC6 TC6-01 | board-tinyc6 |
GPIO6 / GPIO7 | 8 MB | GPIO22 and GPIO23 drive the RGB LED, the firmware does not touch them. It reads the battery voltage on GPIO4. |
Initially the project was targeted against Seeed XIAO ESP32-C6, then I switched for using Tiny C6 as more
convinient alternative. I consider Xiao C6 still as supported one.
- Pins come from the official schematics of the boards
- A Matter controller with Thread support, for example Home Assistant with a Thread border router
direnv allow # loads the project environment from .envrc
mise install # Rust toolchain
mise run setup # ldproxy and espflash into .local/bin
mise run vendor # downloads and patches the Matter crates into vendor/- The first build also downloads ESP-IDF into
.embuild/. It takes a while. mise run vendoris needed once, and again after a change inpatches/.- Patches are in
patches/. They remove idle timers that wake a sleeping device and fix the BLE commissioning phase and the Thread diagnostics.
mise run fmt-check
mise run check # clippy for XIAO, warnings are errors
mise run check-tinyc6 # clippy for TinyC6
mise run test # unit tests on the host- Set the passphrase for the provisioning data. It is compiled into the firmware:
export MLW_PROVISIONING_SECRET='choose-a-long-secret'- Tasks come in pairs for the two boards (add
-tinyc6for the TinyC6):
| Task | What it does |
|---|---|
build, flash |
Product firmware. USB console off, lowest power. |
build-debug, flash-debug |
Same, with USB console and logs. |
build-pairing, flash-pairing |
First commissioning firmware: BLE always on, no light sleep. |
build-fake-sensor |
Generated sensor values, for a board without SHT41. XIAO only. |
monitor |
Serial monitor. Depends on a debug build. |
- Flash tasks take the serial port:
mise run flash -- /dev/cu.usbmodemXXXX. - Flashing keeps the NVS, so a paired device stays paired.
- Flash
flash-pairing(orflash-pairing-tinyc6) on a new board. The low-power firmware cannot pair over BLE yet - Get the pairing code of the board:
mise run pairing-code -- /dev/cu.usbmodemXXXXreads the MAC and prints the manual code and QR text.espflashresets the board.mise run pairing-code-mac -- 10:3c:ca:01:02:03does the same for a MAC, without a board.
- Add the device in the controller with the manual code or the QR text
- Flash the product firmware (
flash)
Note: the passcode can be computed from the MAC, because the code is public. It works only while a commissioning window is open. This must be kept in mind before using the device outside a private home.
- Normally the controller gives the Thread network during pairing
- To restore a lost network, flash a debug build and send an encrypted Thread dataset with
the measurement interval:
mise run provision -- /dev/cu.usbmodemXXXX. - It uses the same
MLW_PROVISIONING_SECRET.
- After the 10 minute awake window the board has no USB port (light sleep).
- To flash a sleeping board: hold BOOT, tap RESET, flash, then tap RESET alone.
- If the sensor loop stalls, a timer resets the board (checked every 10 minutes). Pairing data stays
- The wait before a reset is the interval + 2 minutes. At boot it's 15 minutes, with no pairing 1 hour
- A Matter error restarts the board after 30 seconds
- The last stage and reason survive the reset, a debug build prints them as
recovery: previous=... - Not measured on hardware yet.
mise run test-recoveryruns the host tests
- The main case is a retail one:
TFA Dostmann 98.1114.02 - The parts inside it are 3D printed. The STL files are in
enclosure/ - Details and pictures are in enclosure/README.md
- SHT41 to TinyC6:
| SHT41 | TinyC6 |
|---|---|
| VIN | 3V3 |
| GND | GND |
| SDA | IO6 |
| SCL | IO7 |
| 3Vo | not connected |
The TinyC6 has no I2C pull-ups, so the SHT41 breakout must have its own.
- LiPo battery (3.7 V, 3000 mAh) goes to the battery input of the TinyC6. The board charges it from USB and reads its voltage on IO4 through its own divider, so no extra wiring is needed for that
- The battery curve of the TinyC6 is a generic LiPo curve. The XIAO reports a fixed placeholder battery value
- Battery current is not measured yet
👉 If you found the project useful or it helped to solve your problem, feel free to buy me a coffee.
MIT
This software is provided for personal use. No warranty is provided. Use at your own risk.




