mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Feature/min max improvements (#6786)
* Fix #6783, remove a test that makes no sense anymore * Fix #6784 * Fix typo in docstring * Fix handling of known->unknown state, extended test, fix lint errors * Better handling of mismatch in unit of measurement. Set state to "unkown" and unit of measurement to "ERR" if unit of measurement differs between aggregatet states. Add entity_id to logged warning. * Make icon configurable * Fix typo * Fix lint * Fix lint * Fix lint * Add option to set entity_id on min_max sensor * Fix lint logging-not-lazy * Revert "Add option to set entity_id on min_max sensor" This reverts commit 4685f266477dfaba92cf8a256ecfe62c929c877b. * Revert "Make icon configurable" This reverts commit fe45aec82d9a07e730bebb4d47dc9618fb695fe6. * Fixes
This commit is contained in:
parent
197db6bded
commit
f76a4b2806
@ -34,8 +34,6 @@ ATTR_TO_PROPERTY = [
|
||||
CONF_ENTITY_IDS = 'entity_ids'
|
||||
CONF_ROUND_DIGITS = 'round_digits'
|
||||
|
||||
DEFAULT_NAME = 'Min/Max/Avg Sensor'
|
||||
|
||||
ICON = 'mdi:calculator'
|
||||
|
||||
SENSOR_TYPES = {
|
||||
@ -47,7 +45,7 @@ SENSOR_TYPES = {
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_TYPE, default=SENSOR_TYPES[ATTR_MAX_VALUE]):
|
||||
vol.All(cv.string, vol.In(SENSOR_TYPES.values())),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_ENTITY_IDS): cv.entity_ids,
|
||||
vol.Optional(CONF_ROUND_DIGITS, default=2): vol.Coerce(int),
|
||||
})
|
||||
@ -67,6 +65,39 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
return True
|
||||
|
||||
|
||||
def calc_min(sensor_values):
|
||||
"""Calculate min value, honoring unknown states."""
|
||||
val = STATE_UNKNOWN
|
||||
for sval in sensor_values:
|
||||
if sval != STATE_UNKNOWN:
|
||||
if val == STATE_UNKNOWN or val > sval:
|
||||
val = sval
|
||||
return val
|
||||
|
||||
|
||||
def calc_max(sensor_values):
|
||||
"""Calculate max value, honoring unknown states."""
|
||||
val = STATE_UNKNOWN
|
||||
for sval in sensor_values:
|
||||
if sval != STATE_UNKNOWN:
|
||||
if val == STATE_UNKNOWN or val < sval:
|
||||
val = sval
|
||||
return val
|
||||
|
||||
|
||||
def calc_mean(sensor_values, round_digits):
|
||||
"""Calculate mean value, honoring unknown states."""
|
||||
val = 0
|
||||
count = 0
|
||||
for sval in sensor_values:
|
||||
if sval != STATE_UNKNOWN:
|
||||
val += sval
|
||||
count += 1
|
||||
if count == 0:
|
||||
return STATE_UNKNOWN
|
||||
return round(val/count, round_digits)
|
||||
|
||||
|
||||
class MinMaxSensor(Entity):
|
||||
"""Representation of a min/max sensor."""
|
||||
|
||||
@ -76,10 +107,15 @@ class MinMaxSensor(Entity):
|
||||
self._entity_ids = entity_ids
|
||||
self._sensor_type = sensor_type
|
||||
self._round_digits = round_digits
|
||||
self._name = '{} {}'.format(
|
||||
name, next(v for k, v in SENSOR_TYPES.items()
|
||||
if self._sensor_type == v))
|
||||
|
||||
if name:
|
||||
self._name = name
|
||||
else:
|
||||
self._name = '{} sensor'.format(
|
||||
next(v for k, v in SENSOR_TYPES.items()
|
||||
if self._sensor_type == v)).capitalize()
|
||||
self._unit_of_measurement = None
|
||||
self._unit_of_measurement_mismatch = False
|
||||
self.min_value = self.max_value = self.mean = STATE_UNKNOWN
|
||||
self.count_sensors = len(self._entity_ids)
|
||||
self.states = {}
|
||||
@ -89,6 +125,8 @@ class MinMaxSensor(Entity):
|
||||
def async_min_max_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the sensor changes state."""
|
||||
if new_state.state is None or new_state.state in STATE_UNKNOWN:
|
||||
self.states[entity] = STATE_UNKNOWN
|
||||
hass.async_add_job(self.async_update_ha_state, True)
|
||||
return
|
||||
|
||||
if self._unit_of_measurement is None:
|
||||
@ -97,8 +135,11 @@ class MinMaxSensor(Entity):
|
||||
|
||||
if self._unit_of_measurement != new_state.attributes.get(
|
||||
ATTR_UNIT_OF_MEASUREMENT):
|
||||
_LOGGER.warning("Units of measurement do not match")
|
||||
return
|
||||
_LOGGER.warning(
|
||||
"Units of measurement do not match for entity %s",
|
||||
self.entity_id)
|
||||
self._unit_of_measurement_mismatch = True
|
||||
|
||||
try:
|
||||
self.states[entity] = float(new_state.state)
|
||||
except ValueError:
|
||||
@ -118,12 +159,16 @@ class MinMaxSensor(Entity):
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
if self._unit_of_measurement_mismatch:
|
||||
return STATE_UNKNOWN
|
||||
return getattr(self, next(
|
||||
k for k, v in SENSOR_TYPES.items() if self._sensor_type == v))
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
if self._unit_of_measurement_mismatch:
|
||||
return "ERR"
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
@ -150,10 +195,6 @@ class MinMaxSensor(Entity):
|
||||
"""Get the latest data and updates the states."""
|
||||
sensor_values = [self.states[k] for k in self._entity_ids
|
||||
if k in self.states]
|
||||
if len(sensor_values) == self.count_sensors:
|
||||
self.min_value = min(sensor_values)
|
||||
self.max_value = max(sensor_values)
|
||||
self.mean = round(sum(sensor_values) / self.count_sensors,
|
||||
self._round_digits)
|
||||
else:
|
||||
self.min_value = self.max_value = self.mean = STATE_UNKNOWN
|
||||
self.min_value = calc_min(sensor_values)
|
||||
self.max_value = calc_max(sensor_values)
|
||||
self.mean = calc_mean(sensor_values, self._round_digits)
|
||||
|
@ -30,7 +30,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_min',
|
||||
'type': 'min',
|
||||
'entity_ids': [
|
||||
'sensor.test_1',
|
||||
@ -59,7 +59,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_max',
|
||||
'type': 'max',
|
||||
'entity_ids': [
|
||||
'sensor.test_1',
|
||||
@ -88,7 +88,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_mean',
|
||||
'type': 'mean',
|
||||
'entity_ids': [
|
||||
'sensor.test_1',
|
||||
@ -117,7 +117,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_mean',
|
||||
'type': 'mean',
|
||||
'round_digits': 1,
|
||||
'entity_ids': [
|
||||
@ -147,7 +147,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_mean',
|
||||
'type': 'mean',
|
||||
'round_digits': 4,
|
||||
'entity_ids': [
|
||||
@ -177,7 +177,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
config = {
|
||||
'sensor': {
|
||||
'platform': 'min_max',
|
||||
'name': 'test',
|
||||
'name': 'test_max',
|
||||
'type': 'max',
|
||||
'entity_ids': [
|
||||
'sensor.test_1',
|
||||
@ -191,7 +191,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
|
||||
entity_ids = config['sensor']['entity_ids']
|
||||
|
||||
self.hass.states.set(entity_ids[0], self.values[0])
|
||||
self.hass.states.set(entity_ids[0], STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test_max')
|
||||
@ -201,14 +201,20 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test_max')
|
||||
self.assertEqual(STATE_UNKNOWN, state.state)
|
||||
self.assertNotEqual(STATE_UNKNOWN, state.state)
|
||||
|
||||
self.hass.states.set(entity_ids[2], self.values[2])
|
||||
self.hass.states.set(entity_ids[2], STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test_max')
|
||||
self.assertNotEqual(STATE_UNKNOWN, state.state)
|
||||
|
||||
self.hass.states.set(entity_ids[1], STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test_max')
|
||||
self.assertEqual(STATE_UNKNOWN, state.state)
|
||||
|
||||
def test_different_unit_of_measurement(self):
|
||||
"""Test for different unit of measurement."""
|
||||
config = {
|
||||
@ -232,21 +238,25 @@ class TestMinMaxSensor(unittest.TestCase):
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test_mean')
|
||||
state = self.hass.states.get('sensor.test')
|
||||
|
||||
self.assertEqual(STATE_UNKNOWN, state.state)
|
||||
self.assertEqual(str(float(self.values[0])), state.state)
|
||||
self.assertEqual('°C', state.attributes.get('unit_of_measurement'))
|
||||
|
||||
self.hass.states.set(entity_ids[1], self.values[1],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test')
|
||||
|
||||
self.assertEqual(STATE_UNKNOWN, state.state)
|
||||
self.assertEqual('°C', state.attributes.get('unit_of_measurement'))
|
||||
self.assertEqual('ERR', state.attributes.get('unit_of_measurement'))
|
||||
|
||||
self.hass.states.set(entity_ids[2], self.values[2],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: '%'})
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get('sensor.test')
|
||||
|
||||
self.assertEqual(STATE_UNKNOWN, state.state)
|
||||
self.assertEqual('°C', state.attributes.get('unit_of_measurement'))
|
||||
self.assertEqual('ERR', state.attributes.get('unit_of_measurement'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user