Simplify utility_meter code base with croniter (#55625)
This commit is contained in:
parent
5581f58aad
commit
ad48d78315
3 changed files with 44 additions and 61 deletions
|
@ -65,6 +65,16 @@ def period_or_cron(config):
|
|||
return config
|
||||
|
||||
|
||||
def max_28_days(config):
|
||||
"""Check that time period does not include more then 28 days."""
|
||||
if config.days >= 28:
|
||||
raise vol.Invalid(
|
||||
"Unsupported offset of more then 28 days, please use a cron pattern."
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
METER_CONFIG_SCHEMA = vol.Schema(
|
||||
vol.All(
|
||||
{
|
||||
|
@ -72,7 +82,7 @@ METER_CONFIG_SCHEMA = vol.Schema(
|
|||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_METER_TYPE): vol.In(METER_TYPES),
|
||||
vol.Optional(CONF_METER_OFFSET, default=DEFAULT_OFFSET): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
cv.time_period, cv.positive_timedelta, max_28_days
|
||||
),
|
||||
vol.Optional(CONF_METER_NET_CONSUMPTION, default=False): cv.boolean,
|
||||
vol.Optional(CONF_TARIFFS, default=[]): vol.All(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""Utility meter from sensors providing raw data."""
|
||||
from datetime import date, datetime, timedelta
|
||||
import decimal
|
||||
from decimal import Decimal, DecimalException
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, DecimalException, InvalidOperation
|
||||
import logging
|
||||
|
||||
from croniter import croniter
|
||||
|
@ -29,7 +28,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.helpers.event import (
|
||||
async_track_point_in_time,
|
||||
async_track_state_change_event,
|
||||
async_track_time_change,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
@ -59,6 +57,17 @@ from .const import (
|
|||
YEARLY,
|
||||
)
|
||||
|
||||
PERIOD2CRON = {
|
||||
QUARTER_HOURLY: "{minute}/15 * * * *",
|
||||
HOURLY: "{minute} * * * *",
|
||||
DAILY: "{minute} {hour} * * *",
|
||||
WEEKLY: "{minute} {hour} * * {day}",
|
||||
MONTHLY: "{minute} {hour} {day} * *",
|
||||
BIMONTHLY: "{minute} {hour} {day} */2 *",
|
||||
QUARTERLY: "{minute} {hour} {day} */3 *",
|
||||
YEARLY: "{minute} {hour} {day} 1/12 *",
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_SOURCE_ID = "source"
|
||||
|
@ -152,8 +161,16 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||
self._name = f"{source_entity} meter"
|
||||
self._unit_of_measurement = None
|
||||
self._period = meter_type
|
||||
self._period_offset = meter_offset
|
||||
self._cron_pattern = cron_pattern
|
||||
if meter_type is not None:
|
||||
# For backwards compatibility reasons we convert the period and offset into a cron pattern
|
||||
self._cron_pattern = PERIOD2CRON[meter_type].format(
|
||||
minute=meter_offset.seconds % 3600 // 60,
|
||||
hour=meter_offset.seconds // 3600,
|
||||
day=meter_offset.days + 1,
|
||||
)
|
||||
_LOGGER.debug("CRON pattern: %s", self._cron_pattern)
|
||||
else:
|
||||
self._cron_pattern = cron_pattern
|
||||
self._sensor_net_consumption = net_consumption
|
||||
self._tariff = tariff
|
||||
self._tariff_entity = tariff_entity
|
||||
|
@ -233,39 +250,12 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||
|
||||
async def _async_reset_meter(self, event):
|
||||
"""Determine cycle - Helper function for larger than daily cycles."""
|
||||
now = dt_util.now().date()
|
||||
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
|
||||
and now != now - timedelta(days=now.weekday()) + self._period_offset
|
||||
):
|
||||
return
|
||||
elif (
|
||||
self._period == MONTHLY
|
||||
and now != date(now.year, now.month, 1) + self._period_offset
|
||||
):
|
||||
return
|
||||
elif (
|
||||
self._period == BIMONTHLY
|
||||
and now
|
||||
!= date(now.year, (((now.month - 1) // 2) * 2 + 1), 1) + self._period_offset
|
||||
):
|
||||
return
|
||||
elif (
|
||||
self._period == QUARTERLY
|
||||
and now
|
||||
!= date(now.year, (((now.month - 1) // 3) * 3 + 1), 1) + self._period_offset
|
||||
):
|
||||
return
|
||||
elif (
|
||||
self._period == YEARLY and now != date(now.year, 1, 1) + self._period_offset
|
||||
):
|
||||
return
|
||||
await self.async_reset_meter(self._tariff_entity)
|
||||
|
||||
async def async_reset_meter(self, entity_id):
|
||||
|
@ -294,30 +284,6 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||
self._async_reset_meter,
|
||||
croniter(self._cron_pattern, dt_util.now()).get_next(datetime),
|
||||
)
|
||||
elif self._period == QUARTER_HOURLY:
|
||||
for quarter in range(4):
|
||||
async_track_time_change(
|
||||
self.hass,
|
||||
self._async_reset_meter,
|
||||
minute=(quarter * 15)
|
||||
+ self._period_offset.seconds % (15 * 60) // 60,
|
||||
second=self._period_offset.seconds % 60,
|
||||
)
|
||||
elif self._period == HOURLY:
|
||||
async_track_time_change(
|
||||
self.hass,
|
||||
self._async_reset_meter,
|
||||
minute=self._period_offset.seconds // 60,
|
||||
second=self._period_offset.seconds % 60,
|
||||
)
|
||||
elif self._period in [DAILY, WEEKLY, MONTHLY, BIMONTHLY, QUARTERLY, YEARLY]:
|
||||
async_track_time_change(
|
||||
self.hass,
|
||||
self._async_reset_meter,
|
||||
hour=self._period_offset.seconds // 3600,
|
||||
minute=self._period_offset.seconds % 3600 // 60,
|
||||
second=self._period_offset.seconds % 3600 % 60,
|
||||
)
|
||||
|
||||
async_dispatcher_connect(self.hass, SIGNAL_RESET_METER, self.async_reset_meter)
|
||||
|
||||
|
@ -325,7 +291,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
|
|||
if state:
|
||||
try:
|
||||
self._state = Decimal(state.state)
|
||||
except decimal.InvalidOperation:
|
||||
except InvalidOperation:
|
||||
_LOGGER.error(
|
||||
"Could not restore state <%s>. Resetting utility_meter.%s",
|
||||
state.state,
|
||||
|
|
|
@ -627,7 +627,14 @@ async def test_no_reset_yearly_offset(hass, legacy_patchable_time):
|
|||
"""Test yearly reset of meter."""
|
||||
await _test_self_reset(
|
||||
hass,
|
||||
gen_config("yearly", timedelta(31)),
|
||||
"2018-01-30T23:59:00.000000+00:00",
|
||||
gen_config("yearly", timedelta(27)),
|
||||
"2018-04-29T23:59:00.000000+00:00",
|
||||
expect_reset=False,
|
||||
)
|
||||
|
||||
|
||||
async def test_bad_offset(hass, legacy_patchable_time):
|
||||
"""Test bad offset of meter."""
|
||||
assert not await async_setup_component(
|
||||
hass, DOMAIN, gen_config("monthly", timedelta(days=31))
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue