From e2e9ee84b43c75fa26eb0af302fef73c13ffdd1b Mon Sep 17 00:00:00 2001 From: Jeff McGehee Date: Mon, 11 Sep 2017 18:12:17 -0400 Subject: [PATCH 1/2] 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. --- .../components/binary_sensor/bayesian.py | 33 +++++---- .../www_static/home-assistant-polymer | 2 +- .../components/binary_sensor/test_bayesian.py | 68 ++++++++++++++++++- 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/binary_sensor/bayesian.py index ac328fd1f41e4c..76b11fb213c073 100644 --- a/homeassistant/components/binary_sensor/bayesian.py +++ b/homeassistant/components/binary_sensor/bayesian.py @@ -102,7 +102,13 @@ def __init__(self, name, prior, observations, probability_threshold, 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,19 +126,20 @@ def async_threshold_sensor_state_listener(entity, old_state, 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'] - prior = self.prior - for obs in self.current_obs.values(): - prior = update_probability(prior, obs['prob_true'], - obs['prob_false']) + self.watchers[platform](entity_obs) - self.probability = prior + 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) + self.hass.async_add_job(self.async_update_ha_state, True) entities = [obs['entity_id'] for obs in self._observations] async_track_state_change( @@ -140,20 +147,20 @@ def async_threshold_sensor_state_listener(entity, old_state, 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.""" diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 19187ce5181908..19b903dfb7c940 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 19187ce518190823a8ccc5e7fc3d262cd218fa74 +Subproject commit 19b903dfb7c940bfafdecc5407526647d0aed2f2 diff --git a/tests/components/binary_sensor/test_bayesian.py b/tests/components/binary_sensor/test_bayesian.py index 61b110f247f099..3b403c3702f088 100644 --- a/tests/components/binary_sensor/test_bayesian.py +++ b/tests/components/binary_sensor/test_bayesian.py @@ -73,8 +73,7 @@ def test_sensor_numeric_state(self): '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 @@ def test_sensor_state(self): 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] From 3f0ff90609fd050f50c53e14f2d126c284edf256 Mon Sep 17 00:00:00 2001 From: Jeff McGehee Date: Tue, 12 Sep 2017 09:45:01 -0400 Subject: [PATCH 2/2] fix overzealous indenting --- homeassistant/components/binary_sensor/bayesian.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/binary_sensor/bayesian.py index 76b11fb213c073..13908fb547281f 100644 --- a/homeassistant/components/binary_sensor/bayesian.py +++ b/homeassistant/components/binary_sensor/bayesian.py @@ -133,13 +133,13 @@ def async_threshold_sensor_state_listener(entity, old_state, 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 + 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) + self.hass.async_add_job(self.async_update_ha_state, True) entities = [obs['entity_id'] for obs in self._observations] async_track_state_change(