A self-hosted irrigation controller built on ESPHome and Zigbee, designed for integration with Home Assistant via Zigbee2MQTT. Runs on the ESP32-C6 with a custom PCB, supporting up to 15 zones with local OLED control and full remote control over Zigbee. Designed, tested and debugged by human, coded by Claude.
- Up to 15 irrigation zones (configurable at compile time: 7, 11, or 15)
- Two scheduled cycle types (irrigation and short) with two daily schedules each
- Manual zone control with adjustable runtime
- Local UI: 128×128 OLED, three buttons, full settings menu - fully usable without a network
- Zigbee integration: native Zigbee 3.0 endpoint designed to be used with Zigbee2MQTT
- Home Assistant: Custom dashboard based on button-card
- Power-loss recovery: interrupted cycles resume after reboot; missed schedules detected
- Cycle queue: a second cycle triggered while one is running waits its turn
- Weather scaling: percentage multiplier for cycle durations with optional auto-reset
- DS3231 RTC: keeps time across power loss; Zigbee sync writes to RTC hardware
- Pump sequencing: configurable start/stop offsets relative to zone valves
- Localization: English and Hungarian included; easy to add more
| Component | Purpose |
|---|---|
| ESP32-C6 (DevKitC-1 or custom PCB) | Main controller, Zigbee 3.0 router |
| MCP23017 I/O expander | Drives up to 15 zone relays + 1 pump relay |
| DS3231 RTC | Persistent timekeeping across power loss |
| SH1107 128×128 OLED | Local display |
| WS2812 RGB LED | Status indication |
| 3 momentary buttons (with optional LEDs) | Local control |
See Specification for the full hardware reference (GPIO map, I2C addresses, MCP23017 pin assignment, partition table).
ESPHome multi-file YAML with C++ headers for shared logic. Built around the luar123/zigbee_esphome fork for ESP32-C6 Zigbee support.
irrigation_control.yaml - top-level config
irrigation_control_z2m_converter.mjs - Zigbee2MQTT external converter
irrigation_control/
├── config.h - user-tunable constants
├── helpers.h - config struct, helpers, X-macro zone arrays
├── display.h - OLED page rendering
├── globals.yaml - global state variables
├── hardware.yaml - I2C, expander, display, buttons, RGB, relays
├── schedules.yaml - time platforms, schedule trigger, scale reset
├── scripts_system.yaml - boot sequence, decision tree, ZB push, LED patterns
├── scripts_buttons.yaml - button handlers, settings navigation
├── scripts_cycles.yaml - cycle execution, queue, skip, abort
├── scripts_manual.yaml - manual zone scripts, pump sequencing
├── zigbee_global_endpoints.yaml - EP14 globals cluster
├── boards/ - hardware_{$dev_board}.yaml - board, I2C, IO expander,
| display, buttons, RGB
├── translations/ - config_en.h, config_hu.h
└── zones/ - zone_config_{7,11,15}.h, relays_{7,11,15}.yaml,
zigbee_zone_endpoints_{7,11,15}.yaml
- ESPHome ≥ 2026.4.0
- Zigbee component:
github://luar123/zigbee_esphome(pinned to v1.x branch) - Zigbee2MQTT ≥ 2.9.2
- Home Assistant (any recent version) with the Zigbee2MQTT integration
- button-card HACS module dependency for the included HA dashboard
Clone this repository to your ESPHome folder and edit the top-level YAML to match your zone count and language:
substitutions:
timezone: "Europe/Budapest"
dev_board: "c6-devkitc-1"
zones: "11" # 7, 11, or 15
language: "en" # en or huCompile and flash through the ESPHome GUI or use the command line:
esphome run irrigation_control.yamlIf you need to flash a compiled firmware you can use the Web flasher or ESPConnect
- display defaults are found in
config.hSection 1 - config defaults are found in
helpers.hSection 4 - Cycle names shown in the UI can be customized in
\localisation\config_xx.h - Zone names shown in the UI can be modified in
\zones\zone_config_##.h TOTAL_ZONE_COUNTandCYCLE_ZONE_COUNTinzone_config_##.hcan be used to permanently exclude zones from automation. Zones excluded are always from the end of the list, eg:TOTAL_ 15withCYCLE_ 14excludes Zone 15. This zone can only be started in manual zone mode, and is always put first on the manual zone list in the UI. Use case: Garden hose on a zone valve. Hose gets a failsafe timer with adjustable duration, can be started remotely or on the UI, and is always excluded from starting by mistake with a wrong configuration.
The scripts work with any number of zones defined. 7/11/15 relay configurations are provided for convenience to be used with 8, 8+4 or 8+8 relay boards. The firmware can be customized with minimal work to operate with 1-15 zones.
Files to update:
\zones\relays_15.yaml: add/remove zone relays and zone template switches.\zones\zone_config_15.h: set zone defines, add/remove X() macros and zone names in the name array.\zones\zigbee_zone_endpoints_15.yaml: add/remove Zigbee endpoints. Above zone 15zigbee_global_endpoints.yamlneeds to renumbered.
Zone 16 can be added on GPIO: 2;3;6;7;10;11. Going above 16 zones is possible, but untested. A second MCP23017 I/O board is recommended, and the default binding table size needs to be increased in the external component (luar123/zigbee_esphome) before compiling the firmware.
Add to zigbee.cpp #L538 before esp_zb_init(&zb_nwk_cfg);
esp_zb_aps_src_binding_table_size_set(32);
esp_zb_aps_dst_binding_table_size_set(32);
Copy irrigation_control_z2m_converter.mjs into your Zigbee2MQTT external_converters/ directory, restart Z2M and permit device pairing.
The controller enters pairing mode on every reboot while not paired. Holding B1 for 5 seconds resets the Zigbee stack to default deleting all stored network credentials. Use it when you force-removed the device from Z2M and need to reconnect, or for joining another network.
The device appears in Z2M as an irrigation controller with auto-detected zone count. All controls and configuration exposes are populated automatically.
If pairing is unsuccessful (red exclamation mark in Z2M) remove the device, reboot Z2M and pair again. After firmware upgrades it's recommended to do the re-pairing process to clean any cached values on the coordinator.
Install the Dashboard into Home Assistant or use the local OLED menu to set:
- Zone durations (per zone, per cycle type)
- Zone enable flags
- Daily schedules
- Pump timings, lockout
- Cycle repeat count
- Weather scaling
See the User and the Dashboard guide for full operating instructions.
- User Guide - operating the device locally
- Home assistant - Operating the device remotely via Home Assistant
- Specification - full hardware and firmware reference
- TODO list - open issues and planned features
- Hardware - Parts list and wiring schematic
Active development. Functional and in daily use, with periodic firmware refinements. See the TODO list for current work. Planned:
- Irrigation frequency: set programming to run only every 2nd/3rd/etc. day
- Zigbee sdk 2.0 - subject to luar123 release
- WIFI OTA - AP mode depends on SDK 2.0
- WIFI concurrency/dual mode - if stability testing is successful
Personal project using The Unlicense terms. Use at your own risk; pull requests and discussion welcome.
- ESPHome - the YAML-driven embedded framework that makes this possible
- luar123/zigbee_esphome - ESP32-C6 Zigbee component
- Home Assistant - The Swiss knife of Home Automation
- Zigbee2MQTT - Zigbee gateway and converter ecosystem
- Antrophic - Coded with Claude
- Romrider for button-card
- Google fonts for the Material Design Icons and the Roboto font used in the UI
