diff --git a/climate/heatzy/__init__.py b/climate/heatzy/__init__.py index 0d1f33e..ff9ac1a 100644 --- a/climate/heatzy/__init__.py +++ b/climate/heatzy/__init__.py @@ -1,34 +1,26 @@ import logging -import async_timeout -from homeassistant.components.climate import (STATE_COOL, STATE_ECO, - STATE_HEAT, SUPPORT_AWAY_MODE, - SUPPORT_ON_OFF, - SUPPORT_OPERATION_MODE, - ClimateDevice) from homeassistant.config_entries import SOURCE_IMPORT -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import aiohttp_client, storage from .api import HeatzyAPI from .authenticator import HeatzyAuthenticator -from .const import STORAGE_KEY, STORAGE_VERSION +from .const import (HEATZY_PILOTE_V1_PRODUCT_KEY, HEATZY_PILOTE_V2_PRODUCT_KEY, + STORAGE_KEY, STORAGE_VERSION) +from .pilote_v1 import HeatzyPiloteV1Thermostat +from .pilote_v2 import HeatzyPiloteV2Thermostat + +PRODUCT_KEY_TO_DEVICE_IMPLEMENTATION = { + HEATZY_PILOTE_V1_PRODUCT_KEY: HeatzyPiloteV1Thermostat, + HEATZY_PILOTE_V2_PRODUCT_KEY: HeatzyPiloteV2Thermostat, +} _LOGGER = logging.getLogger(__name__) -HA_TO_HEATZY_STATE = { - STATE_HEAT: 'cft', - STATE_ECO: 'eco', - STATE_COOL: 'fro' -} -HEATZY_TO_HA_STATE = { - 'cft': STATE_HEAT, - 'eco': STATE_ECO, - 'fro': STATE_COOL, -} - async def async_setup_platform(hass, config, add_devices, discovery_info=None): + """Configure Heatzy API using Home Assistant configuration and fetch all Heatzy devices.""" # retrieve platform config username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -41,100 +33,22 @@ async def async_setup_platform(hass, config, add_devices, discovery_info=None): # fetch configured Heatzy devices devices = await api.async_get_devices() - # add all Heatzy devices to home assistant - add_devices(HeatzyPilotThermostat(api, device) for device in devices) + # add all Heatzy devices with HA implementation to home assistant + add_devices(filter(None.__ne__, map(setup_heatzy_device(api), devices))) return True -class HeatzyPilotThermostat(ClimateDevice): - def __init__(self, api, device): - self._api = api - self._device = device - - @property - def temperature_unit(self): - """Return the unit of measurement which this thermostat uses.""" - return TEMP_CELSIUS - - @property - def operation_list(self): - """Return the list of available operation modes.""" - return { - STATE_HEAT, - STATE_ECO, - STATE_COOL - } - - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE | SUPPORT_ON_OFF - - @property - def unique_id(self): - """Return a unique ID.""" - return self._device.get('did') - - @property - def name(self): - return self._device.get('dev_alias') - - @property - def current_operation(self): - """Return current operation ie. heat, cool, idle.""" - return HEATZY_TO_HA_STATE.get(self._device.get('attr').get('mode')) - - @property - def is_away_mode_on(self): - """Return true if away mode is on.""" - return self._device.get('attr').get('derog_mode') == 1 - - @property - def is_on(self): - """Return true if on.""" - return self._device.get('attr').get('mode') != 'stop' - - async def set_operation_mode(self, operation_mode): - """Set new target operation mode.""" - _LOGGER.debug("Setting operation mode '%s' for device '%s'...", - operation_mode, self.unique_id) - await self._api.async_set_mode(self.unique_id, HA_TO_HEATZY_STATE.get(operation_mode)) - await self.async_update() - _LOGGER.info("Operation mode set to '%s' for device '%s'", - operation_mode, self.unique_id) - - async def async_turn_on(self): - """Turn device on.""" - _LOGGER.debug("Turning device '%s' on...", self.unique_id) - await self._api.async_turn_on(self.unique_id) - await self.async_update() - _LOGGER.info("Device '%s' turned on (%s)", self.unique_id) - - async def async_turn_off(self): - """Turn device off.""" - _LOGGER.debug("Turning device '%s' off...", self.unique_id) - await self._api.async_turn_off(self.unique_id) - await self.async_update() - _LOGGER.info("Device '%s' turned off (%s)", self.unique_id) - - async def async_turn_away_mode_on(self): - """Turn away mode on.""" - _LOGGER.debug("Turning device '%s' away mode on...", self.unique_id) - await self._api.async_turn_away_mode_on(self.unique_id) - await self.async_update() - _LOGGER.info("Device '%s' away mode turned on", self.unique_id) - - async def turn_away_mode_off(self): - """Turn away mode off.""" - _LOGGER.debug("Turning device '%s' away mode off...", self.unique_id) - await self._api.async_turn_away_mode_off(self.unique_id) - await self.async_update() - _LOGGER.info("Device '%s' away mode turned off", self.unique_id) - - async def async_update(self): - """Retrieve latest state.""" - _LOGGER.debug("Updating device '%s'... Current state is: %s", - self.unique_id, {'is_on': self.is_on, 'is_away_mode_on': self.is_away_mode_on, 'current_operation': self.current_operation}) - self._device = await self._api.async_get_device(self.unique_id) - _LOGGER.debug("Device '%s' updated. New state is: %s", - self.unique_id, {'is_on': self.is_on, 'is_away_mode_on': self.is_away_mode_on, 'current_operation': self.current_operation}) +def setup_heatzy_device(api): + def find_heatzy_device_implementation(device): + """Find Home Assistant implementation for the Heatzy device. + Implementation search is based on device 'product_key'. + If the implementation is not found, returns None. + """ + DeviceImplementation = PRODUCT_KEY_TO_DEVICE_IMPLEMENTATION.get( + device.get('product_key')) + if DeviceImplementation is None: + _LOGGER.warn('Device %s with product key %s is not supported', + device.get('did'), device.get('product_key')) + return None + return DeviceImplementation(api, device) + return find_heatzy_device_implementation diff --git a/climate/heatzy/api.py b/climate/heatzy/api.py index a45ba9c..c532cc4 100644 --- a/climate/heatzy/api.py +++ b/climate/heatzy/api.py @@ -56,43 +56,11 @@ async def _async_get_device_data(self, device_id): device_data = await response.json() return device_data - async def _async_control_device(self, device_id, payload): + async def async_control_device(self, device_id, payload): """Control state of device with given id""" token = await self._async_get_token() headers = { 'X-Gizwits-Application-Id': HEATZY_APPLICATION_ID, 'X-Gizwits-User-Token': token } - response = await self._session.post(HEATZY_API_URL + '/control/' + device_id, json=payload, headers=headers) - - async def async_set_mode(self, device_id, mode): - """Change device mode. Mode can be 'cft', 'eco', 'fro' or 'stop'.""" - return await self._async_control_device(device_id, { - 'attrs': { - 'mode': mode - } - }) - - async def async_turn_on(self, device_id): - """Turn device on""" - return await self.async_set_mode(device_id, 'cft') - - async def async_turn_off(self, device_id): - """Turn device off""" - return await self.async_set_mode(device_id, 'stop') - - async def _async_set_derog_mode(self, device_id, derog_mode): - """Set device 'derog_mode' (away_mode)""" - return await self._async_control_device(device_id, { - 'attrs': { - 'derog_mode': derog_mode - } - }) - - async def async_turn_away_mode_on(self, device_id): - """Turn device away mode on""" - return await self._async_set_derog_mode(device_id, 1) - - async def async_turn_away_mode_off(self, device_id): - """Turn device away mode off""" - return await self._async_set_derog_mode(device_id, 0) + await self._session.post(HEATZY_API_URL + '/control/' + device_id, json=payload, headers=headers) diff --git a/climate/heatzy/authenticator.py b/climate/heatzy/authenticator.py index 4d0f058..364a560 100644 --- a/climate/heatzy/authenticator.py +++ b/climate/heatzy/authenticator.py @@ -32,7 +32,7 @@ async def async_authenticate(self): if response.status != 200: _LOGGER.error("Heatzy API returned HTTP status %d, response %s", - response.status, result) + response.status, response) return None authentication = await response.json() diff --git a/climate/heatzy/const.py b/climate/heatzy/const.py index e10773d..229d23c 100644 --- a/climate/heatzy/const.py +++ b/climate/heatzy/const.py @@ -2,3 +2,5 @@ STORAGE_KEY = 'heatzy_auth' HEATZY_APPLICATION_ID = 'c70a66ff039d41b4a220e198b0fcc8b3' HEATZY_API_URL = 'https://euapi.gizwits.com/app' +HEATZY_PILOTE_V1_PRODUCT_KEY = '9420ae048da545c88fc6274d204dd25f' +HEATZY_PILOTE_V2_PRODUCT_KEY = '51d16c22a5f74280bc3cfe9ebcdc6402' diff --git a/climate/heatzy/pilote_v1.py b/climate/heatzy/pilote_v1.py new file mode 100644 index 0000000..3d80be1 --- /dev/null +++ b/climate/heatzy/pilote_v1.py @@ -0,0 +1,82 @@ +from homeassistant.components.climate import (STATE_COOL, STATE_ECO, + STATE_HEAT, SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, + ClimateDevice) +from homeassistant.const import STATE_OFF, TEMP_CELSIUS + +HEATZY_TO_HA_STATE = { + '\u8212\u9002': STATE_HEAT, + '\u7ecf\u6d4e': STATE_ECO, + '\u89e3\u51bb': STATE_COOL, + '\u505c\u6b62': STATE_OFF, +} + +HA_TO_HEATZY_STATE = { + STATE_HEAT: [1, 1, 0], + STATE_ECO: [1, 1, 1], + STATE_COOL: [1, 1, 2], + STATE_OFF: [1, 1, 3], +} + + +class HeatzyPiloteV1Thermostat(ClimateDevice): + def __init__(self, api, device): + self._api = api + self._device = device + + @property + def temperature_unit(self): + """Return the unit of measurement which this thermostat uses.""" + return TEMP_CELSIUS + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return { + STATE_HEAT, + STATE_ECO, + STATE_COOL + } + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF + + @property + def unique_id(self): + """Return a unique ID.""" + return self._device.get('did') + + @property + def name(self): + return self._device.get('dev_alias') + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return HEATZY_TO_HA_STATE.get(self._device.get('attr').get('mode')) + + @property + def is_on(self): + """Return true if on.""" + return self.current_operation != STATE_OFF + + async def async_set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + await self._api.async_control_device(self.unique_id, { + 'raw': HA_TO_HEATZY_STATE.get(operation_mode), + }) + await self.async_update() + + async def async_turn_on(self): + """Turn device on.""" + await self._api.async_set_operation_mode(STATE_HEAT) + + async def async_turn_off(self): + """Turn device off.""" + await self._api.async_set_operation_mode(STATE_OFF) + + async def async_update(self): + """Retrieve latest state.""" + self._device = await self._api.async_get_device(self.unique_id) diff --git a/climate/heatzy/pilote_v2.py b/climate/heatzy/pilote_v2.py new file mode 100644 index 0000000..5f5c49f --- /dev/null +++ b/climate/heatzy/pilote_v2.py @@ -0,0 +1,109 @@ +from homeassistant.components.climate import (STATE_COOL, STATE_ECO, + STATE_HEAT, SUPPORT_AWAY_MODE, + SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, + ClimateDevice) +from homeassistant.const import STATE_OFF, TEMP_CELSIUS + +HEATZY_TO_HA_STATE = { + 'cft': STATE_HEAT, + 'eco': STATE_ECO, + 'fro': STATE_COOL, + 'stop': STATE_OFF, +} + +HA_TO_HEATZY_STATE = { + STATE_HEAT: 'cft', + STATE_ECO: 'eco', + STATE_COOL: 'fro', + STATE_OFF: 'stop', +} +AWAY_MODE_ON = 1 +AWAY_MODE_OFF = 0 + + +class HeatzyPiloteV2Thermostat(ClimateDevice): + def __init__(self, api, device): + self._api = api + self._device = device + + @property + def temperature_unit(self): + """Return the unit of measurement which this thermostat uses.""" + return TEMP_CELSIUS + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return { + STATE_HEAT, + STATE_ECO, + STATE_COOL + } + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE | SUPPORT_ON_OFF + + @property + def unique_id(self): + """Return a unique ID.""" + return self._device.get('did') + + @property + def name(self): + return self._device.get('dev_alias') + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return HEATZY_TO_HA_STATE.get(self._device.get('attr').get('mode')) + + @property + def is_away_mode_on(self): + """Return true if away mode is on.""" + return self._device.get('attr').get('derog_mode') == AWAY_MODE_ON + + @property + def is_on(self): + """Return true if on.""" + return self.current_operation != STATE_OFF + + async def async_set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + await self._api.async_control_device(self.unique_id, { + 'attrs': { + 'mode': HA_TO_HEATZY_STATE.get(operation_mode), + }, + }) + await self.async_update() + + async def async_turn_on(self): + """Turn device on.""" + await self.async_set_operation_mode(STATE_HEAT) + + async def async_turn_off(self): + """Turn device off.""" + await self.async_set_operation_mode(STATE_OFF) + + async def async_set_away_mode(self, away_mode): + """Set away mode.""" + await self._api.async_control_device(self.unique_id, { + 'attrs': { + 'derog_mode': away_mode, + }, + }) + await self.async_update() + + async def async_turn_away_mode_on(self): + """Turn away mode on.""" + await self.async_set_away_mode(AWAY_MODE_ON) + + async def async_turn_away_mode_off(self): + """Turn away mode off.""" + await self.async_set_away_mode(AWAY_MODE_OFF) + + async def async_update(self): + """Retrieve latest state.""" + self._device = await self._api.async_get_device(self.unique_id)