[OBSOLETE] Updated Open Source Ecobee Device Type and SmartApps

@greenzkool -

You might try the new ecobee-thermostat.groovy (v0.10.16) - I reworked the UI for the temp sliders…let me know if that works better for you on Android.

Hello,

Just tried this and go this error:

error java.lang.NullPointerException: Cannot get property ‘17a57b8c-294d-4375-bee4-53a02f598612.411992023159’ on null object @ line 1250

6:12:17 PM: error pollEcobeeAPI(): General Exception: java.lang.NullPointerException: Cannot get property ‘value’ on null object.

The device isn’t working either. Any ideas as to what the problem might be?

Major Update

Files changed at my GitHub repository

  • ecobee-connect.groovy v0.10.27
  • ecobee-thermostat.groovy v0.10.17
  • ecobee-sensor.groovy v0.10.3

Updates

  1. Sensors now display their inclusion status in the standard Programs (Home, Away, Sleep). This is in preparation for being able to add/remove a sensor from a Program from within the sensor UI. This does not work yet in this release, as you will see if you click on one of the climates (the inclusion state won’t change).

  2. Only collects & translates updates for sensors that are configured/selected in the settings.

  3. Now properly handles cases where not all of the sensors are selected in settings.

  4. Only queues changed data to both thermostats and sensors

  5. Reduced the CPU overhead for updating large numbers of thermostats and devices significantly, even when all the devices have changes (less load on ST servers to complement the significantly reduced load on Ecobee servers).

  6. Fixed precision translation for both thermostats and sensors; always sends full API precision (which will be what you get from device.currentValue(‘temperature’) and what is displayed in the device’s List view in the mobile app, but the device itself will display the specified precision.

2 Likes

The interface has been messed up on my Android device since Samsung updated the app. It’s hard to adjust the temp with the slider or know what it’s set for. I’ve attached some screenshots.

Also, a couple questions:

  1. Will there be a way to include non-Ecobee sensors outside of the Smart Circulation app? I have an Iris Motion sensor with temperature reporting but it’s not selectable in the Ecobee Connect app.
  2. In the Smart Circulation app, it seems like whatever I set the max fan on time to, it immediately increases the fan time to that regardless. I’ve even tried setting the delta t to 10 when there’s only a difference of 2-3 and it doesn’t seem to have an effect.

Thanks!

Yup - Samsung/SmartThings sure messed up the formatting with their latest updated.

Lots of discussion - lots of people having the same issue: Android 2.3.0 - Release Notes

Unfortunately, there’s not really anything I can do about it - developers have virtually zero control over how things display.

You should open a ticket with Customer Support (support@smartthings.com). Maybe if enough people complain they’ll fix their mistakes. (I’m opening one as well).

Thanks!

  1. Will there be a way to include non-Ecobee sensors outside of the Smart Circulation app? I have an Iris Motion sensor with temperature reporting but it’s not selectable in the Ecobee Connect app.

Sorry, but this effort will only support the native Ecobee sensor integration with Ecobee thermostats for the temperature/motion integration. I believe there is a pay version of an Ecobee DTH that offers what you are looking for (and a lot more).

  1. In the Smart Circulation app, it seems like whatever I set the max fan on time to, it immediately increases the fan time to that regardless. I’ve even tried setting the delta t to 10 when there’s only a difference of 2-3 and it doesn’t seem to have an effect.

Hmmm…haven’t seen this issue. Have you looked at the Live Logging for the Smart Circulation instance in question? Set the Debug Level to 4 in the Ecobee (Connect) SmartApp, then watch the specific SmartApp. You might have to set Max to a low number, save/exit, then edit again and set Max back to a higher number. At log-level 4 it should show you the average temperature it found, the delta and the amount of each change.

Also, I suggest setting the Minutes per adjustment to (say) 5, and the time adjustment frequency to at least 15 or even 30 minutes. This because so long as the scheduled Fan On time is less than or equal to thirty minutes, the Ecobee thermostat will try to split the time into 2 or 4 equal chunks of time per hour (e.g., runs every half hour, or every 15 minutes) - adjusting faster than that has no benefit other than to ramp up the time too quickly.

HOT FIX!

Get ecobee-thermostat.groovy v0.10.17a for a (temporary) work-around to the issues caused by v2.3.0 & 2.3.1 of the Android Mobile App.

This addresses:

  • Thermometer icon too small
  • Temperatures at right of sliders not displaying correctly

Still Broken:

  • font size of the main temperature display is too small
  • Cool/Heat icons to left of slider sometimes render too small, other times Just Right
  • Auto/On Switch/ icon renders with large gap between, instead of as adjacent tiles

I have a MAJOR update coming soon, but this should tide us over the current Android-induced issues…

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.