Skip to content

Commit

Permalink
#4 #5 for 2.0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
jimboca committed Dec 9, 2018
1 parent 5d993e1 commit 15e7745
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 43 deletions.
33 changes: 26 additions & 7 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,36 @@ Install through the NodeServer Store

#### Requirements

On first start up you will be presented a PIN.
On first start up you will be given a PIN.

Login to the Ecobee web page, click on your profile, then
click 'My Apps' > 'Add Application'.
Login to the Ecobee web page, click on your profile, then
click 'My Apps' > 'Add Application'.

You will be prompted to enter the PIN provided. Once you approve
the integration, restart the nodeserver within 10 minutes of
being given the PIN.
You will be prompted to enter the PIN provided.

The nodeserver will check every 60 seconds that you have completed the approval
so do not restart the nodeserver. You can monitor the log to see when the
approval is recognized.

Your thermostat will be added to ISY, along with nodes for any sensors,
a node for the current weather, and a node for the forecast.

After the first run. It will refresh any changes every 3 minutes. This is
After the first run. It will refresh any changes every 3 minutes. This is
a limitation imposed by Ecobee.

# Upgrading

1. Open the Polyglot web page
1. Go to nodeserver store and click "Update" for "Ecobee".
1. Go to the dashboard, select Details for the Ecobee Nodeserver
1. Click Restart
1. If the release has a (Profile Change) then the profile will be updated automatically but if you had the Admin Console open, you will need to close and open it again.

# Release Notes

- 2.0.6: JimBoCA
- [Fix lookup for setting Mode](https://github.com/Einstein42/udi-ecobee-poly/issues/4)
- [Fix crash when changing schedule mode](https://github.com/Einstein42/udi-ecobee-poly/issues/5)
- Fix "Climate Type" initialization when there is a manual change
- Automatically upload new profile when it is out of date.
- Change current temp for F to include one signficant digit, since that's what is sent.
33 changes: 31 additions & 2 deletions ecobee-poly.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def start(self):
#self.removeNoticesAll()
LOGGER.info('Started Ecobee v2 NodeServer')
LOGGER.debug(self.polyConfig['customData'])
self.check_profile()
if 'tokenData' in self.polyConfig['customData']:
self.tokenData = self.polyConfig['customData']['tokenData']
self.auth_token = self.tokenData['access_token']
Expand Down Expand Up @@ -105,7 +106,7 @@ def _getRefresh(self):
if 'error' in data:
LOGGER.error('{} :: {}'.format(data['error'], data['error_description']))
self.auth_token = None
self.refreshingTokens = False
self.refreshingTokens = False
return False
elif 'access_token' in data:
self._saveTokens(data)
Expand Down Expand Up @@ -226,6 +227,7 @@ def discover(self, *args, **kwargs):
tstat = fullData['thermostatList'][0]
useCelsius = True if tstat['settings']['useCelsius'] else False
self.addNode(Thermostat(self, address, address, 'Ecobee - {}'.format(thermostat['name']), thermostat, fullData, useCelsius))
# TODO: Adding remoteSensors and weather should be done inside thermostat so we know it was created since it's the parent
time.sleep(3)
if 'remoteSensors' in tstat:
for sensor in tstat['remoteSensors']:
Expand All @@ -245,6 +247,33 @@ def discover(self, *args, **kwargs):
self.discovery = False
return True

def get_profile_info(self):
pvf = 'profile/version.txt'
try:
with open(pvf) as f:
pv = f.read().replace('\n', '')
f.close()
except Exception as err:
LOGGER.error('get_profile_info: failed to read file {0}: {1}'.format(pvf,err), exc_info=True)
pv = 0
return { 'version': pv }

def check_profile(self):
self.profile_info = self.get_profile_info()
# Set Default profile version if not Found
cdata = deepcopy(self.polyConfig['customData'])
LOGGER.info('check_profile: profile_info={0} customData={1}'.format(self.profile_info,cdata))
if not 'profile_info' in cdata:
cdata['profile_info'] = { 'version': 0 }
if self.profile_info['version'] == cdata['profile_info']['version']:
self.update_profile = False
else:
self.update_profile = True
self.poly.installprofile()
LOGGER.info('check_profile: update_profile={}'.format(self.update_profile))
cdata['profile_info'] = self.profile_info
self.saveCustomData(cdata)

def getThermostats(self):
if not self._checkTokens():
LOGGER.debug('getThermostat failed. Couldn\'t get tokens.')
Expand Down Expand Up @@ -367,4 +396,4 @@ def ecobeePost(self, thermostatId, postData = {}):
control = Controller(polyglot)
control.runForever()
except (KeyboardInterrupt, SystemExit):
sys.exit(0)
sys.exit(0)
90 changes: 64 additions & 26 deletions node_types.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
except ImportError:
import pgc_interface as polyinterface
from copy import deepcopy
# For debugging only
#import json

LOGGER = polyinterface.LOGGER

Expand Down Expand Up @@ -152,7 +154,6 @@ def toF(tempC):

class Thermostat(polyinterface.Node):
def __init__(self, controller, primary, address, name, revData, fullData, useCelsius):
super().__init__(controller, primary, address, name)
self.controller = controller
self.name = name
self.tstat = fullData['thermostatList'][0]
Expand All @@ -164,35 +165,52 @@ def __init__(self, controller, primary, address, name, revData, fullData, useCel
self.drivers = self._convertDrivers(driversMap[self.id]) if self.controller._cloud else deepcopy(driversMap[self.id])
self.revData = revData
self.fullData = fullData

super(Thermostat, self).__init__(controller, primary, address, name)

def start(self):
self.update(self.revData, self.fullData)

def update(self, revData, fullData):
self.revData = revData
self.fullData = fullData
#LOGGER.debug("fullData={}".format(json.dumps(fullData, sort_keys=True, indent=2)))
#LOGGER.debug("revData={}".format(json.dumps(revData, sort_keys=True, indent=2)))
self.tstat = fullData['thermostatList'][0]
self.program = self.tstat['program']
events = self.tstat['events']
equipmentStatus = self.tstat['equipmentStatus'].split(',')
self.settings = self.tstat['settings']
#LOGGER.debug("update: settings={}".format(self.settings))
runtime = self.tstat['runtime']
clihcs = 0
for status in equipmentStatus:
if status in equipmentStatusMap:
clihcs = equipmentStatusMap[status]
break
# This seems to be what the schedule says should be enabled.
climateType = self.program['currentClimateRef']
# And the default mode, unless there is an event
clismd = 0
if len(events) > 0 and events[0]['type'] == 'hold' and events[0]['running']:
clismd = 1 if self.settings['holdAction'] == 'nextPeriod' else 2
climateType = events[0]['holdClimateRef']
LOGGER.debug("clismd={} climateType={}".format(clismd,climateType))
tempCurrent = runtime['actualTemperature'] / 10 if runtime['actualTemperature'] != 0 else 0
tempHeat = runtime['desiredHeat'] / 10
tempCool = runtime['desiredCool'] / 10
if (self.useCelsius):
tempCurrent = toC(tempCurrent)
tempHeat = toC(tempHeat)
tempCool = toC(tempCool)

else:
# F set points must be integer
tempHeat = int(tempHeat)
tempCool = int(tempCool)

#LOGGER.debug("program['climates']={}".format(self.program['climates']))
#LOGGER.debug("settings={}".format(json.dumps(self.settings, sort_keys=True, indent=2)))
#LOGGER.debug("program={}".format(json.dumps(self.program, sort_keys=True, indent=2)))

updates = {
'ST': tempCurrent,
'CLISPH': tempHeat,
Expand All @@ -204,7 +222,11 @@ def update(self, revData, fullData):
'GV1': runtime['desiredHumidity'],
'CLISMD': clismd,
'GV4': self.settings['fanMinOnTime'],
'GV3': climateMap[self.program['currentClimateRef']],
# This assumes our climate is in the last hash in the array
# thought it would work, but still has issues...
#'GV3': climateMap[self.program['climates'][-1]['climateRef']],
#'GV3': climateMap[self.program['climates'][-1]['climateRef']],
'GV3': climateMap[climateType],
'GV5': runtime['desiredDehumidity'],
'GV6': 1 if self.settings['autoAway'] else 0,
'GV7': 1 if self.settings['followMeComfort'] else 0
Expand All @@ -224,7 +246,7 @@ def update(self, revData, fullData):
node.update(weather)

def query(self, command=None):
self.reportDrivers()
self.reportDrivers()

def cmdSetPoint(self, cmd):
if cmd['cmd'] == 'CLISPH':
Expand All @@ -243,52 +265,68 @@ def cmdSetPoint(self, cmd):
climate[cmdtype] = int(cmd['value']) * 10
if self.controller.ecobeePost(self.address, {'thermostat': {'program': currentProgram}}):
self.setDriver(driver, cmd['value'])
LOGGER.debug("getDriver({})={}".format(driver,self.getDriver(driver)))

def getMapName(self,map,val):
for name in map:
if map[name] == val:
return name

def cmdSetMode(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
LOGGER.info('Setting Thermostat {} to mode: {}'.format(self.name, [*modeMap][int(cmd['value'])]))
if self.controller.ecobeePost(self.address, {'thermostat': {'settings': {'hvacMode': [*modeMap][int(cmd['value'])]}}}):
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {} already set to {}".format(cmd['cmd'],int(cmd['value'])))
else:
name = self.getMapName(modeMap,int(cmd['value']))
LOGGER.info('Setting Thermostat {} to mode: {} (value={})'.format(self.name, name, cmd['value']))
if self.controller.ecobeePost(self.address, {'thermostat': {'settings': {'hvacMode': name}}}):
self.setDriver(cmd['cmd'], cmd['value'])


def cmdSetScheduleMode(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {} already set to {}".format(cmd['cmd'],int(cmd['value'])))
else:
func = {}
if cmd['value'] == '0':
if int(cmd['value']) == 0:
func['type'] = 'resumeProgram'
func['params'] = {
'resumeAll': False
}
else:
func['type'] = 'setHold'
heatHoldTemp = int(self.getDriver('CLISPH'))
coolHoldTemp = int(self.getDriver('CLISPH'))
coolHoldTemp = int(self.getDriver('CLISPC'))
if self.useCelsius:
headHoldTemp = toF(heatHoldTemp)
coolHoldTemp = toF(coolHoldTemp)
func['params'] = {
'holdType': 'nextTransition' if cmd['value'] == "1" else 'indefinite',
'holdType': 'nextTransition' if int(cmd['value']) == 1 else 'indefinite',
'heatHoldTemp': heatHoldTemp * 10,
'coolHoldTemp': coolHoldTemp * 10
}
if self.controller.ecobeePost(self.address, {'functions': [func]}):
self.setDriver('CLISMD', cmd['value'])
self.setDriver('CLISMD', int(cmd['value']))

def cmdSetClimate(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {}={} already set to {}".format(cmd['cmd'],int(self.getDriver(cmd['cmd'])),int(cmd['value'])))
else:
command = {
'functions': [{
'type': 'setHold',
'params': {
'holdType': 'indefinite',
'holdClimateRef': [*climateMap][int(cmd['value'])]
'holdClimateRef': self.getMapName(climateMap,int(cmd['value']))
}
}]
}
if self.controller.ecobeePost(self.address, command):
self.setDriver(cmd['cmd'], cmd['value'])

def cmdSetFanOnTime(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {} already set to {}".format(cmd['cmd'],int(cmd['value'])))
else:
command = {
'thermostat': {
'settings': {
Expand All @@ -300,7 +338,9 @@ def cmdSetFanOnTime(self, cmd):
self.setDriver(cmd['cmd'], cmd['value'])

def cmdSmartHome(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {} already set to {}".format(cmd['cmd'],int(cmd['value'])))
else:
command = {
'thermostat': {
'settings': {
Expand All @@ -312,7 +352,9 @@ def cmdSmartHome(self, cmd):
self.setDriver(cmd['cmd'], cmd['value'])

def cmdFollowMe(self, cmd):
if self.getDriver(cmd['cmd']) != cmd['value']:
if int(self.getDriver(cmd['cmd'])) == int(cmd['value']):
LOGGER.debug("cmdSetClimate: {} already set to {}".format(cmd['cmd'],int(cmd['value'])))
else:
command = {
'thermostat': {
'settings': {
Expand Down Expand Up @@ -349,10 +391,6 @@ def setPoint(self, cmd):
if self.controller.ecobeePost(self.address, {'thermostat': {'program': currentProgram}}):
self.setDriver(driver, newTemp)

def getDriver(self, driver):
if driver in self.drivers:
return self.drivers[driver]['value']

commands = { 'QUERY': query,
'CLISPH': cmdSetPoint,
'CLISPC': cmdSetPoint,
Expand All @@ -374,7 +412,7 @@ def __init__(self, controller, primary, address, name, useCelsius):
self.useCelsius = useCelsius
self.id = 'EcobeeSensorC' if self.useCelsius else 'EcobeeSensorF'
self.drivers = self._convertDrivers(driversMap[self.id]) if self.controller._cloud else deepcopy(driversMap[self.id])

def start(self):
pass

Expand Down Expand Up @@ -405,7 +443,7 @@ def __init__(self, controller, primary, address, name, useCelsius, forecast):
self.useCelsius = useCelsius
self.id = 'EcobeeWeatherC' if self.useCelsius else 'EcobeeWeatherF'
self.drivers = self._convertDrivers(driversMap[self.id]) if self.controller._cloud else deepcopy(driversMap[self.id])

def start(self):
pass

Expand Down Expand Up @@ -438,8 +476,8 @@ def update(self, weather):
}
for key, value in updates.items():
self.setDriver(key, value)

def query(self, command=None):
self.reportDrivers()

commands = {'QUERY': query, 'STATUS': query}
commands = {'QUERY': query, 'STATUS': query}
2 changes: 1 addition & 1 deletion profile/editor/editors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<range uom="4" min="-50" max="75" step="0.5" prec="1" />
</editor>
<editor id="I_TEMP_F">
<range uom="17" min="-50" max="150" step="1" prec="0" />
<range uom="17" min="-50" max="150" step="0.5" prec="1" />
</editor>
<editor id="I_SETTEMP_C">
<range uom="4" min="0" max="10" step="0.5" prec="1" />
Expand Down
2 changes: 1 addition & 1 deletion profile/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.1
2.0.6
2 changes: 1 addition & 1 deletion server.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{
"title": "ecobee: Polyglot NodeServer for Ecobee",
"author": "James Milne (Einstein.42)",
"version": "2.0.5",
"version": "2.0.6",
"date": "July 25, 2018",
"source": "https://github.com/Einstein42/udi-ecobee-poly",
"license": "https://github.com/Einstein42/udi-ecobee-poly/master/LICENSE"
Expand Down
5 changes: 0 additions & 5 deletions zipprofile

This file was deleted.

0 comments on commit 15e7745

Please sign in to comment.