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/climate.py b/custom_components/tech/climate.py index 3ce3c88..2e18fb0 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, + SUPPORT_TARGET_TEMPERATURE ) 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.""" diff --git a/custom_components/tech/sensor.py b/custom_components/tech/sensor.py new file mode 100644 index 0000000..b43845d --- /dev/null +++ b/custom_components/tech/sensor.py @@ -0,0 +1,123 @@ +"""Support for Tech HVAC system.""" +import itertools +import logging +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 = 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): + devices = filter(lambda deviceIndex: is_battery_operating_device(zones[deviceIndex]), zones) + 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 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) + 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.