A custom Home Assistant integration that controls the
Windmill Air Purifier
— both the Air Purifier Max (WAP1M1) and the smaller Air Purifier
(SAP1V1) — through Windmill's cloud, a white-labeled Blynk
server at dashboard.windmillair.com.
There's a community integration for the Windmill AC, but nothing for the purifier — this fills that gap.
⚠️ Unofficial. Not associated with, maintained, supported, or endorsed by Windmill. Their cloud can change and break this at any time.
- Fan — power, 4 fan speeds, and presets: auto (air-quality-driven), Eco, Sleep: Whisper, Sleep: White noise
- Auto preset — a mode the integration emulates (the device has no hardware auto): while
active it sets the fan speed from the air-quality category (Good/Moderate/Bad/Unhealthy).
Named exactly
autoso Home Assistant wires it to Apple Home's Auto/Manual toggle. - Air quality — a Good/Moderate/Bad/Unhealthy category sensor (the device's own PM2.5-based signal, and the source the auto preset follows), plus a numeric AQI sensor (V1) that reads a live 0–500 index matching the number shown in the Windmill app.
- Switches — child lock, display auto-dim, beep (audible feedback)
- Diagnostic sensors for every unmapped datastream, to help map other units
- UI config flow (just paste your device Auth Token) with reauth support
- All pins remappable via the options flow, for units that differ
Windmill sells two purifiers, and you pick yours when adding the integration.
| Model number | Size | Retail name (box, windmillair.com) | App / dashboard name |
|---|---|---|---|
WAP1M1 |
larger | Air Purifier Max | Air Purifier |
SAP1V1 |
smaller | Air Purifier | Air Purifier Mini |
Note the trap: "Windmill Air Purifier" is the larger unit in the app but the
smaller unit at retail. The setup picker shows both names plus the model
number so you can't pick wrong. WAP1M1 is what this integration was developed
and confirmed against; SAP1V1 defaults to that same layout until verified on
real hardware (see below).
The chosen model sets the default pin layout / fan-speed count and the model
name (and number) shown for the device in Home Assistant. Every pin is still
remappable afterwards, and unmapped pins show up as diagnostic sensors to help
you adjust — so an unconfirmed unit or a firmware variant can always be tuned by
hand. Model definitions live in
custom_components/windmill_air/models.py;
add a new entry there to support another unit.
Note on the smaller Air Purifier (
SAP1V1): its pin layout is currently assumed identical to the Max and has not yet been confirmed on hardware. If any control misbehaves, map it out withscripts/discover_pins.py(below) and either fix it in the options flow or updateMODEL_SAP1V1inmodels.py.
- HACS → three-dot menu → Custom repositories → add
https://github.com/adeaux/WindmillPurifierwith category Integration. - Install Windmill Air Purifier, then restart Home Assistant.
Copy custom_components/windmill_air/ into your HA config/custom_components/
folder and restart.
- Get your device Auth Token: log in at dashboard.windmillair.com with your Windmill app account → Devices tab → select your purifier → copy the Auth Token.
- Settings → Devices & Services → Add Integration → Windmill Air Purifier, pick your model (Air Purifier Max / Air Purifier), and paste the token.
The model you choose seeds the correct defaults, so no manual mapping is usually needed. Adding both units? Repeat the steps with each device's own token — every config entry keeps its own model and pin mapping.
| Entity | Source | Notes |
|---|---|---|
fan.windmill… |
V0 power, V3 mode, V16 category | 4-speed slider + auto / Eco / Sleep presets |
sensor.…air_quality_index |
V1 | numeric AQI 0–500 (matches the Windmill app) |
sensor.…air_quality |
V16 | category: Good / Moderate / … (drives the auto preset) |
number.…auto_minimum_speed |
— | floor for the auto preset (see below) |
switch.…child_lock |
V11 | |
switch.…display_auto_dim |
V5 | LED auto-fade after interaction |
switch.…beep |
V6 | audible feedback |
sensor.…pin_v# |
unmapped pins | diagnostic, for discovery |
Windmill devices are Blynk devices: state lives in numbered virtual datastreams
("pins"), read/written over Blynk's token-authenticated device HTTPS API. The
integration polls getAll and writes with update.
Two Blynk quirks are handled automatically:
getAllcan omit datastreams that have no web-dashboard widget. Any pin you map that's missing from the bulk response is fetched individually viaget?vN.get?vNreturns human labels for enum pins (e.g. the AQI category), so the category pin is always fetched individually to show "Good"/"Moderate" rather than a raw code.
The Windmill has no hardware auto mode — its mode pin (V3) only holds the numbered speeds and the Eco / Sleep values. This integration emulates one in software:
- Selecting the auto preset marks the fan as "auto-engaged" (tracked inside the integration, not on any pin) and powers the fan on.
- On every poll, while engaged, the integration reads the air-quality category (V16 — the device's own Good / Moderate / Bad / Unhealthy status) and writes the matching numbered speed to V3 — worse air quality → higher speed. The speed slider keeps showing the current auto-selected speed.
- Setting a manual speed, or picking Eco / Sleep, or turning the fan off, exits auto.
The preset is named exactly auto so that, when the fan is exposed to Apple Home as an
air_purifier accessory, Home's Auto/Manual toggle drives it.
Auto follows the device's air-quality category (the same status behind the purifier's indicator light, which is PM2.5-based). The default mapping matches the 4-speed unit:
| Category (V16) | AQI band | Auto speed |
|---|---|---|
| Good | 0–50 | 1 |
| Moderate | 51–100 | 2 |
| Bad | 101–150 | 3 |
| Unhealthy | 151+ | 4 |
Matching is case-insensitive and by keyword, and any unrecognized status simply holds the current speed. (Other AQI wordings — "Unhealthy for Sensitive Groups", "Very Unhealthy", "Hazardous" — are also understood, for units that use them.)
The category is converted to a representative AQI and passed through three tunable thresholds (defaults 50 / 100 / 150, on the 0–500 scale) that decide which status bumps which speed; a hysteresis dead-band (default 10) eases the speed back down only after the air quality improves past a threshold. Adjust both in the Configure dialog — the defaults give the table above and rarely need changing. The preset can be turned off with the Enable the "auto" preset option (it also hides if no category pin is mapped). Auto state is in-memory, so it resets to manual after a Home Assistant restart or an options change — just re-select Auto.
The Auto minimum speed number entity on the device (default 1) is a floor auto never
drops below — set it to 2 to keep more air moving even when the air is Good. It's a live
entity rather than a Configure option so it can be changed from dashboards, scripts, and
automations (number.set_value); a change applies immediately, even while auto is engaged,
and the value survives restarts. It doesn't appear in Apple Home (HomeKit's air-purifier
accessory has no slot for it), but you can get indirect Home-app control by exposing an
input_boolean to HomeKit and letting an automation call number.set_value when it flips.
Apple Home's air-quality tile wants a real PM2.5 density in µg/m³. V1 is a live AQI
index (0–500, confirmed to match the number in the Windmill app), but an index is a
different unit than µg/m³, so it can't drive that tile. A smoke test on the tested unit found
no datastream that reports raw µg/m³ PM2.5 — only the AQI index (V1) and the
Good/Moderate/Bad/Unhealthy category (V16) respond to air quality. The integration keeps a
ready PM2.5 sensor scaffold anyway: if a unit ever exposes such a pin (scan for it with
scripts/discover_pins.py --scan/--watch and look for small numbers that track air quality),
map it to PM2.5 sensor pin in the options to expose sensor.…pm2_5 — which can then be
linked in the HomeKit bridge. The auto preset is unaffected either way: it follows the V16
category.
Open the integration's Configure dialog to remap any pin (blank disables that entity), tune the auto preset, or change the polling interval. The dialog shows a live snapshot of all current pin values. Confirmed default mapping:
| Pin | Function |
|---|---|
| V0 | Power |
| V1 | AQI (numeric 0–500, matches the app) |
| V3 | Mode: 1–4 speeds, 5 = Eco, 6 = Sleep (auto writes a numbered speed here) |
| V4 | Sleep sub-mode: 1 = Whisper, 2 = White noise |
| V5 | Display auto-dim |
| V6 | Beep |
| V11 | Child lock |
| V16 | AQI category (Good / Moderate / Bad / Unhealthy — drives the auto preset) |
scripts/discover_pins.py (standard library only):
python3 scripts/discover_pins.py YOUR_TOKEN --watch # highlight pins that change live
python3 scripts/discover_pins.py YOUR_TOKEN --scan # probe v0..v120, flag getAll-hidden pins
python3 scripts/discover_pins.py YOUR_TOKEN --set v3=5 # test a write (Eco)Change one setting at a time in the Windmill app and note which pin flips.
pip install pytest-homeassistant-custom-component
pytestThe suite runs the integration end-to-end against a mocked cloud (config flow, entities, fan/preset/switch writes, the category-driven auto preset, the getAll-omission fallback, the category label override, offline handling), plus pure unit tests for the category → AQI → speed mapping and hysteresis.
To try an in-development version on your actual purifier without disturbing your stable install, generate a second copy under a different domain:
python3 scripts/make_dev_copy.pyThis writes custom_components/windmill_air_dev/ (gitignored). Restart Home Assistant and
add Windmill Air Purifier (dev) with the same Auth Token — it installs as a separate
integration alongside the stable one, pointed at the same device. Observe/compare freely;
just avoid actively driving controls from both at once (both write the mode pin). Delete the
folder and restart when done.
- API pattern reverse-engineered by the community for the Windmill AC: bzellman/WindmillAC and johnanthonyeletto/homebridge-windmill-ac
- Blynk device HTTPS API docs