diff --git a/README.md b/README.md index e9378a2..feeb925 100644 --- a/README.md +++ b/README.md @@ -42,19 +42,19 @@ Name | Description | Unit | State attributes `fireplace_mode_minutes_left` | minutes left until fireplace mode expires | `min` | `heat_exchanger_rotor_speed_percent` | rotor speed of heat exchanger | `%` | `heat_exchanger_rotor_speed` | rotor speed of heat exchanger | `rpm` | -`heater_active` | auxillary heater active | boolean | +`heater_active` | auxillary heater active | `Running` \| `Not running` | `heater_air_temperature` | air temperature at heater | `°C` | `heater_power_percent` | auxillary heater power | `%` | `product_number` | product number | `str` | `supply_air_temperature` | supply air temperature | `°C` | `supply_fan_speed` | fan speed | `%` | -`system_active` | status of the system | `On` \| `Off` \| `Reset` | +`system_active` | status of the system | `Running` \| `Not running` `system_name` | control system name | `str` | `system_version` | control system version | `str` | -`system_warning` | system warning | boolean | raw system error/warning codes +`system_warning` | system warning | `Problem` \| `No problem` | raw system error/warning codes `target_temperature` | target air temperature | `°C` | -`temperature_mode` | current temperature mode setting | `str` | -`ventilation_mode` | current ventilation mode setting | `str` | +`temperature_mode` | current temperature mode setting | `Cool` \| `Normal` \| `Economy` | +`ventilation_mode` | current ventilation mode setting | `Normal` \| `Away` \| `Boost` | `normal_temperature` | temperature setting for Normal mode | `°C` | `economy_temperature` | temperature setting for Economy mode | `°C` | `cool_temperature` | temperature setting for Cool mode | `°C` | @@ -78,14 +78,14 @@ Name | Description ### Number Name | Description -- | -- -`cool temperature` | Cool temperature installer setting -`economy temperature` | Economy temperature installer setting -`normal temperature` | Normal temperature installer setting +`cool_temperature` | Cool temperature installer setting +`economy_temperature` | Economy temperature installer setting +`normal_temperature` | Normal temperature installer setting ### Button Name | Description -- | -- -`system reset` | Reset system warnings +`system_reset` | Reset system warnings ## Experimental features @@ -99,7 +99,7 @@ Name | Description | Unit Switch | Description -- | -- -`cooking_mode` | Turn `cooking` mode on/off. Emulates cooking mode when fireplace mode is active. When `cooking mode` is active, it automatically deactivates `fireplace mode` before its timer expires. This will reset rotary heat exchanger to normal operation as is desirable in warm weather. +`cooking_mode` | Turn `cooking` mode on/off. Emulates cooking mode when fireplace mode is active. When `cooking mode` is active, it automatically deactivates `fireplace_mode` before its timer expires. This will reset rotary heat exchanger to normal operation as is desirable in warm weather. ## Supported devices diff --git a/custom_components/saleryd_hrv/binary_sensor.py b/custom_components/saleryd_hrv/binary_sensor.py new file mode 100644 index 0000000..44bef2d --- /dev/null +++ b/custom_components/saleryd_hrv/binary_sensor.py @@ -0,0 +1,168 @@ +"""Binary sensor platform""" + +from __future__ import annotations + +from enum import IntEnum +from typing import TYPE_CHECKING, Any + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import slugify +from pysaleryd.const import DataKeyEnum +from pysaleryd.utils import ErrorSystemProperty, SystemProperty +from pysaleryd.websocket import State + +from .const import KEY_CLIENT_STATE, ModeEnum +from .entity import SalerydLokeEntity + +if TYPE_CHECKING: + from .coordinator import SalerydLokeDataUpdateCoordinator + from .data import SalerydLokeConfigEntry + + +class SalerydLokeBinarySensor(SalerydLokeEntity, BinarySensorEntity): + """Sensor base class.""" + + def __init__( + self, + coordinator: SalerydLokeDataUpdateCoordinator, + entry: "SalerydLokeConfigEntry", + entity_description: SensorEntityDescription, + state_when_on: IntEnum = ModeEnum.On, + ) -> None: + """Initialize the sensor.""" + self.entity_id = ( + f"binary_sensor.{entry.unique_id}_{slugify(entity_description.name)}" + ) + self.state_when_on = state_when_on + super().__init__(coordinator, entry, entity_description) + + def _is_on(self, system_property: SystemProperty): + return system_property.value + + @property + def is_on(self): + system_property = SystemProperty.from_str( + self.entity_description.key, + self.coordinator.data.get(self.entity_description.key), + ) + if system_property.value is None: + return + + return system_property.value == self.state_when_on.value + + def _get_extra_state_attributes( + self, system_property: SystemProperty + ) -> dict[str, Any] | None: + return None + + @property + def extra_state_attributes(self): + value = SystemProperty.from_str( + self.entity_description.key, + self.coordinator.data.get(self.entity_description.key), + ) + return self._get_extra_state_attributes(value) + + +class SalerydLokeErrorMessageBinarySensor(SalerydLokeBinarySensor): + + @property + def is_on(self): + error = ErrorSystemProperty( + self.entity_description.key, + self.coordinator.data.get(self.entity_description.key, None), + ) + if error.value is None: + return + + return any(error.value) + + @property + def extra_state_attributes(self): + error = ErrorSystemProperty( + self.entity_description.key, + self.coordinator.data.get(self.entity_description.key, None), + ) + if error.value is None: + return None + + attrs = {} + for v in error.value: + attrs[v] = True + + return attrs + + +class SalerydLokeConnectionStateBinarySensor(SalerydLokeBinarySensor): + + def _get_extra_state_attributes(self, system_property: SystemProperty): + if system_property.value is None: + return None + return {system_property.value: True} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: "SalerydLokeConfigEntry", + async_add_entities: AddEntitiesCallback, +): + """Setup binary sensor platform.""" + coordinator = entry.runtime_data.coordinator + sensors = [ + # control_system_warning + SalerydLokeErrorMessageBinarySensor( + coordinator, + entry, + entity_description=BinarySensorEntityDescription( + key=DataKeyEnum.ERROR_MESSAGE, + icon="mdi:alert", + name="System warning", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + ), + # control_system_active + SalerydLokeBinarySensor( + coordinator, + entry, + entity_description=BinarySensorEntityDescription( + key=DataKeyEnum.CONTROL_SYSTEM_STATE, + icon="mdi:power", + name="System active", + device_class=BinarySensorDeviceClass.RUNNING, + ), + ), + # heater_active + SalerydLokeBinarySensor( + coordinator, + entry, + entity_description=BinarySensorEntityDescription( + key=DataKeyEnum.MODE_HEATER, + icon="mdi:heating-coil", + name="Heater active", + device_class=BinarySensorDeviceClass.RUNNING, + ), + ), + # connection_state + SalerydLokeConnectionStateBinarySensor( + coordinator, + entry, + entity_description=BinarySensorEntityDescription( + key=KEY_CLIENT_STATE, + icon="mdi:wrench-clock", + name="Connection state", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + entity_category=EntityCategory.DIAGNOSTIC, + ), + state_when_on=State.RUNNING, + ), + ] + + async_add_entities(sensors) diff --git a/custom_components/saleryd_hrv/bridge.py b/custom_components/saleryd_hrv/bridge.py index 8c2868b..697fd6a 100644 --- a/custom_components/saleryd_hrv/bridge.py +++ b/custom_components/saleryd_hrv/bridge.py @@ -33,11 +33,11 @@ def update_data_callback(self, data): self.logger.debug("Received data") _data = data.copy() self.__inject_virtual_keys(_data) - self.coordinator.async_set_updated_data(data) + self.coordinator.async_set_updated_data(_data) def __inject_virtual_keys(self, data): """Inject additional keys for virtual sensors not present in the data set""" - data[KEY_CLIENT_STATE] = self.client.state.name + data[KEY_CLIENT_STATE] = self.client.state.value data[KEY_TARGET_TEMPERATURE] = None async def send_command(self, key: DataKeyEnum, data: str | int, auth: bool = False): diff --git a/custom_components/saleryd_hrv/const.py b/custom_components/saleryd_hrv/const.py index cce6156..daec74a 100644 --- a/custom_components/saleryd_hrv/const.py +++ b/custom_components/saleryd_hrv/const.py @@ -23,7 +23,8 @@ SELECT = "select" NUMBER = "number" BUTTON = "button" -PLATFORMS = [SENSOR, SWITCH, SELECT, NUMBER, BUTTON] +BINARY_SENSOR = "binary_sensor" +PLATFORMS = [SENSOR, SWITCH, SELECT, NUMBER, BUTTON, BINARY_SENSOR] # Configuration and options diff --git a/custom_components/saleryd_hrv/sensor.py b/custom_components/saleryd_hrv/sensor.py index d4e2350..cef7f71 100644 --- a/custom_components/saleryd_hrv/sensor.py +++ b/custom_components/saleryd_hrv/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations from enum import IntEnum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from homeassistant.components.sensor import ( SensorDeviceClass, @@ -65,7 +65,9 @@ def native_value(self): ) return self._get_native_value(value) - def _get_extra_state_attributes(self, system_property: SystemProperty): + def _get_extra_state_attributes( + self, system_property: SystemProperty + ) -> dict[str, Any] | None: return None @property @@ -105,35 +107,6 @@ def _get_native_value(self, system_property): return None -class SalerydLokeErrorMessageSensor(SalerydLokeSensor): - - @property - def native_value(self): - error = ErrorSystemProperty( - self.entity_description.key, - self.coordinator.data.get(self.entity_description.key, None), - ) - if error.value is None: - return None - - return any(error.value) - - @property - def extra_state_attributes(self): - error = ErrorSystemProperty( - self.entity_description.key, - self.coordinator.data.get(self.entity_description.key, None), - ) - if error.value is None: - return None - - attrs = {} - for v in error.value: - attrs[v] = True - - return attrs - - class SalerydLokeTargetTemperatureSensor(SalerydLokeSensor): """Target temperature sensor""" @@ -406,18 +379,6 @@ async def async_setup_entry( entity_category=EntityCategory.DIAGNOSTIC, ), ), - # connection_state - SalerydLokeSensor( - coordinator, - entry, - entity_description=SensorEntityDescription( - key=KEY_CLIENT_STATE, - icon="mdi:wrench-clock", - name="Connection state", - device_class=SensorDeviceClass.ENUM, - entity_category=EntityCategory.DIAGNOSTIC, - ), - ), # control_system_version SalerydLokeSensor( coordinator, @@ -430,40 +391,6 @@ async def async_setup_entry( entity_category=EntityCategory.DIAGNOSTIC, ), ), - # control_system_warning - SalerydLokeErrorMessageSensor( - coordinator, - entry, - entity_description=SensorEntityDescription( - key=DataKeyEnum.ERROR_MESSAGE, - icon="mdi:alert", - name="System warning", - device_class=SensorDeviceClass.ENUM, - ), - ), - # control_system_active - SalerydLokeEnumSensor( - coordinator, - entry, - entity_description=SensorEntityDescription( - key=DataKeyEnum.CONTROL_SYSTEM_STATE, - icon="mdi:power", - name="System active", - device_class=SensorDeviceClass.ENUM, - ), - options_enum=SystemActiveModeEnum, - ), - # heater_active - SalerydLokeEnumSensor( - coordinator, - entry, - entity_description=SensorEntityDescription( - key=DataKeyEnum.MODE_HEATER, - icon="mdi:heating-coil", - name="Heater active", - device_class=SensorDeviceClass.ENUM, - ), - ), # normal mode target temperature SalerydLokeSensor( coordinator,