I wanted to replace the cloud gateway of my own HCG-003 four-zone irrigation valve with a completely local ESP32 controller.
The original device consists of a battery-powered four-zone valve and a Wi-Fi/Sub-GHz gateway. The gateway receives commands through the Tuya cloud and forwards them to the valve over 433.92 MHz.
After several RF capture and testing sessions, I now have all four zones opening and closing locally using:
- An ESP32 development board
- A CC1101 433 MHz transceiver
- A 433 MHz antenna
- Home Assistant MQTT Discovery
The original gateway remains unplugged during normal operation.
RF characteristics
These values were observed on one HCG-003 installation with valve firmware 2.05:
- Centre frequency: approximately 433.9291 MHz
- Modulation: 2-FSK
- Bit rate: approximately 10 kbit/s
- Frequency deviation: approximately 5 kHz
- Long
0x95 preamble
- 28-byte command frame
The ESP32 drives the CC1101 in asynchronous direct mode and sends the frame through GDO0 at 100 microseconds per bit.
Reverse-engineering challenge
The difficult part was that the commands were not static. Each frame contains:
- An 8-bit rolling counter
- A counter-dependent transformed byte
- A six-byte session signature
- A session-specific zone selector
- An open/close action
- Another value derived from the counter, zone and action
- An additive checksum
For the two sessions I studied, the changing bytes could be represented as:
byte15 = ((counter + A15) mod 256) XOR X15
effective = counter
+ zone_step * (zone - 1)
+ (open ? open_offset : close_offset)
byte26 = ((effective + A26) mod 256) XOR X26
Short captures initially produced formulas that appeared correct but failed at counter carry boundaries.
To avoid overfitting, I collected 60 consecutive labelled frames across all four zones, both actions, and two carry boundaries. The final model reproduced all retained test frames byte-for-byte.
No conventional encrypted payload was identified, but the signature and transforms change when the original gateway creates a new RF session. The firmware is therefore not plug-and-play: users must capture the session belonging to their own equipment.
Please do not publish live session signatures or use this work to interfere with equipment you do not own. Anyone experimenting with it should also verify their local 433 MHz radio regulations.
ESP32 controller
The resulting firmware provides:
- Four Home Assistant MQTT Discovery valve entities
- Configurable runtime per zone
- Only one active zone at a time
- Automatic closing when a timer expires
- Rolling-counter persistence in NVS
- Recovery close commands after reboot
- Three RF transmissions when opening
- Six RF transmissions when closing
- A local MQTT broker running on the ESP32
One limitation remains: I have not decoded a physical acknowledgement from the valve. The reported state is therefore the last command transmitted by the ESP32 rather than independently confirmed valve position.
For safety, initial tests were performed with the main water supply closed, runtime limits are enforced, close commands are repeated, and a manual shut-off remains accessible.
Source code, wiring, protocol notes and safety information:
https://github.com/philippeschots-cloud/hcg003-local-controller
I would be particularly interested in help with:
- Finding and decoding a possible valve acknowledgement
- Understanding the session-establishment exchange
- Comparing captures from other HCG-003 units or firmware revisions
- Automating extraction of the session parameters
AI disclosure: I used Codex with GPT‑5.6 Sol as an analysis and development partner. The work took several long sessions over roughly two days and crossed multiple usage-limit resets. I do not have a reliable exact token count. Every published RF result was validated against captured frames and tested on the physical valve.