Fan Speed Capability as a Condition/Trigger

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

I can see the possibilities for this app. Looking forward to trying it out.

Here's the Parent that's necessary also.

/**
 *  ****************  Fan Sync Parent App  ****************
 *
 *  Design Usage:
 *  Keep Fans in sync
 *
 *  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 - 4/10/2019 - Initial release.
 *
 *
 *
 */

definition(
    name:"Fan Sync",
    namespace: "ryancasler",
    author: "Ryan Casler",
    description: "Keep Fans in sync - ON/OFF",
    category: "Convenience",
    iconUrl: "",
    iconX2Url: "",
    iconX3Url: "",
)

preferences {
     page name: "mainPage", title: "", install: true, uninstall: true
} 

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

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

def initialize() {
    log.info "There are ${childApps.size()} child apps"
    childApps.each {child ->
    log.info "Child app: ${child.label}"
    }
}

def mainPage() {
    dynamicPage(name: "mainPage") {
    	installCheck()
		if(state.appInstalled == 'COMPLETE'){
			section(getFormat("title", "${app.label}")) {
				paragraph "<div style='color:#00CED1'>Keep Fans in sync - ON/OFF</div>"
				paragraph getFormat("line")
			}
			section("Instructions:", hideable: true, hidden: true) {
				paragraph "<b>Notes:</b>"
				paragraph "- Add master and slave fanes to keep in sync.<br>"
			}
  			section(getFormat("header-darkcyan", " Child Apps")) {
				app(name: "anyOpenApp", appName: "Fan Sync Child", namespace: "ryancasler", title: "<b>Add a new 'Fan Sync' child</b>", multiple: true)
  			}
 			section(getFormat("header-darkcyan", " General")) {
       				label title: "Enter a name for parent app (optional)", required: false
 			}
			display()
		}
	}
}

def installCheck(){         
	state.appInstalled = app.getInstallationState() 
	if(state.appInstalled != 'COMPLETE'){
		section{paragraph "Please hit 'Done' to install '${app.label}' parent app "}
  	}
  	else{
    	log.info "Parent Installed OK"
  	}
}

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 "<h2 style='color:#00CED1;font-weight: bold;font-style: italic'>${myText}</h2>"
}

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

Nice!!!

1 Like

So this will only work on the devices with drivers that have the fanControl capability and the setSpeed command?

I have some fans that do not utilize this functionality (yet), and they are not showing as selectable devices. So I figured this to be the case.

Yes, requires fanControl capability (I base that on looking at the code above).

That's because the virtual device that is controlling them is using the Fan Control capability only. You could add slave dimmers but you would then have to work in logic to translate the speed into the appropriate dimmer level. All my fans have the capability. But you're welcome to modify/update/change whatever you like. After all, this is based off @JasonJoel's Dimmer Sync app. :slight_smile: (with appropriate credit in the code I might add).

I have three of the Hampton Bay fans that have the capability. I have two fans controlled by the Homeseer FC-200+ fan switch that does not support fancontrol or setSpeed out of the box. However, I am re-writing the driver to hopefully incorporate it.

It is my first driver and I am still trying to wrap my head around some of it, but I hope to have it available soon.

1 Like

It isn't too bad adding the fanControl capability and speed attribute. I added it to the GE fan control driver last week. Feel free to look at that as a reference if you need to. The code is sloppy because I was in a hurry, but it works.

1 Like

@JasonJoel, you're the reason I am doing it. I was trying to figure out how to do it and there was not much available. However, when you got your driver done, I kind of sort of figured most of it out.

In fact, when you see my driver, you will think some of it looks vaguely familiar :slight_smile:

Good! Steal/re-use code whenever possible. No point in starting over from scratch on everything if someone else has done something close!

Here's something else crazy...guess what doesn't show up in Rule Machine under "Control Fans"....that's right, the HE built in Virtual Fan device! LMAO The Virtual Fan device can't even be used by a custom command because it doesn't have the Actuator capability!!! Boy, that was all a complete waste of time.

So, @bravenel I think I have three asks at this point:

  1. Add the Fan Control capability as a condition/trigger in RM
  2. Add whatever is necessary to get the virtual fan device to show up under control fans.
  3. Allow for selecting a specific speed under the control fans command in RM rather than just bump to the "next level".

Added Actuator Capability to Virtual Fan Device. This is necessary to use custom command to setSpeed in RM.

/*
 * Virtual Fan Advanced
 *
 * 
 * 
 */
metadata {
    definition(name: "Virtual Fan Advanced", namespace: "ryancasler", author: "Ryan Casler") {
        capability "Actuator"
        capability "Fan Control"
    }
}

preferences {
    section() {
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    if (logEnable) runIn(1800, logsOff)
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def setSpeed(value) {
    if (logEnable) log.debug "Fan speed set to " + value
    sendEvent(name: "speed", value:value, isStateChange: true)
}

Ask and ye shall receive.