Fan Speed Capability as a Condition/Trigger

I was wondering if we could get the fan speed capability added as a trigger and condition in Rule Machine. I wanted to use one virtual fan device to coordinate all the fans in the house but I can't set it up in Rule Machine since I can't use the Virtual Fan as a trigger. The virtual fan only has the speed capability, not the dimmer level. Thanks!!

1 Like

Have you tried my switch bindings app? It can coordinate fans. Give it a try and let me know if it meets your need.

1 Like

That won't work. The Virtual Fan Controller device doesn't have the switch capability. Only the Fan Control capability. Also, I don't always want them bound together. Only sometimes. This is a little too permanent for my uses. Ultimately, I think the capability needs to be added to Rule Machine.

That's a good idea. I can think of a few possible uses for that, too.

I'm not sure if this would be helpful as I haven't looked at it myself but here ya go anyway:

You are right - there are definitely multiple ways to work around it today. Would be a lot simpler if it were added directly to RM, though, obviously.

I don't believe so because I still wouldn't be able to respond to the fan speed change to modify other devices. The device isn't the problem, it's Rule Machine. I am trying to modify the Dimmer Sync app to use the fanControl capability and the setSpeed command but it doesn't pick up on the changes to the virtual device at all. Not quite sure what's wrong.

That's an interesting idea. How did you envision that - a master fan that then sets the slave fan speeds? If so, that doesn't sound like it would be too hard to add to the app.

Exactly. Only problem is I can't seem to find the right event to subscribe to. Nothing is being picked up by the app when I change the speed of the virtual device I created to be the Master in this instance. Here's the Child app.

/**
 *  ****************  Fan Sync Child App  ****************
 *

 *  Design Usage:
 *  Keep Fans in sync - ON/OFF 
 *
 *  Code and ideas used from Jason Bottjen (JasonJoel on Hubitat forum)
 *  
 *-------------------------------------------------------------------------------------------------------------------
 *  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.
 *
 * ------------------------------------------------------------------------------------------------------------------------------
 *
 *  Changes:
 *
 *  V1.0.0 - 12/31/18 - Initial release.
 *
 */

definition(
    name:"Fan Sync Child",
    namespace: "ryancasler",
    author: "Ryan Casler",
    description: "Keep Fans in sync - ON/OFF",
    category: "",

	parent: "ryancasler:Fan Sync",
    
    iconUrl: "",
    iconX2Url: "",
    iconX3Url: "",
)

preferences {
    page(name: "pageConfig")
}

def installed() {
    log.debug "Installed with settings: ${settings}"
    initialize()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initialize()
}

def initialize() {
	setDefaults()
	if(pause1==false){subscribeNow()}
}

def pageConfig() {
    dynamicPage(name: "pageConfig", title: "<h2 style='color:#00CED1;font-weight: bold'>Fan Sync</h2>", nextPage: null, install: true, uninstall: true, refreshInterval:0) {	
	display()
    
	section("Instructions:", hideable: true, hidden: true) {
		paragraph "<b>Notes:</b>"
		paragraph "- Select master and slave fans you want to keep in sync<br>- The slave(s) will follow the master."
	}
		
	section(getFormat("header-darkcyan", " Select Master Fan Device")) {
		input "masterFan", "capability.fanControl", title: "Select Master Fan Device", submitOnChange: true, hideWhenEmpty: true, required: true, multiple: false
	}
	section(getFormat("header-darkcyan", " Select Slave Fan Device(s)")) {
		input "slaveFan", "capability.fanControl", title: "Select Slave Fan Device(s)", submitOnChange: true, hideWhenEmpty: true, required: true, multiple: true
	}
	section(getFormat("header-darkcyan", " General")) {label title: "Enter a name for this child app", required: false}
	section() {
		input(name: "logEnable", type: "bool", defaultValue: "true", title: "Enable Debug Logging", description: "Enable extra logging for debugging.")
   	}
	display2()
	}
}

def display() {
	section() {
		paragraph getFormat("line")
		input "pause1", "bool", title: "Pause This App", required: true, submitOnChange: true, defaultValue: false
	}
}

def display2() {
	section() {
		paragraph getFormat("line")
		paragraph "<div style='color:#00CED1;text-align:center'>Fan Sync - App Version: 1.0.0</div>"
	}
}

def getFormat(type, myText=""){
	if(type == "header-green") return "<div style='color:#ffffff;font-weight: bold;background-color:#81BC00;border: 1px solid;box-shadow: 2px 3px #A9A9A9'>${myText}</div>"
	if(type == "header-darkcyan") return "<div style='color:#ffffff;font-weight: bold;background-color:#008B8B;border: 1px solid;box-shadow: 2px 3px #A9A9A9'>${myText}</div>"
    if(type == "line") return "\n<hr style='background-color:#00CED1; height: 1px; border: 0;'></hr>"
	if(type == "title") return "<div style='color:#00CED1;font-weight: bold; font-style: italic'>${myText}</div>"
}

def LOGDEBUG(txt){
    try {
		if (settings.logEnable) { log.debug("${app.label} - ${txt}") }
    } catch(ex) {
    	log.error("${app.label} - LOGDEBUG unable to output requested data!")
    }
}

def pauseOrNot(){
	LOGDEBUG("In pauseOrNot...")
    state.pauseNow = pause1
        if(state.pauseNow == true){
            state.pauseApp = true
            if(app.label){
            if(app.label.contains('red')){
                log.warn "Paused"}
            else{app.updateLabel(app.label + ("<font color = 'red'> (Paused) </font>" ))
              LOGDEBUG("App Paused - state.pauseApp = $state.pauseApp ")   
            }
            }
        }
     if(state.pauseNow == false){
         state.pauseApp = false
         if(app.label){
     if(app.label.contains('red')){ app.updateLabel(app.label.minus("<font color = 'red'> (Paused) </font>" ))
     	LOGDEBUG("App Released - state.pauseApp = $state.pauseApp ")                          
        }
     }
  }    
}

def setDefaults(){
    pauseOrNot()
    if(pause1 == null){pause1 = false}
    if(state.pauseApp == null){state.pauseApp = false}
	if(logEnable == null){logEnable = false}
}

def subscribeNow() {
	unsubscribe()
	subscribe(masterFan, "setSpeed", masterFANHandler) 
}

def masterFANHandler(evt){
	LOGDEBUG("Event Value: " + evt.value)
	if(evt.value == "off"){
		LOGDEBUG("OFF")
		slaveFan.each{
			it.setSpeed(off)
		}
	}
	if(evt.value == "high"){
		LOGDEBUG("HIGH Speed")
		slaveFan.each{
			it.setSpeed(high)
		}
	}
	if(evt.value == "low"){
		LOGDEBUG("Low Speed")
		slaveFan.each{
			it.setSpeed(low)
		}
	}
	if(evt.value == "medium"){
		LOGDEBUG("Medium Speed")
		slaveFan.each{
			it.setSpeed(medium)
		}
	}
}

Just did a quick skim of your code. I believe you would need to subscribe the "speed" attribute. "setSpeed" is the command.

1 Like

I was just going to suggest the same thing.

subscribe(masterFan, "speed", masterFANHandler)

1 Like

#facepalm. That's what I get for trying to do it at 2am. :stuck_out_tongue: I'll give that a shot.

UPDATE: Nope...that didn't do it either.

Your handler should work like this as well. It will probably work as is, but this way you don't miss any possible "speed" values....just a suggestion.

def masterFANHandler(evt){
	LOGDEBUG("Event Value: " + evt.value)
	slaveFan.each{
			it.setSpeed(evt.value)
		}
}

Are you getting errors in the logs?

Nothing at all in the logs.

hmmm...your code looks good to me and you should at least get something in the logs when you change the master fan.

Do you have the debug logs disabled. I see a setting for that in your code.

Nope...they are enabled.

AH HA! Got it...
Didn't realize that I left the virtual fan on low. So, when I set it to low, it didn't recognize that as an "event". :stuck_out_tongue:

1 Like
/**
 *  ****************  Fan Sync Child App  ****************
 *

 *  Design Usage:
 *  Keep Fans in sync - ON/OFF 
 *
 *  Code and ideas used from Jason Bottjen (JasonJoel on Hubitat forum)
 *  
 *-------------------------------------------------------------------------------------------------------------------
 *  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.
 *
 * ------------------------------------------------------------------------------------------------------------------------------
 *
 *  Changes:
 *
 *  V1.0.0 - 12/31/18 - Initial release.
 *
 */

definition(
    name:"Fan Sync Child",
    namespace: "ryancasler",
    author: "Ryan Casler",
    description: "Keep Fans in sync - ON/OFF",
    category: "",

	parent: "ryancasler:Fan Sync",
    
    iconUrl: "",
    iconX2Url: "",
    iconX3Url: "",
)

preferences {
    page(name: "pageConfig")
}

def installed() {
    log.debug "Installed with settings: ${settings}"
    initialize()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initialize()
}

def initialize() {
	setDefaults()
	if(pause1==false){subscribeNow()}
}

def pageConfig() {
    dynamicPage(name: "pageConfig", title: "<h2 style='color:#00CED1;font-weight: bold'>Fan Sync</h2>", nextPage: null, install: true, uninstall: true, refreshInterval:0) {	
	display()
    
	section("Instructions:", hideable: true, hidden: true) {
		paragraph "<b>Notes:</b>"
		paragraph "- Select master and slave fans you want to keep in sync<br>- The slave(s) will follow the master."
	}
		
	section(getFormat("header-darkcyan", " Select Master Fan Device")) {
		input "masterFan", "capability.fanControl", title: "Select Master Fan Device", submitOnChange: true, hideWhenEmpty: true, required: true, multiple: false
	}
	section(getFormat("header-darkcyan", " Select Slave Fan Device(s)")) {
		input "slaveFan", "capability.fanControl", title: "Select Slave Fan Device(s)", submitOnChange: true, hideWhenEmpty: true, required: true, multiple: true
	}
	section(getFormat("header-darkcyan", " Select Slave Fan Device(s)")) {
		input "slaveSwitch", "capability.switch", title: "Select Slave Switch Device(s)", submitOnChange: true, hideWhenEmpty: true, required: false, multiple: true
	}
	section(getFormat("header-darkcyan", " General")) {label title: "Enter a name for this child app", required: false}
	section() {
		input(name: "logEnable", type: "bool", defaultValue: "true", title: "Enable Debug Logging", description: "Enable extra logging for debugging.")
   	}
	display2()
	}
}

def display() {
	section() {
		paragraph getFormat("line")
		input "pause1", "bool", title: "Pause This App", required: true, submitOnChange: true, defaultValue: false
	}
}

def display2() {
	section() {
		paragraph getFormat("line")
		paragraph "<div style='color:#00CED1;text-align:center'>Fan Sync - App Version: 1.0.0</div>"
	}
}

def getFormat(type, myText=""){
	if(type == "header-green") return "<div style='color:#ffffff;font-weight: bold;background-color:#81BC00;border: 1px solid;box-shadow: 2px 3px #A9A9A9'>${myText}</div>"
	if(type == "header-darkcyan") return "<div style='color:#ffffff;font-weight: bold;background-color:#008B8B;border: 1px solid;box-shadow: 2px 3px #A9A9A9'>${myText}</div>"
    if(type == "line") return "\n<hr style='background-color:#00CED1; height: 1px; border: 0;'></hr>"
	if(type == "title") return "<div style='color:#00CED1;font-weight: bold; font-style: italic'>${myText}</div>"
}

def LOGDEBUG(txt){
    try {
		if (settings.logEnable) { log.debug("${app.label} - ${txt}") }
    } catch(ex) {
    	log.error("${app.label} - LOGDEBUG unable to output requested data!")
    }
}

def pauseOrNot(){
	LOGDEBUG("In pauseOrNot...")
    state.pauseNow = pause1
        if(state.pauseNow == true){
            state.pauseApp = true
            if(app.label){
            if(app.label.contains('red')){
                log.warn "Paused"}
            else{app.updateLabel(app.label + ("<font color = 'red'> (Paused) </font>" ))
              LOGDEBUG("App Paused - state.pauseApp = $state.pauseApp ")   
            }
            }
        }
     if(state.pauseNow == false){
         state.pauseApp = false
         if(app.label){
     if(app.label.contains('red')){ app.updateLabel(app.label.minus("<font color = 'red'> (Paused) </font>" ))
     	LOGDEBUG("App Released - state.pauseApp = $state.pauseApp ")                          
        }
     }
  }    
}

def setDefaults(){
    pauseOrNot()
    if(pause1 == null){pause1 = false}
    if(state.pauseApp == null){state.pauseApp = false}
	if(logEnable == null){logEnable = false}
}

def subscribeNow() {
	unsubscribe()
	subscribe(masterFan, "speed", masterFANHandler)
}

def masterFANHandler(evt){
	LOGDEBUG("Event Value: " + evt.value)
		slaveFan.each{
			it.setSpeed(evt.value)
		}
		slaveSwitch.each{
		if (evt.value == "off")
			it.off()
		else
			it.on()
		}
}

This is the way it landed. Also added slave switches to control table/floor fans that are controlled by smart plugs. Not required of course. Go on with any speed off with off. Seems to work so far.

2 Likes