The bedroom's Ohniyou ceiling fan came with an app — FanLampPro — and nothing else: no hub, no Wi-Fi, no cloud API. It's driven by Bluetooth LE broadcast-advertising: the app shouts commands into the air and the fan silently listens. It never advertises back, so a normal Bluetooth scan finds nothing to pair with. We taught Home Assistant to shout the same commands — from the Pi's own Bluetooth radio, sat in the same room — so the fan, its speed, its reverse and its dimmable downlight all landed in HA and Google, with no extra box on the wall.
Most smart kit here was freed by finding its LAN or radio protocol. The fan is stranger. It belongs to the same family as the house's reverse-engineered "FastCon" Bluetooth bulbs: control is one-way broadcast advertising. The FanLampPro app doesn't connect to the fan — it transmits encrypted BLE advertisement packets that any nearby fan in pairing range will act on. The fan is a pure receiver: it never advertises its presence, exposes no GATT services, and answers no scan. There is simply nothing there to discover and connect to the normal way.
Commands ride in the advertising channel itself, encrypted. There's no session, no bond — whoever transmits the right packet in range wins.
Because the fan only ever listens, a Bluetooth scan turns up nothing. The usual "add device" flow has no candidate to offer.
Early on this looked like an RF remote needing a Broadlink blaster. It isn't — the app path is pure BLE, so the fix needed no extra hardware at all.
The answer is NicoIIT's ha-ble-adv — a custom Home Assistant component that speaks exactly this broadcast-advertising family (FanLampPro, LampSmart Pro, Zhi Jia and friends). It transmits the commands straight from a Bluetooth radio HA already has: the Raspberry Pi's own adapter, which happens to sit in the same bedroom as the fan. No ESP proxy, no Broadlink, no bridge.
/config/custom_components/ble_adv on the Pi; HA runs in a privileged, host-network container with the Pi's Bluetooth passed through, so it can open a raw HCI socket and transmit directly — no proxy in between.The binding produced two proper entities — a fan and a colour-temperature light — with the full feature set, not just on/off.
The component is third-party; the bits we wrote are how it slots into the panel and voice. The fan tile lives on the Lights tab, generated from a small Python model of the rooms — tap toggles it, hold opens the speed & direction control:
# _lights-view-gen.py — the "Kev's Bedroom" room card ("fan.fan_main_fan", "Ceiling fan", "mdi:ceiling-fan", "fan"), # fans are deliberately kept out of the master "N of 21 lights on" # count and the room "All off" — a fan isn't a light ALL = [e for *_, ls in ROOMS for e,_,_,_ in ls if not e.startswith("fan.")]
Tap = on/off · hold = the fan's more-info card (speed slider + reverse) · excluded from the lights count & All-off.
And exposed to Google, so it answers to voice — the fan as a native Google fan (on/off and speed), the downlight as its own controllable light:
# configuration.yaml — google_assistant.entity_config
fan.fan_main_fan:
name: Bedroom Fan
aliases:
- Ceiling Fan
- Bedroom Ceiling Fan
expose: true
light.fan_main_light:
name: Bedroom Fan Light
"Turn on the Bedroom Fan" · "set the Bedroom Fan to 50%" · "turn off the Bedroom Fan Light". Presets and reverse stay on the panel's control card.
A device with no hub, no cloud and no connectable radio — pulled into Home Assistant by having the Pi it already runs on transmit the same Bluetooth adverts the app does. Speed, presets, reverse and a dimmable downlight, on the wall panel and in Google, and the FanLampPro app never has to be opened again.