A Thread Minimal Thread Device (MTD) that reports temperature, humidity, and battery voltage using a Seeed Xiao nRF52840 Sense with an attached SHT41 sensor.
- Thread MTD (always-on) for reliable operation
- Radio always listening for better responsiveness
- Dual Operating Modes:
- Normal Mode: Maximum power savings, no USB console
- Debug Mode: USB console active, green LED on, OpenThread CLI available
- Mode Switching: Press reset button to toggle between modes
- Sensor Data: Temperature, humidity (SHT41), battery voltage, and RSSI via ADC
- Network Configuration: Use OpenThread CLI commands in debug mode
| Xiao Pin | Function | SHT41 Breakout |
|---|---|---|
| D2 (P0.28) | VIN Power | VIN |
| D3 (P0.29) | GND | GND |
| D4 (P0.04) | I2C SCL | SCL |
| D5 (P0.05) | I2C SDA | SDA |
The sensor is powered via GPIO for power savings - D2 is driven high for VIN, D3 is driven low for GND.
Open the project in VS Code with the Dev Containers extension. The container includes the nRF Connect SDK v3.2.1 toolchain. Then:
source /opt/toolchain-env.sh
export ZEPHYR_BASE=/workdir/ncs/zephyr
west build -b xiao_ble/nrf52840/sense -p always# Set up environment (adjust path to your NCS install)
source /opt/toolchain-env.sh
export ZEPHYR_BASE=/path/to/ncs/zephyr
# Build
west build -b xiao_ble/nrf52840/sense -p alwaysThe Xiao nRF52840 supports UF2 flashing:
- Double-tap the reset button to enter bootloader mode
- Copy
build/xiao-humidity/zephyr/zephyr.uf2to the USB mass storage device
Or use:
west flash- After flashing, the device starts in normal mode
- Press reset to switch to debug mode (green LED lights up)
- Connect via USB serial (115200 baud)
- Configure Thread network using OpenThread CLI:
# Set network credentials (example)
ot dataset init new
ot dataset networkname MyThreadNet
ot dataset networkkey 00112233445566778899aabbccddeeff
ot dataset panid 0xabcd
ot dataset channel 15
ot dataset commit active
# Start Thread
ot ifconfig up
ot thread start- Once connected, the device will send sensor readings every 60 seconds to
ff03::1(mesh-local all-nodes multicast) on UDP port 5683
Press reset again to switch to normal mode:
- USB console is disabled
- Green LED is off
- Device enters deeper sleep between CSL windows
CoAP payload format is CBOR (binary), encoded as a map with integer keys:
| Key | Name | Unit | Description |
|---|---|---|---|
| 0 | temp_mdegc | milli-°C | Temperature (e.g. 25123 = 25.123 °C) |
| 1 | hum_mpct | milli-% | Humidity (e.g. 45678 = 45.678 %) |
| 2 | batt_mv | mV | Battery voltage |
| 3 | rssi_dbm | dBm | RSSI to Thread parent |
| 4 | role | enum | Thread device role (2 = child) |
| 5 | uptime_s | seconds | Device uptime |
| 6 | reset_reason | bitmask | nRF reset reason register |
| 7 | mac_tx_total_delta | count | MAC TX total (absolute counter) |
| 8 | mac_rx_total_delta | count | MAC RX total (absolute counter) |
| 9 | mac_tx_retry_delta | count | MAC TX retries total (absolute counter) |
| 10 | mac_tx_data_poll_delta | count | Data poll TX total (absolute counter) |
| 11 | mac_tx_beacon_req_delta | count | Beacon request TX total (absolute counter) |
| 12 | mac_tx_err_cca_delta | count | CCA failures total (absolute counter) |
| 13 | mac_tx_err_busy_delta | count | Busy channel errors total (absolute counter) |
| 14 | detach_events_delta | count | Thread detach events total (absolute counter) |
| 15 | parent_change_delta | count | Parent changes total (absolute counter) |
| 16 | time_detached_ms_delta | ms | Time spent detached total (absolute counter) |
Keys 7-16 are absolute counters (monotonic unless the device/counter resets), useful for long-term diagnostics.
The firmware is CoAP-only:
- Publishes to mesh-local all-nodes multicast address (ff03::1) on UDP port 5683
- Uses CBOR payload format with 17 integer keys (see Sensor Data Format above)
- No MQTT client runs on the sensor node
If you want MQTT/Home Assistant integration, run the CoAP-to-MQTT bridge in coap-mqtt-bridge/.
Edit prj.conf or use menuconfig to customize:
CONFIG_SENSOR_READING_INTERVAL_SEC: Sensor reading interval (default: 60s)CONFIG_OPENTHREAD_CSL_TIMEOUT: CSL timeout in seconds (default: 60s)
In normal mode with CSL enabled:
- Most time spent in deep sleep
- Wakes briefly every 10s (CSL period) to check for messages
- Wakes every 60s to read sensors and transmit data
The firmware now powers the sensor early during boot (POST_KERNEL priority). If you see "SHT4x device not ready" errors:
- Check wiring: Verify D2→VIN, D3→GND, D4→SCL, D5→SDA connections
- Check sensor address: The code uses 0x44 (default for SHT41). If your sensor uses 0x45, edit the overlay file
- Enable I2C debugging: Add to
prj.conf:CONFIG_I2C_LOG_LEVEL_DBG=y - Check boot messages: Look for "Sensor powered on early" message
The firmware uses GPREGRET (retained register) to persist mode across resets. Debug output shows:
GPREGRET was: 0xXX- value before toggledebug_modeflag state- LED initialization result
First boot behavior:
- GPREGRET starts at 0x00 (or random value)
- Device enters normal mode (no USB, no LED)
- GPREGRET is set to 0xDB for next boot
After first reset:
- GPREGRET reads 0xDB
- Device enters debug mode (USB active, green LED on)
- GPREGRET is set to 0x00 for next boot
If mode switching doesn't work:
- Check that GPREGRET messages appear in console
- Verify green LED GPIO (P0.30) is working:
uart:~$ gpio get gpio@50000000 30 - Try setting LED manually:
uart:~$ gpio conf gpio@50000000 30 ohthengpio set gpio@50000000 30 1
If USB doesn't enumerate:
- Make sure you're in debug mode (green LED should be on)
- Try a different USB cable (some are power-only)
- Check dmesg/lsusb on host computer
- The firmware waits up to 3 seconds for DTR signal
# Check GPIO states
uart:~$ gpio get gpio@50000000 28 # Sensor VIN (D2)
uart:~$ gpio get gpio@50000000 29 # Sensor GND (D3)
# Test LED
uart:~$ gpio conf gpio@50000000 30 oh # Configure as output high
uart:~$ gpio set gpio@50000000 30 1 # Turn on green LED
uart:~$ gpio set gpio@50000000 30 0 # Turn off
# Scan I2C bus
uart:~$ i2c scan i2c@40003000
# Check Thread status
uart:~$ ot state
uart:~$ ot channel
uart:~$ ot panid