mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
Add "cron patterns" to define utility_meter cycles (#46795)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
2f7a7b0309
commit
fb28665cfa
@ -2,6 +2,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from croniter import croniter
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
@ -14,6 +15,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_TARIFF,
|
ATTR_TARIFF,
|
||||||
|
CONF_CRON_PATTERN,
|
||||||
CONF_METER,
|
CONF_METER,
|
||||||
CONF_METER_NET_CONSUMPTION,
|
CONF_METER_NET_CONSUMPTION,
|
||||||
CONF_METER_OFFSET,
|
CONF_METER_OFFSET,
|
||||||
@ -40,17 +42,45 @@ ATTR_TARIFFS = "tariffs"
|
|||||||
DEFAULT_OFFSET = timedelta(hours=0)
|
DEFAULT_OFFSET = timedelta(hours=0)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_cron_pattern(pattern):
|
||||||
|
"""Check that the pattern is well-formed."""
|
||||||
|
if croniter.is_valid(pattern):
|
||||||
|
return pattern
|
||||||
|
raise vol.Invalid("Invalid pattern")
|
||||||
|
|
||||||
|
|
||||||
|
def period_or_cron(config):
|
||||||
|
"""Check that if cron pattern is used, then meter type and offsite must be removed."""
|
||||||
|
if CONF_CRON_PATTERN in config and CONF_METER_TYPE in config:
|
||||||
|
raise vol.Invalid(f"Use <{CONF_CRON_PATTERN}> or <{CONF_METER_TYPE}>")
|
||||||
|
if (
|
||||||
|
CONF_CRON_PATTERN in config
|
||||||
|
and CONF_METER_OFFSET in config
|
||||||
|
and config[CONF_METER_OFFSET] != DEFAULT_OFFSET
|
||||||
|
):
|
||||||
|
raise vol.Invalid(
|
||||||
|
f"When <{CONF_CRON_PATTERN}> is used <{CONF_METER_OFFSET}> has no meaning"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
METER_CONFIG_SCHEMA = vol.Schema(
|
METER_CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
vol.All(
|
||||||
vol.Required(CONF_SOURCE_SENSOR): cv.entity_id,
|
{
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
vol.Required(CONF_SOURCE_SENSOR): cv.entity_id,
|
||||||
vol.Optional(CONF_METER_TYPE): vol.In(METER_TYPES),
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
vol.Optional(CONF_METER_OFFSET, default=DEFAULT_OFFSET): vol.All(
|
vol.Optional(CONF_METER_TYPE): vol.In(METER_TYPES),
|
||||||
cv.time_period, cv.positive_timedelta
|
vol.Optional(CONF_METER_OFFSET, default=DEFAULT_OFFSET): vol.All(
|
||||||
),
|
cv.time_period, cv.positive_timedelta
|
||||||
vol.Optional(CONF_METER_NET_CONSUMPTION, default=False): cv.boolean,
|
),
|
||||||
vol.Optional(CONF_TARIFFS, default=[]): vol.All(cv.ensure_list, [cv.string]),
|
vol.Optional(CONF_METER_NET_CONSUMPTION, default=False): cv.boolean,
|
||||||
}
|
vol.Optional(CONF_TARIFFS, default=[]): vol.All(
|
||||||
|
cv.ensure_list, [cv.string]
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_CRON_PATTERN): validate_cron_pattern,
|
||||||
|
},
|
||||||
|
period_or_cron,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
@ -32,9 +32,11 @@ CONF_PAUSED = "paused"
|
|||||||
CONF_TARIFFS = "tariffs"
|
CONF_TARIFFS = "tariffs"
|
||||||
CONF_TARIFF = "tariff"
|
CONF_TARIFF = "tariff"
|
||||||
CONF_TARIFF_ENTITY = "tariff_entity"
|
CONF_TARIFF_ENTITY = "tariff_entity"
|
||||||
|
CONF_CRON_PATTERN = "cron"
|
||||||
|
|
||||||
ATTR_TARIFF = "tariff"
|
ATTR_TARIFF = "tariff"
|
||||||
ATTR_VALUE = "value"
|
ATTR_VALUE = "value"
|
||||||
|
ATTR_CRON_PATTERN = "cron pattern"
|
||||||
|
|
||||||
SIGNAL_START_PAUSE_METER = "utility_meter_start_pause"
|
SIGNAL_START_PAUSE_METER = "utility_meter_start_pause"
|
||||||
SIGNAL_RESET_METER = "utility_meter_reset"
|
SIGNAL_RESET_METER = "utility_meter_reset"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"domain": "utility_meter",
|
"domain": "utility_meter",
|
||||||
"name": "Utility Meter",
|
"name": "Utility Meter",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/utility_meter",
|
"documentation": "https://www.home-assistant.io/integrations/utility_meter",
|
||||||
|
"requirements": ["croniter==1.0.6"],
|
||||||
"codeowners": ["@dgomes"],
|
"codeowners": ["@dgomes"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Utility meter from sensors providing raw data."""
|
"""Utility meter from sensors providing raw data."""
|
||||||
from datetime import date, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from decimal import Decimal, DecimalException
|
from decimal import Decimal, DecimalException
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from croniter import croniter
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
@ -25,6 +26,7 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
|
async_track_point_in_time,
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
async_track_time_change,
|
async_track_time_change,
|
||||||
)
|
)
|
||||||
@ -32,8 +34,10 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
|||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_CRON_PATTERN,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
BIMONTHLY,
|
BIMONTHLY,
|
||||||
|
CONF_CRON_PATTERN,
|
||||||
CONF_METER,
|
CONF_METER,
|
||||||
CONF_METER_NET_CONSUMPTION,
|
CONF_METER_NET_CONSUMPTION,
|
||||||
CONF_METER_OFFSET,
|
CONF_METER_OFFSET,
|
||||||
@ -91,6 +95,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
conf_meter_tariff_entity = hass.data[DATA_UTILITY][meter].get(
|
conf_meter_tariff_entity = hass.data[DATA_UTILITY][meter].get(
|
||||||
CONF_TARIFF_ENTITY
|
CONF_TARIFF_ENTITY
|
||||||
)
|
)
|
||||||
|
conf_cron_pattern = hass.data[DATA_UTILITY][meter].get(CONF_CRON_PATTERN)
|
||||||
|
|
||||||
meters.append(
|
meters.append(
|
||||||
UtilityMeterSensor(
|
UtilityMeterSensor(
|
||||||
@ -101,6 +106,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
conf_meter_net_consumption,
|
conf_meter_net_consumption,
|
||||||
conf.get(CONF_TARIFF),
|
conf.get(CONF_TARIFF),
|
||||||
conf_meter_tariff_entity,
|
conf_meter_tariff_entity,
|
||||||
|
conf_cron_pattern,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,6 +133,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||||||
net_consumption,
|
net_consumption,
|
||||||
tariff=None,
|
tariff=None,
|
||||||
tariff_entity=None,
|
tariff_entity=None,
|
||||||
|
cron_pattern=None,
|
||||||
):
|
):
|
||||||
"""Initialize the Utility Meter sensor."""
|
"""Initialize the Utility Meter sensor."""
|
||||||
self._sensor_source_id = source_entity
|
self._sensor_source_id = source_entity
|
||||||
@ -141,6 +148,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||||||
self._unit_of_measurement = None
|
self._unit_of_measurement = None
|
||||||
self._period = meter_type
|
self._period = meter_type
|
||||||
self._period_offset = meter_offset
|
self._period_offset = meter_offset
|
||||||
|
self._cron_pattern = cron_pattern
|
||||||
self._sensor_net_consumption = net_consumption
|
self._sensor_net_consumption = net_consumption
|
||||||
self._tariff = tariff
|
self._tariff = tariff
|
||||||
self._tariff_entity = tariff_entity
|
self._tariff_entity = tariff_entity
|
||||||
@ -207,29 +215,37 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||||||
async def _async_reset_meter(self, event):
|
async def _async_reset_meter(self, event):
|
||||||
"""Determine cycle - Helper function for larger than daily cycles."""
|
"""Determine cycle - Helper function for larger than daily cycles."""
|
||||||
now = dt_util.now().date()
|
now = dt_util.now().date()
|
||||||
if (
|
if self._cron_pattern is not None:
|
||||||
|
async_track_point_in_time(
|
||||||
|
self.hass,
|
||||||
|
self._async_reset_meter,
|
||||||
|
croniter(self._cron_pattern, dt_util.now()).get_next(datetime),
|
||||||
|
)
|
||||||
|
elif (
|
||||||
self._period == WEEKLY
|
self._period == WEEKLY
|
||||||
and now != now - timedelta(days=now.weekday()) + self._period_offset
|
and now != now - timedelta(days=now.weekday()) + self._period_offset
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if (
|
elif (
|
||||||
self._period == MONTHLY
|
self._period == MONTHLY
|
||||||
and now != date(now.year, now.month, 1) + self._period_offset
|
and now != date(now.year, now.month, 1) + self._period_offset
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if (
|
elif (
|
||||||
self._period == BIMONTHLY
|
self._period == BIMONTHLY
|
||||||
and now
|
and now
|
||||||
!= date(now.year, (((now.month - 1) // 2) * 2 + 1), 1) + self._period_offset
|
!= date(now.year, (((now.month - 1) // 2) * 2 + 1), 1) + self._period_offset
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if (
|
elif (
|
||||||
self._period == QUARTERLY
|
self._period == QUARTERLY
|
||||||
and now
|
and now
|
||||||
!= date(now.year, (((now.month - 1) // 3) * 3 + 1), 1) + self._period_offset
|
!= date(now.year, (((now.month - 1) // 3) * 3 + 1), 1) + self._period_offset
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self._period == YEARLY and now != date(now.year, 1, 1) + self._period_offset:
|
elif (
|
||||||
|
self._period == YEARLY and now != date(now.year, 1, 1) + self._period_offset
|
||||||
|
):
|
||||||
return
|
return
|
||||||
await self.async_reset_meter(self._tariff_entity)
|
await self.async_reset_meter(self._tariff_entity)
|
||||||
|
|
||||||
@ -253,7 +269,13 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||||||
"""Handle entity which will be added."""
|
"""Handle entity which will be added."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
if self._period == QUARTER_HOURLY:
|
if self._cron_pattern is not None:
|
||||||
|
async_track_point_in_time(
|
||||||
|
self.hass,
|
||||||
|
self._async_reset_meter,
|
||||||
|
croniter(self._cron_pattern, dt_util.now()).get_next(datetime),
|
||||||
|
)
|
||||||
|
elif self._period == QUARTER_HOURLY:
|
||||||
for quarter in range(4):
|
for quarter in range(4):
|
||||||
async_track_time_change(
|
async_track_time_change(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -360,6 +382,8 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||||||
}
|
}
|
||||||
if self._period is not None:
|
if self._period is not None:
|
||||||
state_attr[ATTR_PERIOD] = self._period
|
state_attr[ATTR_PERIOD] = self._period
|
||||||
|
if self._cron_pattern is not None:
|
||||||
|
state_attr[ATTR_CRON_PATTERN] = self._cron_pattern
|
||||||
if self._tariff is not None:
|
if self._tariff is not None:
|
||||||
state_attr[ATTR_TARIFF] = self._tariff
|
state_attr[ATTR_TARIFF] = self._tariff
|
||||||
return state_attr
|
return state_attr
|
||||||
|
@ -486,6 +486,9 @@ construct==2.10.56
|
|||||||
# homeassistant.components.coronavirus
|
# homeassistant.components.coronavirus
|
||||||
coronavirus==1.1.1
|
coronavirus==1.1.1
|
||||||
|
|
||||||
|
# homeassistant.components.utility_meter
|
||||||
|
croniter==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.datadog
|
# homeassistant.components.datadog
|
||||||
datadog==0.15.0
|
datadog==0.15.0
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ responses==0.12.0
|
|||||||
respx==0.17.0
|
respx==0.17.0
|
||||||
stdlib-list==0.7.0
|
stdlib-list==0.7.0
|
||||||
tqdm==4.49.0
|
tqdm==4.49.0
|
||||||
|
types-croniter==1.0.0
|
||||||
types-backports==0.1.3
|
types-backports==0.1.3
|
||||||
types-certifi==0.1.4
|
types-certifi==0.1.4
|
||||||
types-chardet==0.1.5
|
types-chardet==0.1.5
|
||||||
|
@ -282,6 +282,9 @@ construct==2.10.56
|
|||||||
# homeassistant.components.coronavirus
|
# homeassistant.components.coronavirus
|
||||||
coronavirus==1.1.1
|
coronavirus==1.1.1
|
||||||
|
|
||||||
|
# homeassistant.components.utility_meter
|
||||||
|
croniter==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.datadog
|
# homeassistant.components.datadog
|
||||||
datadog==0.15.0
|
datadog==0.15.0
|
||||||
|
|
||||||
|
@ -10,15 +10,49 @@ from homeassistant.components.utility_meter.const import (
|
|||||||
SERVICE_SELECT_NEXT_TARIFF,
|
SERVICE_SELECT_NEXT_TARIFF,
|
||||||
SERVICE_SELECT_TARIFF,
|
SERVICE_SELECT_TARIFF,
|
||||||
)
|
)
|
||||||
|
import homeassistant.components.utility_meter.sensor as um_sensor
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_PLATFORM,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from tests.common import mock_restore_cache
|
||||||
|
|
||||||
|
|
||||||
|
async def test_restore_state(hass):
|
||||||
|
"""Test utility sensor restore state."""
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {
|
||||||
|
"source": "sensor.energy",
|
||||||
|
"tariffs": ["onpeak", "midpeak", "offpeak"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock_restore_cache(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
State(
|
||||||
|
"utility_meter.energy_bill",
|
||||||
|
"midpeak",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# restore from cache
|
||||||
|
state = hass.states.get("utility_meter.energy_bill")
|
||||||
|
assert state.state == "midpeak"
|
||||||
|
|
||||||
|
|
||||||
async def test_services(hass):
|
async def test_services(hass):
|
||||||
"""Test energy sensor reset service."""
|
"""Test energy sensor reset service."""
|
||||||
@ -81,6 +115,13 @@ async def test_services(hass):
|
|||||||
assert state.state == "1"
|
assert state.state == "1"
|
||||||
|
|
||||||
# Change tariff
|
# Change tariff
|
||||||
|
data = {ATTR_ENTITY_ID: "utility_meter.energy_bill", ATTR_TARIFF: "wrong_tariff"}
|
||||||
|
await hass.services.async_call(DOMAIN, SERVICE_SELECT_TARIFF, data)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Inexisting tariff, ignoring
|
||||||
|
assert hass.states.get("utility_meter.energy_bill").state != "wrong_tariff"
|
||||||
|
|
||||||
data = {ATTR_ENTITY_ID: "utility_meter.energy_bill", ATTR_TARIFF: "peak"}
|
data = {ATTR_ENTITY_ID: "utility_meter.energy_bill", ATTR_TARIFF: "peak"}
|
||||||
await hass.services.async_call(DOMAIN, SERVICE_SELECT_TARIFF, data)
|
await hass.services.async_call(DOMAIN, SERVICE_SELECT_TARIFF, data)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -111,3 +152,82 @@ async def test_services(hass):
|
|||||||
|
|
||||||
state = hass.states.get("sensor.energy_bill_offpeak")
|
state = hass.states.get("sensor.energy_bill_offpeak")
|
||||||
assert state.state == "0"
|
assert state.state == "0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cron(hass, legacy_patchable_time):
|
||||||
|
"""Test cron pattern and offset fails."""
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {
|
||||||
|
"source": "sensor.energy",
|
||||||
|
"cron": "*/5 * * * *",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cron_and_meter(hass, legacy_patchable_time):
|
||||||
|
"""Test cron pattern and meter type fails."""
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {
|
||||||
|
"source": "sensor.energy",
|
||||||
|
"cycle": "hourly",
|
||||||
|
"cron": "0 0 1 * *",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_both_cron_and_meter(hass, legacy_patchable_time):
|
||||||
|
"""Test cron pattern and meter type passes in different meter."""
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {
|
||||||
|
"source": "sensor.energy",
|
||||||
|
"cron": "0 0 1 * *",
|
||||||
|
},
|
||||||
|
"water_bill": {
|
||||||
|
"source": "sensor.water",
|
||||||
|
"cycle": "hourly",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cron_and_offset(hass, legacy_patchable_time):
|
||||||
|
"""Test cron pattern and offset fails."""
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {
|
||||||
|
"source": "sensor.energy",
|
||||||
|
"offset": {"days": 1},
|
||||||
|
"cron": "0 0 1 * *",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bad_cron(hass, legacy_patchable_time):
|
||||||
|
"""Test bad cron pattern."""
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"utility_meter": {"energy_bill": {"source": "sensor.energy", "cron": "*"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_missing_discovery(hass):
|
||||||
|
"""Test setup with configuration missing discovery_info."""
|
||||||
|
assert not await um_sensor.async_setup_platform(hass, {CONF_PLATFORM: DOMAIN}, None)
|
||||||
|
@ -11,7 +11,10 @@ from homeassistant.components.sensor import (
|
|||||||
from homeassistant.components.utility_meter.const import (
|
from homeassistant.components.utility_meter.const import (
|
||||||
ATTR_TARIFF,
|
ATTR_TARIFF,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
|
DAILY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HOURLY,
|
||||||
|
QUARTER_HOURLY,
|
||||||
SERVICE_CALIBRATE_METER,
|
SERVICE_CALIBRATE_METER,
|
||||||
SERVICE_SELECT_TARIFF,
|
SERVICE_SELECT_TARIFF,
|
||||||
)
|
)
|
||||||
@ -27,6 +30,7 @@ from homeassistant.const import (
|
|||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -162,6 +166,26 @@ async def test_state(hass):
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == "0.123"
|
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}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.energy_bill_midpeak")
|
||||||
|
assert state is not None
|
||||||
|
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}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.energy_bill_midpeak")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "0.123"
|
||||||
|
|
||||||
|
|
||||||
async def test_device_class(hass):
|
async def test_device_class(hass):
|
||||||
"""Test utility device_class."""
|
"""Test utility device_class."""
|
||||||
@ -421,6 +445,44 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True):
|
|||||||
start_time_str = dt_util.parse_datetime(start_time).isoformat()
|
start_time_str = dt_util.parse_datetime(start_time).isoformat()
|
||||||
assert state.attributes.get("last_reset") == start_time_str
|
assert state.attributes.get("last_reset") == start_time_str
|
||||||
|
|
||||||
|
# Check next day when nothing should happen for weekly, monthly, bimonthly and yearly
|
||||||
|
if config["utility_meter"]["energy_bill"].get("cycle") in [
|
||||||
|
QUARTER_HOURLY,
|
||||||
|
HOURLY,
|
||||||
|
DAILY,
|
||||||
|
]:
|
||||||
|
now += timedelta(minutes=5)
|
||||||
|
else:
|
||||||
|
now += timedelta(days=5)
|
||||||
|
with alter_time(now):
|
||||||
|
async_fire_time_changed(hass, now)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
10,
|
||||||
|
{ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR},
|
||||||
|
force_update=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.energy_bill")
|
||||||
|
if expect_reset:
|
||||||
|
assert state.attributes.get("last_period") == "2"
|
||||||
|
assert state.state == "7"
|
||||||
|
else:
|
||||||
|
assert state.attributes.get("last_period") == 0
|
||||||
|
assert state.state == "9"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_self_reset_cron_pattern(hass, legacy_patchable_time):
|
||||||
|
"""Test cron pattern reset of meter."""
|
||||||
|
config = {
|
||||||
|
"utility_meter": {
|
||||||
|
"energy_bill": {"source": "sensor.energy", "cron": "0 0 1 * *"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _test_self_reset(hass, config, "2017-01-31T23:59:00.000000+00:00")
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_quarter_hourly(hass, legacy_patchable_time):
|
async def test_self_reset_quarter_hourly(hass, legacy_patchable_time):
|
||||||
"""Test quarter-hourly reset of meter."""
|
"""Test quarter-hourly reset of meter."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user