Connecting to Smart Home Devices

@dkhayes117, good to hear. If your feeling generous please share the code (either on another thread or on the Exchange (could have a chance at winning the Exchange Challenge))

I will post it when completed. What would be really cool for the winner of the exchange competition would be a special badge on the forum that would show beside your username :grinning:.

As long as the next competition is made open internationally!!

did you ever get this working with the Ecobee? I'm working on a similar project.

Yes it works, but it has been a while since I've gotten to work on it. I'll post some code when I have time if you'd like.

Also, welcome to the community!

Thank you!

This is a ways off from being completed. My functions can be polished more, I basically wrote each one to work individually and was going to go back and combine some functionality, which I haven't done yet. When it is more polished, I will probably post the whole project on Ignition Exchange.

I have my api tokens and authorization stuff held in tags, which is not very secure. When I'm done with the functionality, I plan on storing those with encryption. Note it has been a while since these have been fully tested. Also note that you have to setup an ecobee dev account before you can use the APIs, ecobee API

def getAuthorization():
	import system
	apiKey = system.tag.readBlocking('[default]Ecobee/APIKey').value
	params = {"response_type":"ecobeePin", "client_id":apiKey, "scope":"smartWrite"}
	#url = "https://api.ecobee.com/authorize?response_type=ecobeePin&client_id=%s&scope=smartWrite" % apiKey
	url = "https://api.ecobee.com/authorize"
	response = http.client.post(url=url, params = params)	
	json = response.getJson()
	tags = ['[default]Ecobee/AuthCode','[default]Ecobee/Pin']
	values = [json['code'],json['ecobeePin']]
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################

def getTokens():
	import system
	tags = ['[default]Ecobee/AuthCode','[default]Ecobee/APIKey']
	values = system.tag.readBlocking(tags)
	authCode = values[0].value
	apiKey = values[1].value	
	url = "https://api.ecobee.com/token"	
	postData = "grant_type=ecobeePin&code=%s&client_id=%s" % (authCode, apiKey)
	response = system.net.httpPost(url=url,postData=postData)
	json = system.util.jsonDecode(response)	
	tags = ['[default]Ecobee/Access Timeout','[default]Ecobee/Access Token', '[default]Ecobee/Refresh Token']
	values = [json['expires_in'],json['access_token'],json['refresh_token']]
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################

def refreshTokens():
	import system
	tags = ['[default]Ecobee/Refresh Token','[default]Ecobee/APIKey']
	values = system.tag.readBlocking(tags)
	refreshToken = values[0].value
	apiKey = values[1].value	
	params = {"grant_type":"refresh_token", "refresh_token":refreshToken, "client_id":apiKey, "ecobee_type":"jwt"}
	url = "https://api.ecobee.com/token"
	response = http.client.post(url=url,params=params)
	json = response.getJson()
	tags = ['[default]Ecobee/Access Timeout','[default]Ecobee/Access Token', '[default]Ecobee/Refresh Token']
	values = [json['expires_in'],json['access_token'],json['refresh_token']]
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################

def getRunTimeInfo():
	import system
	raw = system.tag.readBlocking('[default]Ecobee/Access Token')
	accessToken = raw[0].value
	headers = {'Content-Type': 'text/json','Authorization':'Bearer %s' % accessToken} 
	params = {"format":"json","body":{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":"true"}}} #"includeAlerts":true
	
	url = 'https://api.ecobee.com/1/thermostat'
	response = http.client.get(url, params = params, headers=headers)
	json = response.getJson()	
	tags = []
	values = []
			
	for key in json['status']:
		tags.append('[default]Ecobee/API Return %s' % key)
		values.append(json['status'][key])
		
	for thermostat in json['thermostatList']:
		name = thermostat['name']
		baseTagPath = '[default]Ecobee/Thermostats/%s/Runtime/' % name
		
		for key in thermostat['runtime']:
			tags.append(baseTagPath + key)
			values.append(thermostat['runtime'][key])
			
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################

def getSettingsInfo():
	import system
	raw = system.tag.readBlocking('[default]Ecobee/Access Token')
	accessToken = raw[0].value
	headers = {"Content-Type": "text/json","Authorization":"Bearer %s" % accessToken} 
	params = {"format":"json", "body":{"selection":{"selectionType":"registered","selectionMatch":"","includeSettings":"true"}}} #"includeAlerts":true

	url = 'https://api.ecobee.com/1/thermostat'
	response = http.client.get(url, params=params, headers=headers)
	json = response.getJson()		
	tags = []
	values = []
		
	for key in json['status']:
		tags.append('[default]Ecobee/API Return %s' % key)
		values.append(json['status'][key])
		
	for thermostat in json['thermostatList']:
		name = thermostat['name']
		baseTagPath = '[default]Ecobee/Thermostats/%s/Settings/' % name
		
		for key in thermostat['settings']:
			tags.append(baseTagPath + key)
			values.append(thermostat['settings'][key])
			
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################
		
def getStatus():
	import system
	raw = system.tag.readBlocking('[default]Ecobee/Access Token')
	accessToken = raw[0].value
	headers = {'Content-Type': 'text/json','Authorization':'Bearer %s' % accessToken} 
	params = {"format":"json", "body":{"selection":{"selectionType":"registered","selectionMatch":"","includeEquipmentStatus":"true"}}} #"includeAlerts":true
	
	url = 'https://api.ecobee.com/1/thermostat' % body
	response = http.client.get(url, params=params, headers=headers)
	json = response.getJson()	
	tags = []
	values = []	
				
	for key in json['status']:
		tags.append('[default]Ecobee/API Return %s' % key)
		values.append(json['status'][key])
	
	for thermostat in json['thermostatList']:
		name = thermostat['name']
		baseTagPath = '[default]Ecobee/Thermostats/%s/' % name
		
		for key in thermostat:
			if key != 'name':
				tags.append(baseTagPath + key)
				values.append(thermostat[key])
			
	system.tag.writeBlocking(tags,values)
##########################################################################################################################################

def setTempHold(newHoldTemp, thermostatName, holdType, **kwargs):
	'''
		Args: newHoldTemp = temperature to hold at (must be between thermostat temp ranges)
			  thermostatName = Name of the thermostat to apply hold to
			  holdType = Type of hold ('dateTime', 'nextTranistion', 'indefinte', 'holdHours')
			  **kwargs = For optional arguments
			  			 optional args:
			  			 	startDate, startTime, endDate, endTime = required for 'dateTime' hold type, when to start and stop the temp hold
			  			 	fanSpeed = speed options (LOW, MEDIUM, HIGH, and OPTIMIZED
			  			 	holdHours = required for 'holdHours' hold type, how long to hold temp
			  kwargs is a dictionary. To use kwargs the arguments must be defined in the call 
			  example: ecobee.setTempHold(70, 'Main Floor', 'holdHours', holdHours = 2)			 	
	'''
	import system
		
	if holdType == 'dateTime':
		reqKeys = ('startDate','startTime','endDate', 'endTime')
		if not all(key in kwargs for key in reqKeys):
			# write to status tags 
			return
	elif holdType == 'holdHours':
		if 'holdHours' not in kwargs:
			# write to status tags 
			return	
	
	newHoldTemp *= 10	# Multiple temp by 10 because 700 = 70.0 deg
	keys = ['coolRangeHigh','coolRangeLow','heatRangeHigh','heatRangeLow', 'heatCoolMinDelta', 'hvacMode'] 
	tags = []
	for key in keys:
		tags.append('[default]Ecobee/Thermostats/%s/Settings/%s' % (thermostatName,key))
	tags.append('[default]Ecobee/Access Token')
	values = system.tag.readBlocking(tags)
	coolHigh = values[0].value
	coolLow = values[1].value
	heatHigh = values[2].value
	heatLow = values[3].value
	minDelta = values[4].value 
	hvacMode = values[5].value
	accessToken = values[6].value
	
	coolHold = heatHold = newHoldTemp	# Recommended that coolHold = heatHold
	
	if hvacMode == 'auto':				# If hvacMode equals auto, cool hold should minDelta higher than heat hold
		coolHold += minDelta
		
	if coolLow <= coolHold <= coolHigh and heatLow <= heatHold <= heatHigh:	# Hold must be inside range
		reqDict = {'holdType':holdType, 'heatHoldTemp':heatHold, 'coolHoldTemp':coolHold}
		newDict = reqDict.copy()
		newDict.update(kwargs)
		dataParams = system.util.jsonEncode(newDict)

		data = {
			  "selection": {
				"selectionType":"registered",
				"selectionMatch":thermostatName
			  },
			  "functions": [
				{
				  "type":"setHold",
				  "params": dataParams
				}
			  ]
			} 
		
		headers = {"Content-Type": "application/json;charset=UTF-8","Authorization":"Bearer %s" % accessToken}
		params = {"format":"json"}
		url = 'https://api.ecobee.com/1/thermostat'
		response = http.client.post(url, headers=headers, data=data)	
		print response.getText()
	else:
		pass
		# Add script to write to status tag with error message
##########################################################################################################################################

def resumeProgram(thermostatName):
	import system
	raw = system.tag.readBlocking('[default]Ecobee/Access Token')
	accessToken = raw[0].value
	data = {
		  "selection": {
			"selectionType":"registered",
			"selectionMatch":thermostatName
		  },
		  "functions": [
			{
			  "type":"resumeProgram",
			  "params":{
				"resumeAll":"false"
			  }      
			}
		  ]
		}
	headers = {"Content-Type":"text/json","Authorization":"Bearer %s" % accessToken}
	params = {"format":"json"}
	url = 'https://api.ecobee.com/1/thermostat'
	response = http.client.post(url, params=params, headers=headers, data=data)	
	
##########################################################################################################################################

def getEvents():
	import system
	raw = system.tag.readBlocking('[default]Ecobee/Access Token')
	accessToken = raw[0].value
	headers = {'Content-Type': 'text/json','Authorization':'Bearer %s' % accessToken} 
	params = {"format":"json","body":{"selection":{"selectionType":"registered","selectionMatch":"","includeEvents":"true"}}} #"includeAlerts":true
	
	url = 'https://api.ecobee.com/1/thermostat'
	response = http.client.get(url=url, params=params, headers=headers)
	print response.getText()

I know this is an old thread but I've encountered some difficulties with this.

  1. It is implied that the auth code remains the same. I have not found that to be the case and I find that I have to request access or I get IOError: Server returned HTTP response code: 400 for URL: https://www.googleapis.com/oauth2/v4/token unless I go through the access request process again. Even when I am trying to refresh the token.

  2. I kept getting an error in the getToken script as the response could not be parsed until I stripped all of the newline,tabs and whitespaces.
    I will continue to poke around at this but am curious if I am doing something wrong.
    I modified the getToken and refreshToken Script to use the system.net.getClient() and that works really good..

I did get this to work. The only problem is the refresh token expires every 7 days so I'm working on that now.

Interesting. I wonder if that refresh token expiration timeframe is a recent change? Admittedly, I haven't touched this code in probably a year and a half and I haven't been checking if it works either. I find that Home Assistant works well enough for the majority of my home automation needs that I haven't bothered doing much with Ignition other than using it to log aquarium water parameters into a database whenever I test my tank's water.

I've read it happening to other people. There is a way to publish the project that eliminates it, but I'm not worried about that now. I've actually have setup up data flow and am pretty happy right now. I also am going to attempt to pull live feeds on our camera's.

When are you implementing the Ecobee integration with the Kyvis Labs API Client Module?

Wow, very interesting. I'll definitely have to check it out.

I totally get your struggle with Google’s OAuth2 process—it’s no joke! I ran into the same roadblocks while trying to connect my devices through Google Home, and honestly, it felt like they assumed everyone had a commercial-level setup.