Z-Wave 4 Speed Fan Control

Hey there, I’m new SmartThings in general, but I’m a software engineer by trade so writing custom device handlers seems pretty straightforward to me.

I just got a HomeSeer HS-FC200+ fan controller and used a custom DTH I found here to control it:

Now I’m using this in 4 speed mode, so I modified the code a bit to allow for 4 different power levels which works great. The app works fine in the old smart things app, but in the newer app, it only shows as a dimmer. That technically works, but I’d prefere buttons or a staged slider. So I went looking for other DTH options. I found the built in “Z-Wave Fan Controller” handler which seems to work in the new app as well as the old. It gives you a nice 4 position slider (0-3) to control things. Of couse I want 4 speeds (0-4) on the slider, so I found the code in Github and got to customizing again:

Even if I use this code as a custom DTH as-is, it won’t even load the UI in the new smart things app. It claims there was a server error and won’t let me control the fan. Does anyone have any idea how this code linkned above would different from the built in option? My assumption is that it should be identical.

I believe one of the current limitations of the new ST app is the inability to use custom device handlers.

This is painfully incorrect

1 Like

Hey Geoff, i’ll leave here a link to where you can get technical support and solve any of your doubts.

@Geoff_Brown - Did you ever end up creating a custom DTH? I have a couple I am using, including an ST one I added code to to enable custom features of the FC200 but they are both ‘hack jobs’ as I am not a programmer. Since I am having weird issues (lock ups mostly) I would like to try other DTHs to see if the issues go away.

@aruffell, I did! I actually need to go back and reinvestgate it. I have it working on two different controllers. I never get any lockups, but one of them works fine, and one of them seems to have issues with the 4 different levels sometimes (1+2 sometimes are the same). But I have a feeling that is more the fan than anything. But I was just about to check that out this week. I’ll get back to you once I test that out more.

I’ve had at least a couple where one of the speeds broke and the controller had to be replaced. I believe in all cases, the speed step would simply not power the fan. I have one, right now, with speed #2 that doesn’t power the fan and I am in the process of setting up an RMA (BTW, they have 2yr warranty). I also currently have 2 out of 7 where all the speeds are slower than they should be but I believe that to be caused by a bad capacitor in the fan. I will be replacing the capacitors in the next few days. I also had at least a couple where the LEDs on the controller did nothing and no power to the fan, or the LEDs worked but no power to the fan. Overall, I am not happy, and will likely switch to something else once I find a good alternative (hopefully Zigbee to reduce my zwave traffic).

Interesting! All of my speeds power the fan just fine. I think it has more to do with the voltage threshold being slightly off. But I haven’t had a chance to mess with it yet. It is rather annoying that there isn’t an out of the box way to make this work (and that you’re also having failures.

The default 3 speed DTH by ST works with this fan controller however you lose the ability to configure the LEDs and you can’t take advantage of the 4th speed. One experiment I began but never completed was to add the additional code needed to configure the LEDs to the default ST DTH.

As for control of the motor, in my past research I was told that the control is done using the VFD method. Quoting Google: " A variable frequency drive ( VFD ) is a type of motor controller that drives an electric motor by varying the frequency and voltage of its power supply. The VFD also has the capacity to control ramp-up and ramp-down of the motor during start or stop, respectively."

In all my 7 fans, I removed the additional capacitors originally used for the switchable speed, and I also removed the electronics that allowed remote controlled speed control. The only thing that must remain is the start/run capacitor. I got a few replacements yesterday for the large fans that no longer rotate at their original speeds no matter what speed selection is made on the fan controller. I’ll replace one and report back. I might actually also use a tachometer to get numbers even though the difference is visible with the naked eye when comparing to a 3rd identical fan that still works right.

I just bought one of these switches and like it so far. I am using the SmartThings Classic app to configure the switch, but I would be really interested in operating it from the new app. Please share your custom DTH and let us know how it’s working for you. Hopefully it improves on the DTH that HomeSeer points to that was written in 2018.

Hey there! I finally got around to messing with this as one of my 2 switches reverted back to the default behavior. I’ve found that it doesn’t seem to communicate with Smartthings at all anymore. I can install it on my network, but nothing I do can get it to be controlled from Smartthings. Have you had this experience? Any suggestions? I’m about to to go to try and fix my second controller that seems to have its fan speeds off a bit. I’ll let you know how that goes. Assuming my custom DTH works, I can repost it here.

1 Like

@aruffell Here is my handler:

 *  Copyright 2018 SmartThings
 *  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.
metadata {
	definition(name: "Z-Wave Fan Controller 4-Level", namespace: "darthdurden", author: "geoffrey.j.brown@gmail.com", ocfDeviceType: "oic.d.fan", genericHandler: "Z-Wave") {
		capability "Switch"
		capability "Fan Speed"
		capability "Health Check"

		command "low"
		command "medium"
		command "high"
		command "max"
		command "raiseFanSpeed"
		command "lowerFanSpeed"
        command "setFanSpeed"
        command "setSwitchModeNormal"
        command "setSwitchModeStatus"
        command "setSwitchModeNormalTemp"

        fingerprint mfr: "000C", prod: "0203", model: "0001", deviceJoinName: "HomeSeer 4 Speed Fan Control"

	simulator {
		status "00%": "command: 2003, payload: 00"
		status "24%": "command: 2003, payload: 18"
		status "49%": "command: 2003, payload: 31"
		status "74%": "command: 2003, payload: 4A"
		status "99%": "command: 2003, payload: 63"

	tiles(scale: 2) {
		multiAttributeTile(name: "fanSpeed", type: "generic", width: 6, height: 4, canChangeIcon: true) {
			tileAttribute("device.fanSpeed", key: "PRIMARY_CONTROL") {
				attributeState "0", label: "off", action: "switch.on", icon: "st.thermostat.fan-off", backgroundColor: "#ffffff"
				attributeState "1", label: "low", action: "switch.off", icon: "st.thermostat.fan-on", backgroundColor: "#00a0dc"
				attributeState "2", label: "medium", action: "switch.off", icon: "st.thermostat.fan-on", backgroundColor: "#00a0dc"
				attributeState "3", label: "high", action: "switch.off", icon: "st.thermostat.fan-on", backgroundColor: "#00a0dc"
				attributeState "4", label: "max", action: "switch.off", icon: "st.thermostat.fan-on", backgroundColor: "#00a0dc"
			tileAttribute("device.fanSpeed", key: "VALUE_CONTROL") {
				attributeState "VALUE_UP", action: "raiseFanSpeed"
				attributeState "VALUE_DOWN", action: "lowerFanSpeed"

		main "fanSpeed"


def installed() {
	log.debug "installing ..."

	sendHubCommand(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 5, size: 1).format())
	sendHubCommand(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 13, size: 1).format())
	sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"])

def parse(String description) {
	def result = null
	if (description != "updated") {
		log.debug "parse() >> zwave.parse($description)"
        def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
		if (cmd) {
			result = zwaveEvent(cmd)
	if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
		result = [result, response(zwave.basicV1.basicGet())]
		log.debug "Was hailed: requesting state update"
	} else {
		log.debug "Parse returned ${result?.descriptionText}"
	return result

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
	return fanEvents(cmd)

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
	return fanEvents(cmd)

def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
	return fanEvents(cmd)

def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
	return fanEvents(cmd)

def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
	log.debug "received hail from device"

def zwaveEvent(physicalgraph.zwave.Command cmd) {
	sendHubCommand(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format())

	// Handles all Z-Wave commands we aren't interested in
	log.debug "Unhandled: ${cmd.toString()}"

def fanEvents(physicalgraph.zwave.Command cmd) {
	def rawLevel = cmd.value as int
	def result = []

	if (0 <= rawLevel && rawLevel <= 100) {
		def value = (rawLevel ? "on" : "off")
		result << createEvent(name: "switch", value: value)
		result << createEvent(name: "level", value: rawLevel == 99 ? 100 : rawLevel)

		def fanLevel = 0

		if (1 <= rawLevel && rawLevel <= 24) {
			fanLevel = 1
		} else if (25 <= rawLevel && rawLevel <= 49) {
			fanLevel = 2
        } else if (50 <= rawLevel && rawLevel <= 74) {
			fanLevel = 3
		} else if (75 <= rawLevel && rawLevel <= 100) {
			fanLevel = 4
		result << createEvent(name: "fanSpeed", value: fanLevel)

	return result

def on() {
	def cmds = []

	cmds << zwave.switchMultilevelV3.switchMultilevelSet(value: 0xFF).format()
	cmds << zwave.switchMultilevelV1.switchMultilevelGet().format()
    return cmds

def off() {
	def cmds = []
    cmds << zwave.switchMultilevelV3.switchMultilevelSet(value: 0x00).format()
    cmds << zwave.switchMultilevelV1.switchMultilevelGet().format()

	return cmds

def setLevel(value, rate = null) {
	def cmds = []

	def level = value as Integer
	level = level == 255 ? level : Math.max(Math.min(level, 99), 0)
	log.debug "setLevel >> value: $level"

    cmds << zwave.switchMultilevelV3.switchMultilevelSet(value: level).format()
    cmds << zwave.switchMultilevelV1.switchMultilevelGet().format()
    cmds << zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format()

	return cmds

def setFanSpeed(speed) {
	log.debug "setFanSpeed >> $speed"

	if (speed as Integer == 0) {
	} else if (speed as Integer == 1) {
	} else if (speed as Integer == 2) {
	} else if (speed as Integer == 3) {
	} else if (speed as Integer == 4) {

def raiseFanSpeed() {
	setFanSpeed(Math.min((device.currentValue("fanSpeed") as Integer) + 1, 4))

def lowerFanSpeed() {
	setFanSpeed(Math.max((device.currentValue("fanSpeed") as Integer) - 1, 0))

def low() {

def medium() {

def high() {

def max() {

 * Set the color of the LEDS for normal dimming mode, shows the current dim level
def setSwitchModeNormal() {
	log.debug "switch mode >> normal"
    def cmds = []
    cmds << zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format()
    return cmds

def set4Speed() {
	cmds << zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 5, size: 1).format()

 * Set Dimmer to Status mode (exit normal mode)
def setSwitchModeStatus() {
	log.debug "switch mode >> status"

    def cmds = []
    cmds << zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 13, size: 1).format()
    return cmds

def setSwitchModeNormalTemp() {
    def cmds = []
    cmds << setSwitchModeNormal()
    cmds << setSwitchModeStatus()
    return delayBetween(cmds, 5000)

def refresh() {

def ping() {

I believe it should work just fine for 4 speeds out of the box. I’m having trouble with my one fan (speed 2 runs the same as speed 1), but I’m 99% sure that is a bad capacitor on the fan now. It used to work, so I think something just died on it. I hope this helps!

I ended up using the standard DTH and the ST classic app to configure the switch. I know the classic app’s days are numbered, so that won’t be a long-term solution. That led me to setting up WebCoRE and using it to configure & control the fan. I have 2 pistons that I use right now: 1 to turn off the fan with a double down press (which maintains the fan set speed), and 1 to change the status LED color based on my HVAC system status.

Power off with 2 down presses:

HVAC status LED:

I’ll play with your DTH also and see how that works for me compared to the one from HomeSeer. Thanks for sharing it!

1 Like

If you set your fan to the highest speed, it shouldn’t be using the speed capacitors. You can try eliminating the capacitor bank and direct wiring the fan to the switch, which would control the fan speed anyway. Good luck!

@Geoff_Brown - I’ve had several Homeseer fan controller failures, at least 4 but likely more, I just can’t recall exactly. These are the failure modes I’ve had:

  1. No power to fan controller - completely dead
  2. One speed no longer worked (latest one was speed 2 out of 4)
  3. Very slow speeds except for speed 4 that seemed normal
  4. Can be added, but no load control

Maybe more but this is what comes to mind. In a couple cases I actually heard the controller pop just before it stopped working correctly. Judging by the internal pictures of the older FC100 (see below) I am guessing that the pop that killed a speed or two was one of the yellow large caps failing.

I have 7 of these controllers installed on 2 different types of fans. The larger fan of the two had a canopy module to control the speed and I completely removed that. There is one large 6.5uF start capacitor that must remain inside the motor housing. I’ve had that cap fail on 2 out of 3 fans after 5 or 6 years. The result is that the fan spins at slower speeds no matter what speed setting you use on the controller. I replaced the cap on one of the fans and it fixed the issue. The failed cap was around 5uF instead of 6.5uF. The other fan is installed on a 6m/19ft ceiling so that will be an issue to take care of…

The other fans all had the pull chain to change speed. Those you typically set to HIGH and leave them like that if using a wall mounted fan controller. I actually removed the switch and the absurd power limiter on the light part (which causes an incredible number of issues!!!). The fan still needs a cap, but instead of using all the wires on the cap, or multiple caps (whichever way your fan is made), you just retain the single cap that is always wired to the motor no matter the speed setting. Do not remove all caps as the fan will not work at all or properly.

In a scenario like the one above, say 3 is HIGH (is it?), I would remove the switch and connect LIVE to the purple wire directly and cap off the grey and brown wires. Al alternative would be to eliminate the 3 in 1 cap and replace it with an equal (HIGH) value single cap connected between LIVE and grey.


I believe you are seeing my 4th failure mode. The fan controller seems to work but it isn’t controlling the fan. While it can be a fan failure, in my case it was a FC200 failure. I had to replace it to fix it. If you installed it fresh and it never worked, then I’d ask if you left the cap that is always in series, if not, I would try adding that back.


FC100 is identical to FC200 other than RGB LEDs on FC200 and white on FC100.