ESP ZeroCode AI · Community

Water heater switch

A switch for my electric water heater that I control from my phone. For safety, after a power cut it should always come back OFF — never turn itself on automatically.

esp32c6 zigbee
Download the firmware Fork it in the studio

What it does

Manual

Device Type
A wall-mounted Zigbee on/off switch that controls a domestic electric water heater. It replaces reaching behind the tank to a breaker or isolator: the heater can be switched from a phone app through the Zigbee hub, or from a button on the unit itself. The defining requirement is fail-safe behaviour — the heater must never be able to turn itself on, and after any power interruption it always comes up OFF and waits to be switched on deliberately.

Hardware
chip: esp32c6 — board: esp32-c6-devkitc-1 — gpios: GPIO6: Relay drive output (active-high) — the pad is driven low at reset and stays low until the application enables the relay, so the load is inert from power-up — GPIO7: Manual button input, active-low with internal pull-up; connected to GND when pressed — GPIO8: On-board addressable LED (ws2812) on the DevKitC-1 — NOT used; the product is deliberately dark — GPIO9: On-board BOOT button on the DevKitC-1 — NOT used by the application; reserved for flashing — peripherals: part: Relay, 30 A / 250 VAC contact rating, 5 V coil — purpose: Switches the heater mains circuit. Contacts are the only mains-carrying part. — wiring: Coil driven from the board 5 V rail through a transistor/MOSFET with a flyback diode; logic input from GPIO6. Contact rating chosen generously so any domestic water heater element is inside the relay's rating. — note: Commissioning and enclosure are the user's build step — verified parts list is not fixed here. · part: Momentary push button (normally open) — purpose: Local manual control and factory reset. — wiring: GPIO7 to GND, internal pull-up enabled, active-low.

Inputs
kind: button — label: Manual toggle button — hardware: GPIO7, active-low, internal pull-up, debounced in firmware — behavior: A short press toggles the relay: if the heater is OFF it turns ON, and if it is ON it turns OFF. The new state is reported to the Zigbee network. A long press (about 5 seconds) performs a factory reset — see factoryReset. · kind: network_command — label: Zigbee on/off commands from the hub / phone app — hardware: 802.15.4 radio on the ESP32-C6 — behavior: On/Off cluster commands received from the coordinator or bound devices drive the relay: ON energises the coil, OFF de-energises it. Unknown or out-of-range commands are ignored. The state reported back to the network is always the actual relay state, never an assumed one.

Outputs
kind: actuator — label: Heater relay — hardware: 30 A relay on GPIO6, active-high drive through a transistor — behavior: Energised = heater mains live; de-energised = heater isolated. OFF is the default and the safe state at every reset, boot, and power-up. There is no other user-visible output. · kind: indicator — label: None — no status LED — hardware: The DevKitC-1 on-board LED (GPIO8) is intentionally left unused — behavior: The unit shows no light in any state: powered, joined, joined-but-off, or unjoined. All feedback is via the phone app. Consequence to accept: a device that is powered but not yet paired looks identical to one that is working.

Connectivity
stack: zigbee — transport: 802.15.4 (2.4 GHz) — purpose: Zigbee 3.0 end device — on/off cluster. The device is permanently mains-powered, so it must join as a router-capable device and strengthens the mesh rather than sleeping.

Commissioning
method: Zigbee network steering (standard Zigbee 3.0 join) — payload: No custom install code or payload required for a first build. — instructions: On first power-up the device has no network and auto-starts network steering (joins an open Zigbee network within range; a coordinator in pairing/permit-join mode is required). On a successful join it appears in the hub as an on/off switch; the relay stays OFF throughout commissioning — pairing never energises the heater. If the join fails or the hub is replaced, a long press (~5 s) resets the device and it starts steering again.

Business Logic
powerOnBehavior: Every boot begins with the relay de-energised: the drive pad is low at reset and the application does not energise it until it has decided to. The device holds NO persisted on/off state — there is nothing to restore and nothing that can turn the heater on by itself. This is the fail-safe rule: whether the reboot was a power cut, a brownout, a crash, or a firmware update, the heater comes back OFF. It is deliberate that a heater that was running when the power failed does NOT resume. It does not need to be re-commissioned after a power cut — it rejoins the existing network automatically and just sits in the OFF state until told otherwise. — automationRules: Local button and network commands are equal peers: whichever arrives last wins, and the resulting state is reported to the network. · There is no schedule, no timer, and no auto-on rule of any kind. Nothing in the device may turn the heater on without a deliberate command (button press or network on command). — dayToDayOperation: The unit sits behind/next to the heater, permanently powered. The user normally switches the heater from the phone app through the Zigbee hub, and sees the current state there. Pressing the button on the unit toggles the heater locally for the times the phone is not at hand. Because the device is always powered and mains-fed, it also relays for other Zigbee devices on the mesh. Nothing lights up, ever.

Factory Reset
trigger: Long press (~5 s) on the manual button on GPIO7. — effect: Forget the Zigbee network credentials and all bindings, return the on/off state to OFF (relay de-energised), and restart network steering so the device can join a network again. The heater is left isolated by the reset. A power-cycle is an additional escape hatch: because no state is persisted, a device stuck in a bad ON state returns to OFF as soon as it reboots.

Failure Modes
scenario: Mains power cut or brownout — behavior: Both the device and the heater lose power. When power returns the relay is de-energised by design and the heater stays OFF even if it was heating before the cut. The device reapplies to its known Zigbee network by itself; if the network is gone it retries steering. · scenario: Zigbee network / hub down or unreachable — behavior: Phone control stops working, but the device keeps working: the manual button still toggles the heater. The device retries periodically to rejoin/resume. The relay holds its current state; nothing fails ON. · scenario: Device crashes or watchdog reset — behavior: Firmware resets, the drive line returns to its reset state (low), and the heater goes OFF. No state is restored from memory. · scenario: Firmware update / reflash — behavior: Treated exactly like a reset: the heater comes up OFF and must be switched on deliberately afterwards. · scenario: Device powered but not yet paired (or pairing lost after a hub change) — behavior: The unit looks and behaves like a hot but idle device — no light indicates the difference. The button still toggles the relay locally; the phone shows nothing until it is re-paired (long press to reset, then steering).

Custom Behavior
The product is intentionally fail-safe in the strong sense: OFF is the only automatic state. There is no state persistence anywhere in the design — no NVS on/off flag, no restore-on-boot — because the user's requirement is that the heater can never come back on by itself. Any future feature that adds memory (schedules, last-state restore, child lock) must not weaken this: state restoration of ON is out of scope by design. The mains-side relay sizing (30 A / 250 VAC) is also generous on purpose, because the actual heater rating was not specified.

Published 2026-09-11
Built with ESP ZeroCode AI — describe a device, get production firmware.
Terms of Service · Privacy Policy