When we renovated our bathroom, a new ventilation unit was installed — including a wireless remote control. Sounds convenient. In reality… not so much. The remote was constantly misplaced, forgotten, or — even worse — the battery was empty again. And surprisingly fast. That meant the ventilation was either running unnecessarily or not running when it should. As an embedded engineer, that simply could not stay like that. So I built a proper solution.

The Goal
- Integrate the bathroom fan into Apple HomeKit
- Enable fully automatic humidity-based control
- Keep manual control available
- Work completely offline
- Maintain professional HAP compliance
- Avoid hacking the 230V installation directly
In short: Make the bathroom ventilation intelligent, reliable and autonomous.
First Attempt – RF Sniffing
The ventilation unit uses an RF remote (868 MHz). My first approach was to clone the signal using a: CC1101 868MHz SPI RF module


The idea was simple:
- Sniff the transmission
- Decode the payload
- Replay it from an ESP32
After many evenings of analyzing waveforms, modulation, and packet structure…
It became clear: The protocol is secured / encrypted. Replay attacks were not working. Rolling codes or crypto was involved. That path was closed.
The “Dirty” Hack (That Actually Works)
Since cloning the RF protocol was not feasible, I took a more pragmatic approach. Instead of emulating the radio signal, I simulated the button presses on the original remote. The remote PCB was opened and wired as follows:
- 3.3V → Powered from ESP32
- GND → Shared ground
- Each button line → Connected to ESP32 GPIO
- ESP32 outputs configured as open-drain
- A short active-low pulse simulates a button press
So effectively: The ESP32 “presses” the remote buttons electronically. Not pretty. Not elegant. But extremely reliable!
And since the remote already handles pairing and secure communication — we reuse the original certified hardware. Sometimes engineering means shipping, not perfection.
Hardware Architecture
The controller is built around:
- ESP32-C3
- SHT3X temperature & humidity sensor
- HomeKit (HAP compliant)
- Lifecycle Manager (LCM)
- OTA update support
- Physical hardware button
- Identify LED
The ESP32 runs everything locally. HomeKit is optional for monitoring and manual override. If Wi-Fi fails? It continues working autonomously.



Ventilation Logic (ISO-Style Behavior)
This is not just “if humidity > X then ON”. The logic is designed to behave like professional ventilation controllers.
Fan activates when:
- Humidity > 65%
- OR rapid humidity spike detected (shower detection)
- OR temperature > 26°C (optional boost)
Fan stops only when:
- Humidity < 60% (hysteresis)
- AND minimum runtime (15 minutes) passed
This prevents:
- Rapid toggling
- Relay chatter
- Under-ventilation
- Mold formation
The system also learns baseline humidity when idle.
Fan Speeds
| Mode | Output |
|---|---|
| LOW | 30% |
| MID | 60% |
| HIGH | 100% |
Speed selection is implemented via active-low GPIO pulses to the remote PCB.
Manual Override
From HomeKit you can:
- Turn the fan ON / OFF
- Select speed
When manually controlled:
- AUTO mode is disabled
- After 20 minutes, it safely returns to automatic mode
This prevents the “fan left on forever” problem.
Sensor Processing
To avoid noise and unstable readings:
- EMA smoothing (α = 0.2)
- Baseline humidity tracking
- Relative rise detection
- Delta-based reporting
This allows accurate shower detection without false triggers.
HAP-Safe Notification Strategy
HomeKit is sensitive to excessive updates. To remain stable and production-grade:
- Temperature notify only if change > 0.2°C
- Humidity notify only if change > 0.5%
- Minimum notify interval enforced
- Event-per-minute limits applied
- Spike mode allows temporary faster updates
This prevents:
- TCP congestion
- iOS throttling
- OTA instability
- Random HomeKit disconnects
Professional embedded systems require guardrails.
Lifecycle Manager Integration
Using:
esp32-lcm- OTA triggers
- Hardware button management
- Wi-Fi provisioning
- Automatic reconnect
Hardware button functions:
| Action | Result |
|---|---|
| Single press | OTA update |
| Double press | Reset HomeKit pairing |
| Long press | Factory reset |
Wiring Overview
- Identify LED → GPIO 8
- Hardware button → GPIO 3
- Fan LOW/MED/HIGH → configurable GPIO
- I2C SCL → GPIO 7
- I2C SDA → GPIO 6
- SHT3X address → 0x44
All fan control GPIOs are configured as open-drain to safely simulate button presses.
Code
Result
After flashing:
- Device boots
- Wi-Fi connects (or provisioning mode starts)
- Sensor initializes
- AUTO mode active
- Fan responds intelligently to humidity
- Manual override works from Home app
- OTA updates available
No more forgotten remote. No more empty batteries. No more condensation. Just silent automation!

Why This Matters
Smart home is not about adding apps. It is about:
- Removing friction
- Increasing reliability
- Solving real problems
- Designing systems that fail gracefully
This controller now runs fully autonomous. Even if:
- Wi-Fi is down
- HomeKit is unreachable
- Internet is offline
The bathroom stays dry.
Engineering Lessons
- RF protocols are often secured — plan for that.
- Reusing certified hardware can save enormous time.
- Stability matters more than feature count.
- HAP rate limiting is not optional.
- Sometimes the “dirty” solution is the most robust one.
Conclusion
What started as an annoying remote control issue became a fully autonomous, production-grade bathroom ventilation controller.
Powered by:
- ESP32-C3
- HomeKit
- SHT3X
- Lifecycle Manager
- Secure RF remote reuse
Reliable. Autonomous. Professional!