187 lines
6.6 KiB
Python
187 lines
6.6 KiB
Python
"""Sensor platform for Daily Sensor."""
|
|
|
|
import asyncio
|
|
from datetime import datetime
|
|
import logging
|
|
from statistics import StatisticsError, median, stdev, variance
|
|
|
|
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
|
from homeassistant.core import Event, callback
|
|
|
|
from .const import ( # pylint: disable=unused-import
|
|
ATTR_DATETIME_OF_OCCURRENCE,
|
|
CONF_AUTO_RESET,
|
|
CONF_INPUT_SENSOR,
|
|
CONF_INTERVAL,
|
|
CONF_MAX,
|
|
CONF_MEAN,
|
|
CONF_MEDIAN,
|
|
CONF_MIN,
|
|
CONF_OPERATION,
|
|
CONF_STDEV,
|
|
CONF_SUM,
|
|
CONF_UNIT_OF_MEASUREMENT,
|
|
CONF_VARIANCE,
|
|
COORDINATOR,
|
|
DOMAIN,
|
|
EVENT_RESET,
|
|
EVENT_UPDATE,
|
|
ICON,
|
|
)
|
|
from .entity import DailySensorEntity
|
|
|
|
# from homeassistant.helpers import entity_registry as er
|
|
from .helpers import parse_sensor_state, convert_to_float
|
|
import contextlib
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(hass, entry, async_add_devices):
|
|
"""Set up the platform and add to HA."""
|
|
coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
|
|
|
|
async_add_devices([DailySensor(hass, coordinator, entry)])
|
|
|
|
|
|
class DailySensor(DailySensorEntity):
|
|
"""DailySensor class."""
|
|
|
|
def __init__(self, hass, coordinator, entity):
|
|
"""Init for DailySensor."""
|
|
super(DailySensor, self).__init__(coordinator, entity)
|
|
self._state = None
|
|
self._values = []
|
|
self._occurrence = None
|
|
|
|
async def async_added_to_hass(self):
|
|
"""Complete the initialization."""
|
|
await super().async_added_to_hass()
|
|
# register this sensor in the coordinator
|
|
self.coordinator.register_entity(self.name, self.entity_id)
|
|
|
|
# listen to the update event and reset event
|
|
event_to_listen = f"{self.coordinator.name}_{EVENT_RESET}"
|
|
self.hass.bus.async_listen(
|
|
event_to_listen,
|
|
lambda event: self._handle_reset( # pylint: disable=unnecessary-lambda
|
|
event
|
|
),
|
|
)
|
|
event_to_listen_2 = f"{self.coordinator.name}_{EVENT_UPDATE}"
|
|
self.hass.bus.async_listen(
|
|
event_to_listen_2,
|
|
lambda event: self._handle_update( # pylint: disable=unnecessary-lambda
|
|
event
|
|
),
|
|
)
|
|
|
|
state = await self.async_get_last_state()
|
|
self._state = parse_sensor_state(state)
|
|
|
|
@callback
|
|
def _handle_reset(self, event: Event):
|
|
"""Receive the reset event."""
|
|
# reset the sensor
|
|
self._state = None
|
|
self._occurrence = None
|
|
self._values = []
|
|
self.hass.add_job(self.async_write_ha_state)
|
|
|
|
@callback
|
|
def _handle_update(self, event: Event):
|
|
"""Receive the update event."""
|
|
# update the sensor
|
|
input_state = self.hass.states.get(self.coordinator.input_sensor)
|
|
state_minmax_changed = False
|
|
try:
|
|
if input_state not in (None, STATE_UNKNOWN, STATE_UNAVAILABLE):
|
|
input_state = parse_sensor_state(input_state)
|
|
the_val = convert_to_float(input_state)
|
|
if self._state not in (None, STATE_UNKNOWN, STATE_UNAVAILABLE):
|
|
self._state = convert_to_float(self._state)
|
|
# apply the operation and update self._state
|
|
if self.coordinator.operation == CONF_SUM:
|
|
if self._state in (None, STATE_UNKNOWN, STATE_UNAVAILABLE):
|
|
self._state = the_val
|
|
else:
|
|
self._state = self._state + the_val
|
|
elif self.coordinator.operation == CONF_MAX:
|
|
if (
|
|
self._state in (None, STATE_UNKNOWN, STATE_UNAVAILABLE)
|
|
or the_val > self._state
|
|
):
|
|
self._state = the_val
|
|
state_minmax_changed = True
|
|
elif self.coordinator.operation == CONF_MIN:
|
|
if (
|
|
self._state in (None, STATE_UNKNOWN, STATE_UNAVAILABLE)
|
|
or the_val < self._state
|
|
):
|
|
self._state = the_val
|
|
state_minmax_changed = True
|
|
elif self.coordinator.operation == CONF_MEAN:
|
|
self._values.append(the_val)
|
|
self._state = round(
|
|
(sum(self._values) * 1.0) / len(self._values), 1
|
|
)
|
|
elif self.coordinator.operation == CONF_MEDIAN:
|
|
self._values.append(the_val)
|
|
self._state = median(self._values)
|
|
elif self.coordinator.operation == CONF_STDEV:
|
|
self._values.append(the_val)
|
|
self._state = stdev(self._values)
|
|
elif self.coordinator.operation == CONF_VARIANCE:
|
|
self._values.append(the_val)
|
|
with contextlib.suppress(StatisticsError):
|
|
self._state = variance(self._values)
|
|
if state_minmax_changed:
|
|
self._occurrence = datetime.now()
|
|
self.hass.add_job(self.async_write_ha_state)
|
|
else:
|
|
# sensor is unknown at startup, state which comes after is considered as initial state
|
|
_LOGGER.debug(
|
|
"Initial state for {} is {}".format(
|
|
self.coordinator.input_sensor, input_state
|
|
)
|
|
)
|
|
return
|
|
except ValueError:
|
|
_LOGGER.error(
|
|
"unable to convert to float. Please check the source sensor ({}) is available.".format(
|
|
self.coordinator.input_sensor
|
|
)
|
|
)
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the sensor."""
|
|
return f"{self.coordinator.name}"
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the sensor."""
|
|
return self._state
|
|
|
|
@property
|
|
def unit_of_measurement(self):
|
|
"""Return the unit of measurement for the sensor."""
|
|
return self.coordinator.unit_of_measurement
|
|
|
|
@property
|
|
def extra_state_attributes(self):
|
|
"""Return the state attributes."""
|
|
return {
|
|
CONF_INPUT_SENSOR: self.coordinator.input_sensor,
|
|
CONF_OPERATION: self.coordinator.operation,
|
|
CONF_INTERVAL: self.coordinator.interval,
|
|
CONF_UNIT_OF_MEASUREMENT: self.unit_of_measurement,
|
|
CONF_AUTO_RESET: self.coordinator.auto_reset,
|
|
ATTR_DATETIME_OF_OCCURRENCE: self._occurrence,
|
|
}
|
|
|
|
@property
|
|
def icon(self):
|
|
"""Return the icon of the sensor."""
|
|
return ICON
|