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:.

4 Likes

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

3 Likes

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()
3 Likes

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.

1 Like

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

2 Likes

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.