Projects Automation

Homeostat — Home Automation Hub

A local-first home automation hub: ESP-NOW sensor nodes, a Rust rules engine on a Raspberry Pi, and sub-50ms actuation with zero cloud dependencies.

Active since May 2025 #home-automation#esp-now#rust#raspberry-pi
11
Sensor nodes deployed
38 ms
Median actuation latency
99.2 %
Packet delivery rate
0
Cloud services required

Homeostat exists because I got tired of a smart bulb that needed a round trip to a datacenter to turn on. Cloud-backed automation is slow, dies with your internet connection, and stops working when a vendor sunsets an API. I wanted a system where every packet stays inside my house and a light switch feels like a light switch — which in practice means actuation under 50 ms, wall to bulb.

Why local-first

The architecture is deliberately boring: ESP32-C3 sensor nodes speak ESP-NOW to a gateway node, which bridges frames over USB serial to a Raspberry Pi 4 running the rules engine in Rust. No WiFi association on the battery nodes, no broker in the cloud, no accounts. ESP-NOW skips the DHCP/association dance entirely, so a node can wake from deep sleep, transmit a reading, and be back asleep in about 120 ms. That’s the difference between weeks and months of battery life on an 18650.

Making ESP-NOW reliable

Raw ESP-NOW is fire-and-forget, and early on I was losing 4-5% of packets whenever the microwave ran. The fix was a lightweight reliability layer: every frame carries a sequence number, the gateway ACKs, and nodes retry up to three times with jittered backoff. Delivery went from ~95% to 99.2% measured over two weeks of logs. The remaining losses are almost all from one node behind a brick wall — antenna placement matters more than any retry logic.

The rules DSL

Rules started as YAML and became unreadable fast. The current DSL is a small language parsed once at startup into an expression tree, so evaluation on each incoming event is a tree walk with no allocation:

rule "hallway_night" {
  when motion.hallway == triggered
   and time between 22:00 and 06:30
  then set light.hallway brightness 15% for 90s
}

The parser is about 400 lines of Rust using nom, and getting decent error messages (“expected duration after ‘for’, found ‘90’”) took longer than the parser itself. Worth it — I edit rules on my phone over SSH and a good error saves a debugging session.

Median event-to-actuation latency is 38 ms, most of it radio airtime. The Pi’s share is under a millisecond, which is a nice reminder that the slow part of home automation was never the computer.

Development timeline

  1. 2025-05

    First node

    A single ESP32-C3 temperature node talking ESP-NOW to a breadboard gateway.

  2. 2025-07

    Rules engine

    First Rust rules engine prototype replacing a pile of if-statements in the gateway firmware.

  3. 2025-10

    Whole-room deploy

    Eight nodes covering lights, temperature, and door sensors across three rooms.

  4. 2026-03

    DSL rewrite

    Replaced YAML rule config with a small compiled DSL.