diff --git a/custom_components/danfoss_ally/__init__.py b/custom_components/danfoss_ally/__init__.py index b139189..7b8ad35 100644 --- a/custom_components/danfoss_ally/__init__.py +++ b/custom_components/danfoss_ally/__init__.py @@ -1,7 +1,7 @@ """Adds support for Danfoss Ally Gateway.""" import asyncio import logging -from datetime import timedelta +from datetime import datetime, timedelta import voluptuous as vol from homeassistant.components.climate.const import PRESET_AWAY, PRESET_HOME @@ -89,11 +89,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _LOGGER.error("Error authorizing") return False - await hass.async_add_executor_job(allyconnector.update) + async def _update(now): + """Periodic update.""" + await allyconnector.async_update() + await _update(None) update_track = async_track_time_interval( hass, - lambda now: allyconnector.update(), + _update, timedelta(seconds=SCAN_INTERVAL), ) @@ -148,6 +151,8 @@ def __init__(self, hass, key, secret): self._secret = secret self.ally = DanfossAlly() self._authorized = False + self._latest_write_time = datetime.min + self._latest_poll_time = datetime.min def setup(self) -> None: """Setup API connection.""" @@ -156,10 +161,20 @@ def setup(self) -> None: self._authorized = auth @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self) -> None: + async def async_update(self) -> None: """Update API data.""" _LOGGER.debug("Updating Danfoss Ally devices") - self.ally.getDeviceList() + + # Postpone poll if a recent change were made - Attempt to avoid UI glitches + seconds_since_write = (datetime.utcnow() - self._latest_write_time).total_seconds() + if (seconds_since_write < 1): + _LOGGER.debug("Seconds since last write %f. Postponing update for 1 sec.", seconds_since_write) + await asyncio.sleep(1) + + # Poll API + await self.hass.async_add_executor_job(self.ally.getDeviceList) #self.ally.getDeviceList() + self._latest_poll_time = datetime.utcnow() + for device in self.ally.devices: # pylint: disable=consider-using-dict-items _LOGGER.debug("%s: %s", device, self.ally.devices[device]) dispatcher_send(self.hass, SIGNAL_ALLY_UPDATE_RECEIVED) @@ -169,14 +184,26 @@ def devices(self): """Return device list from API.""" return self.ally.devices - def set_temperature(self, device_id: str, temperature: float) -> None: + def set_temperature(self, device_id: str, temperature: float, code = "manual_mode_fast") -> None: """Set temperature for device_id.""" - self.ally.setTemperature(device_id, temperature) + self._latest_write_time = datetime.utcnow() + self.ally.setTemperature(device_id, temperature, code) + + # Debug info - log if update was done approximately as the same time as write + seconds_since_poll = (datetime.utcnow() - self._latest_poll_time).total_seconds() + if (seconds_since_poll < 0.5): + _LOGGER.warn("set_temperature: Time since last poll %f sec.", seconds_since_poll) def set_mode(self, device_id: str, mode: str) -> None: """Set operating mode for device_id.""" + self._latest_write_time = datetime.utcnow() self.ally.setMode(device_id, mode) + # Debug info - log if update was done approximately as the same time as write + seconds_since_poll = (datetime.utcnow() - self._latest_poll_time).total_seconds() + if (seconds_since_poll < 0.5): + _LOGGER.warn("set_mode: Time since last poll %f sec.", seconds_since_poll) + @property def authorized(self) -> bool: """Return authorized state.""" diff --git a/custom_components/danfoss_ally/binary_sensor.py b/custom_components/danfoss_ally/binary_sensor.py index de66d29..123d32c 100644 --- a/custom_components/danfoss_ally/binary_sensor.py +++ b/custom_components/danfoss_ally/binary_sensor.py @@ -4,6 +4,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_LOCK, + DEVICE_CLASS_TAMPER, DEVICE_CLASS_WINDOW, BinarySensorEntity, ) @@ -31,7 +32,7 @@ async def async_setup_entry( entities.extend( [ AllyBinarySensor( - ally, ally.devices[device]["name"], device, "open window" + ally, ally.devices[device]["name"], device, "open window", ally.devices[device]["model"] ) ] ) @@ -42,7 +43,7 @@ async def async_setup_entry( entities.extend( [ AllyBinarySensor( - ally, ally.devices[device]["name"], device, "child lock" + ally, ally.devices[device]["name"], device, "child lock", ally.devices[device]["model"] ) ] ) @@ -53,7 +54,18 @@ async def async_setup_entry( entities.extend( [ AllyBinarySensor( - ally, ally.devices[device]["name"], device, "connectivity" + ally, ally.devices[device]["name"], device, "connectivity", ally.devices[device]["model"] + ) + ] + ) + if 'banner_ctrl' in ally.devices[device]: + _LOGGER.debug( + "Found banner_ctrl detector for %s", ally.devices[device]["name"] + ) + entities.extend( + [ + AllyBinarySensor( + ally, ally.devices[device]["name"], device, 'banner control', ally.devices[device]["model"] ) ] ) @@ -65,13 +77,13 @@ async def async_setup_entry( class AllyBinarySensor(AllyDeviceEntity, BinarySensorEntity): """Representation of an Ally binary_sensor.""" - def __init__(self, ally, name, device_id, device_type): + def __init__(self, ally, name, device_id, device_type, model): """Initialize Ally binary_sensor.""" self._ally = ally self._device = ally.devices[device_id] self._device_id = device_id self._type = device_type - super().__init__(name, device_id, device_type) + super().__init__(name, device_id, device_type, model) _LOGGER.debug("Device_id: %s --- Device: %s", self._device_id, self._device) @@ -86,9 +98,11 @@ def __init__(self, ally, name, device_id, device_type): elif self._type == "open window": self._state = bool(self._device["window_open"]) elif self._type == "child lock": - self._state = bool(self._device["child_lock"]) + self._state = not bool(self._device["child_lock"]) elif self._type == "connectivity": self._state = bool(self._device["online"]) + elif self._type == "banner control": + self._state = bool(self._device["banner_ctrl"]) async def async_added_to_hass(self): """Register for sensor updates.""" @@ -123,10 +137,12 @@ def device_class(self): return DEVICE_CLASS_CONNECTIVITY elif self._type == "open window": return DEVICE_CLASS_WINDOW - elif self._type == "child_lock": + elif self._type == "child lock": return DEVICE_CLASS_LOCK elif self._type == "connectivity": return DEVICE_CLASS_CONNECTIVITY + elif self._type == "banner control": + return DEVICE_CLASS_TAMPER return None @callback @@ -146,6 +162,8 @@ def _async_update_data(self): elif self._type == "open window": self._state = bool(self._device["window_open"]) elif self._type == "child lock": - self._state = bool(self._device["child_lock"]) + self._state = not bool(self._device["child_lock"]) elif self._type == "connectivity": self._state = bool(self._device["online"]) + elif self._type == "banner control": + self._state = bool(self._device['banner_ctrl']) diff --git a/custom_components/danfoss_ally/climate.py b/custom_components/danfoss_ally/climate.py index 43d7860..a22b66c 100644 --- a/custom_components/danfoss_ally/climate.py +++ b/custom_components/danfoss_ally/climate.py @@ -1,8 +1,11 @@ """Support for Danfoss Ally thermostats.""" import logging +import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO, @@ -10,18 +13,21 @@ PRESET_AWAY, PRESET_HOME, SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect +import functools as ft from . import AllyConnector from .const import ( DATA, DOMAIN, - HVAC_MODE_MANUAL, + PRESET_HOLIDAY_AWAY, + PRESET_HOLIDAY_HOME, PRESET_MANUAL, PRESET_PAUSE, SIGNAL_ALLY_UPDATE_RECEIVED, @@ -42,6 +48,7 @@ def __init__( ally, name, device_id, + model, heat_min_temp, heat_max_temp, heat_step, @@ -52,7 +59,7 @@ def __init__( self._ally = ally self._device = ally.devices[device_id] self._device_id = device_id - super().__init__(name, device_id, "climate") + super().__init__(name, device_id, "climate", model) _LOGGER.debug("Device_id: %s --- Device: %s", self._device_id, self._device) @@ -64,6 +71,8 @@ def __init__( PRESET_AWAY, PRESET_PAUSE, PRESET_MANUAL, + PRESET_HOLIDAY_HOME, + PRESET_HOLIDAY_AWAY ] self._support_flags = support_flags @@ -74,7 +83,7 @@ def __init__( self._cur_temp = self._device["temperature"] else: # TEMPORARY fix for missing temperature sensor - self._cur_temp = self._device["setpoint"] + self._cur_temp = self.get_setpoint_for_current_mode() # Low temperature set in Ally app if "lower_temp" in self._device: @@ -124,8 +133,7 @@ def current_temperature(self): if "temperature" in self._device: return self._device["temperature"] else: - # TEMPORARY fix for missing temperature sensor - return self._device["setpoint"] + return self.get_setpoint_for_current_mode() @property def hvac_mode(self): @@ -136,9 +144,14 @@ def hvac_mode(self): if ( self._device["mode"] == "at_home" or self._device["mode"] == "leaving_home" + or self._device["mode"] == "holiday_sat" ): return HVAC_MODE_AUTO - elif self._device["mode"] == "manual": + elif ( + self._device["mode"] == "manual" + or self._device["mode"] == "pause" + or self._device["mode"] == "holiday" + ): return HVAC_MODE_HEAT @property @@ -153,6 +166,10 @@ def preset_mode(self): return PRESET_PAUSE elif self._device["mode"] == "manual": return PRESET_MANUAL + elif self._device["mode"] == "holiday_sat": + return PRESET_HOLIDAY_HOME + elif self._device["mode"] == "holiday": + return PRESET_HOLIDAY_AWAY @property def hvac_modes(self): @@ -179,12 +196,20 @@ def set_preset_mode(self, preset_mode): mode = "pause" elif preset_mode == PRESET_MANUAL: mode = "manual" + elif preset_mode == PRESET_HOLIDAY_HOME: + mode = "holiday_sat" + elif preset_mode == PRESET_HOLIDAY_AWAY: + mode = "holiday" if mode is None: return + self._device["mode"] = mode # Update current copy of device data self._ally.set_mode(self._device_id, mode) + # Update UI + self.async_write_ha_state() + @property def hvac_action(self): """Return the current running hvac operation if supported. @@ -209,16 +234,38 @@ def target_temperature_step(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._device["setpoint"] + return self.get_setpoint_for_current_mode() def set_temperature(self, **kwargs): """Set new target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if temperature is None: - return + if ATTR_TEMPERATURE in kwargs: + temperature = kwargs.get(ATTR_TEMPERATURE) + + if ATTR_PRESET_MODE in kwargs: + setpoint_code = self.get_setpoint_code_for_mode(kwargs.get(ATTR_PRESET_MODE)) # Preset_mode sent from action + elif ATTR_HVAC_MODE in kwargs: + value = kwargs.get(ATTR_HVAC_MODE) # HVAC_mode sent from action + if value == HVAC_MODE_AUTO: + setpoint_code = self.get_setpoint_code_for_mode("at_home") + if value == HVAC_MODE_HEAT: + setpoint_code = self.get_setpoint_code_for_mode("manual") + else: + setpoint_code = self.get_setpoint_code_for_mode(self._device["mode"]) # Current preset_mode - self._ally.set_mode(self._device_id, "manual") - self._ally.set_temperature(self._device_id, temperature) + changed = False + if temperature is not None and setpoint_code is not None: + self._device[setpoint_code] = temperature # Update temperature in current copy + self._ally.set_temperature(self._device_id, temperature, setpoint_code) + changed = True + + # Update UI + if changed: + self.async_write_ha_state() + + async def set_preset_temperature(self, **kwargs): + await self.hass.async_add_executor_job( + ft.partial(self.set_temperature, **kwargs) + ) @property def available(self): @@ -260,17 +307,99 @@ def set_hvac_mode(self, hvac_mode): if mode is None: return + self._device["mode"] = mode # Update current copy of device data self._ally.set_mode(self._device_id, mode) + # Update UI + self.async_write_ha_state() + + def get_setpoint_code_for_mode(self, mode, for_writing = True): + setpoint_code = None + if for_writing == False and "banner_ctrl" in self._device and bool(self._device['banner_ctrl']): + # Temperature setpoint is overridden locally at the thermostate + setpoint_code = "manual_mode_fast" + elif mode == "at_home" or mode == "home": + setpoint_code = "at_home_setting" + elif mode == "leaving_home" or mode == "away": + setpoint_code = "leaving_home_setting" + elif mode == "pause": + setpoint_code = "pause_setting" + elif mode == "manual": + setpoint_code = "manual_mode_fast" + elif mode == "holiday": + setpoint_code = "holiday_setting" + elif mode == "holiday_sat": + setpoint_code = "at_home_setting" + return setpoint_code + + def get_setpoint_for_current_mode(self): + setpoint = None + if "mode" in self._device: + setpoint_code = self.get_setpoint_code_for_mode(self._device["mode"], False) + + if setpoint_code is not None and setpoint_code in self._device: + setpoint = self._device[setpoint_code] + + return setpoint + + +class IconClimate(AllyClimate): + """Representation of a Danfoss Icon climate entity.""" + + def __init__( + self, + ally, + name, + device_id, + model, + heat_min_temp, + heat_max_temp, + heat_step, + supported_hvac_modes, + support_flags + ): + """Initialize Danfoss Icon climate entity.""" + super().__init__( + ally, + name, + device_id, + model, + heat_min_temp, + heat_max_temp, + heat_step, + supported_hvac_modes, + support_flags + ) + + @property + def hvac_action(self): + """Return the current running hvac operation if supported. + Need to be one of CURRENT_HVAC_*. + """ + if "work_state" in self._device: + if self._device["work_state"] == "heat_active": + return CURRENT_HVAC_HEAT + elif self._device["work_state"] == "Heat": + return CURRENT_HVAC_IDLE + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ): """Set up the Danfoss Ally climate platform.""" + platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + "set_preset_temperature", + { + vol.Required("temperature"): vol.Coerce(float), + vol.Optional("preset_mode"): str, + }, + "set_preset_temperature" + ) + ally: AllyConnector = hass.data[DOMAIN][entry.entry_id][DATA] entities = await hass.async_add_executor_job(_generate_entities, ally) - _LOGGER.debug(ally.devices) if entities: async_add_entities(entities, True) @@ -281,13 +410,14 @@ def _generate_entities(ally: AllyConnector): entities = [] for device in ally.devices: if ally.devices[device]["isThermostat"]: - entity = create_climate_entity(ally, ally.devices[device]["name"], device) + _LOGGER.debug("Found climate entity for %s", ally.devices[device]["name"]) + entity = create_climate_entity(ally, ally.devices[device]["name"], device, ally.devices[device]["model"]) if entity: entities.append(entity) return entities -def create_climate_entity(ally, name: str, device_id: str) -> AllyClimate: +def create_climate_entity(ally, name: str, device_id: str, model: str) -> AllyClimate: """Create a Danfoss Ally climate entity.""" support_flags = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -296,14 +426,28 @@ def create_climate_entity(ally, name: str, device_id: str) -> AllyClimate: heat_max_temp = 35.0 heat_step = 0.5 - entity = AllyClimate( - ally, - name, - device_id, - heat_min_temp, - heat_max_temp, - heat_step, - supported_hvac_modes, - support_flags, - ) + if model == "Icon RT": + entity = IconClimate( + ally, + name, + device_id, + model, + heat_min_temp, + heat_max_temp, + heat_step, + supported_hvac_modes, + support_flags, + ) + else: + entity = AllyClimate( + ally, + name, + device_id, + model, + heat_min_temp, + heat_max_temp, + heat_step, + supported_hvac_modes, + support_flags, + ) return entity diff --git a/custom_components/danfoss_ally/const.py b/custom_components/danfoss_ally/const.py index 156c116..803bcda 100644 --- a/custom_components/danfoss_ally/const.py +++ b/custom_components/danfoss_ally/const.py @@ -13,6 +13,8 @@ PRESET_MANUAL = "Manual" PRESET_PAUSE = "Pause" +PRESET_HOLIDAY_AWAY = "Holiday (Away)" +PRESET_HOLIDAY_HOME = "Holiday (Home)" HA_TO_DANFOSS_HVAC_MODE_MAP = { HVAC_MODE_OFF: THERMOSTAT_MODE_OFF, @@ -33,3 +35,6 @@ UNIQUE_ID = "unique_id" UPDATE_LISTENER = "update_listener" UPDATE_TRACK = "update_track" + +ACTION_TYPE_SET_PRESET_TEMPERATURE = "set_preset_temperature" +ATTR_SETPOINT = "setpoint" diff --git a/custom_components/danfoss_ally/device_action.py b/custom_components/danfoss_ally/device_action.py new file mode 100644 index 0000000..e5c112c --- /dev/null +++ b/custom_components/danfoss_ally/device_action.py @@ -0,0 +1,110 @@ +"""Provides device automations for Climate.""" +from __future__ import annotations + +import logging +import voluptuous as vol +import json + +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_TYPE, + ATTR_TEMPERATURE, +) +from homeassistant.core import Context, HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import get_capability, get_supported_features +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, ATTR_PRESET_MODE, ATTR_PRESET_MODES + +from .const import DOMAIN, ACTION_TYPE_SET_PRESET_TEMPERATURE, ATTR_SETPOINT + +_LOGGER = logging.getLogger(__name__) + +ACTION_TYPES = {ACTION_TYPE_SET_PRESET_TEMPERATURE} + +SET_SETPOINT_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): ACTION_TYPE_SET_PRESET_TEMPERATURE, + vol.Required(CONF_ENTITY_ID): cv.entity_domain(CLIMATE_DOMAIN), + vol.Required(ATTR_TEMPERATURE): vol.Coerce(float), + vol.Optional(ATTR_PRESET_MODE): str, + } +) + +ACTION_SCHEMA = vol.Any(SET_SETPOINT_SCHEMA) + + +async def async_get_actions( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: + """List device actions for Climate devices.""" + registry = entity_registry.async_get(hass) + actions = [] + + # Get all the integrations entities for this device + for entry in entity_registry.async_entries_for_device(registry, device_id): + if entry.domain != CLIMATE_DOMAIN: + continue + + supported_features = get_supported_features(hass, entry.entity_id) + + base_action = { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + } + + _LOGGER.debug("Action: " + json.dumps({**base_action, CONF_TYPE: ACTION_TYPE_SET_PRESET_TEMPERATURE})) + + actions.append({**base_action, CONF_TYPE: ACTION_TYPE_SET_PRESET_TEMPERATURE}) + # if supported_features & const.SUPPORT_PRESET_MODE: + # actions.append({**base_action, CONF_TYPE: "set_preset_mode"}) + + return actions + + +async def async_call_action_from_config( + hass: HomeAssistant, config: dict, variables: dict, context: Context | None +) -> None: + """Execute a device action.""" + service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} + + if config[CONF_TYPE] == ACTION_TYPE_SET_PRESET_TEMPERATURE: + service = ACTION_TYPE_SET_PRESET_TEMPERATURE + service_data[ATTR_TEMPERATURE] = config[ATTR_TEMPERATURE] + if ATTR_PRESET_MODE in config: + service_data[ATTR_PRESET_MODE] = config[ATTR_PRESET_MODE] + domain = DOMAIN # danfoss_ally + + await hass.services.async_call( + domain, service, service_data, blocking=True, context=context + ) + + +async def async_get_action_capabilities(hass, config): + """List action capabilities.""" + action_type = config[CONF_TYPE] + + fields = {} + + if action_type == ACTION_TYPE_SET_PRESET_TEMPERATURE: + try: + preset_modes = ( + get_capability(hass, config[ATTR_ENTITY_ID], ATTR_PRESET_MODES) + or [] + ) + except HomeAssistantError: + preset_modes = [] + + preset_modes_kv = {} + for entry in preset_modes: + preset_modes_kv[entry.lower()] = entry.capitalize() + + fields[vol.Required(ATTR_TEMPERATURE)] = vol.Coerce(float) + fields[vol.Optional(ATTR_PRESET_MODE)] = vol.In(preset_modes_kv) + + return {"extra_fields": vol.Schema(fields)} diff --git a/custom_components/danfoss_ally/entity.py b/custom_components/danfoss_ally/entity.py index 26dce19..c3c18da 100644 --- a/custom_components/danfoss_ally/entity.py +++ b/custom_components/danfoss_ally/entity.py @@ -7,12 +7,13 @@ class AllyDeviceEntity(Entity): """Base implementation for Ally device.""" - def __init__(self, name, device_id, device_type): + def __init__(self, name, device_id, device_type, model = None): """Initialize a Ally device.""" super().__init__() self._type = device_type self._name = name self._device_id = device_id + self._model = model @property def device_info(self): @@ -21,32 +22,7 @@ def device_info(self): "identifiers": {(DOMAIN, self._device_id)}, "name": self._name, "manufacturer": DEFAULT_NAME, - "model": None, - } - - @property - def should_poll(self): - """Do not poll.""" - return False - - -class AllyClimateEntity(Entity): - """Base implementation for Danfoss Ally Thermostat.""" - - def __init__(self, name, device_id): - """Initialize a Danfoss Ally zone.""" - super().__init__() - self._device_id = device_id - self._name = name - - @property - def device_info(self): - """Return the device_info of the device.""" - return { - "identifiers": {(DOMAIN, self._device_id)}, - "name": self._name, - "manufacturer": DEFAULT_NAME, - "model": None, + "model": self._model, } @property diff --git a/custom_components/danfoss_ally/manifest.json b/custom_components/danfoss_ally/manifest.json index b9aba5b..b7b98ae 100644 --- a/custom_components/danfoss_ally/manifest.json +++ b/custom_components/danfoss_ally/manifest.json @@ -1,15 +1,15 @@ -{ - "domain": "danfoss_ally", - "name": "Danfoss Ally", - "documentation": "https://github.com/MTrab/danfoss_ally/blob/master/README.md", - "issue_tracker": "https://github.com/MTrab/danfoss_ally/issues", - "requirements": [ - "pydanfossally==0.0.26" - ], - "codeowners": [ - "@MTrab" - ], - "version": "1.0.7", - "config_flow": true, - "iot_class": "cloud_polling" +{ + "domain": "danfoss_ally", + "name": "Danfoss Ally", + "documentation": "https://github.com/MTrab/danfoss_ally/blob/master/README.md", + "issue_tracker": "https://github.com/MTrab/danfoss_ally/issues", + "requirements": [ + "pydanfossally==0.0.27" + ], + "codeowners": [ + "@MTrab" + ], + "version": "1.0.7", + "config_flow": true, + "iot_class": "cloud_polling" } \ No newline at end of file diff --git a/custom_components/danfoss_ally/sensor.py b/custom_components/danfoss_ally/sensor.py index 7dc3e26..1099335 100644 --- a/custom_components/danfoss_ally/sensor.py +++ b/custom_components/danfoss_ally/sensor.py @@ -4,6 +4,7 @@ import logging from enum import IntEnum +from homeassistant.config_entries import ConfigEntry from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -29,7 +30,6 @@ class AllySensorType(IntEnum): BATTERY = 1 HUMIDITY = 2 - SENSORS = [ SensorEntityDescription( key=AllySensorType.TEMPERATURE, @@ -37,7 +37,7 @@ class AllySensorType(IntEnum): entity_category=None, native_unit_of_measurement=TEMP_CELSIUS, state_class=SensorStateClass.MEASUREMENT, - name="{} Temperature", + name="{} temperature", ), SensorEntityDescription( key=AllySensorType.BATTERY, @@ -45,7 +45,7 @@ class AllySensorType(IntEnum): entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, - name="{} Battery", + name="{} battery", ), SensorEntityDescription( key=AllySensorType.HUMIDITY, @@ -53,8 +53,8 @@ class AllySensorType(IntEnum): entity_category=None, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, - name="{} Humidity", - ), + name="{} humidity", + ) ] @@ -68,15 +68,16 @@ async def async_setup_entry( for device in ally.devices: for sensor in SENSORS: - sensor_type = AllySensorType(sensor.key).name + sensor_type = AllySensorType(sensor.key).name.lower() if sensor_type in ally.devices[device]: _LOGGER.debug( "Found %s sensor for %s", sensor_type, ally.devices[device]["name"] ) entities.extend( - [AllySensor(ally, ally.devices[device]["name"], device, sensor)] + [AllySensor(ally, ally.devices[device]["name"], device, sensor, ally.devices[device]["model"])] ) + if entities: async_add_entities(entities, True) @@ -85,18 +86,18 @@ class AllySensor(AllyDeviceEntity, SensorEntity): """Representation of an Ally sensor.""" def __init__( - self, ally: DanfossAlly, name, device_id, description: SensorEntityDescription + self, ally: DanfossAlly, name, device_id, description: SensorEntityDescription, model = None ): """Initialize Ally binary_sensor.""" self.entity_description = description self._ally = ally self._device = ally.devices[device_id] self._device_id = device_id - self._type = AllySensorType(description.key).name - super().__init__(name, device_id, self._type) + self._type = AllySensorType(description.key).name.lower() + super().__init__(name, device_id, self._type, model) _LOGGER.debug("Device_id: %s --- Device: %s", self._device_id, self._device) - + self._attr_native_value = None self._attr_extra_state_attributes = None self._attr_name = self.entity_description.name.format(name) @@ -122,12 +123,8 @@ def _async_update_callback(self): @callback def _async_update_data(self): """Load data.""" - _LOGGER.debug("Loading new sensor data for device %s", self._device_id) + _LOGGER.debug("Loading new sensor data for Ally Sensor for device %s", self._device_id) self._device = self._ally.devices[self._device_id] - if self._type == "battery": - self._attr_native_value = self._device["battery"] - elif self._type == "temperature": - self._attr_native_value = self._device["temperature"] - elif self._type == "humidity": - self._attr_native_value = self._device["humidity"] + if self._type in self._device: + self._attr_native_value = self._device[self._type] diff --git a/custom_components/danfoss_ally/services.yaml b/custom_components/danfoss_ally/services.yaml new file mode 100644 index 0000000..5947810 --- /dev/null +++ b/custom_components/danfoss_ally/services.yaml @@ -0,0 +1,36 @@ +# Describes the format for available climate services + +set_preset_temperature: + name: Set preset temperature + description: Set target temperature of climate device. + target: + entity: + domain: climate + fields: + temperature: + name: Temperature + description: New target temperature for HVAC. + selector: + number: + min: 0 + max: 250 + step: 0.5 + mode: box + preset_mode: + name: Preset mode + description: Optional. Using current preset mode if not specified. + selector: + select: + options: + - label: "Home" + value: "home" + - label: "Away" + value: "away" + - label: "Manual" + value: "manual" + - label: "Pause" + value: "pause" + - label: "Holiday (Home)" + value: "holiday_sat" + - label: "Holiday (Away)" + value: "holiday" diff --git a/custom_components/danfoss_ally/strings.json b/custom_components/danfoss_ally/strings.json index 5172678..5b664ed 100644 --- a/custom_components/danfoss_ally/strings.json +++ b/custom_components/danfoss_ally/strings.json @@ -19,5 +19,14 @@ "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } + }, + "device_automation": { + "condition_type": { + }, + "trigger_type": { + }, + "action_type": { + "set_preset_temperature": "Set preset temperature" + } } } \ No newline at end of file diff --git a/custom_components/danfoss_ally/translations/da.json b/custom_components/danfoss_ally/translations/da.json index f6efe35..70ae6b3 100644 --- a/custom_components/danfoss_ally/translations/da.json +++ b/custom_components/danfoss_ally/translations/da.json @@ -16,5 +16,14 @@ } } } + }, + "device_automation": { + "condition_type": { + }, + "trigger_type": { + }, + "action_type": { + "set_preset_temperature": "Set preset temperature" + } } } \ No newline at end of file diff --git a/custom_components/danfoss_ally/translations/en.json b/custom_components/danfoss_ally/translations/en.json index b211d5b..ee04430 100644 --- a/custom_components/danfoss_ally/translations/en.json +++ b/custom_components/danfoss_ally/translations/en.json @@ -16,5 +16,14 @@ } } } + }, + "device_automation": { + "condition_type": { + }, + "trigger_type": { + }, + "action_type": { + "set_preset_temperature": "Set preset temperature" + } } } \ No newline at end of file