From 85e9a6aa8bcc9e3ddad29b406700eb9247e7104e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Le=C5=9Bniak?= Date: Sat, 21 May 2022 23:23:38 +0200 Subject: [PATCH 1/5] do not load invisible zones --- custom_components/tech/tech.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom_components/tech/tech.py b/custom_components/tech/tech.py index 2a22cfc..586c3fd 100644 --- a/custom_components/tech/tech.py +++ b/custom_components/tech/tech.py @@ -46,7 +46,7 @@ async def get(self, request_path): data = await response.json() _LOGGER.debug(data) return data - + async def post(self, request_path, post_data): url = self.base_url + request_path _LOGGER.debug("Sending POST request: " + url) @@ -58,7 +58,7 @@ async def post(self, request_path, post_data): data = await response.json() _LOGGER.debug(data) return data - + async def authenticate(self, username, password): path = "authentication" post_data = '{"username": "' + username + '", "password": "' + password + '"}' @@ -81,7 +81,7 @@ async def list_modules(self): else: raise TechError(401, "Unauthorized") return result - + async def get_module_data(self, module_udid): _LOGGER.debug("Getting module data..." + module_udid + ", " + self.user_id) if self.authenticated: @@ -90,7 +90,7 @@ async def get_module_data(self, module_udid): else: raise TechError(401, "Unauthorized") return result - + async def get_module_zones(self, module_udid): """Returns Tech module zones either from cache or it will update all the cached values for Tech module assuming @@ -107,15 +107,15 @@ async def get_module_zones(self, module_udid): now = time.time() _LOGGER.debug("Geting module zones: now: %s, last_update %s, interval: %s", now, self.last_update, self.update_interval) if self.last_update is None or now > self.last_update + self.update_interval: - _LOGGER.debug("Updating module zones cache..." + module_udid) + _LOGGER.debug("Updating module zones cache..." + module_udid) result = await self.get_module_data(module_udid) zones = result["zones"]["elements"] - zones = list(filter(lambda e: e['zone']['zoneState'] != "zoneUnregistered", zones)) + zones = list(filter(lambda e: e['zone']['visibility'], zones)) for zone in zones: self.zones[zone["zone"]["id"]] = zone self.last_update = now return self.zones - + async def get_zone(self, module_udid, zone_id): """Returns zone from Tech API cache. @@ -131,7 +131,7 @@ async def get_zone(self, module_udid, zone_id): async def set_const_temp(self, module_udid, zone_id, target_temp): """Sets constant temperature of the zone. - + Parameters: module_udid (string): The Tech module udid. zone_id (int): The Tech module zone ID. @@ -162,7 +162,7 @@ async def set_const_temp(self, module_udid, zone_id, target_temp): async def set_zone(self, module_udid, zone_id, on = True): """Turns the zone on or off. - + Parameters: module_udid (string): The Tech module udid. zone_id (int): The Tech module zone ID. From b1ad18db2d4ae99b5079db1e7672dcf4481ab9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Le=C5=9Bniak?= Date: Sat, 21 May 2022 23:25:08 +0200 Subject: [PATCH 2/5] refactored climate.py --- custom_components/tech/climate.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/custom_components/tech/climate.py b/custom_components/tech/climate.py index 3ce3c88..503bbc6 100644 --- a/custom_components/tech/climate.py +++ b/custom_components/tech/climate.py @@ -1,19 +1,14 @@ """Support for Tech HVAC system.""" import logging -import json from typing import List, Optional from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from .const import DOMAIN @@ -24,10 +19,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up entry.""" - _LOGGER.debug("Setting up entry, module udid: " + config_entry.data["udid"]) + _LOGGER.debug("Setting up climate entry, module udid: " + config_entry.data["udid"]) api = hass.data[DOMAIN][config_entry.entry_id] zones = await api.get_module_zones(config_entry.data["udid"]) - + async_add_entities( [ TechThermostat( @@ -79,7 +74,7 @@ def update_properties(self, device): def unique_id(self) -> str: """Return a unique ID.""" return self._id - + @property def name(self): """Return the name of the device.""" @@ -88,7 +83,7 @@ def name(self): @property def supported_features(self): """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE + return ClimateEntityFeature.TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE @property def hvac_mode(self): From 0bb9c5dcaeacb36e285199eb7e2f2a18ca0e0992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Le=C5=9Bniak?= Date: Sun, 22 May 2022 01:05:14 +0200 Subject: [PATCH 3/5] add battery and temperature sensors --- custom_components/tech/__init__.py | 6 +- custom_components/tech/sensor.py | 126 +++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 custom_components/tech/sensor.py diff --git a/custom_components/tech/__init__.py b/custom_components/tech/__init__.py index 0a0960c..ee2c779 100644 --- a/custom_components/tech/__init__.py +++ b/custom_components/tech/__init__.py @@ -13,7 +13,7 @@ # List the platforms that you want to support. # For your initial PR, limit it to 1 platform. -PLATFORMS = ["climate"] +PLATFORMS = ["climate", "sensor"] async def async_setup(hass: HomeAssistant, config: dict): @@ -29,12 +29,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) http_session = aiohttp_client.async_get_clientsession(hass) hass.data[DOMAIN][entry.entry_id] = Tech(http_session, entry.data["user_id"], entry.data["token"]) - + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) - + return True diff --git a/custom_components/tech/sensor.py b/custom_components/tech/sensor.py new file mode 100644 index 0000000..2c25ff9 --- /dev/null +++ b/custom_components/tech/sensor.py @@ -0,0 +1,126 @@ +"""Support for Tech HVAC system.""" +import itertools +import logging +from config.custom_components.tech.tech import Tech +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TEMP_CELSIUS, PERCENTAGE +from homeassistant.core import HomeAssistant +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities +) -> None: + """Set up entry.""" + _LOGGER.debug("Setting up sensor entry, module udid: %s", config_entry.data["udid"]) + api: Tech = hass.data[DOMAIN][config_entry.entry_id] + zones = await api.get_module_zones(config_entry.data["udid"]) + + battery_devices = map_to_battery_sensors(zones, api, config_entry) + temperature_sensors = map_to_temperature_sensors(zones, api, config_entry) + + async_add_entities( + itertools.chain(battery_devices, temperature_sensors), + True, + ) + +def map_to_battery_sensors(zones, api, config_entry): + for deviceIndex in zones: + _LOGGER.info("Device %s is battery opperated: %s", deviceIndex, zones[deviceIndex]['zone']['batteryLevel']) + devices = filter(lambda deviceIndex: is_battery_operating_device(zones[deviceIndex]), zones) + return list(map(lambda deviceIndex: TechBatterySensor(zones[deviceIndex], api, config_entry), devices)) + +def is_battery_operating_device(device) -> bool: + return device['zone']['batteryLevel'] is not None + +def map_to_temperature_sensors(zones, api, config_entry): + return list(map(lambda deviceIndex: TechTemperatureSensor(zones[deviceIndex], api, config_entry), zones)) + +class TechBatterySensor(SensorEntity): + """Representation of a Tech battery sensor.""" + + _attr_native_unit_of_measurement = PERCENTAGE + _attr_device_class = SensorDeviceClass.BATTERY + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, device, api, config_entry): + """Initialize the Tech battery sensor.""" + _LOGGER.debug("Init TechBatterySensor... ") + self._config_entry = config_entry + self._api = api + self._id = device["zone"]["id"] + self.update_properties(device) + + def update_properties(self, device): + self._name = device["description"]["name"] + self._attr_native_value = device["zone"]["batteryLevel"] + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return "{}_battery".format(self._id) + + @property + def name(self): + """Return the name of the device.""" + return "{} battery".format(self._name) + + async def async_update(self): + """Call by the Tech device callback to update state.""" + _LOGGER.debug( + "Updating Tech battery sensor: %s, udid: %s, id: %s", + self._name, + self._config_entry.data["udid"], + self._id, + ) + device = await self._api.get_zone(self._config_entry.data["udid"], self._id) + self.update_properties(device) + +class TechTemperatureSensor(SensorEntity): + """Representation of a Tech temperature sensor.""" + + _attr_native_unit_of_measurement = TEMP_CELSIUS + _attr_device_class = SensorDeviceClass.TEMPERATURE + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, device, api, config_entry): + """Initialize the Tech temperature sensor.""" + _LOGGER.debug("Init TechTemperatureSensor... ") + self._config_entry = config_entry + self._api = api + self._id = device["zone"]["id"] + self.update_properties(device) + + def update_properties(self, device): + self._name = device["description"]["name"] + if device["zone"]["currentTemperature"] is not None: + self._attr_native_value = device["zone"]["currentTemperature"] / 10 + else: + self._attr_native_value = None + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return "{}_temperature".format(self._id) + + @property + def name(self): + """Return the name of the device.""" + return "{} temperature".format(self._name) + + async def async_update(self): + """Call by the Tech device callback to update state.""" + _LOGGER.debug( + "Updating Tech temp. sensor: %s, udid: %s, id: %s", + self._name, + self._config_entry.data["udid"], + self._id, + ) + device = await self._api.get_zone(self._config_entry.data["udid"], self._id) + self.update_properties(device) + From 38d305693291539b8529ffd7e28ff7c9810213cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Le=C5=9Bniak?= Date: Sun, 22 May 2022 01:28:30 +0200 Subject: [PATCH 4/5] fixes --- custom_components/tech/climate.py | 4 ++-- custom_components/tech/sensor.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/custom_components/tech/climate.py b/custom_components/tech/climate.py index 503bbc6..2e18fb0 100644 --- a/custom_components/tech/climate.py +++ b/custom_components/tech/climate.py @@ -8,7 +8,7 @@ CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, - ClimateEntityFeature, + SUPPORT_TARGET_TEMPERATURE ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from .const import DOMAIN @@ -83,7 +83,7 @@ def name(self): @property def supported_features(self): """Return the list of supported features.""" - return ClimateEntityFeature.TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE + return SUPPORT_TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE @property def hvac_mode(self): diff --git a/custom_components/tech/sensor.py b/custom_components/tech/sensor.py index 2c25ff9..469657c 100644 --- a/custom_components/tech/sensor.py +++ b/custom_components/tech/sensor.py @@ -30,16 +30,14 @@ async def async_setup_entry( ) def map_to_battery_sensors(zones, api, config_entry): - for deviceIndex in zones: - _LOGGER.info("Device %s is battery opperated: %s", deviceIndex, zones[deviceIndex]['zone']['batteryLevel']) devices = filter(lambda deviceIndex: is_battery_operating_device(zones[deviceIndex]), zones) - return list(map(lambda deviceIndex: TechBatterySensor(zones[deviceIndex], api, config_entry), devices)) + return map(lambda deviceIndex: TechBatterySensor(zones[deviceIndex], api, config_entry), devices) def is_battery_operating_device(device) -> bool: return device['zone']['batteryLevel'] is not None def map_to_temperature_sensors(zones, api, config_entry): - return list(map(lambda deviceIndex: TechTemperatureSensor(zones[deviceIndex], api, config_entry), zones)) + return map(lambda deviceIndex: TechTemperatureSensor(zones[deviceIndex], api, config_entry), zones) class TechBatterySensor(SensorEntity): """Representation of a Tech battery sensor.""" From faafbe4aa7f9e40973ff8a75d5fa48dd996ad8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Le=C5=9Bniak?= Date: Sun, 22 May 2022 01:50:42 +0200 Subject: [PATCH 5/5] fixes --- custom_components/tech/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/tech/sensor.py b/custom_components/tech/sensor.py index 469657c..b43845d 100644 --- a/custom_components/tech/sensor.py +++ b/custom_components/tech/sensor.py @@ -1,7 +1,6 @@ """Support for Tech HVAC system.""" import itertools import logging -from config.custom_components.tech.tech import Tech from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, PERCENTAGE @@ -18,7 +17,7 @@ async def async_setup_entry( ) -> None: """Set up entry.""" _LOGGER.debug("Setting up sensor entry, module udid: %s", config_entry.data["udid"]) - api: Tech = hass.data[DOMAIN][config_entry.entry_id] + api = hass.data[DOMAIN][config_entry.entry_id] zones = await api.get_module_zones(config_entry.data["udid"]) battery_devices = map_to_battery_sensors(zones, api, config_entry)