Allow multiple observations of same entity (#9391)

* Allow multiple observations of same entity

Why:

* There may be different probabilities for multiple states of the same
entity.

This change addresses the need by:

* Keeping a list of observations for each entity to check on each state
change of the given entity.
* Adding a numeric id to each observation so that they can be
effectively added and removed from `self.current_obs`.
* Adding a test to confirm functionality.

* fix overzealous indenting
pull/8913/head^2
Jeff McGehee 2017-09-12 12:52:09 -04:00 committed by Martin Hjelmare
parent c9fc3fae6e
commit 29b62f814f
2 changed files with 81 additions and 10 deletions

View File

@ -102,7 +102,13 @@ class BayesianBinarySensor(BinarySensorDevice):
self.current_obs = OrderedDict({})
self.entity_obs = {obs['entity_id']: obs for obs in self._observations}
to_observe = set(obs['entity_id'] for obs in self._observations)
self.entity_obs = dict.fromkeys(to_observe, [])
for ind, obs in enumerate(self._observations):
obs["id"] = ind
self.entity_obs[obs['entity_id']].append(obs)
self.watchers = {
'numeric_state': self._process_numeric_state,
@ -120,16 +126,17 @@ class BayesianBinarySensor(BinarySensorDevice):
if new_state.state == STATE_UNKNOWN:
return
entity_obs = self.entity_obs[entity]
platform = entity_obs['platform']
entity_obs_list = self.entity_obs[entity]
self.watchers[platform](entity_obs)
for entity_obs in entity_obs_list:
platform = entity_obs['platform']
self.watchers[platform](entity_obs)
prior = self.prior
for obs in self.current_obs.values():
prior = update_probability(prior, obs['prob_true'],
obs['prob_false'])
self.probability = prior
self.hass.async_add_job(self.async_update_ha_state, True)
@ -140,20 +147,20 @@ class BayesianBinarySensor(BinarySensorDevice):
def _update_current_obs(self, entity_observation, should_trigger):
"""Update current observation."""
entity = entity_observation['entity_id']
obs_id = entity_observation['id']
if should_trigger:
prob_true = entity_observation['prob_given_true']
prob_false = entity_observation.get(
'prob_given_false', 1 - prob_true)
self.current_obs[entity] = {
self.current_obs[obs_id] = {
'prob_true': prob_true,
'prob_false': prob_false
}
else:
self.current_obs.pop(entity, None)
self.current_obs.pop(obs_id, None)
def _process_numeric_state(self, entity_observation):
"""Add entity to current_obs if numeric state conditions are met."""

View File

@ -73,8 +73,7 @@ class TestBayesianBinarySensor(unittest.TestCase):
'prob_false': 0.1,
'prob_true': 0.9
}], state.attributes.get('observations'))
self.assertAlmostEqual(0.77,
state.attributes.get('probability'))
self.assertAlmostEqual(0.77, state.attributes.get('probability'))
assert state.state == 'on'
@ -155,6 +154,71 @@ class TestBayesianBinarySensor(unittest.TestCase):
assert state.state == 'off'
def test_multiple_observations(self):
"""Test sensor with multiple observations of same entity."""
config = {
'binary_sensor': {
'name':
'Test_Binary',
'platform':
'bayesian',
'observations': [{
'platform': 'state',
'entity_id': 'sensor.test_monitored',
'to_state': 'blue',
'prob_given_true': 0.8,
'prob_given_false': 0.4
}, {
'platform': 'state',
'entity_id': 'sensor.test_monitored',
'to_state': 'red',
'prob_given_true': 0.2,
'prob_given_false': 0.4
}],
'prior':
0.2,
'probability_threshold':
0.32,
}
}
assert setup_component(self.hass, 'binary_sensor', config)
self.hass.states.set('sensor.test_monitored', 'off')
state = self.hass.states.get('binary_sensor.test_binary')
self.assertEqual([], state.attributes.get('observations'))
self.assertEqual(0.2, state.attributes.get('probability'))
assert state.state == 'off'
self.hass.states.set('sensor.test_monitored', 'blue')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'off')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'blue')
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
self.assertEqual([{
'prob_true': 0.8,
'prob_false': 0.4
}], state.attributes.get('observations'))
self.assertAlmostEqual(0.33, state.attributes.get('probability'))
assert state.state == 'on'
self.hass.states.set('sensor.test_monitored', 'blue')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'red')
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
self.assertAlmostEqual(0.11, state.attributes.get('probability'))
assert state.state == 'off'
def test_probability_updates(self):
"""Test probability update function."""
prob_true = [0.3, 0.6, 0.8]