diff --git a/custom_components/badnest/api.py b/custom_components/badnest/api.py index 17602ac..2608721 100644 --- a/custom_components/badnest/api.py +++ b/custom_components/badnest/api.py @@ -1,4 +1,5 @@ import requests +import logging API_URL = "https://home.nest.com" CAMERA_WEBAPI_BASE = "https://webapi.camera.home.nest.com" @@ -8,14 +9,22 @@ USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) " \ "Chrome/75.0.3770.100 Safari/537.36" URL_JWT = "https://nestauthproxyservice-pa.googleapis.com/v1/issue_jwt" +_LOGGER = logging.getLogger(__name__) + class NestAPI: - def __init__(self, email, password, issue_token, cookie, api_key): + def __init__(self, + email, + password, + issue_token, + cookie, + api_key, + device_id=None): self._user_id = None self._access_token = None self._session = requests.Session() self._session.headers.update({"Referer": "https://home.nest.com/"}) - self._device_id = None + self._device_id = device_id if not email and not password: self._login_google(issue_token, cookie, api_key) else: @@ -57,14 +66,20 @@ class NestAPI: class NestThermostatAPI(NestAPI): - def __init__(self, email, password, issue_token, cookie, api_key): + def __init__(self, + email, + password, + issue_token, + cookie, + api_key, + device_id=None): super(NestThermostatAPI, self).__init__( email, password, issue_token, cookie, - api_key) - self._shared_id = None + api_key, + device_id) self._czfe_url = None self._compressor_lockout_enabled = None self._compressor_lockout_time = None @@ -85,6 +100,23 @@ class NestThermostatAPI(NestAPI): self.current_humidity = None self.update() + def get_devices(self): + r = self._session.post( + f"{API_URL}/api/0.1/user/{self._user_id}/app_launch", + json={ + "known_bucket_types": ["buckets"], + "known_bucket_versions": [], + }, + headers={"Authorization": f"Basic {self._access_token}"}, + ) + devices = [] + buckets = r.json()['updated_buckets'][0]['value']['buckets'] + for bucket in buckets: + if bucket.startswith('device.'): + devices.append(bucket.replace('device.', '')) + + return devices + def get_action(self): if self._hvac_ac_state: return "cooling" @@ -106,8 +138,7 @@ class NestThermostatAPI(NestAPI): self._czfe_url = r.json()["service_urls"]["urls"]["czfe_url"] for bucket in r.json()["updated_buckets"]: - if bucket["object_key"].startswith("shared."): - self._shared_id = bucket["object_key"] + if bucket["object_key"].startswith(f"shared.{self._device_id}"): thermostat_data = bucket["value"] self.current_temperature = \ thermostat_data["current_temperature"] @@ -128,8 +159,7 @@ class NestThermostatAPI(NestAPI): thermostat_data["target_temperature_low"] self.can_heat = thermostat_data["can_heat"] self.can_cool = thermostat_data["can_cool"] - elif bucket["object_key"].startswith("device."): - self._device_id = bucket["object_key"] + elif bucket["object_key"].startswith(f"device.{self._device_id}"): thermostat_data = bucket["value"] self._time_to_target = thermostat_data["time_to_target"] self._fan_timer_timeout = thermostat_data["fan_timer_timeout"] @@ -145,7 +175,7 @@ class NestThermostatAPI(NestAPI): json={ "objects": [ { - "object_key": self._shared_id, + "object_key": f'shared.{self._device_id}', "op": "MERGE", "value": {"target_temperature": temp}, } @@ -159,7 +189,7 @@ class NestThermostatAPI(NestAPI): json={ "objects": [ { - "object_key": self._shared_id, + "object_key": f'shared.{self._device_id}', "op": "MERGE", "value": { "target_temperature_low": temp, @@ -177,7 +207,7 @@ class NestThermostatAPI(NestAPI): json={ "objects": [ { - "object_key": self._shared_id, + "object_key": f'shared.{self._device_id}', "op": "MERGE", "value": {"target_temperature_type": mode}, } @@ -192,7 +222,7 @@ class NestThermostatAPI(NestAPI): json={ "objects": [ { - "object_key": self._device_id, + "object_key": f'device.{self._device_id}', "op": "MERGE", "value": {"fan_timer_timeout": date}, } @@ -207,7 +237,7 @@ class NestThermostatAPI(NestAPI): json={ "objects": [ { - "object_key": self._device_id, + "object_key": f'device.{self._device_id}', "op": "MERGE", "value": {"eco": {"mode": "manual-eco"}}, } diff --git a/custom_components/badnest/climate.py b/custom_components/badnest/climate.py index bca77db..01a5099 100644 --- a/custom_components/badnest/climate.py +++ b/custom_components/badnest/climate.py @@ -65,7 +65,7 @@ async def async_setup_platform(hass, async_add_entities, discovery_info=None): """Set up the Nest climate device.""" - nest = NestThermostatAPI( + api = NestThermostatAPI( hass.data[DOMAIN][CONF_EMAIL], hass.data[DOMAIN][CONF_PASSWORD], hass.data[DOMAIN][CONF_ISSUE_TOKEN], @@ -73,17 +73,31 @@ async def async_setup_platform(hass, hass.data[DOMAIN][CONF_APIKEY] ) - async_add_entities([NestClimate(nest)]) + thermostats = [] + _LOGGER.info("Adding thermostats") + for thermostat in api.get_devices(): + _LOGGER.info(f"Adding nest thermostat uuid: {thermostat}") + thermostats.append(NestClimate(thermostat, NestThermostatAPI( + hass.data[DOMAIN][CONF_EMAIL], + hass.data[DOMAIN][CONF_PASSWORD], + hass.data[DOMAIN][CONF_ISSUE_TOKEN], + hass.data[DOMAIN][CONF_COOKIE], + hass.data[DOMAIN][CONF_APIKEY], + thermostat + ))) + + async_add_entities(thermostats) class NestClimate(ClimateDevice): """Representation of a Nest climate device.""" - def __init__(self, api): + def __init__(self, device_id, api): """Initialize the thermostat.""" self._name = "Nest Thermostat" self._unit_of_measurement = TEMP_CELSIUS self._fan_modes = [FAN_ON, FAN_AUTO] + self.device_id = device_id # Set the default supported features self._support_flags = SUPPORT_TARGET_TEMPERATURE @@ -111,6 +125,11 @@ class NestClimate(ClimateDevice): if self.device.has_fan: self._support_flags = self._support_flags | SUPPORT_FAN_MODE + @property + def unique_id(self): + """Return an unique ID.""" + return self.device_id + @property def supported_features(self): """Return the list of supported features.""" @@ -124,7 +143,7 @@ class NestClimate(ClimateDevice): @property def name(self): """Return the name of the climate device.""" - return self._name + return self.device_id @property def temperature_unit(self): @@ -136,6 +155,11 @@ class NestClimate(ClimateDevice): """Return the current temperature.""" return self.device.current_temperature + @property + def current_humidity(self): + """Return the current humidity.""" + return self.device.current_humidity + @property def target_temperature(self): """Return the temperature we try to reach."""