from nuheat import NuHeat # Initalize an API session with your login credentials api = NuHeat("https://www.mythermostat.info/api", "<email>", "<pwd>") api.authenticate() thermostat = api.get_thermostat("<serial>") current = thermostat.celsius target = thermostat.target_celsius heating = thermostat.heating online = thermostat.online serial = thermostat.serial_number
class Controller(polyinterface.Controller): def __init__(self, polyglot): super(Controller, self).__init__(polyglot) self.name = 'NuHeat' # self.poly.onConfig(self.process_config) self.server_data = {} self.temperature_scale = None self.temp_uom = None self.NuHeat = None self.tz = None self.disco = 0 def start(self): if self.get_credentials(): self.check_params() if self.refresh_token(): self.discover() else: self.auth_prompt() def get_credentials(self): LOGGER.info('---- Environment: ' + self.poly.stage + ' ----') if self.poly.stage == 'test': if 'clientId' in self.poly.init['oauth']['test']: self.server_data['clientId'] = self.poly.init['oauth']['test']['clientId'] else: LOGGER.error('Unable to find Client ID in the init data') return False if 'secret' in self.poly.init['oauth']['test']: self.server_data['clientSecret'] = self.poly.init['oauth']['test']['secret'] else: LOGGER.error('Unable to find Client Secret in the init data') return False if 'redirectUrl' in self.poly.init['oauth']['test']: self.server_data['url'] = self.poly.init['oauth']['test']['redirectUrl'] else: LOGGER.error('Unable to find URL in the init data') return False if self.poly.init['worker']: self.server_data['worker'] = self.poly.init['worker'] else: return False return True elif self.poly.stage == 'prod': if 'clientId' in self.poly.init['oauth']['prod']: self.server_data['clientId'] = self.poly.init['oauth']['prod']['clientId'] else: LOGGER.error('Unable to find Client ID in the init data') return False if 'secret' in self.poly.init['oauth']['test']: self.server_data['clientSecret'] = self.poly.init['oauth']['prod']['secret'] else: LOGGER.error('Unable to find Client Secret in the init data') return False if 'redirectUrl' in self.poly.init['oauth']['test']: self.server_data['url'] = self.poly.init['oauth']['prod']['redirectUrl'] else: LOGGER.error('Unable to find URL in the init data') return False if self.poly.init['worker']: self.server_data['worker'] = self.poly.init['worker'] else: return False return True def auth_prompt(self): _auth_url = "https://identity.mynuheat.com/connect/authorize" _response_type = "code" _scope = "openapi openid profile offline_access" _user_auth_url = _auth_url + \ "?client_id=" + self.server_data['clientId'] + \ "&response_type=" + _response_type + \ "&scope=" + _scope + \ "&redirect_uri=" + self.server_data['url'] + \ "&state=" + self.server_data['worker'] self.addNotice( {'myNotice': 'Click <a href="' + _user_auth_url + '">here</a> to link your NuHeat account'}) def oauth(self, oauth): LOGGER.info('OAUTH Received: {}'.format(oauth)) if 'code' in oauth: if self.get_token(oauth['code']): self.remove_notices_all() self.discover() def get_token(self, code): _token_url = "https://identity.mynuheat.com/connect/token" _response_type = "token" _scope = "openapi openid profile offline_access" headers = {'Content-Type': 'application/x-www-form-urlencoded'} payload = {"grant_type": "authorization_code", "code": code, "client_id": self.server_data['clientId'], "response_type": _response_type, "client_secret": self.server_data['clientSecret'], "scope": _scope, "redirect_uri": self.server_data['url']} try: r = requests.post(_token_url, headers=headers, data=payload) if r.status_code == requests.codes.ok: try: resp = r.json() id_token = resp['id_token'] access_token = resp['access_token'] refresh_token = resp['refresh_token'] expires_in = resp['expires_in'] cust_data = {'id_token': id_token, 'access_token': access_token, 'refresh_token': refresh_token, 'expires_in': expires_in} self.saveCustomData(cust_data) self.NuHeat = NuHeat(access_token) self.remove_notices_all() return True except KeyError as ex: LOGGER.error("get_token Error: " + str(ex)) else: return False except requests.exceptions.RequestException as e: LOGGER.error("NuHeat.set_thermostat_setpoint Error: " + str(e)) def refresh_token(self): token_url = "https://identity.mynuheat.com/connect/token" if 'refresh_token' in self.polyConfig['customData']: refresh_token = self.polyConfig['customData']['refresh_token'] _response_type = "token" _scope = "openapi profile openid offline_access" headers = {'Content-Type': 'application/x-www-form-urlencoded'} payload = {"grant_type": "refresh_token", "client_id": self.server_data['clientId'], "response_type": _response_type, "client_secret": self.server_data['clientSecret'], "scope": _scope, "redirect_uri": self.server_data['url'], "refresh_token": refresh_token} try: r = requests.post(token_url, headers=headers, data=payload) if r.status_code == requests.codes.ok: try: resp = r.json() id_token = resp['id_token'] access_token = resp['access_token'] refresh_token = resp['refresh_token'] expires_in = resp['expires_in'] cust_data = {'id_token': id_token, 'access_token': access_token, 'refresh_token': refresh_token, 'expires_in': expires_in} self.saveCustomData(cust_data) self.NuHeat = NuHeat(access_token) return True except KeyError as ex: LOGGER.error("get_token Error: " + str(ex)) else: return False except requests.exceptions.RequestException as e: LOGGER.error("NuHeat.set_thermostat_setpoint Error: " + str(e)) else: return False def shortPoll(self): if self.disco == 1: for node in self.nodes: if self.nodes[node].address != self.address: self.nodes[node].start() def longPoll(self): """ The token expires every 1 hour (3600 seconds). The long Poll is set to 15 minutes by default and refreshes on Nodeserver start. :return: """ self.refresh_token() def query(self, command=None): self.check_params() for node in self.nodes: self.nodes[node].reportDrivers() def discover(self, *args, **kwargs): account_info = self.NuHeat.get_account() if account_info is not None: self.temperature_scale = account_info['temperatureScale'] if self.temperature_scale == "Fahrenheit": self.temp_uom = 17 else: self.temp_uom = 4 thermostats = self.NuHeat.get_thermostat() for stat in thermostats: name = stat['name'] stat_address = stat['serialNumber'] energy_log_day_address = "eld" + str(stat_address) energy_log_week_address = "elw" + str(stat_address) energy_log_year_address = "ely" + str(stat_address) if self.temp_uom == 17: self.addNode(ThermostatNode_F(self, stat_address, stat_address, name)) elif self.temp_uom == 4: self.addNode(ThermostatNode_C(self, stat_address, stat_address, name)) else: LOGGER.error("Invalid Temperature Measure (UOM) Not Fahrenheit or Celsius") time.sleep(2) self.addNode(EnergyLogDayNode(self, stat_address, energy_log_day_address, "Energy-Day")) time.sleep(2) self.addNode(EnergyLogWeekNode(self, stat_address, energy_log_week_address, "Energy-Week")) time.sleep(2) self.addNode(EnergyLogYearNode(self, stat_address, energy_log_year_address, "Energy-Year")) time.sleep(2) self.disco = 1 def delete(self): LOGGER.info('Removing Nuheat Nodeserver') def stop(self): LOGGER.debug('NodeServer stopped.') def process_config(self, config): # this seems to get called twice for every change, why? # What does config represent? LOGGER.info("process_config: Enter config={}".format(config)); LOGGER.info("process_config: Exit"); def check_params(self): self.removeNoticesAll() default_tz = "America/New_York" if 'tz' in self.polyConfig['customParams']: self.tz = self.polyConfig['customParams']['tz'] else: self.tz = default_tz LOGGER.info("Change the TimeZone to match your location and restart") self.addNotice("Change the TimeZone to match your location and restart") self.addCustomParam({'tz': default_tz}) def remove_notice_test(self, command): LOGGER.info('remove_notice_test: notices={}'.format(self.poly.config['notices'])) # Remove all existing notices self.removeNotice('test') def remove_notices_all(self): LOGGER.info('remove_notices_all: notices={}'.format(self.poly.config['notices'])) # Remove all existing notices self.removeNoticesAll() def update_profile(self, command): LOGGER.info('update_profile:') st = self.poly.installprofile() return st id = 'controller' commands = { 'QUERY': query, 'DISCOVER': discover, 'UPDATE_PROFILE': update_profile } drivers = [{'driver': 'ST', 'value': 1, 'uom': 2}]
def test_get_thermostat(self, _): api = NuHeat(None, None) serial_number = "serial-123" thermostat = api.get_thermostat(serial_number) self.assertTrue(isinstance(thermostat, NuHeatThermostat))
from nuheat import NuHeat # Initalize an API session with your login credentials api = NuHeat("AU", "email", "pwd") api.authenticate() thermostat = api.get_thermostat("device_id") current = thermostat.celsius target = thermostat.target_celsius heating = thermostat.heating online = thermostat.online serial = thermostat.serial_number
class ThermostatNode_F(polyinterface.Node): def __init__(self, controller, primary, address, name): super(ThermostatNode_F, self).__init__(controller, primary, address, name) self.access_token = None self.NuHeat = None self.temp_uom = controller.temp_uom def start(self): self.access_token = self.controller.polyConfig['customData'][ 'access_token'] self.NuHeat = NuHeat(self.access_token) thermostats = self.NuHeat.get_thermostat() if thermostats is not None: for stat in thermostats: if stat['serialNumber'] == self.address: if self.temp_uom == 17: clitemp = self.NuHeat.nuheat_celsius_to_fahrenheit( stat['currentTemperature']) clisph = self.NuHeat.nuheat_celsius_to_fahrenheit( stat['setPointTemp']) else: clitemp = self.NuHeat.nuheat_celsius_to_normal( stat['currentTemperature']) clisph = self.NuHeat.nuheat_celsius_to_normal( stat['setPointTemp']) climd = 0 if stat['operatingMode'] == 1: climd = 3 elif stat['operatingMode'] == 2: climd = 1 if stat['isHeating']: clihcs = 1 else: clihcs = 0 # self.setDriver('ST', clitemp, uom=self.temp_uom) # self.setDriver('CLISPH', clisph, uom=self.temp_uom) # self.setDriver('CLIMD', climd, uom=67) # self.setDriver('CLIHCS', clihcs, uom=66) self.setDriver('ST', clitemp) self.setDriver('CLISPH', clisph) self.setDriver('CLIMD', climd) self.setDriver('CLIHCS', clihcs) else: LOGGER.error("Thermostat Serial Number not available") else: LOGGER.error( "thermostat_node.Nuheat.get_thermostat: Returned None") def query(self, command=None): self.reportDrivers() def setpoint_heat(self, command): val = command['value'] if self.temp_uom == 17: new_setpoint = self.NuHeat.nuheat_fahrenheit_to_celsius_json(val) else: new_setpoint = self.NuHeat.nuheat_celsius_to_json(val) _status = self.NuHeat.set_thermostat_setpoint(self.address, new_setpoint) if _status is not None: self.setDriver('CLISPH', val) else: print("thermostat_node.setpoint_heat: " + str(_status)) # "Hints See: https://github.com/UniversalDevicesInc/hints" # hint = [1, 12, 1, 0] drivers = [{ 'driver': 'ST', 'value': 0, 'uom': 17 }, { 'driver': 'CLISPH', 'value': 0, 'uom': 17 }, { 'driver': 'CLIMD', 'value': 0, 'uom': 67 }, { 'driver': 'CLIHCS', 'value': 0, 'uom': 66 }] id = 'THERMOSTAT_F' commands = {'QUERY': query, 'CLISPH': setpoint_heat}