[BETA RELEASE] Updated Open Source Ecobee Device Type and SmartApps

Hello… thanks for your awesome device and app! They’re working great for me so far!

One thing I haven’t figured out how to do yet is trigger a ST mode or other action based on the current mode of the thermostat. For example, I want ST to change it’s mode to “vacation” when my thermostats are in vacation mode. Any thoughts on how to do this?

Wow - that’s an excellent suggestion!

While I think you can do this using CoRE, it’s a great idea for a helper app. Give me some time to think about it, I will see if I can whip something together.

Do you think thermostat programs/climates Home, Away, Sleep and Vacation would suffice?

Thanks!
Barry

That would suffice for what I’m trying to do. I obviously can’t speak for everyone, though. Thank you!

I would think you want this the other way around… you want SmartThings to be telling ecobee to change to Vacation mode, right?

Nope, I want ST to change based on the ecobee. I typically program my planned vacations into my ecobees long before they actually happen, so I’d like that to be the trigger for when the rest of the vacation activities happen.

Try this - it is a replacement for the included Routines helper app, that adds the support you ask for as the 3rd option (Mode controls Program, Routine Controls Program, Program controls Mode or Routine). You should be able to just save this over your current smartthings: ecobee Routines in the IDE and publish it…

WARNING I threw this together quickly, without much more than cursory testing, so I may have broken something. Let me know if it does what you want!

/**
 *  ecobee Routines
 *
 *  Copyright 2015 Sean Kendall Schneyer
 *
 *
 *  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.
 *
 */
def getVersionNum() { return "0.1.7a" }
private def getVersionLabel() { return "ecobee Routines Version ${getVersionNum()}" }

/*
 *
 * 0.1.4 - Fix Custom Mode Handling
 * 0.1.5 - SendNotificationMessage so that the action shows up in Notification log after the mode/routine notices
 * 0.1.6 - Logic tweaks, fixed bad state.variableNames
 * 0.1.7 - Extended to support the inverse: Ecobee Program change (including Vacation) can change ST Mode or run Routine
 * 0.1.7a- Fixed typo
 */

definition(
	name: "ecobee Routines",
	namespace: "smartthings",
	author: "Sean Kendall Schneyer (smartthings at linuxbox dot org)",
	description: "Change ecobee Programs based on SmartThings Routine execution or Mode changes, OR change Mode/run Routine based on Ecobee Program/Vacation changes",
	category: "Convenience",
	parent: "smartthings:Ecobee (Connect)",
	iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
	iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
	singleInstance: false
)

preferences {
	page(name: "mainPage")
}

// Preferences Pages
def mainPage() {
	dynamicPage(name: "mainPage", title: "Setup Routines", uninstall: true, install: true) {
    	section(title: "Name for Routine Handler") {
        	label title: "Name this Routine Handler", required: true
        }
        
        section(title: "Select Thermostats") {
        	if(settings.tempDisable == true) {
            	paragraph "WARNING: Temporarily Disabled as requested. Turn back on to activate handler."
            } else {
        		input ("myThermostats", "capability.Thermostat", title: "Pick Ecobee Thermostat(s)", required: true, multiple: true, submitOnChange: true)            
			}
        }
            
        section(title: "Select ST Mode or Routine, or Ecobee Program") {
        	// Settings option for using Mode or Routine
            input(name: "modeOrRoutine", title: "Use Mode Change, Routine Execution or Ecobee Program: ", type: "enum", required: true, multiple: false, description: "Tap to choose...", metadata:[values:["Mode", "Routine", "Ecobee Program"]], submitOnChange: true)
		}
        
	        if (myThermostats?.size() > 0) {
            	if(settings.modeOrRoutine == "Mode") {
	    	    	// Start defining which Modes(s) to allow the SmartApp to execute in
                    // TODO: Need to run in all modes now and then filter on which modes were selected!!!
    	            //mode(title: "When Hello Mode(s) changes to: ", required: true)
                    section(title: "Modes") {
                    	input(name: "modes", type: "mode", title: "When Hello Mode(s) change to: ", description: "Tap to choose Modes...", required: true, multiple: true)
					}
                } else if(settings.modeOrRoutine == "Routine") {
                	// Routine based inputs
                    def actions = location.helloHome?.getPhrases()*.label
					if (actions) {
            			// sort them alphabetically
            			actions.sort()
						LOG("Actions found: ${actions}", 4)
						// use the actions as the options for an enum input
                        section(title: "Routines") {
							input(name:"action", type:"enum", title: "When these Routines execute: ", description: "Tap to choose Routines...", options: actions, required: true, multiple: true)
                        }
					} // End if (actions)
                } else if(settings.modeOrRoutine == "Ecobee Program") {
                	def programs = getEcobeePrograms()
                    programs = programs + ["Vacation"]
                    LOG("Found the following programs: ${programs}", 4) 
                    section(title: "Programs") {
                    	def n = myThermostats?.size()
                    	if (n > 1) paragraph("NOTE: It is recommended (but not required) to select only one thermostat when using Ecobee Programs to control SmartThings Modes or Routines")
                    	input(name: "ctrlProgram", title: "When Ecobee${n>1?'s':''} switch to Program: ", type: "enum", description: "Tap to choose Programs...", options: programs, required: true, multiple: true)
                    }
                }

				section(title: "Actions") {
                	if (settings.modeOrRoutine != "Ecobee Program") {
                		def programs = getEcobeePrograms()
                   		programs = programs + ["Resume Program"]
                		LOG("Found the following programs: ${programs}", 4)
                    
	               		input(name: "whichProgram", title: "Switch to this Ecobee Program: ", type: "enum", required: true, multiple:false, description: "Tap to choose...", options: programs, submitOnChange: true)
    	       	    	input(name: "fanMode", title: "Select a Fan Mode to use\n(Optional) ", type: "enum", required: false, multiple: false, description: "Tap to choose...", metadata:[values:["On", "Auto", "default"]], submitOnChange: true)
        	       		if(settings.whichProgram != "Resume Program") input(name: "holdType", title: "Select the Hold Type to use\n(Optional) ", type: "enum", required: false, multiple: false, description: "Tap to choose...", metadata:[values:["Until I Change", "Until Next Program", "default"]], submitOnChange: true)
            	   		input(name: "useSunriseSunset", title: "Also at Sunrise or Sunset?\n(Optional) ", type: "enum", required: false, multiple: true, description: "Tap to choose...", metadata:[values:["Sunrise", "Sunset"]], submitOnChange: true)                
                	} else {
                    	input(name: "runModeOrRoutine", title: "Change Mode or Execute Routine: ", type: "enum", required: true, multiple: false, description: "Tap to choose...", metadata:[values:["Mode", "Routine"]], submitOnChange: true)
                        if (settings.runModeOrRoutine == "Mode") {
    	                	input(name: "runMode", type: "mode", title: "Change Hello Mode to: ", required: true, multiple: false)
                		} else if (settings.runModeOrRoutine == "Routine") {
                			// Routine based inputs
                    		def actions = location.helloHome?.getPhrases()*.label
							if (actions) {
            					// sort them alphabetically
            					actions.sort()
								LOG("Actions found: ${actions}", 4)
								// use the actions as the options for an enum input
								input(name:"runAction", type:"enum", title: "Execute this Routine: ", options: actions, required: true, multiple: false)
							} // End if (actions)
                        } // End if (Routine)
                    } // End else Program --> Mode/Routine
                } // End of "Actions" section
            } // End if myThermostats size
            section(title: "Temporarily Disable?") {
            	input(name: "tempDisable", title: "Temporarily Disable Handler? ", type: "bool", required: false, description: "", submitOnChange: true)                
        	}
        
        section (getVersionLabel())
    }
}

// Main functions
def installed() {
	LOG("installed() entered", 5)
	initialize()  
}

def updated() {
	LOG("updated() entered", 5)
	unsubscribe()
    initialize()
}

def initialize() {
	LOG("initialize() entered")
    if(tempDisable == true) {
    	LOG("Temporarily Disabled as per request.", 2, null, "warn")
    	return true
    }
	
	if (settings.modeOrRoutine == "Routine") {
    	subscribe(location, "routineExecuted", changeProgramHandler)
    } else if (settings.modeOrRoutine == "Mode") {
    	subscribe(location, "mode", changeProgramHandler)
    } else {
    	subscribe(myThermostats, "currentProgramName", changeSTHandler)
    }
   
    if(useSunriseSunset?.size() > 0) {
		// Setup subscriptions for sunrise and/or sunset as well
        if( useSunriseSunset.contains("Sunrise") ) subscribe(location, "sunrise", changeProgramHandler)
        if( useSunriseSunset.contains("Sunset") ) subscribe(location, "sunset", changeProgramHandler)
    }
    
	// Normalize settings data
    normalizeSettings()
    LOG("initialize() exiting")
}

// get the combined set of Ecobee Programs applicable for these thermostats
private def getEcobeePrograms() {
	def programs

	if (myThermostats?.size() > 0) {
		myThermostats.each { stat ->
        	def DNI = stat.device.deviceNetworkId
            LOG("Getting list of programs for stat (${stat}) with DNI (${DNI})", 4)
        	if (!programs) {
            	LOG("No programs yet, adding to the list", 5)
                programs = parent.getAvailablePrograms(stat)
            } else {
            	LOG("Already have some programs, need to create the set of overlapping", 5)
                programs = programs.intersect(parent.getAvailablePrograms(stat))
            }
        }
	} 
    LOG("getEcobeePrograms: returning ${programs}", 4)
    return programs
}

private def normalizeSettings() {
	if (settings.modeOrRoutine == "Ecobee Program") return
    
	// whichProgram
	state.programParam = ""
	if (whichProgram != null && whichProgram != "") {
    	if (whichProgram == "Resume Program") {
        	state.doResumeProgram = true
        } else {        	
    		state.programParam = whichProgram
    	}
	}
    
    // fanMode
    state.fanCommand = ""
    if (fanMode != null && fanMode != "") {
    	if (fanMode == "On") {
        	state.fanCommand = "fanOn"
        } else if (fanMode == "Auto") {
        	state.fanCommand = "fanAuto"
        } else {
        	state.fanCommand = ""
        }
    }
    
    // holdType
    state.holdTypeParam = null
    if (holdType != null && holdType != "") {
    	if (holdType == "Until I Change") {
        	state.holdTypeParam = "indefinite"
        } else if (holdType == "Until Next Program") {
        	state.holdTypeParam = "nextTransition"
        } else {
        	state.holdTypeParam = null
        }
    }
    
	if (settings.modeOrRoutine == "Routine") {
    	state.expectedEvent = settings.action
    } else {
    	state.expectedEvent = settings.modes
    }
	LOG("state.expectedEvent set to ${state.expectedEvent}", 4)
}

def changeSTHandler(evt) {
	LOG("changeSTHandler() entered with evt: ${evt.name}: ${evt.value}", 5)
    
    // adjust because currentProgramName carries some informational baggage
    String newProgram = evt.value
    if (newProgram.startsWith("Hold: ")) { 
    	newProgram = newProgram.drop(6)
    } else if (newProgram.startsWith("Away ")) {
    	newProgram = newProgram.drop(5)
    }
    
    if (settings.ctrlProgram.contains(newProgram)) {
    	if (!state.ecobeeThatChanged) {
        	state.ecobeeThatChanged = evt.displayName
            state.ecobeeNewProgram = evt.value
        }
    	if (settings.runModeOrRoutine == "Mode") {
        	if (settings.myThermostats.size() == 1) { 
            	changeMode() 
            } else { 
            	// trick to avoid multiple calls if more than 1 thermostat - they could change simultaneously or within the next pollCycle
                // to avoid this delayed response, assign to only one thermostat
            	runIn((parent.getPollingInterval()*66), changeMode, [overwrite: true]) 
            }	
        } else {
        	if (settings.myThermostats.size() == 1) { runRoutine() } else {	runIn((parent.getPollingInterval()*66), runRoutine, [overwrite: true]) }	// trick to avoid multiple calls if more than 1 thermostat
        }
    }
}

def changeMode() {
	if (!settings.runMode.contains(location.mode)) { 
		if (state.ecobeeThatChanged) sendNotificationEvent("Changing Mode to ${settings.runMode} because ${state.ecobeeThatChanged} changed to ${state.ecobeeNewProgram}")
    	location.mode(settings.runMode)
    }
    state.ecobeeThatChanged = null
}

def runRoutine() {
	if (state.ecobeeThatChanged) sendNotificationEvent("Executing Routine ${settings.runAction} because ${state.ecobeeThatChanged} changed to ${state.ecobeeNewProgram}")
    state.ecobeeThatChanged = null
	location.helloHome?.execute(settings.runAction)
}

def changeProgramHandler(evt) {
	LOG("changeProgramHander() entered with evt: ${evt.name}: ${evt.value}", 5)
	
    def gotEvent 
    if (settings.modeOrRoutine == "Routine") {
    	gotEvent = evt.displayName?.toLowerCase()
    } else {
    	gotEvent = evt.value?.toLowerCase()
    }
    LOG("Event name received (in lowercase): ${gotEvent}  and current expected: ${state.expectedEvent}", 5)

    if ( !state.expectedEvent*.toLowerCase().contains(gotEvent) ) {
    	LOG("Received an mode/routine that we aren't watching. Nothing to do.", 4)
        return true
    }
    
    settings.myThermostats.each { stat ->
    	LOG("In each loop: Working on stat: ${stat}", 4, null, 'trace')
    	// First let's change the Thermostat Program
        if(state.doResumeProgram == true) {
        	LOG("Resuming Program for ${stat}", 4, null, 'trace')
            if (stat.currentValue("thermostatHold") == 'hold') {
            	def scheduledProgram = stat.currentValue("scheduledProgram")
        		stat.resumeProgram(true) // resumeAll to get back to the schedules program
				sendNotificationEvent("And I resumed the scheduled ${scheduledProgram} program on ${stat}.")
            }
        } else {
        	LOG("Setting Thermostat Program to programParam: ${state.programParam} and holdType: ${state.holdTypeParam}", 4, null, 'trace')
            
            boolean done = false
            def thermostatHold = stat.currentValue('thermostatHold')
            if (stat.currentValue('currentProgram') == state.programParam) {
            	if (thermostatHold == "") {
                	sendNotificationEvent("And I verified that ${stat} is already in the ${state.programParam} program.")
                    done = true
                }
            } else if (thermostatHold == 'hold') {
                if (stat.currentValue('scheduledProgram') == state.programParam) {
                    if (state.programParam == 'nextTransition') {
                    	stat.resumeProgram(true)	// resumeAll to get back to the originally scheduled program
                        sendNotificationEvent("And I resumed the scheduled ${state.programParam} on ${stat}.")
                        done = true
                    }
                }
            }
            if (!done) {    
        		stat.setThermostatProgram(state.programParam, state.holdTypeParam)
				sendNotificationEvent("And I set ${stat} to the ${state.programParam} program${(state.holdTypeParam!='nextTransition') ? ' indefinitely.' : '.'}")
            }
		}
        if (state.fanCommand != "" && state.fanCommand != null) stat."${state.fanCommand}"()
    }
    return true
}

// Helper Functions
private def LOG(message, level=3, child=null, logType="debug", event=true, displayEvent=true) {
	message = "${app.label} ${message}"
	parent.LOG(message, level, null, logType, event, displayEvent)
    log."${logType}" message
}

Loaded it up, and I was able to create the routine. A 10 minute test didn’t do anything though. I tried setting “When Ecobee switch to Program - Vacation” to “Change Mode or Execute Routine” and tried both the mode and routine actions. Neither of them triggered, meaning the mode wasn’t set or the routine wasn’t run.

FIXED - simple typo…I updated the above version to 0.1.7a - @chrismar this should solve the problem…

You will have to open the close the Smart App to make it work, however…

Interesting, choice is good as @JDRoberts says. :slight_smile:
For me I always saw the vacation feature for those using ecobee standalone without home automation. For me I use the global mode of SmartThings for controlling everything else. I set global mode AWAY which triggers the “away” for Smart Home Monitor, “away” in ecobee, “away” in automation control, etc. No need for redundancy with vacation feature because when I leave (whether on vacation or just for a few hours) I simply run the GoodBye! routine for setting SmartThings global AWAY mode. So instead of thinking only vacation for just the heating/cooling I think GoodBye to all automation subsystems.

1 Like

Thanks, but it doesn’t work. When setting up the routine in the ST app I get a blank screen after selecting the thermostat.

IDE says:

9:45:00 AM: error groovy.lang.MissingMethodException: No signature of method: script14894988826071809991857.LOG() is applicable for argument types: (org.codehaus.groovy.runtime.GStringImpl, java.lang.Integer) values: [Getting list of programs for stat (HVAC - Downstairs) with DNI (44dc1f4d-d2f9-412c-a3d1-0d0f384420d9.316523621557), ...]
Possible solutions: now(), url(), run(), any(), page(), wait() @ line 177

Aditionally, when I try to edit my already configured Helper Apps I get nothing once I click on them. For that, the IDE says:

 9:47:54 AM: error groovy.lang.MissingMethodException: No signature of method: script14894989380881809991857.LOG() is applicable for argument types: (org.codehaus.groovy.runtime.GStringImpl, java.lang.Integer) values: [Getting list of programs for stat (HVAC - Upstairs) with DNI (44dc1f4d-d2f9-412c-a3d1-0d0f384420d9.318368543807), ...]
Possible solutions: now(), url(), run(), any(), page(), wait() @ line 177

To answer your earlier question, yes, I’m using both your ecobee connect app and DH.

@chrismar - could there possibly have been a cut/paste error?

Is this your line 177?

LOG(“Getting list of programs for stat (${stat}) with DNI (${DNI})”, 4)

That line wasn’t modified, so I’m at a loss for why it would stop working. My only change was to fix the name “changeSTHandler” at line 156.

I’ve tried both editing an existing and adding a new Ecobee Program-driven handler, as well as existing ST Mode/Routine driven handlers, all without problem.

Yep, that’s my line 177. I’ve tried re-copying/pasting, same result.

I’ll try removing the smart app completely and re-installing.

Weird…

It shouldn’t cause the problem, but try changing the next to the last line to:

log."${logType}" message

Would there ever be an option to pull updates from GitHub ? Help in keeping the code updated…

thanks

I will update my gitHub tonight with the major updates I have been developing on the side, but I still have not updated my GitHub to integrate with the IDE…

1 Like

@chrismar - you might want to try logging in to Ecobee again (even if it says you are currently connected) - it is possible that the earlier errors caused an internal synchronization error. Logging back in sometimes fixes this…

Will this allow me to use location modes as my trigger for thermostat settings? Similar to what the Keep Me Cozy II smart app does. So, if I’m in sleep mode then the temperature sensor in my bedroom is used to control the temperature. Day mode would use the living room sensor to control the thermostat, etc.

Thanks

John -

Indeed, you can configure your Ecobee to use different temperature sensors for different Programs (Home, Away, Sleep) - and you can do that without any SmartThings integration.

With this development project, there are several “Helper Apps” included, on the one I’ve been enhancing above serves to change the Thermostat Program based upon the Hello Home Mode or a Routine. So, even though you may have your thermostat programmed to go into “Sleep” at 11pm, if you where to put your SmartThings Home into “Night” mode at 10pm (directly or via routine) this Helper app can be configured to put the thermostat into a (temporary or permanent) Hold: Sleep early.

The added enhancement to Ecobee Routines under development also allows for the inverse - to have the house change modes based on which program the thermostat is running. The OP wanted to use Ecobee Vacations to put the house into Away mode (and presumably to exit out when the Vacation ended). In addition to Vacation, this can be helpful for both manual and automated Hold events - when the Ecobee thinks the house is empty can be configured such that it goes into “Auto Away” mode…you might want the whole house to go into “Away” mode also…

For those interested, I am just finishing up a new “Ecobee Smart Room” Helper App. This one is intended for those rooms in your house that you may not use regularly (e.g. spare bedroom, closed off dining room, etc.). Smart Room automates the process of making such a room active or inactive based on the door being opened or closed.

With the door normally closed, the room will be made inactive after a configurable number of hours, and will be made active again if the door is opened for a specified number of minutes. An “active” Smart Room has its ecobee Sensor added to its controlling thermostat for the selected Programs (Home, Away, Sleep, etc.); when the room goes inactive, the sensor is removed from the separately selected Programs (so you can leave the room included in the Away program to keep the pipes from freezing, for example). If the room also has SmartThings controlled vents (Ecobee or Keen), the vents will be opened when the room goes “active”, and closed when inactive. A room will go inactive after the configured number of hours of being closed (e.g., 12 hours if its a bedroom, or maybe on 2 hours for the dining room). The ecobee sensor’s occupancy/motion sensor is also used to disable the “inactive” automation if someone is detected in the room after the door has been closed…the automation is reset the next time the door is opened and then closed again.

There are perhaps a couple of kinks to still work out on this one, but I hope to post my entire suite of updates to my ecobee Smart Apps, Thermostat and Sensor devices on my Github tonight and then I will post a [Release] announcement here.

Meanwhile, feedback/comments are welcome - let me know what you think about this Smart Room idea…

@storageanarchy Ok, so I finally made some progress. I deleted everything (DH, connect app, all helper apps) and re-installed. The routines app was still broken, so I put in the code from github, which let the routine app start working again. For giggles I tried the code from above (again) and it magically started working this time.

BUT… there’s still an error in the IDE:

8:48:54 PM: error groovy.lang.MissingMethodException: No signature of method: physicalgraph.app.LocationWrapper.mode() is applicable for argument types: (java.lang.String) values: [Vacation]
Possible solutions: setMode(java.lang.String), getMode(), use([Ljava.lang.Object;), wait(), grep(), find() @ line 269
8:48:54 PM: debug Ecobee Set Vacation Mode changeSTHandler() entered with evt: currentProgramName: Vacation
8:41:38 PM: debug Ecobee Set Vacation Mode changeSTHandler() entered with evt: currentProgramName: Home