A smart home is really two things: switches you can throw from anywhere, and rules that throw them for you. The usual DIY route bolts those onto a hub you keep alive in a closet and a broker you have to secure. This build skips both. An ESP32 switches the lights and appliances through relays, and every decision — the dashboard, the scenes, the schedules, the sunset trigger — lives in nodrix on your own Cloudflare account.
The board’s whole job is to flip relays when it’s told to and report what state they’re in. Nothing about the house is compiled into it, so you add a room, retime the porch light, or build a “Goodnight” scene from the dashboard, and the firmware you flashed once never changes.
The idea: your house, your cloud, no hub
Put the automation logic on the ESP32 and every change means reflashing, every rule is invisible, and the board can’t tell you a relay stuck on. Split it the other way:
- The device switches and reports — it listens for
light_living,light_bedroom,light_porch, andfan, and echoes each relay’s real state back. That contract almost never changes. - The cloud holds the logic — scenes, schedules, the sunset trigger, and any condition are edited in nodrix and take effect immediately, no reflash.
- The cloud holds the controls — one dashboard drives every relay from any phone or laptop, on the home network or off it.
What you’ll build
- A dashboard with a toggle per light and appliance, reachable from anywhere.
- A porch light that follows the sun — on at dusk, off in the morning, no timer to reset.
- A Goodnight scene that turns the house off in one tap, and again on a schedule.
- State that stays honest — each toggle reflects the relay’s actual position, even after a reboot or a reconnect.
What you’ll need
- An ESP32 dev board (any common DevKit variant).
- A relay module — a 4- or 8-channel, opto-isolated board. Match the channel count to how many circuits you’re switching.
- The loads: light fixtures, LED strips, a fan, or smart plugs. Prefer low-voltage loads while you’re learning; leave mains wiring to a qualified electrician.
- Jumper wires, and a 5V supply for the relay board if it draws more than the ESP32 can source.
- The Arduino IDE with the ESP32 board package and the Nodrix library from the Library Manager (it pulls in ArduinoJson and WebSockets).
- A nodrix instance with a project and a project token.
Wiring the relays
Each relay is switched by one GPIO. Wire four control lines from the ESP32 to the relay board’s inputs, power the board, and share a ground:
| From | To | Wire |
|---|---|---|
| ESP32 GPIO16 | Relay IN1 | Signal (living) |
| ESP32 GPIO17 | Relay IN2 | Signal (bedroom) |
| ESP32 GPIO18 | Relay IN3 | Signal (porch) |
| ESP32 GPIO19 | Relay IN4 | Signal (fan) |
| ESP32 5V (VIN) | Relay VCC | Power |
| ESP32 GND | Relay GND | Ground |
Each relay’s COM and NO terminals go in series with the load’s supply — the relay is just a switch in that circuit. GPIO16–19 are safe general-purpose outputs; avoid the strapping pins (GPIO0, 2, 12, 15) for relay control so a relay’s power-on state can’t hold the ESP32 in boot mode.
Mains voltage is dangerous. Anything switching household AC must use a properly rated, opto-isolated relay with the mains side fully enclosed and isolated from the ESP32’s low-voltage wiring — and in many places that wiring must be done by a licensed electrician. If any of that is uncertain, switch low-voltage LED strips or a smart plug instead. The firmware doesn’t care what the relay switches.
The firmware
One socket carries the whole house: toggle states come down it the instant you tap the dashboard or a scene fires, and each relay’s real state goes back up so the controls never lie. The nodrix Arduino library holds that socket, acks each command, and reconnects on its own, so the sketch is just four relays and their handlers — the complete version is the HomeLights example.
#include <Nodrix.h>
#define WIFI_SSID "your-wifi"
#define WIFI_PASS "your-password"
#define HOST "nodrix.you.workers.dev"
#define TOKEN "tok_your_project_token"
const int LIGHT_LIVING = 16;
const int LIGHT_BEDROOM = 17;
const int LIGHT_PORCH = 18;
const int FAN = 19;
const int RELAY_ON = LOW; // most relay boards are active-LOW — swap if yours isn't
const int RELAY_OFF = HIGH;
void setRelay(int pin, const char* var, bool on) {
digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
Nodrix.send(var, on); // echo the real state so the dashboard stays honest
}
NODRIX_WRITE("light_living") { setRelay(LIGHT_LIVING, "light_living", value.asBool()); }
NODRIX_WRITE("light_bedroom") { setRelay(LIGHT_BEDROOM, "light_bedroom", value.asBool()); }
NODRIX_WRITE("light_porch") { setRelay(LIGHT_PORCH, "light_porch", value.asBool()); }
NODRIX_WRITE("fan") { setRelay(FAN, "fan", value.asBool()); }
void setup() {
int pins[] = { LIGHT_LIVING, LIGHT_BEDROOM, LIGHT_PORCH, FAN };
for (int p : pins) { pinMode(p, OUTPUT); digitalWrite(p, RELAY_OFF); }
Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN);
}
void loop() {
Nodrix.run();
}
Worth understanding rather than copying:
- The echo keeps controls honest. Reporting state back inside each handler means a toggle shows the relay’s true position — a scene, a schedule, or a manual tap can’t leave the dashboard out of sync with the wall. Because a handler just sets a pin, a duplicate delivery across a reconnect is harmless, and the library re-applies the last known states so the house comes back the way you left it after a power blip.
- The rest is the library’s job. At-least-once delivery, acking, reconnects, and — for
production —
Nodrix.setCACert()TLS pinning all sit below your handlers; the downlink and HTTPS firmware guides cover them in full.
Build the dashboard
Add the controls in the dashboard editor, each bound to a variable:
| Widget | Bind to | Does |
|---|---|---|
| Toggle | light_living | switch the living-room light |
| Toggle | light_bedroom | switch the bedroom light |
| Toggle | light_porch | switch the porch light |
| Toggle | fan | switch the fan |
| Value | light_porch | show whether the porch light is on |
Each toggle writes its variable, the library delivers it to the board, and the relay’s echoed state settles the toggle — so the dashboard mirrors the house whether the change came from your thumb, a schedule, or a scene. Controls update live over a hibernating WebSocket, so nothing polls and an idle house costs almost nothing to keep connected.
Automations that run without you
The point of a smart home is the rules you don’t touch. In nodrix these are trigger → condition → action flows evaluated at the edge — no code on the board. Build them in the automation editor.
Porch light at dusk. A sunset trigger fires at your location’s sunset and sets light_porch
to on. A schedule trigger at, say, 6:30 in the morning sets it back to off. The porch now
tracks the seasons on its own — no timer to reset when the days get shorter.
Fan on a schedule. A schedule trigger can turn the fan on before you get home and off
overnight. Add a condition later — only if a temperature variable is above a threshold — without
rewiring anything; that’s the plant-watering pattern of a
sensor reading gating an action.
Goodnight, in one tap. A scene is a saved set of variable states you apply together —
light_living, light_bedroom, and fan off, light_porch on. Put a scene control on the
dashboard to run it when you head to bed, and add a schedule trigger that applies the same scene at
11:30pm as a backstop. Because the board echoes state, every toggle settles to match the scene the
moment it runs.
Swap a channel — send to Slack or Telegram instead of switching a relay, or add an “only on weekdays” condition — and none of it touches the firmware.
Control it from anywhere, privately
The ESP32 opens the connection outward to nodrix, so there’s nothing to expose — no port forwarding, no dynamic DNS, no VPN — and it works behind the strictest home router or cellular NAT. Port 443 is open everywhere, so the dashboard runs the house from the next room or another country.
It’s also yours: nodrix is single-tenant on your own Cloudflare account, so the device history, scenes, and schedules stay in your tenancy, not a shared vendor cloud. One project token authorizes the whole project — treat it as a secret and load it from config for anything permanent.
When the internet drops
A smart home has to fail gracefully:
- Wall switches never stop working. The relays sit alongside the existing switches, so the house is always operable by hand.
- Cloud control pauses, then catches up. While the link is down the board holds its last state; a command you send meanwhile is queued and delivered at-least-once the moment it reconnects, and automations resume on their own.
- Critical rules belong on the board. If one routine must run during an outage — a safety cutoff, say — keep that single rule in the firmware and leave the convenience logic in the cloud.
Going further
- Add a room by repeating. Flash a second ESP32 with its own token and namespaced variables
(
light_kitchen,light_garage); one dashboard shows every board, no firmware change. - Make it sense the house. Feed a temperature, motion, or door sensor as telemetry and gate actions on it — fan on when it’s warm, porch light on motion after dark.
- Dim instead of switch. Drive a PWM channel or a dimmer module and bind a slider widget to
a
brightnessvariable for smooth control rather than on/off. - Voice and presence. Trigger scenes from an event the firmware emits, or from a phone-presence webhook, so “arriving home” sets the lights without a tap.
Notes
- No hub, no broker. The device speaks plain HTTPS and WebSocket; the house logic runs on your Cloudflare account, with nothing to keep alive at home.
- Configurable without reflashing. Scenes, schedules, the sunset trigger, and every condition are edited in the dashboard — the firmware is flashed once.
- Single-tenant data. Every state change stays in your own account, queryable through the read API.
- Scales by repeating, not rewriting. The same sketch runs one room or the whole house; you add boards and variables, never new firmware.