diff --git a/.coveragerc b/.coveragerc index 74fde968370..4d0f78a81f5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1110,6 +1110,7 @@ omit = homeassistant/components/refoss/bridge.py homeassistant/components/refoss/coordinator.py homeassistant/components/refoss/entity.py + homeassistant/components/refoss/sensor.py homeassistant/components/refoss/switch.py homeassistant/components/refoss/util.py homeassistant/components/rejseplanen/sensor.py diff --git a/homeassistant/components/refoss/__init__.py b/homeassistant/components/refoss/__init__.py index 666a17847c9..0f0c852b043 100644 --- a/homeassistant/components/refoss/__init__.py +++ b/homeassistant/components/refoss/__init__.py @@ -15,6 +15,7 @@ from .const import COORDINATORS, DATA_DISCOVERY_SERVICE, DISCOVERY_SCAN_INTERVAL from .util import refoss_discovery_server PLATFORMS: Final = [ + Platform.SENSOR, Platform.SWITCH, ] diff --git a/homeassistant/components/refoss/const.py b/homeassistant/components/refoss/const.py index 86e40fce43c..0542afe8afb 100644 --- a/homeassistant/components/refoss/const.py +++ b/homeassistant/components/refoss/const.py @@ -19,3 +19,14 @@ DOMAIN = "refoss" COORDINATOR = "coordinator" MAX_ERRORS = 2 + +CHANNEL_DISPLAY_NAME: dict[str, dict[int, str]] = { + "em06": { + 1: "A1", + 2: "B1", + 3: "C1", + 4: "A2", + 5: "B2", + 6: "C2", + } +} diff --git a/homeassistant/components/refoss/entity.py b/homeassistant/components/refoss/entity.py index 3032c32ed51..502101608ec 100644 --- a/homeassistant/components/refoss/entity.py +++ b/homeassistant/components/refoss/entity.py @@ -18,11 +18,6 @@ class RefossEntity(CoordinatorEntity[RefossDataUpdateCoordinator]): mac = coordinator.device.mac self.channel_id = channel - if channel == 0: - self._attr_name = None - else: - self._attr_name = str(channel) - self._attr_unique_id = f"{mac}_{channel}" self._attr_device_info = DeviceInfo( connections={(CONNECTION_NETWORK_MAC, mac)}, diff --git a/homeassistant/components/refoss/sensor.py b/homeassistant/components/refoss/sensor.py new file mode 100644 index 00000000000..018c438ba3c --- /dev/null +++ b/homeassistant/components/refoss/sensor.py @@ -0,0 +1,174 @@ +"""Support for refoss sensors.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from refoss_ha.controller.electricity import ElectricityXMix + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfPower, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from .bridge import RefossDataUpdateCoordinator +from .const import ( + CHANNEL_DISPLAY_NAME, + COORDINATORS, + DISPATCH_DEVICE_DISCOVERED, + DOMAIN, +) +from .entity import RefossEntity + + +@dataclass(frozen=True) +class RefossSensorEntityDescription(SensorEntityDescription): + """Describes Refoss sensor entity.""" + + subkey: str | None = None + fn: Callable[[float], float] | None = None + + +SENSORS: dict[str, tuple[RefossSensorEntityDescription, ...]] = { + "em06": ( + RefossSensorEntityDescription( + key="power", + translation_key="power", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_display_precision=2, + subkey="power", + fn=lambda x: x / 1000.0, + ), + RefossSensorEntityDescription( + key="voltage", + translation_key="voltage", + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, + suggested_display_precision=2, + suggested_unit_of_measurement=UnitOfElectricPotential.VOLT, + subkey="voltage", + ), + RefossSensorEntityDescription( + key="current", + translation_key="current", + device_class=SensorDeviceClass.CURRENT, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE, + suggested_display_precision=2, + suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + subkey="current", + ), + RefossSensorEntityDescription( + key="factor", + translation_key="power_factor", + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + subkey="factor", + ), + RefossSensorEntityDescription( + key="energy", + translation_key="this_month_energy", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_display_precision=2, + subkey="mConsume", + fn=lambda x: x if x > 0 else 0, + ), + RefossSensorEntityDescription( + key="energy_returned", + translation_key="this_month_energy_returned", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_display_precision=2, + subkey="mConsume", + fn=lambda x: abs(x) if x < 0 else 0, + ), + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Refoss device from a config entry.""" + + @callback + def init_device(coordinator): + """Register the device.""" + device = coordinator.device + + if not isinstance(device, ElectricityXMix): + return + descriptions = SENSORS.get(device.device_type) + new_entities = [] + for channel in device.channels: + for description in descriptions: + entity = RefossSensor( + coordinator=coordinator, + channel=channel, + description=description, + ) + new_entities.append(entity) + + async_add_entities(new_entities) + + for coordinator in hass.data[DOMAIN][COORDINATORS]: + init_device(coordinator) + + config_entry.async_on_unload( + async_dispatcher_connect(hass, DISPATCH_DEVICE_DISCOVERED, init_device) + ) + + +class RefossSensor(RefossEntity, SensorEntity): + """Refoss Sensor Device.""" + + entity_description: RefossSensorEntityDescription + + def __init__( + self, + coordinator: RefossDataUpdateCoordinator, + channel: int, + description: RefossSensorEntityDescription, + ) -> None: + """Init Refoss sensor.""" + super().__init__(coordinator, channel) + self.entity_description = description + self._attr_unique_id = f"{super().unique_id}{description.key}" + device_type = coordinator.device.device_type + channel_name = CHANNEL_DISPLAY_NAME[device_type][channel] + self._attr_translation_placeholders = {"channel_name": channel_name} + + @property + def native_value(self) -> StateType: + """Return the native value.""" + value = self.coordinator.device.get_value( + self.channel_id, self.entity_description.subkey + ) + if value is None: + return None + if self.entity_description.fn is not None: + return self.entity_description.fn(value) + return value diff --git a/homeassistant/components/refoss/strings.json b/homeassistant/components/refoss/strings.json index ad8f0f41ae7..67b4e4a8335 100644 --- a/homeassistant/components/refoss/strings.json +++ b/homeassistant/components/refoss/strings.json @@ -9,5 +9,27 @@ "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } + }, + "entity": { + "sensor": { + "power": { + "name": "{channel_name} power" + }, + "voltage": { + "name": "{channel_name} voltage" + }, + "current": { + "name": "{channel_name} current" + }, + "power_factor": { + "name": "{channel_name} power factor" + }, + "this_month_energy": { + "name": "{channel_name} this month energy" + }, + "this_month_energy_returned": { + "name": "{channel_name} this month energy returned" + } + } } } diff --git a/homeassistant/components/refoss/switch.py b/homeassistant/components/refoss/switch.py index c51f166059e..0f5aba0cfc4 100644 --- a/homeassistant/components/refoss/switch.py +++ b/homeassistant/components/refoss/switch.py @@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from .bridge import RefossDataUpdateCoordinator from .const import COORDINATORS, DISPATCH_DEVICE_DISCOVERED, DOMAIN from .entity import RefossEntity @@ -48,6 +49,15 @@ async def async_setup_entry( class RefossSwitch(RefossEntity, SwitchEntity): """Refoss Switch Device.""" + def __init__( + self, + coordinator: RefossDataUpdateCoordinator, + channel: int, + ) -> None: + """Init Refoss switch.""" + super().__init__(coordinator, channel) + self._attr_name = str(channel) + @property def is_on(self) -> bool | None: """Return true if switch is on."""