mirror of https://github.com/home-assistant/core
245 lines
8.4 KiB
Python
245 lines
8.4 KiB
Python
"""Support for monitoring emoncms feeds."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.sensor import (
|
|
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|
from homeassistant.const import (
|
|
CONF_API_KEY,
|
|
CONF_ID,
|
|
CONF_UNIT_OF_MEASUREMENT,
|
|
CONF_URL,
|
|
CONF_VALUE_TEMPLATE,
|
|
UnitOfPower,
|
|
)
|
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.helpers import template
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
|
|
from .config_flow import sensor_name
|
|
from .const import (
|
|
CONF_EXCLUDE_FEEDID,
|
|
CONF_ONLY_INCLUDE_FEEDID,
|
|
DOMAIN,
|
|
FEED_ID,
|
|
FEED_NAME,
|
|
FEED_TAG,
|
|
)
|
|
from .coordinator import EmoncmsCoordinator
|
|
|
|
ATTR_FEEDID = "FeedId"
|
|
ATTR_FEEDNAME = "FeedName"
|
|
ATTR_LASTUPDATETIME = "LastUpdated"
|
|
ATTR_LASTUPDATETIMESTR = "LastUpdatedStr"
|
|
ATTR_SIZE = "Size"
|
|
ATTR_TAG = "Tag"
|
|
ATTR_USERID = "UserId"
|
|
CONF_SENSOR_NAMES = "sensor_names"
|
|
DECIMALS = 2
|
|
DEFAULT_UNIT = UnitOfPower.WATT
|
|
|
|
ONLY_INCL_EXCL_NONE = "only_include_exclude_or_none"
|
|
|
|
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_API_KEY): cv.string,
|
|
vol.Required(CONF_URL): cv.string,
|
|
vol.Required(CONF_ID): cv.positive_int,
|
|
vol.Exclusive(CONF_ONLY_INCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All(
|
|
cv.ensure_list, [cv.positive_int]
|
|
),
|
|
vol.Exclusive(CONF_EXCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All(
|
|
cv.ensure_list, [cv.positive_int]
|
|
),
|
|
vol.Optional(CONF_SENSOR_NAMES): vol.All(
|
|
{cv.positive_int: vol.All(cv.string, vol.Length(min=1))}
|
|
),
|
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT): cv.string,
|
|
}
|
|
)
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Import config from yaml."""
|
|
if CONF_VALUE_TEMPLATE in config:
|
|
async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
f"remove_{CONF_VALUE_TEMPLATE}_{DOMAIN}",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=IssueSeverity.ERROR,
|
|
translation_key=f"remove_{CONF_VALUE_TEMPLATE}",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"parameter": CONF_VALUE_TEMPLATE,
|
|
},
|
|
)
|
|
return
|
|
if CONF_ONLY_INCLUDE_FEEDID not in config:
|
|
async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
f"missing_{CONF_ONLY_INCLUDE_FEEDID}_{DOMAIN}",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=IssueSeverity.WARNING,
|
|
translation_key=f"missing_{CONF_ONLY_INCLUDE_FEEDID}",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
},
|
|
)
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
|
|
)
|
|
if (
|
|
result.get("type") == FlowResultType.CREATE_ENTRY
|
|
or result.get("reason") == "already_configured"
|
|
):
|
|
async_create_issue(
|
|
hass,
|
|
HOMEASSISTANT_DOMAIN,
|
|
f"deprecated_yaml_{DOMAIN}",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
breaks_in_ha_version="2025.3.0",
|
|
severity=IssueSeverity.WARNING,
|
|
translation_key="deprecated_yaml",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": "emoncms",
|
|
},
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the emoncms sensors."""
|
|
name = sensor_name(entry.data[CONF_URL])
|
|
exclude_feeds = entry.data.get(CONF_EXCLUDE_FEEDID)
|
|
include_only_feeds = entry.options.get(
|
|
CONF_ONLY_INCLUDE_FEEDID, entry.data.get(CONF_ONLY_INCLUDE_FEEDID)
|
|
)
|
|
|
|
if exclude_feeds is None and include_only_feeds is None:
|
|
return
|
|
|
|
coordinator = entry.runtime_data
|
|
# uuid was added in emoncms database 11.5.7
|
|
unique_id = entry.unique_id if entry.unique_id else entry.entry_id
|
|
elems = coordinator.data
|
|
if not elems:
|
|
return
|
|
sensors: list[EmonCmsSensor] = []
|
|
|
|
for idx, elem in enumerate(elems):
|
|
if include_only_feeds is not None and elem[FEED_ID] not in include_only_feeds:
|
|
continue
|
|
sensors.append(
|
|
EmonCmsSensor(
|
|
coordinator,
|
|
unique_id,
|
|
elem["unit"],
|
|
name,
|
|
idx,
|
|
)
|
|
)
|
|
async_add_entities(sensors)
|
|
|
|
|
|
class EmonCmsSensor(CoordinatorEntity[EmoncmsCoordinator], SensorEntity):
|
|
"""Implementation of an Emoncms sensor."""
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: EmoncmsCoordinator,
|
|
unique_id: str,
|
|
unit_of_measurement: str | None,
|
|
name: str,
|
|
idx: int,
|
|
) -> None:
|
|
"""Initialize the sensor."""
|
|
super().__init__(coordinator)
|
|
self.idx = idx
|
|
elem = {}
|
|
if self.coordinator.data:
|
|
elem = self.coordinator.data[self.idx]
|
|
self._attr_name = f"{name} {elem[FEED_NAME]}"
|
|
self._attr_native_unit_of_measurement = unit_of_measurement
|
|
self._attr_unique_id = f"{unique_id}-{elem[FEED_ID]}"
|
|
if unit_of_measurement in ("kWh", "Wh"):
|
|
self._attr_device_class = SensorDeviceClass.ENERGY
|
|
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
|
|
elif unit_of_measurement == "W":
|
|
self._attr_device_class = SensorDeviceClass.POWER
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement == "V":
|
|
self._attr_device_class = SensorDeviceClass.VOLTAGE
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement == "A":
|
|
self._attr_device_class = SensorDeviceClass.CURRENT
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement == "VA":
|
|
self._attr_device_class = SensorDeviceClass.APPARENT_POWER
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement in ("°C", "°F", "K"):
|
|
self._attr_device_class = SensorDeviceClass.TEMPERATURE
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement == "Hz":
|
|
self._attr_device_class = SensorDeviceClass.FREQUENCY
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
elif unit_of_measurement == "hPa":
|
|
self._attr_device_class = SensorDeviceClass.PRESSURE
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
self._update_attributes(elem)
|
|
|
|
def _update_attributes(self, elem: dict[str, Any]) -> None:
|
|
"""Update entity attributes."""
|
|
self._attr_extra_state_attributes = {
|
|
ATTR_FEEDID: elem[FEED_ID],
|
|
ATTR_TAG: elem[FEED_TAG],
|
|
ATTR_FEEDNAME: elem[FEED_NAME],
|
|
}
|
|
if elem["value"] is not None:
|
|
self._attr_extra_state_attributes[ATTR_SIZE] = elem["size"]
|
|
self._attr_extra_state_attributes[ATTR_USERID] = elem["userid"]
|
|
self._attr_extra_state_attributes[ATTR_LASTUPDATETIME] = elem["time"]
|
|
self._attr_extra_state_attributes[ATTR_LASTUPDATETIMESTR] = (
|
|
template.timestamp_local(float(elem["time"]))
|
|
)
|
|
|
|
self._attr_native_value = None
|
|
if elem["value"] is not None:
|
|
self._attr_native_value = round(float(elem["value"]), DECIMALS)
|
|
|
|
@callback
|
|
def _handle_coordinator_update(self) -> None:
|
|
"""Handle updated data from the coordinator."""
|
|
data = self.coordinator.data
|
|
if data:
|
|
self._update_attributes(data[self.idx])
|
|
super()._handle_coordinator_update()
|