ESP32 and MicroPython for the Homelab: Custom Sensors and Home Assistant Integrations from Scratch
The ESP32 sits in an interesting position for software engineers. It is powerful enough to run a real Python interpreter, cheap enough to deploy a dozen of them around a house without agonising over the budget, and well-documented enough that you can go from zero to a working temperature sensor publishing to Home Assistant in an afternoon. But the hardware side is unfamiliar territory for most server-side or web engineers, and the ecosystem is fragmented enough to waste several afternoons picking the wrong chip, wrong toolchain, or wrong integration approach before you find the combination that actually works.
This post is the practical entry point for engineers who write software professionally and want to start building real hardware sensors without learning C++ or wading through Arduino tutorials aimed at complete beginners. It covers chip selection, MicroPython setup, the sensor wiring patterns you will use repeatedly, MQTT integration, Home Assistant connectivity, and the honest comparison between MicroPython and ESPHome — because the right answer for a homelab is almost always “both, for different things.”
The ESP32 Family: Which Chip to Buy
Espressif has shipped enough ESP32 variants that the selection question now takes real effort. The families break down by CPU architecture, connectivity, and intended use case.
| Chip | CPU | Cores | Wi-Fi | BT | Thread/Zigbee | Notes |
|---|---|---|---|---|---|---|
| ESP32 (original) | Xtensa LX6 | 2 | 802.11n | BT 4.2 + Classic | No | Still widely available, most tutorials target this |
| ESP32-S2 | Xtensa LX7 | 1 | 802.11n | None | No | USB-OTG, very low power; no Bluetooth is a real limitation |
| ESP32-S3 | Xtensa LX7 | 2 | 802.11n | BLE 5.0 | No | Best choice for camera, AI inference, display projects |
| ESP32-C3 | RISC-V | 1 | 802.11n | BLE 5.0 | No | Cheapest modern option, good security features |
| ESP32-C6 | RISC-V | 1+1 | 802.11ax (Wi-Fi 6) | BLE 5.3 | Yes (802.15.4) | Best for new smart home builds; Matter/Thread-ready |
| ESP32-H2 | RISC-V | 1 | None | BLE 5.3 | Yes | Thread/Zigbee coordinator only, no Wi-Fi |
| ESP32-P4 | RISC-V | 2+2 | None (external) | None | No | High-performance compute, not a sensor node chip |
For a homelab sensor node starting in 2026, buy the ESP32-C6. It is Wi-Fi 6 capable, supports Thread and Zigbee via its 802.15.4 radio, and costs roughly the same as the original ESP32. If your Home Assistant instance runs Matter or you plan to add a Thread border router, the C6 gives you the hardware foundation. The ESP32-C3 is a reasonable budget choice if you are buying in quantity and genuinely don’t need Thread.
The original ESP32 (dual-core Xtensa) is fine if you have them on hand or find them at a significant discount. The S3 is the right choice for anything that needs a camera or more compute — a doorbell, a people-counting sensor, or an edge inference project. Avoid the S2 unless you specifically need USB-OTG and have no use for Bluetooth.
Development boards
The bare chip needs to be on a development board for easy prototyping. The form factors that matter:
- 38-pin DevKit (most common): breadboard-friendly, USB-C on most modern versions, exposes all GPIOs. The standard for any project without space constraints.
- Super Mini / C3 Mini / C6 Mini: half the size of a DevKit, designed to fit in small enclosures. Fewer pins exposed but usually enough for a sensor node.
- WROOM / WROVER modules: the module (chip + flash + antenna) without a full DevKit carrier. Used when you’re building your own PCB.
- Seeed XIAO ESP32C6: high quality small form factor, USB-C, good antenna, popular for enclosure builds.
For breadboard prototyping, the standard 38-pin DevKit form factor works with every tutorial you’ll find. For final deployed sensors, a mini/small form factor board fits into standard electrical boxes, project enclosures, and 3D-printed cases.
MicroPython: Setup and First Steps
MicroPython is a Python 3 implementation designed for microcontrollers. It is not CPython with a hardware shim — it has its own bytecode compiler, garbage collector, and a hardware abstraction layer that maps Python objects directly to hardware peripherals. The subset of the standard library it supports is substantial: os, sys, json, re, struct, time, math, random, socket, ssl, and the full machine module for GPIO, I2C, SPI, UART, timers, ADC, and PWM.
Flashing MicroPython
Install esptool from pip:
|
|
Download the MicroPython firmware for your chip from micropython.org/download. Each chip family has its own build — don’t mix them.
Find your board’s serial port:
|
|
Erase the flash, then write the firmware:
|
|
For the original ESP32 substitute --chip esp32; for S3 use --chip esp32s3. After flashing, the board boots into the MicroPython REPL.
Connecting to the REPL
The simplest way to interact with the board is mpremote, which supersedes the older ampy and rshell tools:
|
|
In the REPL you have a live Python shell running on the microcontroller:
|
|
Press Ctrl-D for a soft reset. Press Ctrl-C to interrupt a running script.
Thonny IDE
For visual development and easier file management, Thonny (free, cross-platform) has first-class MicroPython support. It shows the board’s filesystem in a sidebar, lets you run scripts directly on the device, and includes a serial terminal. It’s worth having for debugging hardware issues even if you prefer editing in your regular editor.
Boot files
MicroPython runs two files at boot:
boot.py— Runs first. Put low-level hardware init and Wi-Fi connection here.main.py— Runs after boot. Your application code.
If either file raises an exception, MicroPython drops to the REPL so you can diagnose. On a deployed device with no serial connection, exceptions in main.py will prevent your application from running — error handling and a watchdog timer are not optional on production deployments.
Wi-Fi Connection
Almost every homelab ESP32 project needs Wi-Fi. The standard pattern:
|
|
Store credentials in a separate config.py that you do not commit to version control:
|
|
GPIO: Digital I/O
|
|
I2C Sensors
I2C is the dominant protocol for small sensors. It uses two wires — SDA (data) and SCL (clock) — and supports up to 127 devices on a single bus. Every I2C device has a 7-bit address; common sensor addresses are documented in the datasheet.
ESP32 Sensor
(BME280, SHT31, etc.)
3.3V ──────────────── VCC
GND ───────────────── GND
GPIO21 (SDA) ──────── SDA
GPIO22 (SCL) ──────── SCL
Scan the bus to find connected device addresses:
|
|
BME280 — Temperature, Humidity, Pressure
The BME280 is the workhorse environmental sensor. About $3–5 on a breakout board, accurate, and well-supported. The BME680 adds a gas/air quality sensor at slightly higher cost; the BME688 adds AI classification for air quality at higher cost still.
Install the driver:
|
|
Or copy the driver manually:
|
|
SHT31 — Temperature and Humidity
The SHT31 is more accurate than a DHT22 and communicates over I2C rather than the DHT one-wire protocol. Worth the extra dollar for anything that will stay deployed.
|
|
Common I2C sensor addresses
| Sensor | Default address | What it measures |
|---|---|---|
| BME280 / BME680 | 0x76 (SDO low) or 0x77 | Temp, humidity, pressure, gas |
| SHT31 / SHT40 | 0x44 | Temp, humidity |
| AHT20 / AHT21 | 0x38 | Temp, humidity |
| BMP390 | 0x76 or 0x77 | Pressure, temp (high precision) |
| VL53L0X | 0x29 | Time-of-flight distance |
| VEML7700 | 0x10 | Ambient light lux |
| SGP30 / SGP41 | 0x58 | VOC / air quality |
| INA219 / INA226 | 0x40–0x4F | Current, voltage, power |
MQTT: Publishing Sensor Data
MQTT is the standard protocol for IoT sensor data. A broker (typically Mosquitto, running on the same machine as Home Assistant or on a dedicated container) receives published messages and routes them to subscribers. Home Assistant can consume MQTT topics directly and turn them into sensors, switches, and devices.
Install the umqtt.simple module if it’s not already on the board:
|
|
A complete sensor publish loop:
|
|
The watchdog timer is essential. If the board locks up for any reason — a network stack hang, an unhandled exception, memory fragmentation — the watchdog fires and reboots it. A deployed sensor that hangs silently for weeks is worse than one that reboots and recovers.
Home Assistant: MQTT Discovery
MQTT Discovery is the mechanism by which Home Assistant automatically creates sensors, switches, and devices from MQTT messages without any manual YAML configuration. A device publishes a discovery message to a well-known topic; Home Assistant reads it and creates the entity.
The discovery topic pattern:
homeassistant/<component>/<device_id>/<object_id>/config
Publish this once at boot (with retain=True so Home Assistant picks it up even after a HA restart):
|
|
After this runs, open Home Assistant’s Devices & Services page — under MQTT you will find a new device with all three sensors auto-populated and ready to add to dashboards or use in automations.
MicroPython vs ESPHome
These are different tools for different situations. Understanding the trade-off saves you from picking the wrong one.
| MicroPython | ESPHome | |
|---|---|---|
| Language | Python | YAML (with C++ lambda escape hatch) |
| Setup overhead | Low for Python devs | Very low for simple sensors |
| Flexibility | Full programmability | Constrained to what ESPHome supports |
| Custom logic | Arbitrary Python | Limited; lambdas are verbose |
| HA integration | Via MQTT (manual) or ESPHome native API | Native, zero-config, auto-discovery |
| OTA updates | Via webrepl or mpremote | Built-in, OTA from HA dashboard |
| Debugging | REPL, print statements | ESPHome logs via web UI or HA |
| Memory footprint | Larger (Python runtime) | Smaller (compiled C++) |
| Community resources | Large Python community | Large HA/ESPHome community |
| Good for | Custom logic, non-HA integrations, learning | Standard sensors wired to HA |
Use MicroPython when:
- You need custom business logic that doesn’t map cleanly to ESPHome’s component model
- You’re integrating with something other than Home Assistant (your own MQTT pipeline, a REST API, a database)
- You want to write real code rather than configure YAML
- You’re learning embedded programming and want transferable skills
- The project involves multi-step state machines, complex timing, or non-standard protocols
Use ESPHome when:
- You’re building a standard sensor node (temperature, humidity, motion, door contact) that feeds Home Assistant
- You want automatic OTA updates managed from the HA dashboard
- The sensor will be deployed and forgotten — ESPHome’s zero-config HA discovery is more reliable at scale than manually maintained MQTT topics
- You want your non-programmer household members to be able to see device status
For a homelab, the practical answer is usually: use ESPHome for simple, permanent sensors (temperature nodes, door/window contacts, motion detectors), and MicroPython for anything that needs real logic — a custom display driver, a sensor that publishes to your own Prometheus endpoint, a controller that reads multiple sensors and drives an output based on derived calculations.
A minimal ESPHome config for comparison
|
|
Flash with: esphome run living-room-sensor.yaml
That’s the entire configuration. Home Assistant discovers the device automatically. Compare the code surface to the MicroPython version — ESPHome wins on simplicity for this use case.
Power Management and Battery Operation
Mains-powered sensors can poll and publish continuously without concern. Battery-powered sensors — door contacts, outdoor weather nodes, anything where running a cable is impractical — need deep sleep.
The ESP32’s deep sleep current is around 10–20 µA. A 1000 mAh LiPo cell powering a sensor that wakes every 5 minutes, reads a sensor, and publishes over Wi-Fi (typically 100–200 mA for 2–3 seconds) will last many months.
|
|
With deep sleep, main.py runs from the top on every wake. There is no persistent loop — execution ends with deepsleep() and resumes at the top of main.py after the sleep interval. Keep the wake path short and fast; every millisecond awake costs battery.
A few deep sleep caveats:
- GPIO pin state is not preserved across deep sleep unless you use RTC GPIO and store state in RTC memory
- Wi-Fi association takes 2–4 seconds on each wake. Using a static IP instead of DHCP shaves ~1 second off every wake cycle
- Store persistent values (counters, calibration data) in
esp32.RTCmemory:import esp32; esp32.RTC().memory(b'data')
Practical Project Recipes
Whole-house temperature map
Six ESP32-C6 boards, each with a BME280, scattered through the house. Each publishes to homelab/sensors/<room>/state. A Home Assistant dashboard shows a floor plan with real-time temperature and humidity per room. Total hardware cost under $40.
Boot.py: connect Wi-Fi
Main.py: publish discovery → publish readings → sleep 5m
Door and window contact sensor
A reed switch wired between a GPIO pin (with internal pull-up) and GND. The pin reads LOW when the magnet is near (door closed) and HIGH when the magnet moves away (door open). With deep sleep and event-driven wake via ext0 wakeup pin, a battery can last a year.
|
|
Relay controller for a dumb appliance
A relay module (5V coil, 10A contacts) controlled by a GPIO pin lets an ESP32 switch mains-voltage devices. Pair with an MQTT subscription and you have a remotely controllable switch.
|
|
OTA Updates with MicroPython
The mpremote tool handles one-off file pushes over USB. For sensors deployed around the house without USB access, webrepl provides Wi-Fi-based file transfer.
Enable webrepl in boot.py:
|
|
Then connect from a browser at ws://<device-ip>:8266 or use the webrepl_cli.py script:
|
|
For fleet-scale OTA, the ota_updater pattern checks a remote URL for a version file and downloads updated modules if the version has changed. This is more fragile than ESPHome’s OTA implementation — if a bad update bricks the board and you can’t get to it physically, you need to reflash via serial. For permanent installations, ESPHome’s OTA is more reliable.
Tooling Summary
| Tool | Purpose |
|---|---|
esptool.py |
Flash firmware to the chip |
mpremote |
File transfer, REPL access, package install |
| Thonny | Visual IDE with built-in MicroPython support |
mip (built into mpremote) |
MicroPython package manager |
| ESPHome | YAML-configured firmware for HA-integrated sensors |
| Mosquitto | MQTT broker (runs in Docker alongside HA) |
| MQTT Explorer | GUI for inspecting MQTT topics during development |
The development loop for a new sensor build: flash MicroPython, open Thonny, prototype in the REPL, move working code into main.py, push via mpremote, iterate. Once the design is stable and you want easier OTA for permanent deployment, consider rewriting the simplest sensors in ESPHome.
Sources:
Comments