Skip to content
My Account

HomeKit

ESP32 HomeKit Sunshade with Calibration and Weather Protection

A HomeKit Window Covering accessory for the ESP32 that replaces an up, down and stop switch, with touch buttons, calibration, power-loss recovery and optional wind, rain and light protection.

  • ESP32
  • HomeKit
  • Safety notes
  • 11 min read
No ratings yet, be the first.

This is a complete Apple HomeKit Window Covering accessory for the ESP32-WROOM-32D, built with ESP-IDF v5. It replaces a standard UP, DOWN and STOP switch and adds HomeKit control, capacitive touch buttons, time-based position tracking, calibration and automatic recovery after a power loss. Optional wind, rain and light sensors close the sunshade for you, and restore the position when the weather calms down.

The whole project is open source on GitHub.

High voltage warning

The wiring diagram in the repository carries a high voltage electrical shock hazard warning, and for good reason: the relays switch the motor of the sunshade, which is normally powered from the mains. Only work on mains wiring if you know how, disconnect the power first, and test the firmware with the motor mechanically disconnected.

AC tubular motors must not be switched straight from one direction to the other. The firmware keeps both relays off for a short dead time before reversing, and the README still recommends a hardware interlock with cross-wired NC contacts for mains-driven motors.

What it does

FeatureDetails
HomeKit serviceWindow Covering: current position, target position, position state and hold position
Relay outputsGPIO16 (open, up) and GPIO17 (close, down), with a software interlock, configurable active level and a direction-reversal dead time
Touch buttonsThree TTP223 capacitive modules: UP, STOP and DOWN
Physical buttonA push button on GPIO25 with single, double and long press
CalibrationMeasures the real travel time of your motor and saves it to flash
Power-loss recoveryOn boot it closes fully, then goes back to the last HomeKit position
Position trackingTime-based, 0 to 100 %, reported at most every 500 ms
OTA updatesThrough a HomeKit characteristic or a single button press
Weather protection (optional)Closes on high wind (HWFS-1), rain (MH-RD) or bright sun (BH1750), and restores the previous position when it clears
Sensors (optional)Temperature and humidity (SHT3x) and ambient light (BH1750) as HomeKit sensor tiles
Lifecycle ManagerWiFi, NVS storage, factory reset and reboot counter, through ESP32 Lifecycle Manager

Hardware

Required

ComponentQuantityNotes
ESP32-WROOM-32D1Any ESP32 module with enough GPIOs. See the ESP32-WROOM-32D pinout
Relay module, 2 channel1One relay per direction. Optoisolated is recommended. See the dual channel relay module guide
Copper ring or touch pad3UP, STOP and DOWN, with shielded cable and a ground shield
Push button, momentary1Normally open, wired GND to button to GPIO25
LED with resistor1Identify LED on GPIO23

Optional

ComponentPurpose
HWFS-1 anemometer and voltage dividerWind protection. See the HWFS-1 anemometer guide
MH-RD or FC-37 rain moduleRain protection. See the MH-RD raindrop detection module guide
BH1750 (GY-302) light sensorSun protection and a HomeKit light tile. See the BH1750 guide
SHT3x (SHT30, SHT31 or SHT35)HomeKit temperature and humidity tiles. See the SHT sensor series guide

The BH1750 and SHT3x share one I²C bus, and each optional sensor is switched on separately in idf.py menuconfig.

Wiring

Wiring diagram of the sunshade controller: an ESP32 relay board with three touch pads for up, stop and down, a wind sensor, a rain module and a BH1750 light sensor, mains connections for live, neutral and ground, and a high voltage warning sign

The two relay outputs switch the motor direction. The open relay (GPIO16) feeds motor terminal A, the close relay (GPIO17) feeds motor terminal B, and the common goes to the motor common. Never wire open and close to the same motor terminal at once. The software interlock already prevents both relays from being energised together, but check your wiring anyway.

The firmware drives the relays active-high by default. Many common optoisolated 2-channel modules are active-low. If both relays click on at boot and the motor runs unexpectedly, set ESP_RELAY_ACTIVE_LEVEL to 0 in menuconfig, and always test with the motor disconnected first.

Touch pads

Each capacitive button uses a TTP223 module in momentary, active-high mode between the electrode and the ESP32. The electrode is a copper ring or pad, wired to the TTP223 SIG input, and a shielded cable with the braid to GND runs between pad and module to prevent false triggers. The ESP32 reads the TTP223 output as a normal GPIO input, so its internal touch peripheral is not used.

GPIO pinout

GPIOFunctionNotes
GPIO16Open or up relayOutput, active level configurable
GPIO17Close or down relayOutput, active level configurable
GPIO23Identify LEDBlinks on a HomeKit Identify request
GPIO25Physical push buttonInput, active-low
GPIO27TTP223 DOWNDigital input, active-high
GPIO32TTP223 UPDigital input, active-high
GPIO33TTP223 STOPDigital input, active-high; also starts calibration
GPIO34HWFS-1 wind sensor (optional)ADC1 input, through a voltage divider
GPIO35MH-RD rain sensor (optional)Digital input, active-low
GPIO21 and GPIO22Shared I²C SDA and SCL (optional)BH1750 and SHT3x

All defaults can be changed with idf.py menuconfig under StudioPieters. Only ADC1 GPIOs (32 to 39) work for the wind sensor, because ADC2 conflicts with WiFi.

Build and flash

git clone https://github.com/AchimPieters/esp32-homekit-Sunshade.git
cd esp32-homekit-Sunshade
idf.py set-target esp32
idf.py menuconfig
idf.py build flash monitor

If idf.py set-target fails with a CMake error, remove the stale build folder first with rm -rf build and run the set-target step again. New to the toolchain? Start with ESP32 HomeKit for total beginners or ESP32 HomeKit Development with ESP-IDF.

WiFi credentials are not stored on the first boot. Use any ESP-IDF provisioning method, for example the esp-idf-prov app on iOS or Android, to set the SSID and password. The device remembers them after that.

First-time setup and calibration

  1. Flash the firmware.
  2. Provision WiFi.
  3. Pair with HomeKit using the QR code or the setup code from menuconfig. The repository ships a default code, so change it before you use the device for real. Changing the code or setup ID needs a new QR code.
  4. Calibrate, so the device learns how long your motor takes.

To calibrate, hold the STOP touch pad for 3 seconds. The LED starts blinking rapidly.

  • Phase 1: the motor closes for the stored travel time multiplied by 1.5, which makes sure it reaches the end stop. The LED blinks fast.
  • Phase 2: the motor opens and a timer runs. The Home app shows the sunshade opening in real time, based on the previous value. When it is fully open, tap STOP once. The elapsed time is saved to flash. The LED blinks slowly.
ResultLEDHomeKit
Success5 short flashesPosition set to 100 %, all positions 0 to 100 % available
Aborted (STOP within 2 s of phase 2)3 long flashesPosition reset to 0 %
Timeout (no STOP within 2 minutes)3 long flashesPosition reset to 0 %

To update the travel time, for example after replacing the motor, run the calibration again.

Power-loss recovery

After a power loss the device does not know where the sunshade is. If it has been calibrated, it homes in the background on boot: it runs the motor closed for the calibrated time multiplied by 1.5, which ends at the physical end stop and counts as 0 %. It then waits 500 ms, loads the last saved target position and moves there. WiFi and HomeKit start in parallel, and commands are blocked while homing runs.

Every time you command a new position, from HomeKit or the touch buttons, the target is saved. If power fails while the shade is moving from 0 % to 70 %, it closes fully after the reboot and then moves to 70 %. Without calibration the device starts normally and logs a warning.

Controlling it from HomeKit

ControlResult
Slider to 0 %Closes the sunshade fully
Slider to 100 %Opens it fully
Slider to 1 to 99 %Moves to that position
Tap the tileFully opens or fully closes
Hey Siri, stop the sunshadeStops at the current position

At 0 % and 100 % the motor runs for the full calibrated time, and the motor’s own end stop gives the final accuracy. Intermediate positions are reached by running a fraction of the calibrated time, so their accuracy depends on how well the calibration matches the real travel time.

Touch pads and physical button

InputAction
UP padOpen to 100 %
STOP pad, tapStop, or confirm the open position during calibration
STOP pad, hold 3 sStart calibration
DOWN padClose to 0 %
Button, single pressRequest an OTA update and reboot
Button, double pressReset HomeKit pairing and restart
Button, long press (1 s or more)Factory reset of WiFi and HomeKit, then reboot

The relays are switched off before any reset or reboot. Inputs are polled every 25 ms by default and debounced over 60 ms, both configurable.

The identify LED is not a HomeKit service. It blinks three times in pairs when HomeKit sends an Identify request, for example while adding the accessory.

Weather protection and sensors

Wind, rain and light protection share one override mechanism: whichever triggers saves the current HomeKit target and restores it when the condition clears. Each sensor is enabled separately in menuconfig.

SensorSensor detailsCloses whenReopens when
HWFS-1 windPassive cup anemometer, 0 to 3.3 V, full scale 14 m/s, no supply neededWind is 8.0 m/s or more (default)Wind is below 5.0 m/s (default)
MH-RD rainLM393 module, digital output used, active-low when wetRain detected, stable for 2 sDry, stable for 2 s
BH1750 lightI²C, address 0x23 or 0x5C, 1 lux resolutionLight is 40000 lux or more (default)Light is below 20000 lux (default)

Wind sensor

The HWFS-1 output reaches 3.3 V at full scale, but the ESP32 ADC is only accurate up to about 2450 mV with 11 dB attenuation. The README therefore uses a voltage divider of 10 kΩ and 22 kΩ, which gives 2.27 V at 14 m/s. The signal wire goes to GPIO34, the ground wire to GND, and no supply voltage is connected. The firmware averages 16 samples per reading, and a 10 kΩ plus 10 µF RC filter on the signal wire is recommended against the ripple of the sensor’s DC generator. Readings at or below 30 mV are reported as 0 m/s.

The README also describes a field calibration: compare the log output with a reference anemometer in steady wind, and adjust WIND_SENSOR_MAX_SPEED_DS until they match.

Rain sensor

VCC goes to 3.3 V, GND to GND, and DO to GPIO35. The analog output is not used, and the sensitivity is set with the potentiometer on the module. Mount the sensing pad under a sheltered overhang so only falling rain reaches it, which extends its life and reduces false triggers.

Light and climate sensors

The BH1750 and the SHT3x (address 0x44 or 0x45) share SDA on GPIO21 and SCL on GPIO22. The GY-302 breakout has pull-ups, and the firmware also enables the ESP32 internal ones. The SHT3x readings appear as HomeKit temperature and humidity tiles only, and do not move the sunshade. Each sample is CRC-8 validated.

Sunrise and sunset automations

No firmware changes are needed. The Home app has built-in sunrise and sunset automations that use the location of your home hub, which needs a HomePod mini, HomePod or Apple TV 4K running continuously and location services on for the Home app. Without a home hub, time-based automations do not run.

Create an automation in the Home app with A time of day occurs, choose Sunrise (optionally with an offset), select the sunshade tile and set the position. Do the same for Sunset with 0 %. The README suggests opening to 80 % at sunrise, to 100 % two hours later, and closing to 0 % half an hour before sunset.

The weather protection in the firmware overrides HomeKit commands while it is active. When it clears, the sunshade goes back to the last position HomeKit commanded, not to an earlier sunrise position.

How position tracking works

There are no limit switches or encoders, so the position is estimated from time. Every 500 ms the movement task adds or subtracts 100 % times 500 ms divided by the calibrated time. HomeKit is notified only when the whole-number position changes, and always when the target is reached.

The mechanical end stop corrects the accumulated error each time the shade reaches 0 % or 100 %. For intermediate positions the README expects an error of 2 to 5 % over many cycles without recalibration. A new command during a move re-syncs the position and immediately changes direction, with the relay interlock making sure the opposite relay is off first.

Troubleshooting

ProblemWhat to check
No response to touchThe boot log lists the TTP223 GPIOs and active level. Check momentary mode, active-high output, 3.3 V supply and ground. Reduce the debounce time if the output is stable but not registering.
Calibration aborts at onceSTOP was tapped within 2 seconds of phase 2. Wait until the sunshade is fully open before tapping.
Position drifts over timeRecalibrate. Measure the real travel time with a stopwatch, because motor speed can vary with load or temperature.
Wrong position after a power lossConfirm the device is calibrated and look for the homing message in the log. After a corrupted NVS, do a factory reset with a long button press and recalibrate.
Relay clicks but the motor does not runSome relay modules need 5 V for the coil supply even with 3.3 V logic. Check the NO and COM wiring.
WiFi will not connectDouble press the button to reset the pairing and provision again, or long press for a full factory reset.
Wind sensor reads 0Check that it is enabled, that the ready message is in the boot log, the divider voltage with a multimeter, and that the GPIO is on ADC1.
Rain sensor does not trigger, or triggers falselyCheck the DO voltage while wetting the pad, adjust the potentiometer, and tune the debounce time. Move the pad away from condensation and irrigation spray.

Get the code

The firmware needs ESP-IDF 5.0 up to but not including 7.0, the achimpieters/esp32-homekit component (3.0.0 or newer), achimpieters/esp32-button (1.2.3 or newer) and espressif/mdns (1.8.0 or newer).

Every push runs host unit tests for the pure logic (relay polarity, position maths, sensor conversions, SHT3x CRC and hysteresis), then builds the firmware for the ESP32 on ESP-IDF 5.3.2 and 5.4.1, with and without all optional sensors enabled. The repository has releases up to 0.0.7 with a signed main.bin.

Everything is in the esp32-homekit-Sunshade repository on GitHub.

Advertisement
All HomeKit

More HomeKit

Keep building.

Switches, sensors, plugs and blinds: more accessories that you build yourself and add to the Home app.

All HomeKit