"""Sensor for myUplink.""" from myuplink import DevicePoint from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( Platform, UnitOfElectricCurrent, UnitOfEnergy, UnitOfFrequency, UnitOfPower, UnitOfPressure, UnitOfTemperature, UnitOfTime, UnitOfVolumeFlowRate, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from . import MyUplinkDataCoordinator from .const import DOMAIN from .entity import MyUplinkEntity from .helpers import find_matching_platform, skip_entity DEVICE_POINT_UNIT_DESCRIPTIONS: dict[str, SensorEntityDescription] = { "°C": SensorEntityDescription( key="celsius", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), "°F": SensorEntityDescription( key="fahrenheit", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, ), "A": SensorEntityDescription( key="ampere", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, ), "bar": SensorEntityDescription( key="pressure", device_class=SensorDeviceClass.PRESSURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfPressure.BAR, ), "h": SensorEntityDescription( key="hours", device_class=SensorDeviceClass.DURATION, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTime.HOURS, suggested_display_precision=1, ), "Hz": SensorEntityDescription( key="hertz", device_class=SensorDeviceClass.FREQUENCY, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfFrequency.HERTZ, ), "kW": SensorEntityDescription( key="power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfPower.KILO_WATT, ), "kWh": SensorEntityDescription( key="energy", device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, ), "m3/h": SensorEntityDescription( key="airflow", translation_key="airflow", device_class=SensorDeviceClass.VOLUME_FLOW_RATE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, ), "s": SensorEntityDescription( key="seconds", device_class=SensorDeviceClass.DURATION, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTime.SECONDS, suggested_display_precision=0, ), } MARKER_FOR_UNKNOWN_VALUE = -32768 CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, SensorEntityDescription]] = { "NIBEF": { "43108": SensorEntityDescription( key="fan_mode", translation_key="fan_mode", ), "43427": SensorEntityDescription( key="status_compressor", translation_key="status_compressor", device_class=SensorDeviceClass.ENUM, ), "49993": SensorEntityDescription( key="elect_add", translation_key="elect_add", device_class=SensorDeviceClass.ENUM, ), "49994": SensorEntityDescription( key="priority", translation_key="priority", device_class=SensorDeviceClass.ENUM, ), }, "NIBE": {}, } def get_description(device_point: DevicePoint) -> SensorEntityDescription | None: """Get description for a device point. Priorities: 1. Category specific prefix e.g "NIBEF" 2. Global parameter_unit e.g. "°C" 3. Default to None """ description = None prefix, _, _ = device_point.category.partition(" ") description = CATEGORY_BASED_DESCRIPTIONS.get(prefix, {}).get( device_point.parameter_id ) if description is None: description = DEVICE_POINT_UNIT_DESCRIPTIONS.get(device_point.parameter_unit) return description async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up myUplink sensor.""" entities: list[SensorEntity] = [] coordinator: MyUplinkDataCoordinator = hass.data[DOMAIN][config_entry.entry_id] # Setup device point sensors for device_id, point_data in coordinator.data.points.items(): for point_id, device_point in point_data.items(): if skip_entity(device_point.category, device_point): continue if find_matching_platform(device_point) == Platform.SENSOR: description = get_description(device_point) entity_class = MyUplinkDevicePointSensor if ( description is not None and description.device_class == SensorDeviceClass.ENUM ): entities.append( MyUplinkEnumRawSensor( coordinator=coordinator, device_id=device_id, device_point=device_point, entity_description=description, unique_id_suffix=f"{point_id}-raw", ) ) entity_class = MyUplinkEnumSensor entities.append( entity_class( coordinator=coordinator, device_id=device_id, device_point=device_point, entity_description=description, unique_id_suffix=point_id, ) ) async_add_entities(entities) class MyUplinkDevicePointSensor(MyUplinkEntity, SensorEntity): """Representation of a myUplink device point sensor.""" def __init__( self, coordinator: MyUplinkDataCoordinator, device_id: str, device_point: DevicePoint, entity_description: SensorEntityDescription | None, unique_id_suffix: str, ) -> None: """Initialize the sensor.""" super().__init__( coordinator=coordinator, device_id=device_id, unique_id_suffix=unique_id_suffix, ) # Internal properties self.point_id = device_point.parameter_id # Remove soft hyphens self._attr_name = device_point.parameter_name.replace("\u00ad", "") if entity_description is not None: self.entity_description = entity_description else: self._attr_native_unit_of_measurement = device_point.parameter_unit @property def native_value(self) -> StateType: """Sensor state value.""" device_point = self.coordinator.data.points[self.device_id][self.point_id] if device_point.value == MARKER_FOR_UNKNOWN_VALUE: return None return device_point.value # type: ignore[no-any-return] class MyUplinkEnumSensor(MyUplinkDevicePointSensor): """Representation of a myUplink device point sensor for ENUM device_class.""" def __init__( self, coordinator: MyUplinkDataCoordinator, device_id: str, device_point: DevicePoint, entity_description: SensorEntityDescription | None, unique_id_suffix: str, ) -> None: """Initialize the sensor.""" super().__init__( coordinator=coordinator, device_id=device_id, device_point=device_point, entity_description=entity_description, unique_id_suffix=unique_id_suffix, ) self._attr_options = [x["text"].capitalize() for x in device_point.enum_values] self.options_map = { x["value"]: x["text"].capitalize() for x in device_point.enum_values } @property def native_value(self) -> str: """Sensor state value for enum sensor.""" device_point = self.coordinator.data.points[self.device_id][self.point_id] return self.options_map[str(int(device_point.value))] # type: ignore[no-any-return] class MyUplinkEnumRawSensor(MyUplinkDevicePointSensor): """Representation of a myUplink device point sensor for raw value from ENUM device_class.""" _attr_entity_registry_enabled_default = False _attr_device_class = None def __init__( self, coordinator: MyUplinkDataCoordinator, device_id: str, device_point: DevicePoint, entity_description: SensorEntityDescription | None, unique_id_suffix: str, ) -> None: """Initialize the sensor.""" super().__init__( coordinator=coordinator, device_id=device_id, device_point=device_point, entity_description=entity_description, unique_id_suffix=unique_id_suffix, ) self._attr_name = f"{device_point.parameter_name} raw"