Add config flow for utility_meter (#68457)
parent
d81ee9b2da
commit
bdb61e0222
|
@ -7,10 +7,12 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant, split_entity_id
|
||||
from homeassistant.helpers import discovery, entity_registry as er
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
|
@ -30,6 +32,8 @@ from .const import (
|
|||
DATA_UTILITY,
|
||||
DOMAIN,
|
||||
METER_TYPES,
|
||||
SERVICE_RESET,
|
||||
SIGNAL_RESET_METER,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -100,6 +104,34 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
|
||||
hass.data[DATA_UTILITY] = {}
|
||||
|
||||
async def async_reset_meters(service_call):
|
||||
"""Reset all sensors of a meter."""
|
||||
entity_id = service_call.data["entity_id"]
|
||||
|
||||
domain = split_entity_id(entity_id)[0]
|
||||
if domain == DOMAIN:
|
||||
for entity in hass.data[DATA_LEGACY_COMPONENT].entities:
|
||||
if entity_id == entity.entity_id:
|
||||
_LOGGER.debug(
|
||||
"forward reset meter from %s to %s",
|
||||
entity_id,
|
||||
entity.tracked_entity_id,
|
||||
)
|
||||
entity_id = entity.tracked_entity_id
|
||||
|
||||
_LOGGER.debug("reset meter %s", entity_id)
|
||||
async_dispatcher_send(hass, SIGNAL_RESET_METER, entity_id)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_RESET,
|
||||
async_reset_meters,
|
||||
vol.Schema({ATTR_ENTITY_ID: cv.entity_id}),
|
||||
)
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
for meter, conf in config[DOMAIN].items():
|
||||
_LOGGER.debug("Setup %s.%s", DOMAIN, meter)
|
||||
|
||||
|
@ -151,3 +183,59 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Utility Meter from a config entry."""
|
||||
entity_registry = er.async_get(hass)
|
||||
hass.data[DATA_UTILITY][entry.entry_id] = {}
|
||||
hass.data[DATA_UTILITY][entry.entry_id][DATA_TARIFF_SENSORS] = []
|
||||
|
||||
try:
|
||||
er.async_validate_entity_id(entity_registry, entry.options[CONF_SOURCE_SENSOR])
|
||||
except vol.Invalid:
|
||||
# The entity is identified by an unknown entity registry ID
|
||||
_LOGGER.error(
|
||||
"Failed to setup utility_meter for unknown entity %s",
|
||||
entry.options[CONF_SOURCE_SENSOR],
|
||||
)
|
||||
return False
|
||||
|
||||
if not entry.options.get(CONF_TARIFFS):
|
||||
# Only a single meter sensor is required
|
||||
hass.data[DATA_UTILITY][entry.entry_id][CONF_TARIFF_ENTITY] = None
|
||||
hass.config_entries.async_setup_platforms(entry, (Platform.SENSOR,))
|
||||
else:
|
||||
# Create tariff selection + one meter sensor for each tariff
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
Platform.SELECT, DOMAIN, entry.entry_id, suggested_object_id=entry.title
|
||||
)
|
||||
hass.data[DATA_UTILITY][entry.entry_id][
|
||||
CONF_TARIFF_ENTITY
|
||||
] = entity_entry.entity_id
|
||||
hass.config_entries.async_setup_platforms(
|
||||
entry, (Platform.SELECT, Platform.SENSOR)
|
||||
)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Update listener, called when the config entry options are changed."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||
entry,
|
||||
(
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
),
|
||||
):
|
||||
hass.data[DATA_UTILITY].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
"""Config flow for Utility Meter integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.helpers.helper_config_entry_flow import (
|
||||
HelperConfigFlowHandler,
|
||||
HelperFlowError,
|
||||
HelperFlowFormStep,
|
||||
HelperFlowMenuStep,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
BIMONTHLY,
|
||||
CONF_METER_DELTA_VALUES,
|
||||
CONF_METER_NET_CONSUMPTION,
|
||||
CONF_METER_OFFSET,
|
||||
CONF_METER_TYPE,
|
||||
CONF_SOURCE_SENSOR,
|
||||
CONF_TARIFFS,
|
||||
DAILY,
|
||||
DOMAIN,
|
||||
HOURLY,
|
||||
MONTHLY,
|
||||
QUARTER_HOURLY,
|
||||
QUARTERLY,
|
||||
WEEKLY,
|
||||
YEARLY,
|
||||
)
|
||||
|
||||
METER_TYPES = [
|
||||
{"value": "none", "label": "No cycle"},
|
||||
{"value": QUARTER_HOURLY, "label": "Every 15 minutes"},
|
||||
{"value": HOURLY, "label": "Hourly"},
|
||||
{"value": DAILY, "label": "Daily"},
|
||||
{"value": WEEKLY, "label": "Weekly"},
|
||||
{"value": MONTHLY, "label": "Monthly"},
|
||||
{"value": BIMONTHLY, "label": "Every two months"},
|
||||
{"value": QUARTERLY, "label": "Quarterly"},
|
||||
{"value": YEARLY, "label": "Yearly"},
|
||||
]
|
||||
|
||||
|
||||
def _validate_config(data: Any) -> Any:
|
||||
"""Validate config."""
|
||||
tariffs: list[str]
|
||||
if not data[CONF_TARIFFS]:
|
||||
tariffs = []
|
||||
else:
|
||||
tariffs = data[CONF_TARIFFS].split(",")
|
||||
try:
|
||||
vol.Unique()(tariffs)
|
||||
except vol.Invalid as exc:
|
||||
raise HelperFlowError("tariffs_not_unique") from exc
|
||||
|
||||
return data
|
||||
|
||||
|
||||
OPTIONS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_SOURCE_SENSOR): selector.selector(
|
||||
{"entity": {"domain": "sensor"}},
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
||||
vol.Required(CONF_SOURCE_SENSOR): selector.selector(
|
||||
{"entity": {"domain": "sensor"}},
|
||||
),
|
||||
vol.Required(CONF_METER_TYPE): selector.selector(
|
||||
{"select": {"options": METER_TYPES}}
|
||||
),
|
||||
vol.Required(CONF_METER_OFFSET, default=0): selector.selector(
|
||||
{
|
||||
"number": {
|
||||
"min": 0,
|
||||
"max": 28,
|
||||
"mode": "box",
|
||||
CONF_UNIT_OF_MEASUREMENT: "days",
|
||||
}
|
||||
}
|
||||
),
|
||||
vol.Optional(CONF_TARIFFS): selector.selector({"text": {}}),
|
||||
vol.Required(CONF_METER_NET_CONSUMPTION, default=False): selector.selector(
|
||||
{"boolean": {}}
|
||||
),
|
||||
vol.Required(CONF_METER_DELTA_VALUES, default=False): selector.selector(
|
||||
{"boolean": {}}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
|
||||
"user": HelperFlowFormStep(CONFIG_SCHEMA, validate_user_input=_validate_config)
|
||||
}
|
||||
|
||||
OPTIONS_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
|
||||
"init": HelperFlowFormStep(OPTIONS_SCHEMA)
|
||||
}
|
||||
|
||||
|
||||
class ConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
|
||||
"""Handle a config or options flow for Utility Meter."""
|
||||
|
||||
config_flow = CONFIG_FLOW
|
||||
options_flow = OPTIONS_FLOW
|
||||
|
||||
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
||||
"""Return config entry title."""
|
||||
|
||||
return cast(str, options[CONF_NAME])
|
|
@ -1,10 +1,18 @@
|
|||
{
|
||||
"domain": "utility_meter",
|
||||
"integration_type": "helper",
|
||||
"name": "Utility Meter",
|
||||
"documentation": "https://www.home-assistant.io/integrations/utility_meter",
|
||||
"requirements": ["croniter==1.0.6"],
|
||||
"codeowners": ["@dgomes"],
|
||||
"requirements": [
|
||||
"croniter==1.0.6"
|
||||
],
|
||||
"codeowners": [
|
||||
"@dgomes"
|
||||
],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["croniter"]
|
||||
"loggers": [
|
||||
"croniter"
|
||||
],
|
||||
"config_flow": true
|
||||
}
|
||||
|
|
|
@ -12,11 +12,12 @@ from homeassistant.components.select.const import (
|
|||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE
|
||||
from homeassistant.core import Event, callback, split_entity_id
|
||||
from homeassistant.core import Event, HomeAssistant, callback, split_entity_id
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
|
@ -26,17 +27,34 @@ from .const import (
|
|||
CONF_METER,
|
||||
CONF_TARIFFS,
|
||||
DATA_LEGACY_COMPONENT,
|
||||
DOMAIN,
|
||||
SERVICE_RESET,
|
||||
SERVICE_SELECT_NEXT_TARIFF,
|
||||
SERVICE_SELECT_TARIFF,
|
||||
SIGNAL_RESET_METER,
|
||||
TARIFF_ICON,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Utility Meter config entry."""
|
||||
name = config_entry.title
|
||||
|
||||
# Remove when frontend list selector is available
|
||||
if not config_entry.options.get(CONF_TARIFFS):
|
||||
tariffs = []
|
||||
else:
|
||||
tariffs = config_entry.options[CONF_TARIFFS].split(",")
|
||||
|
||||
legacy_add_entities = None
|
||||
unique_id = config_entry.entry_id
|
||||
tariff_select = TariffSelect(name, tariffs, legacy_add_entities, unique_id)
|
||||
async_add_entities([tariff_select])
|
||||
|
||||
|
||||
async def async_setup_platform(hass, conf, async_add_entities, discovery_info=None):
|
||||
"""Set up the utility meter select."""
|
||||
legacy_component = hass.data[DATA_LEGACY_COMPONENT]
|
||||
|
@ -46,35 +64,11 @@ async def async_setup_platform(hass, conf, async_add_entities, discovery_info=No
|
|||
discovery_info[CONF_METER],
|
||||
discovery_info[CONF_TARIFFS],
|
||||
legacy_component.async_add_entities,
|
||||
None,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
async def async_reset_meters(service_call):
|
||||
"""Reset all sensors of a meter."""
|
||||
entity_id = service_call.data["entity_id"]
|
||||
|
||||
domain = split_entity_id(entity_id)[0]
|
||||
if domain == DOMAIN:
|
||||
for entity in legacy_component.entities:
|
||||
if entity_id == entity.entity_id:
|
||||
_LOGGER.debug(
|
||||
"forward reset meter from %s to %s",
|
||||
entity_id,
|
||||
entity.tracked_entity_id,
|
||||
)
|
||||
entity_id = entity.tracked_entity_id
|
||||
|
||||
_LOGGER.debug("reset meter %s", entity_id)
|
||||
async_dispatcher_send(hass, SIGNAL_RESET_METER, entity_id)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_RESET,
|
||||
async_reset_meters,
|
||||
vol.Schema({ATTR_ENTITY_ID: cv.entity_id}),
|
||||
)
|
||||
|
||||
legacy_component.async_register_entity_service(
|
||||
SERVICE_SELECT_TARIFF,
|
||||
{vol.Required(ATTR_TARIFF): cv.string},
|
||||
|
@ -89,9 +83,10 @@ async def async_setup_platform(hass, conf, async_add_entities, discovery_info=No
|
|||
class TariffSelect(SelectEntity, RestoreEntity):
|
||||
"""Representation of a Tariff selector."""
|
||||
|
||||
def __init__(self, name, tariffs, add_legacy_entities):
|
||||
def __init__(self, name, tariffs, add_legacy_entities, unique_id):
|
||||
"""Initialize a tariff selector."""
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
self._current_tariff = None
|
||||
self._tariffs = tariffs
|
||||
self._attr_icon = TARIFF_ICON
|
||||
|
@ -112,7 +107,8 @@ class TariffSelect(SelectEntity, RestoreEntity):
|
|||
"""Run when entity about to be added."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
await self._add_legacy_entities([LegacyTariffSelect(self.entity_id)])
|
||||
if self._add_legacy_entities:
|
||||
await self._add_legacy_entities([LegacyTariffSelect(self.entity_id)])
|
||||
|
||||
state = await self.async_get_last_state()
|
||||
if not state or state.state not in self._tariffs:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Utility meter from sensors providing raw data."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal, DecimalException, InvalidOperation
|
||||
import logging
|
||||
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
|
|||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_NAME,
|
||||
|
@ -24,7 +25,7 @@ from homeassistant.const import (
|
|||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers import entity_platform, entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import (
|
||||
|
@ -49,6 +50,7 @@ from .const import (
|
|||
CONF_SOURCE_SENSOR,
|
||||
CONF_TARIFF,
|
||||
CONF_TARIFF_ENTITY,
|
||||
CONF_TARIFFS,
|
||||
DAILY,
|
||||
DATA_TARIFF_SENSORS,
|
||||
DATA_UTILITY,
|
||||
|
@ -93,6 +95,84 @@ PAUSED = "paused"
|
|||
COLLECTING = "collecting"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Utility Meter config entry."""
|
||||
entry_id = config_entry.entry_id
|
||||
registry = er.async_get(hass)
|
||||
# Validate + resolve entity registry id to entity_id
|
||||
source_entity_id = er.async_validate_entity_id(
|
||||
registry, config_entry.options[CONF_SOURCE_SENSOR]
|
||||
)
|
||||
|
||||
cron_pattern = None
|
||||
delta_values = config_entry.options[CONF_METER_DELTA_VALUES]
|
||||
meter_offset = timedelta(days=config_entry.options[CONF_METER_OFFSET])
|
||||
meter_type = config_entry.options[CONF_METER_TYPE]
|
||||
if meter_type == "none":
|
||||
meter_type = None
|
||||
name = config_entry.title
|
||||
net_consumption = config_entry.options[CONF_METER_NET_CONSUMPTION]
|
||||
tariff_entity = hass.data[DATA_UTILITY][entry_id][CONF_TARIFF_ENTITY]
|
||||
|
||||
meters = []
|
||||
|
||||
# Remove when frontend list selector is available
|
||||
if not config_entry.options.get(CONF_TARIFFS):
|
||||
tariffs = []
|
||||
else:
|
||||
tariffs = config_entry.options[CONF_TARIFFS].split(",")
|
||||
|
||||
if not tariffs:
|
||||
# Add single sensor, not gated by a tariff selector
|
||||
meter_sensor = UtilityMeterSensor(
|
||||
cron_pattern=cron_pattern,
|
||||
delta_values=delta_values,
|
||||
meter_offset=meter_offset,
|
||||
meter_type=meter_type,
|
||||
name=name,
|
||||
net_consumption=net_consumption,
|
||||
parent_meter=entry_id,
|
||||
source_entity=source_entity_id,
|
||||
tariff_entity=tariff_entity,
|
||||
tariff=None,
|
||||
unique_id=entry_id,
|
||||
)
|
||||
meters.append(meter_sensor)
|
||||
hass.data[DATA_UTILITY][entry_id][DATA_TARIFF_SENSORS].append(meter_sensor)
|
||||
else:
|
||||
# Add sensors for each tariff
|
||||
for tariff in tariffs:
|
||||
meter_sensor = UtilityMeterSensor(
|
||||
cron_pattern=cron_pattern,
|
||||
delta_values=delta_values,
|
||||
meter_offset=meter_offset,
|
||||
meter_type=meter_type,
|
||||
name=f"{name} {tariff}",
|
||||
net_consumption=net_consumption,
|
||||
parent_meter=entry_id,
|
||||
source_entity=source_entity_id,
|
||||
tariff_entity=tariff_entity,
|
||||
tariff=tariff,
|
||||
unique_id=f"{entry_id}_{tariff}",
|
||||
)
|
||||
meters.append(meter_sensor)
|
||||
hass.data[DATA_UTILITY][entry_id][DATA_TARIFF_SENSORS].append(meter_sensor)
|
||||
|
||||
async_add_entities(meters)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_CALIBRATE_METER,
|
||||
{vol.Required(ATTR_VALUE): vol.Coerce(Decimal)},
|
||||
"async_calibrate",
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
|
@ -121,16 +201,17 @@ async def async_setup_platform(
|
|||
)
|
||||
conf_cron_pattern = hass.data[DATA_UTILITY][meter].get(CONF_CRON_PATTERN)
|
||||
meter_sensor = UtilityMeterSensor(
|
||||
meter,
|
||||
conf_meter_source,
|
||||
conf.get(CONF_NAME),
|
||||
conf_meter_type,
|
||||
conf_meter_offset,
|
||||
conf_meter_delta_values,
|
||||
conf_meter_net_consumption,
|
||||
conf.get(CONF_TARIFF),
|
||||
conf_meter_tariff_entity,
|
||||
conf_cron_pattern,
|
||||
cron_pattern=conf_cron_pattern,
|
||||
delta_values=conf_meter_delta_values,
|
||||
meter_offset=conf_meter_offset,
|
||||
meter_type=conf_meter_type,
|
||||
name=conf.get(CONF_NAME),
|
||||
net_consumption=conf_meter_net_consumption,
|
||||
parent_meter=meter,
|
||||
source_entity=conf_meter_source,
|
||||
tariff_entity=conf_meter_tariff_entity,
|
||||
tariff=conf.get(CONF_TARIFF),
|
||||
unique_id=None,
|
||||
)
|
||||
meters.append(meter_sensor)
|
||||
|
||||
|
@ -152,18 +233,21 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cron_pattern,
|
||||
delta_values,
|
||||
meter_offset,
|
||||
meter_type,
|
||||
name,
|
||||
net_consumption,
|
||||
parent_meter,
|
||||
source_entity,
|
||||
name,
|
||||
meter_type,
|
||||
meter_offset,
|
||||
delta_values,
|
||||
net_consumption,
|
||||
tariff=None,
|
||||
tariff_entity=None,
|
||||
cron_pattern=None,
|
||||
tariff_entity,
|
||||
tariff,
|
||||
unique_id,
|
||||
):
|
||||
"""Initialize the Utility Meter sensor."""
|
||||
self._attr_unique_id = unique_id
|
||||
self._parent_meter = parent_meter
|
||||
self._sensor_source_id = source_entity
|
||||
self._state = None
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "New Utility Meter",
|
||||
"description": "The utility meter sensor provides functionality to track consumptions of various utilities (e.g., energy, gas, water, heating) over a configured period of time, typically monthly. The utility meter sensor also supports splitting the consumption by tariffs.\nMeter reset offset allows offsetting the day of monthly meter reset.\nSupported tariffs is a comma separated list of supported tariffs, leave empty if only a single tariff is needed.",
|
||||
"data": {
|
||||
"cycle": "Meter reset cycle",
|
||||
"delta_values": "Delta values",
|
||||
"name": "Name",
|
||||
"net_consumption": "Net consumption",
|
||||
"offset": "Meter reset offset",
|
||||
"source": "Input sensor",
|
||||
"tariffs": "Supported tariffs"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"source": "[%key:component::utility_meter::config::step::user::data::source%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"cycle": "Meter reset cycle",
|
||||
"delta_values": "Delta values",
|
||||
"name": "Name",
|
||||
"net_consumption": "Net consumption",
|
||||
"offset": "Meter reset offset",
|
||||
"source": "Input sensor",
|
||||
"tariffs": "Supported tariffs"
|
||||
},
|
||||
"description": "The utility meter sensor provides functionality to track consumptions of various utilities (e.g., energy, gas, water, heating) over a configured period of time, typically monthly. The utility meter sensor also supports splitting the consumption by tariffs.\nMeter reset offset allows offsetting the day of monthly meter reset.\nSupported tariffs is a comma separated list of supported tariffs, leave empty if only a single tariff is needed.",
|
||||
"title": "New Utility Meter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"source": "Input sensor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -405,6 +405,7 @@ FLOWS = {
|
|||
"min_max",
|
||||
"switch_as_x",
|
||||
"threshold",
|
||||
"tod"
|
||||
"tod",
|
||||
"utility_meter"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
"""Test the Utility Meter config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.utility_meter.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ("sensor",))
|
||||
async def test_config_flow(hass: HomeAssistant, platform) -> None:
|
||||
"""Test the config flow."""
|
||||
input_sensor_entity_id = "sensor.input"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.utility_meter.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"cycle": "monthly",
|
||||
"name": "Electricity meter",
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "Electricity meter"
|
||||
assert result["data"] == {}
|
||||
assert result["options"] == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "",
|
||||
}
|
||||
assert config_entry.title == "Electricity meter"
|
||||
|
||||
|
||||
async def test_tariffs(hass: HomeAssistant) -> None:
|
||||
"""Test tariffs."""
|
||||
input_sensor_entity_id = "sensor.input"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"cycle": "monthly",
|
||||
"name": "Electricity meter",
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "cat,dog,horse,cow",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "Electricity meter"
|
||||
assert result["data"] == {}
|
||||
assert result["options"] == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "cat,dog,horse,cow",
|
||||
}
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "cat,dog,horse,cow",
|
||||
}
|
||||
assert config_entry.title == "Electricity meter"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"cycle": "monthly",
|
||||
"name": "Electricity meter",
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": "cat,cat,cat,cat",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"]["base"] == "tariffs_not_unique"
|
||||
|
||||
|
||||
def get_suggested(schema, key):
|
||||
"""Get suggested value for key in voluptuous schema."""
|
||||
for k in schema.keys():
|
||||
if k == key:
|
||||
if k.description is None or "suggested_value" not in k.description:
|
||||
return None
|
||||
return k.description["suggested_value"]
|
||||
# Wanted key absent from schema
|
||||
raise Exception
|
||||
|
||||
|
||||
async def test_options(hass: HomeAssistant) -> None:
|
||||
"""Test reconfiguring."""
|
||||
input_sensor1_entity_id = "sensor.input1"
|
||||
input_sensor2_entity_id = "sensor.input2"
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor1_entity_id,
|
||||
"tariffs": "",
|
||||
},
|
||||
title="Electricity meter",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
schema = result["data_schema"].schema
|
||||
assert get_suggested(schema, "source") == input_sensor1_entity_id
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"source": input_sensor2_entity_id},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor2_entity_id,
|
||||
"tariffs": "",
|
||||
}
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor2_entity_id,
|
||||
"tariffs": "",
|
||||
}
|
||||
assert config_entry.title == "Electricity meter"
|
||||
|
||||
# Check config entry is reloaded with new options
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("sensor.electricity_meter")
|
||||
assert state.attributes["source"] == input_sensor2_entity_id
|
|
@ -1,7 +1,11 @@
|
|||
"""The tests for the utility_meter component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select.const import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
|
@ -22,12 +26,14 @@ from homeassistant.const import (
|
|||
EVENT_HOMEASSISTANT_START,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.exceptions import ServiceNotFound
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import mock_restore_cache
|
||||
from tests.common import MockConfigEntry, mock_restore_cache
|
||||
|
||||
|
||||
async def test_restore_state(hass):
|
||||
|
@ -168,8 +174,138 @@ async def test_services(hass):
|
|||
assert state.state == "4"
|
||||
|
||||
|
||||
async def test_services_config_entry(hass):
|
||||
"""Test energy sensor reset service."""
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "peak,offpeak",
|
||||
},
|
||||
title="Energy bill",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill2",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "peak,offpeak",
|
||||
},
|
||||
title="Energy bill2",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
entity_id = "sensor.energy"
|
||||
hass.states.async_set(
|
||||
entity_id, 1, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
now = dt_util.utcnow() + timedelta(seconds=10)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
3,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_peak")
|
||||
assert state.state == "2"
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_offpeak")
|
||||
assert state.state == "0"
|
||||
|
||||
# Next tariff - only supported on legacy entity
|
||||
with pytest.raises(ServiceNotFound):
|
||||
data = {ATTR_ENTITY_ID: "utility_meter.energy_bill"}
|
||||
await hass.services.async_call(DOMAIN, SERVICE_SELECT_NEXT_TARIFF, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Change tariff
|
||||
data = {ATTR_ENTITY_ID: "select.energy_bill", "option": "offpeak"}
|
||||
await hass.services.async_call(SELECT_DOMAIN, SERVICE_SELECT_OPTION, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
now += timedelta(seconds=10)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
4,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_peak")
|
||||
assert state.state == "2"
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_offpeak")
|
||||
assert state.state == "1"
|
||||
|
||||
# Change tariff
|
||||
data = {ATTR_ENTITY_ID: "select.energy_bill", "option": "wrong_tariff"}
|
||||
await hass.services.async_call(SELECT_DOMAIN, SERVICE_SELECT_OPTION, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Inexisting tariff, ignoring
|
||||
assert hass.states.get("select.energy_bill").state != "wrong_tariff"
|
||||
|
||||
data = {ATTR_ENTITY_ID: "select.energy_bill", "option": "peak"}
|
||||
await hass.services.async_call(SELECT_DOMAIN, SERVICE_SELECT_OPTION, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
now += timedelta(seconds=10)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
5,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_peak")
|
||||
assert state.state == "3"
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_offpeak")
|
||||
assert state.state == "1"
|
||||
|
||||
# Reset meters
|
||||
data = {ATTR_ENTITY_ID: "select.energy_bill"}
|
||||
await hass.services.async_call(DOMAIN, SERVICE_RESET, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_peak")
|
||||
assert state.state == "0"
|
||||
|
||||
state = hass.states.get("sensor.energy_bill_offpeak")
|
||||
assert state.state == "0"
|
||||
|
||||
# meanwhile energy_bill2_peak accumulated all kWh
|
||||
state = hass.states.get("sensor.energy_bill2_peak")
|
||||
assert state.state == "4"
|
||||
|
||||
|
||||
async def test_cron(hass, legacy_patchable_time):
|
||||
"""Test cron pattern and offset fails."""
|
||||
"""Test cron pattern."""
|
||||
|
||||
config = {
|
||||
"utility_meter": {
|
||||
|
@ -327,3 +463,61 @@ async def test_legacy_support(hass):
|
|||
await hass.services.async_call(DOMAIN, SERVICE_RESET, data)
|
||||
await hass.async_block_till_done()
|
||||
assert reset_calls == ["select.energy_bill"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tariffs,expected_entities",
|
||||
(
|
||||
(
|
||||
"",
|
||||
["sensor.electricity_meter"],
|
||||
),
|
||||
(
|
||||
"high,low",
|
||||
[
|
||||
"sensor.electricity_meter_low",
|
||||
"sensor.electricity_meter_high",
|
||||
"select.electricity_meter",
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_setup_and_remove_config_entry(
|
||||
hass: HomeAssistant, tariffs: str, expected_entities: list[str]
|
||||
) -> None:
|
||||
"""Test setting up and removing a config entry."""
|
||||
input_sensor_entity_id = "sensor.input"
|
||||
registry = er.async_get(hass)
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"cycle": "monthly",
|
||||
"delta_values": False,
|
||||
"name": "Electricity meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": input_sensor_entity_id,
|
||||
"tariffs": tariffs,
|
||||
},
|
||||
title="Electricity meter",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == len(expected_entities)
|
||||
assert len(registry.entities) == len(expected_entities)
|
||||
for entity in expected_entities:
|
||||
assert hass.states.get(entity)
|
||||
assert entity in registry.entities
|
||||
|
||||
# Remove the config entry
|
||||
assert await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check the state and entity registry entry are removed
|
||||
assert len(hass.states.async_all()) == 0
|
||||
assert len(registry.entities) == 0
|
||||
|
|
|
@ -3,6 +3,8 @@ from contextlib import contextmanager
|
|||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select.const import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
|
@ -39,7 +41,7 @@ from homeassistant.core import State
|
|||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import async_fire_time_changed, mock_restore_cache
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, mock_restore_cache
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
@ -52,24 +54,55 @@ def alter_time(retval):
|
|||
yield
|
||||
|
||||
|
||||
async def test_state(hass):
|
||||
"""Test utility sensor state."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
"tariffs": "onpeak,midpeak,offpeak",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_state(hass, yaml_config, config_entry_config):
|
||||
"""Test utility sensor state."""
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = yaml_config[DOMAIN]["energy_bill"]["source"]
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = config_entry_config["source"]
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, 2, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -175,7 +208,6 @@ async def test_state(hass):
|
|||
assert state.state == "0.123"
|
||||
|
||||
# test invalid state
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, "*", {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -185,7 +217,6 @@ async def test_state(hass):
|
|||
assert state.state == "0.123"
|
||||
|
||||
# test unavailable source
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -195,19 +226,51 @@ async def test_state(hass):
|
|||
assert state.state == "0.123"
|
||||
|
||||
|
||||
async def test_init(hass):
|
||||
"""Test utility sensor state initializtion."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
"tariffs": "onpeak,midpeak,offpeak",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_init(hass, yaml_config, config_entry_config):
|
||||
"""Test utility sensor state initializtion."""
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = yaml_config[DOMAIN]["energy_bill"]["source"]
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = config_entry_config["source"]
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -220,7 +283,6 @@ async def test_init(hass):
|
|||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, 2, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -238,31 +300,74 @@ async def test_init(hass):
|
|||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
|
||||
|
||||
|
||||
async def test_device_class(hass):
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_configs",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_meter": {
|
||||
"source": "sensor.energy",
|
||||
"net_consumption": True,
|
||||
},
|
||||
"gas_meter": {
|
||||
"source": "sensor.gas",
|
||||
},
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
[
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy meter",
|
||||
"net_consumption": True,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "",
|
||||
},
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Gas meter",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.gas",
|
||||
"tariffs": "",
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_device_class(hass, yaml_config, config_entry_configs):
|
||||
"""Test utility device_class."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_meter": {
|
||||
"source": "sensor.energy",
|
||||
"net_consumption": True,
|
||||
},
|
||||
"gas_meter": {
|
||||
"source": "sensor.gas",
|
||||
},
|
||||
}
|
||||
}
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
else:
|
||||
for config_entry_config in config_entry_configs:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id_energy = "sensor.energy"
|
||||
entity_id_gas = "sensor.gas"
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id_energy = config[DOMAIN]["energy_meter"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id_energy, 2, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
entity_id_gas = config[DOMAIN]["gas_meter"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id_gas, 2, {ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit"}
|
||||
)
|
||||
|
@ -283,17 +388,37 @@ async def test_device_class(hass):
|
|||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "some_archaic_unit"
|
||||
|
||||
|
||||
async def test_restore_state(hass):
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "onpeak,midpeak,offpeak",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_restore_state(hass, yaml_config, config_entry_config):
|
||||
"""Test utility sensor restore state."""
|
||||
last_reset = "2020-12-21T00:00:00.013073+00:00"
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"source": "sensor.energy",
|
||||
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
[
|
||||
|
@ -322,8 +447,19 @@ async def test_restore_state(hass):
|
|||
],
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# restore from cache
|
||||
state = hass.states.get("sensor.energy_bill_onpeak")
|
||||
|
@ -355,19 +491,53 @@ async def test_restore_state(hass):
|
|||
assert state.attributes.get("status") == PAUSED
|
||||
|
||||
|
||||
async def test_net_consumption(hass):
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"net_consumption": True,
|
||||
"source": "sensor.energy",
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": True,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_net_consumption(hass, yaml_config, config_entry_config):
|
||||
"""Test utility sensor state."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {"source": "sensor.energy", "net_consumption": True}
|
||||
}
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = yaml_config[DOMAIN]["energy_bill"]["source"]
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = config_entry_config["source"]
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, 2, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -389,19 +559,53 @@ async def test_net_consumption(hass):
|
|||
assert state.state == "-1"
|
||||
|
||||
|
||||
async def test_non_net_consumption(hass):
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"net_consumption": False,
|
||||
"source": "sensor.energy",
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": False,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_non_net_consumption(hass, yaml_config, config_entry_config):
|
||||
"""Test utility sensor state."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {"source": "sensor.energy", "net_consumption": False}
|
||||
}
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = yaml_config[DOMAIN]["energy_bill"]["source"]
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = config_entry_config["source"]
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
hass.states.async_set(
|
||||
entity_id, 2, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}
|
||||
)
|
||||
|
@ -423,21 +627,55 @@ async def test_non_net_consumption(hass):
|
|||
assert state.state == "0"
|
||||
|
||||
|
||||
async def test_delta_values(hass):
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_config,config_entry_config",
|
||||
(
|
||||
(
|
||||
{
|
||||
"utility_meter": {
|
||||
"energy_bill": {
|
||||
"delta_values": True,
|
||||
"source": "sensor.energy",
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"cycle": "none",
|
||||
"delta_values": True,
|
||||
"name": "Energy bill",
|
||||
"net_consumption": False,
|
||||
"offset": 0,
|
||||
"source": "sensor.energy",
|
||||
"tariffs": "",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_delta_values(hass, yaml_config, config_entry_config):
|
||||
"""Test utility meter "delta_values" mode."""
|
||||
config = {
|
||||
"utility_meter": {
|
||||
"energy_bill": {"source": "sensor.energy", "delta_values": True}
|
||||
}
|
||||
}
|
||||
|
||||
now = dt_util.utcnow()
|
||||
with alter_time(now):
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
if yaml_config:
|
||||
assert await async_setup_component(hass, DOMAIN, yaml_config)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = yaml_config[DOMAIN]["energy_bill"]["source"]
|
||||
else:
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=config_entry_config,
|
||||
title=config_entry_config["name"],
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_id = config_entry_config["source"]
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
entity_id = config[DOMAIN]["energy_bill"]["source"]
|
||||
|
||||
async_fire_time_changed(hass, now)
|
||||
hass.states.async_set(
|
||||
|
|
Loading…
Reference in New Issue