From 79555a20b835604486bf1dbead597c0e9b9ecfd4 Mon Sep 17 00:00:00 2001 From: Kyle Wooten Date: Fri, 14 Jul 2017 20:28:25 -0400 Subject: [PATCH] Initial upload of Hydrogen. Still in development. --- .gitignore | 2 + README.md | 4 + __init__.py | 0 ecobee/__init__.py | 0 ecobee/data/ecobee_authentication.json | 3 + ecobee/data/ecobee_secret.json | 0 ecobee/data/ecobee_tokens.json | 0 ecobee/ecobee.py | 116 +++++++++++++++++++++++++ hydrogen/__init__.py | 0 hydrogen/data/__init__.py | 0 hydrogen/data/client_secret.json | 0 hydrogen/hydrogen.py | 73 ++++++++++++++++ main.py | 38 ++++++++ 13 files changed, 236 insertions(+) create mode 100644 README.md create mode 100644 __init__.py create mode 100644 ecobee/__init__.py create mode 100644 ecobee/data/ecobee_authentication.json create mode 100644 ecobee/data/ecobee_secret.json create mode 100644 ecobee/data/ecobee_tokens.json create mode 100644 ecobee/ecobee.py create mode 100644 hydrogen/__init__.py create mode 100644 hydrogen/data/__init__.py create mode 100644 hydrogen/data/client_secret.json create mode 100644 hydrogen/hydrogen.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore index 7bbc71c..b3d2a18 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,5 @@ ENV/ # mypy .mypy_cache/ + +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..1b5f1a9 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Hydrogen + +#### Description +Hydrogen is a python app that will control your ecobee thermostats to get around the CobbEMC peak hours. Thus saving you money! \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ecobee/__init__.py b/ecobee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ecobee/data/ecobee_authentication.json b/ecobee/data/ecobee_authentication.json new file mode 100644 index 0000000..b162790 --- /dev/null +++ b/ecobee/data/ecobee_authentication.json @@ -0,0 +1,3 @@ +{ + "apikey": "" +} \ No newline at end of file diff --git a/ecobee/data/ecobee_secret.json b/ecobee/data/ecobee_secret.json new file mode 100644 index 0000000..e69de29 diff --git a/ecobee/data/ecobee_tokens.json b/ecobee/data/ecobee_tokens.json new file mode 100644 index 0000000..e69de29 diff --git a/ecobee/ecobee.py b/ecobee/ecobee.py new file mode 100644 index 0000000..3a3a1c6 --- /dev/null +++ b/ecobee/ecobee.py @@ -0,0 +1,116 @@ +import json +import requests +import logging + +logging.basicConfig(filename='log/hydrogen.log', level=logging.DEBUG) + +with open('ecobee/data/ecobee_secret.json') as data_file: + data = json.load(data_file) + apikey = data['apikey'] + +with open('ecobee/data/ecobee_authentication.json') as data_file: + data = json.load(data_file) + authcode = data['code'] + + +# Only used to initialize the ecobee application on the developer console (myapp) console on ecobee.com +def get_ecobee_pin(): + url = "https://api.ecobee.com/authorize" + params = {'response_type': 'ecobeePin', 'client_id': apikey, 'scope': 'smartWrite'} + request = requests.get(url, params=params) + with open('ecobee/data/ecobee_authentication.json', 'w') as outfile: + json.dump(request.json(), outfile) + + +# Get initial tokens +def get_tokens(): + url = 'https://api.ecobee.com/token' + params = {'grant_type': 'ecobeePin', 'code': authcode, 'client_id': apikey} + request = requests.post(url, params=params) + if request.status_code == requests.codes.ok: + with open('ecobee/data/ecobee_tokens.json', 'w') as outfile: + json.dump(request.json(), outfile) + else: + print('Error while requesting tokens from ecobee.com.' + ' Status code: ' + str(request.status_code)) + + +# Refresh bad tokens +def refresh_tokens(refresh_token): + url = 'https://api.ecobee.com/token' + params = {'grant_type': 'refresh_token', + 'refresh_token': refresh_token, + 'client_id': apikey} + request = requests.post(url, params=params) + if request.status_code == requests.codes.ok: + with open('ecobee/data/ecobee_tokens.json', 'w') as outfile: + json.dump(request.json(), outfile) + else: + sys.exit(4) + + +# Get all data for all thermostats +def get_thermostats(): + with open('ecobee/data/ecobee_tokens.json') as data_file: + data = json.load(data_file) + accesstoken = data['access_token'] + refresh_token = data['refresh_token'] + + url = 'https://api.ecobee.com/1/thermostat' + header = {'Content-Type': 'application/json;charset=UTF-8', 'Authorization': 'Bearer ' + accesstoken} + params = {'json': ('{"selection":{"selectionType":"registered",' + '"includeRuntime":"true",' + '"includeSensors":"true",' + '"includeProgram":"true",' + '"includeEquipmentStatus":"true",' + '"includeEvents":"true",' + '"includeSettings":"true"}}')} + request = requests.get(url, headers=header, params=params) + if request.status_code == requests.codes.ok: + authenticated = True + thermostats = request.json()['thermostatList'] + return thermostats + else: + authenticated = False + if refresh_tokens(refresh_token): + return get_thermostats() + else: + return None + + +# Set your HVAC Mode +def set_hvac_mode(index, hvac_mode): + """ possible hvac modes are auto, auxHeatOnly, cool, heat, off + indexes for thermostats are 0 (downstairs) and 1 (upstairs) """ + with open('ecobee/data/ecobee_tokens.json') as data_file: + data = json.load(data_file) + accesstoken = data['access_token'] + thermostats = get_thermostats() + url = 'https://api.ecobee.com/1/thermostat' + header = {'Content-Type': 'application/json;charset=UTF-8', 'Authorization': 'Bearer ' + accesstoken} + params = {'format': 'json'} + body = ('{"selection":{"selectionType":"thermostats","selectionMatch":' + '"' + thermostats[index]['identifier'] + + '"},"thermostat":{"settings":{"hvacMode":"' + hvac_mode + + '"}}}') + request = requests.post(url, headers=header, params=params, data=body) + if request.status_code == requests.codes.ok: + logging.info("Setting Ecobee to " + hvac_mode + " hvac mode on Ecobee index " + str(index)) + return request + else: + logger.warn("Error connecting to Ecobee while attempting to set HVAC mode. Refreshing tokens...") + refresh_tokens() + + +def get_weather(index): + with open('ecobee/data/ecobee_tokens.json') as data_file: + data = json.load(data_file) + accesstoken = data['access_token'] + thermostats = get_thermostats() + url = 'https://api.ecobee.com/1/thermostat' + header = {'Content-Type': 'application/json;charset=UTF-8', 'Authorization': 'Bearer ' + accesstoken} + params = {'format': 'json'} + body = ('{"selection":{"selectionType":"thermostats","selectionMatch":' + '"' + thermostats[index]['identifier'] + + '"},"thermostat":{"weatherforecast":{"temperature":"}}}') + request = requests.post(url, headers=header, params=params, data=body) + print request.json() diff --git a/hydrogen/__init__.py b/hydrogen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hydrogen/data/__init__.py b/hydrogen/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hydrogen/data/client_secret.json b/hydrogen/data/client_secret.json new file mode 100644 index 0000000..e69de29 diff --git a/hydrogen/hydrogen.py b/hydrogen/hydrogen.py new file mode 100644 index 0000000..3e68ae1 --- /dev/null +++ b/hydrogen/hydrogen.py @@ -0,0 +1,73 @@ +from __future__ import print_function +import httplib2 +import os +import logging +import datetime + +from apiclient import discovery +from oauth2client import client +from oauth2client import tools +from oauth2client.file import Storage + +try: + import argparse + flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() +except ImportError: + flags = None + +logging.basicConfig(filename='log/hydrogen.log', level=logging.DEBUG) + +SCOPES = 'https://www.googleapis.com/auth/gmail.readonly' +CLIENT_SECRET_FILE = 'hydrogen/data/client_secret.json' +APPLICATION_NAME = 'Hydrogen' + + +def get_credentials(): + home_dir = os.path.expanduser('~') + credential_dir = os.path.join(home_dir, '.credentials') + if not os.path.exists(credential_dir): + os.makedirs(credential_dir) + credential_path = os.path.join(credential_dir, 'gmail-python-quickstart.json') + + store = Storage(credential_path) + credentials = store.get() + if not credentials or credentials.invalid: + flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) + flow.user_agent = APPLICATION_NAME + if flags: + credentials = tools.run_flow(flow, store, flags) + else: # Needed only for compatibility with Python 2.6 + credentials = tools.run(flow, store) + logging.info('Storing credentials to ' + credential_path) + return credentials + + +def check_gmail(): + valid='' + credentials = get_credentials() + http = credentials.authorize(httplib2.Http()) + service = discovery.build('gmail', 'v1', http=http) + today = str(datetime.date.today()) + yesterday = str(datetime.date.fromordinal(datetime.date.today().toordinal() - 1)) + todayhours = int(datetime.datetime.now().strftime("%H")) #Hour of the day in 24 hour format + + if todayhours not in range(14, 19): #Check if it is peak hours for CobbEMC + return 2 + + #Check Gmail messages with label 41(CobbEMC Peak Hours) + messages = service.users().messages().list(userId='me', labelIds='Label_41').execute().get('messages', []) + for message in messages: + tdata = service.users().messages().get(userId='me', id=message['id']).execute() + epochtime = str(tdata['internalDate']) + emaildate = str(datetime.datetime.fromtimestamp(float(epochtime.replace(' ', '')[:-3].upper())).strftime("%Y-%m-%d")) + if emaildate == yesterday: + valid = 1 + break + + if valid == 1: + logging.info("Ecobee's need to be turned off to avoid peak hours that CobbEMC has set for " + today + " from 2 P.M. EST to 7 P.M. EST") + return 1 + else: + return 0 + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..012e1b7 --- /dev/null +++ b/main.py @@ -0,0 +1,38 @@ +import sys +import logging +import requests +import datetime + + +from ecobee import ecobee +from hydrogen import hydrogen + +logging.basicConfig(filename='log/hydrogen.log', level=logging.DEBUG) +now = datetime.datetime.now() +logging.info("Hydrogen ran at " + str(now)) + +# Set your ecobee index list +thermostatlist = [0, 1] + + +def main(): + """ Result of 1 means that the ecobees need to be turned off to avoid peak hours """ + result = hydrogen.check_gmail() + if result == 1: + logging.info("Gmail check has come back with peak hours, setting ecobees to off hvac mode") + for i in thermostatlist: + requesthvacset = ecobee.set_hvac_mode(i, 'off') + if requesthvacset.status_code == requests.codes.ok: + logging.info("Successfully set thermostat index " + str(i) + " to off hvac mode") + elif result == 2: + logging.info("It's not peak hours right now, setting ecobees to cool") + for i in thermostatlist: + requesthvacset = ecobee.set_hvac_mode(i, 'cool') + if requesthvacset.status_code == requests.codes.ok: + logging.info("Successfully set thermostat index " + str(i) + " to cool hvac mode") + elif result == 0: + logging.info("No Peak Hours have been set by CobbEMC, we're good!") + sys.exit(0) + +if __name__ == '__main__': + main()