Skip to content

Commit

Permalink
Add Sensor for Refoss Integration (#116965)
Browse files Browse the repository at this point in the history
Co-authored-by: Robert Resch <robert@resch.dev>
  • Loading branch information
ashionky and edenhaus authored Jun 20, 2024
1 parent 1eb8b5a commit 3224224
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 5 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/refoss/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .util import refoss_discovery_server

PLATFORMS: Final = [
Platform.SENSOR,
Platform.SWITCH,
]

Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/refoss/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@
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",
}
}
5 changes: 0 additions & 5 deletions homeassistant/components/refoss/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ def __init__(self, coordinator: RefossDataUpdateCoordinator, channel: int) -> No

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)},
Expand Down
174 changes: 174 additions & 0 deletions homeassistant/components/refoss/sensor.py
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions homeassistant/components/refoss/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
10 changes: 10 additions & 0 deletions homeassistant/components/refoss/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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

Expand Down Expand Up @@ -48,6 +49,15 @@ def init_device(coordinator):
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."""
Expand Down

0 comments on commit 3224224

Please sign in to comment.