home-assistant/tests/components/binary_sensor/test_bayesian.py
Jeff McGehee 7de73e9ef7 Bayesian Binary Sensor (#8810)
* Bayesian Binary Sensor

Why:

* It would be beneficial to leverage various sensor outputs in a
Bayesian manner in order to sense more complex events.

This change addresses the need by:

* `BayesianBinarySensor` class in
`./homeassistant/components/binary_sensor/bayesian.py`
* Tests in `./tests/components/binary_sensor/test_bayesian.py`

Caveats:
This is my first time in this code-base. I did try to follow conventions
that I was able to find, but I'm sure there will be some issues to
straighten out.

* minor cleanup

* Address reviewer's comments

This change addresses the need by:

* Removing `CONF_SENSOR_CLASS` and its usage in `get_deprecated`.
* Make probability update function a static method, and use single `_`
to match project conventions.

* Address linter failures

* fix `device_class` declaration

* Address Comments

Why:
* Not validating config schema enough.
* Not following common practices for async initialization.
* Naive implementation of Bayes' rule.

This change addresses the need by:
* Improving config validation for observations.
* Moving initialization logic into `async_added_to_hass`.
* Re-configuring Bayesian updates to allow true P|Q usage.

* address linting issues

* Improve DRYness by adding `_update_current_obs` method

* update doc strings and ensure functions are set up properly for async

* Make only 1 state change handle

* fix style

* fix style part 2

* fix lint
2017-08-29 23:53:41 +02:00

176 lines
5.7 KiB
Python

"""The test for the bayesian sensor platform."""
import unittest
from homeassistant.setup import setup_component
from homeassistant.components.binary_sensor import bayesian
from tests.common import get_test_home_assistant
class TestBayesianBinarySensor(unittest.TestCase):
"""Test the threshold sensor."""
def setup_method(self, method):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
"""Stop everything that was started."""
self.hass.stop()
def test_sensor_numeric_state(self):
"""Test sensor on numeric state platform observations."""
config = {
'binary_sensor': {
'platform':
'bayesian',
'name':
'Test_Binary',
'observations': [{
'platform': 'numeric_state',
'entity_id': 'sensor.test_monitored',
'below': 10,
'above': 5,
'prob_given_true': 0.6
}, {
'platform': 'numeric_state',
'entity_id': 'sensor.test_monitored1',
'below': 7,
'above': 5,
'prob_given_true': 0.9,
'prob_given_false': 0.1
}],
'prior':
0.2,
}
}
assert setup_component(self.hass, 'binary_sensor', config)
self.hass.states.set('sensor.test_monitored', 4)
self.hass.block_till_done()
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', 6)
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 4)
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 6)
self.hass.states.set('sensor.test_monitored1', 6)
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
self.assertEqual([{
'prob_false': 0.4,
'prob_true': 0.6
}, {
'prob_false': 0.1,
'prob_true': 0.9
}], state.attributes.get('observations'))
self.assertAlmostEqual(0.7714285714285715,
state.attributes.get('probability'))
assert state.state == 'on'
self.hass.states.set('sensor.test_monitored', 6)
self.hass.states.set('sensor.test_monitored1', 0)
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 4)
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
self.assertEqual(0.2, state.attributes.get('probability'))
assert state.state == 'off'
self.hass.states.set('sensor.test_monitored', 15)
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
assert state.state == 'off'
def test_sensor_state(self):
"""Test sensor on state platform observations."""
config = {
'binary_sensor': {
'name':
'Test_Binary',
'platform':
'bayesian',
'observations': [{
'platform': 'state',
'entity_id': 'sensor.test_monitored',
'to_state': 'off',
'prob_given_true': 0.8,
'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', 'on')
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', 'off')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'on')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'off')
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.33333333, state.attributes.get('probability'))
assert state.state == 'on'
self.hass.states.set('sensor.test_monitored', 'off')
self.hass.block_till_done()
self.hass.states.set('sensor.test_monitored', 'on')
self.hass.block_till_done()
state = self.hass.states.get('binary_sensor.test_binary')
self.assertAlmostEqual(0.2, 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]
prob_false = [0.7, 0.4, 0.2]
prior = 0.5
for pt, pf in zip(prob_true, prob_false):
prior = bayesian.update_probability(prior, pt, pf)
self.assertAlmostEqual(0.720000, prior)
prob_true = [0.8, 0.3, 0.9]
prob_false = [0.6, 0.4, 0.2]
prior = 0.7
for pt, pf in zip(prob_true, prob_false):
prior = bayesian.update_probability(prior, pt, pf)
self.assertAlmostEqual(0.9130434782608695, prior)