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 indentingpull/8913/head^2
parent
c9fc3fae6e
commit
29b62f814f
|
@ -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."""
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue