I just paired a TAPO light/fan Homekit device. Paired to HE just fine. I can control the light just fine.
I have the same issue with the fan that mentioned here. Can't turn it on or off. If I turn it on or off from the switch HE shows the correct status. If I set a speed in HE the fan will change speed then a couple seconds later the speed goes to zero.
Correction to my post: Apparently the speed/dimmer bars go blank after a few seconds. Actual value doesn't change.
So only issue I seem to have now is being able to turn the fan on/off. Same as @msmatn
Not sure about the Hunter, but doing some playing with mine I came up with an observation.
I don't have this installed yet, just sitting on desk with a patch cord to power it up.
I removed it from HE, reset it, added it back into Home. There is no on/off switch in Home, just a slider to control the speed. This particular unit allows 4 speeds, 25%, 50%, 75%, and 100%. When you adjust the speed it will latch in at one of those. In Home when you set it at 0% it shuts off. In HE setting it to 0% it stays at 25%. So don't know if there is some limit in HE driver or what that might cause that.
I have the same problem with the hunter fan. In HE the ON/OFF function does not work. But when I increase the speed from 0 it powers ON and to power OFF I have to set the speed below 25%.
So I am using Alexa skill to link the HE device to alex. To power ON the fan I must ask Alexa to increase the fan speed and to power OFF, ask to Alexa to reduce fan speed until it goes below 25%
I used home assistant before and it doesn't have this problem controlling the same fan.
I actually returned mine. Not so much because of this issue but just didn't like the physical layout. The missus was not happy with the small button to manually turn light or fan on. But will be interested in seeing what you find out.
EDIT: Found this one which I am going to try. It's fan only and that's all we needed.
Let me know how you like it. I am looking into fan control options. Though I would really like something that will let me switch the direction as well without needing to flip a switch on the fan...
Inovelli has a couple options but the price is about double. When ever I add something new I’m trying to keep it ‘generic’ so to speak for the next owner of the house. Thus trying to avoid Zwave or Zigbee. At our age the next move will be to either a nursing home or cemetery… So whatever is here stays.
Got the Leviton Fan switch. Complete control from Apple Home.
Added it to HE. Can't issue a straight on/off command. But if I enter a speed value it turns it on. Rounds the actual speed to 25 or 50 or 75 or 100. That is fine. If I enter a speed less than 13 it will turn it off. Except zero. 1 will work but not zero.
So all in all that is aceptable for me. The on/off would be nice but I can make it work otherwise.
Installed the switch. Wrote an app to control it using virtual switches. On/Off, change speed, etc. Using those I can control it from Google or Apple Home. Seems to be working great.
I wrote this specific for use on a bedroom fan. So it will have reference to bedroom in the code. But you're welcome to it.
/**
* Fan Control
*
* Copyright 2026 Jim White
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Fan Controller",
namespace: "jwwhite001",
author: "Jim White",
description: "Control Bedroom Fans",
category: "My Apps",
iconUrl: "",
iconX2Url: "",
iconX3Url: "")
preferences {
section("Bedroom Fan") {
input "BRfanSwitch", "capability.switch", required: true, multiple: false, title: "Fan Wall Switch"
input "BRfanONOFF", "capability.switch", required: true, multiple: false, title: "Fan On/Off Switch"
input "BRfanSpeedUp", "capability.switch", required: true, multiple: false, title: "Increase Fan Speed Switch"
input "BRfanSpeedDwn", "capability.switch", required: true, multiple: false, title: "Decrease Fan Speed Switch"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
unsubscribe()
unschedule
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
unschedule
initialize()
}
def initialize() {
subscribe(BRfanSwitch,"switch",BRfanManual)
subscribe(BRfanONOFF,"switch",BRfanAuto)
subscribe(BRfanSpeedUp,"switch",BRSpeedUp)
subscribe(BRfanSpeedDwn,"switch",BRSpeedDwn)
}
//Control Routines
def BRfanManual(evt) {
if (evt.value == "on") {
BRfanONOFF.on()
BRfanSwitch.setLevel(50)
}
if (evt.value == "off") {
BRfanONOFF.off()
}
}
def BRfanAuto(evt) {
if (evt.value == "on") {
if (BRfanSwitch.currentswitch == "off") {
BRfanSwitch.setLevel(50)
}
}
if (evt.value == "off") {
if (BRfanSwitch.currentswitch == "on") {
BRfanSwitch.setLevel(10)
}
}
}
def BRSpeedUp(evt) {
if (evt.value == "on" && BRfanSwitch.currentswitch == "on") {
if (BRfanSwitch.currentLevel == 25) {BRfanSwitch.setLevel(50)}
if (BRfanSwitch.currentLevel == 50) {BRfanSwitch.setLevel(75)}
if (BRfanSwitch.currentLevel == 75) {BRfanSwitch.setLevel(100)}
}
}
def BRSpeedDwn(evt) {
if (evt.value == "on" && BRfanSwitch.currentswitch == "on") {
if (BRfanSwitch.currentLevel == 100) {BRfanSwitch.setLevel(75)}
if (BRfanSwitch.currentLevel == 75) {BRfanSwitch.setLevel(50)}
if (BRfanSwitch.currentLevel == 50) {BRfanSwitch.setLevel(25)}
}
}
Thanks much. i tried the app and was able to make it work with my Hunter fan. So it has 3 virtual switches and also when you enable it to Alexa/Google did you enable all the three virtual switches and call each a different name? By the way rather than using 3 virtual switch, can this be done with one single virtual dimmer?
i tried to play around with your code to replace the 3 virtual switches with 1 virtual dimmer. ON, speed increase and decrease works but i cannot Switch off the fan, tired multiple options as suggested by chatgpt but still OFF function doesn't work.
/**
* Fan Controller - Single Virtual Dimmer with Minimal OFF Fix
*
* Copyright 2026 Jim White
*/
definition(
name: "Fan Controller Single Dimmer Fixed OFF 15",
namespace: "jwwhite001",
author: "Jim White",
description: "Control Bedroom Fans with a single virtual dimmer, minimal speed on OFF, and retain last speed",
category: "My Apps",
iconUrl: "",
iconX2Url: "",
iconX3Url: ""
)
preferences {
section("Bedroom Fan") {
input "fanDimmer", "capability.switchLevel", required: true, multiple: false, title: "Virtual Fan Dimmer"
input "fanDevice", "capability.switchLevel", required: true, multiple: false, title: "Actual Fan Device"
}
}
// Lifecycle methods
def installed() {
log.debug "Installed with settings: ${settings}"
unsubscribe()
unschedule()
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
unschedule()
initialize()
}
def initialize() {
// Subscribe to virtual dimmer events
subscribe(fanDimmer, "switch", dimmerHandler)
subscribe(fanDimmer, "level", dimmerHandler)
// Initialize lastLevel if not set
if (!state.lastLevel) {
state.lastLevel = 33
}
}
// Handler for dimmer changes
def dimmerHandler(evt) {
// Ignore programmatic events caused by setLevel()
if (state.ignoreNextLevelEvent) {
log.debug "Ignoring programmatic event: ${evt.name}=${evt.value}"
state.ignoreNextLevelEvent = false
return
}
def level = fanDimmer.currentLevel
log.debug "Dimmer event: ${evt.name} = ${evt.value}, currentLevel = ${level}, lastLevel = ${state.lastLevel}"
// -------------------
// Switch OFF → minimal speed 10
// -------------------
if (evt.name == "switch" && evt.value == "off") {
if (!state.lastLevel || state.lastLevel == 10) state.lastLevel = 33
// Set guard before changing dimmer to avoid feedback
state.ignoreNextLevelEvent = true
// Set fan to minimal speed 10 without turning it ON
fanDevice.setLevel(10)
// Keep virtual dimmer in sync
if (fanDimmer.currentLevel != 10) fanDimmer.setLevel(10)
return
}
// -------------------
// Switch ON → restore last speed
// -------------------
if (evt.name == "switch" && evt.value == "on") {
if (!state.lastLevel || state.lastLevel == 10) state.lastLevel = 33
setFanSpeed(state.lastLevel)
return
}
// -------------------
// Slider level changes → map to discrete fan speeds
// -------------------
def newLevel = 0
if (level == 0) {
newLevel = 10
} else if (level <= 33) {
newLevel = 33
} else if (level <= 66) {
newLevel = 66
} else {
newLevel = 99
}
setFanSpeed(newLevel)
// Only update lastLevel if not minimal
if (newLevel != 10) {
state.lastLevel = newLevel
}
}
// Helper method to set fan speed
def setFanSpeed(level) {
log.debug "Setting fan speed to ${level}"
fanDevice.setLevel(level)
// Only turn ON if level > 10
if (level > 10) {
fanDevice.on()
}
// Keep virtual dimmer in sync, avoid feedback
if (fanDimmer.currentLevel != level) {
state.ignoreNextLevelEvent = true
fanDimmer.setLevel(level)
}
}
The reason I did it this way was for voice control. The only thing I use Google for is voice control.
I didn't try a dimmer as there are only 4 speeds anyway. When I shared the switch back to Apple Home it shows up as a dimmer and works ok there. I use that as my 'dashboard' so to speak.
As far as turning off the fan. The original switch I had, Tapo one, I couldn't turn it off either. Setting the speed value low did not turn it off. This one seems to work differently. So don't know what the differences are. I think @gopher.ny is working on why the straight on/off commands don't work.