Skip to content

Bayesian Binary Sensor#8810

Merged
pvizeli merged 13 commits into
home-assistant:devfrom
jlmcgehee21:feature/bayesian_binary
Aug 29, 2017
Merged

Bayesian Binary Sensor#8810
pvizeli merged 13 commits into
home-assistant:devfrom
jlmcgehee21:feature/bayesian_binary

Conversation

@jlmcgehee21
Copy link
Copy Markdown
Contributor

@jlmcgehee21 jlmcgehee21 commented Aug 3, 2017

Description:

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.

Example entry for configuration.yaml (if applicable):

binary_sensor:
  name: 'currently_cooking'
  platform: 'bayesian'
  prior: 0.1
  probability_threshold: 0.7
  observations:
    - entity_id: 'switch.kitchen_lights'
      prob_given_true: 0.6
      prob_given_false: 0.2
      platform: 'state'
      to_state: 'on'
    - entity_id: 'sensor.stove_temperature'
      prob_given_true: 0.9
      platform: 'numeric_state'
      above: 100
    - entity_id: 'sensor.kitchen_motion'
      prob_given_true: 0.5
      prob_given_false: 0.2
      platform: 'state'
      to_state: 'on'

binary_sensor:
  name: 'in_bed'
  platform: 'bayesian'
  prior: 0.5
  probability_threshold: 0.95
  observations:
    - entity_id: 'sensor.living_room_motion'
      prob_given_true: 0.4
      prob_given_false: 0.2
      platform: 'state'
      to_state: 'off'
    - entity_id: 'sensor.basement_motion'
      prob_given_true: 0.5
      prob_given_false: 0.4
      platform: 'state'
      to_state: 'off'
    - entity_id: 'sensor.bedroom_motion'
      prob_given_true: 0.5
      platform: 'state'
      to_state: 'on'
    - entity_id: 'sensor.sun'
      prob_given_true: 0.7
      platform: 'state'
      to_state: 'below_horizon'

Checklist:

If user exposed functionality or configuration variables are added/changed:

If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • Tests have been added to verify that the new code works.

@homeassistant
Copy link
Copy Markdown
Contributor

Hi @jlmcgehee21,

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closing bracket does not match visual indentation

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'homeassistant.const.CONF_ENTITY_ID' imported but unused
'homeassistant.const.CONF_TYPE' imported but unused
'homeassistant.const.ATTR_ENTITY_ID' imported but unused

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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'homeassistant.const.ATTR_UNIT_OF_MEASUREMENT' imported but unused
'homeassistant.const.TEMP_CELSIUS' imported but unused

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'homeassistant.const.ATTR_UNIT_OF_MEASUREMENT' imported but unused
'homeassistant.const.TEMP_CELSIUS' imported but unused

@jlmcgehee21
Copy link
Copy Markdown
Contributor Author

Was running tests locally with ptw tests/components/binary_sensor homeassistant/components/binary_sensor/ -- -x tests/components/binary_sensor/test_bayesian.py and had no trouble getting them to pass in isolation.

I probably will need some pointers regarding whatever side effects I may be causing when running the full test suite.

@arsaboo
Copy link
Copy Markdown
Contributor

arsaboo commented Aug 4, 2017

Love it....will be great for presence detection as well.

@cmsimike
Copy link
Copy Markdown
Contributor

cmsimike commented Aug 4, 2017

This will be fantastic to try to infer when my apartment should go into night/sleep mode!

import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (BinarySensorDevice,
PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, STATE_UNKNOWN, CONF_SENSOR_CLASS,
Copy link
Copy Markdown
Member

@Danielhiversen Danielhiversen Aug 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONF_SENSOR_CLASS does not exists in homeassistant.const anymore

else:
self.current_obs.pop(entity, None)

def __update_probability(self, prior, observation):
Copy link
Copy Markdown
Member

@Danielhiversen Danielhiversen Aug 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not using self inside the function, so the function could be extracted from the class.
We normally use only one _ in the beginning of the function name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, was updating self.probability before a refactor. Changed to a @staticmethod to keep the namespace of the class (my usual approach), if this is not preferred, I can move outside.

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.
observations = config.get(CONF_OBSERVATIONS)
prior = config.get(CONF_PRIOR)
probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD)
device_class = CONF_DEVICE_CLASS
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be config.get(CONF_DEVICE_CLASS) ?

@jlmcgehee21
Copy link
Copy Markdown
Contributor Author

@Danielhiversen, once I add docs in home-assistant.github.io, will this make the next release?

Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! Some comments below.

BayesianBinarySensor(hass, name, prior, observations,
probability_threshold, device_class)
], True)
return True
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup_platform shouldn't return anything.

vol.Optional(CONF_NAME, default=DEFAULT_NAME):
cv.string,
vol.Required(CONF_OBSERVATIONS):
vol.Schema([dict]),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add cv.ensure_list. Something like:

vol.Schema(vol.All(cv.ensure_list, [dict])),

It seems the only platforms that are supported are state and numeric_state. Shouldn't we try to check that the user configures the correct platforms? Right now a bad platform will cause an uncaught error.


for obs in self._observations:
entity_id = obs['entity_id']
async_track_state_change(hass, entity_id,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this in the method async_added_to_hass which will be called when the entity is added to home assistant.

class BayesianBinarySensor(BinarySensorDevice):
"""Representation of a Bayesian sensor."""

def __init__(self, hass, name, prior, observations, probability_threshold,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't pass in hass. It will be set on the entity when it has been added to home assistant.

entity_observation.get('below'),
entity_observation.get('above'), None,
entity_observation):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line.


@asyncio.coroutine
def async_update(self):
"""Get the latest data and updates the states."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write all the verbs in imperative mood: "Get... update..."

Use Bayesian Inference to trigger a binary sensor.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bayesian_binary/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://home-assistant.io/components/binary_sensor.bayesian/


prior = self.prior
for obs in self.current_obs.values():
prior = self._update_probability(obs, prior)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature of the _update_probability method has the parameters in the reverse order of this call. Is this deliberate? I'm not very familiar with the Bayesian theory in application.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! It actually didn't matter (and hence didn't fail my tests) because I was treating the scenario a bit naively (P(Event|False) = 1 - P(Event|True)). I thought this would be a decent first shot, but after some reflection, I preferred to allow the user to specify P(Event|False) and default to the above case if they do not.

observations = config.get(CONF_OBSERVATIONS)
prior = config.get(CONF_PRIOR)
probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD)
device_class = config.get(CONF_DEVICE_CLASS)
Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare Aug 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONF_DEVICE_CLASS isn't part of your config schema.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'homeassistant.const.CONF_STATE' imported but unused

Copy link
Copy Markdown
Member

@pvizeli pvizeli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

async_track_state_change(self.hass, entity_id,
async_threshold_sensor_state_listener)

def _update_current_obs(self, entity_observation, should_trigger):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a function documentation. Add decorator "@callback" if they need run inside async loop or add "Async friendly" to function documentation if that dosn't matters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I basically adapted the binary.threshold sensor and its test for this implementation, and it seems the test is made to run in what looks like a full hass environment. With this in mind, I would guess that the @callback decorator is not needed because my tests are passing? If this is true, then appropriate solution would be to add "Async friendly" to the docstring, correct?

As a side note, the pylintrc and the travis tests both designate docstrings are only needed for public methods. For ease of contribution, would it be appropriate to include modification of these files as part of my PR in order for linting to fail if these methods have no documentation?

I appreciate the feedback, and hope to address it the best way possible.

Copy link
Copy Markdown
Member

@pvizeli pvizeli Aug 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I make a PR to remove that from pylintrc. This small info about that function is in every case helpfull.

Yeah, the function can run async/sync. So but "Async friendly" to this function like https://github.com/home-assistant/home-assistant/blob/c56f99baafac33483dd13699993d7da4ee5d7efd/homeassistant/helpers/location.py#L11-L14.
That help to prevent on future change on that platform.

A async/sync problem is only visible on real world and not every time inside tests. It is very hard to find a problem like this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks for the clarification.

else:
self.current_obs.pop(entity, None)

def _process_numeric_state(self, entity_observation):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a function documentation. Add decorator "@callback" if they need run inside async loop or add "Async friendly" to function documentation if that dosn't matters.


self._update_current_obs(entity_observation, should_trigger)

def _process_state(self, entity_observation):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a function documentation. Add decorator "@callback" if they need run inside async loop or add "Async friendly" to function documentation if that dosn't matters.

@jlmcgehee21
Copy link
Copy Markdown
Contributor Author

jlmcgehee21 commented Aug 28, 2017

@Danielhiversen @MartinHjelmare @pvizeli hopefully I have addressed all comments/concerns. I have also added documentation, and linked to the PR in the description for this PR. Unfortunately, it seems I missed the 0.52 release, but hopefully we can make the next one! Is there anything else I need to do?

Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the callback annotations, but the rest looks good.
Everything is good.

@home-assistant home-assistant deleted a comment from houndci-bot Aug 29, 2017
@pvizeli pvizeli closed this Aug 29, 2017
@pvizeli pvizeli reopened this Aug 29, 2017
@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Aug 29, 2017

Retrigger travis API

@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Aug 29, 2017

@pvizeli pvizeli merged commit 7de73e9 into home-assistant:dev Aug 29, 2017
@moskovskiy82
Copy link
Copy Markdown

Will this make into 0.53?

@MartinHjelmare
Copy link
Copy Markdown
Member

Yes.

matemaciek added a commit to matemaciek/home-assistant that referenced this pull request Aug 30, 2017
* upstream/dev: (113 commits)
  Fix fitbit error when trying to access token after upgrade. (home-assistant#9183)
  Upgrade sendgrid to 5.0.1 (home-assistant#9215)
  Upgrade pyasn1 to 0.3.3 and pyasn1-modules to 0.1.1 (home-assistant#9216)
  directv: extended discovery via REST api, bug fix  (home-assistant#8800)
  Bayesian Binary Sensor (home-assistant#8810)
  Add cloud auth support (home-assistant#9208)
  Abode push events and lock, cover, and switch components (home-assistant#9095)
  Lint Sonarr tests
  Upgrade pymysensors to 0.11.1 (home-assistant#9212)
  Refactor rfxtrx (home-assistant#9117)
  Issue home-assistant#6893 in rfxtrx (home-assistant#9130)
  Support for season sensor (home-assistant#8958)
  Add counter component (home-assistant#9146)
  Fix and optimize digitalloggers platform (home-assistant#9203)
  Prevent error when no forecast data was available (home-assistant#9176)
  Add "status" to Sonarr sensor (home-assistant#9204)
  fix worldtidesinfo home-assistant#9184 (home-assistant#9201)
  Update pushbullet.py (home-assistant#9200)
  Fix dht22 when no data was read initially home-assistant#8976 (home-assistant#9198)
  Prevent iCloud exceptions in logfile (home-assistant#9179)
  ...
@balloob balloob mentioned this pull request Sep 7, 2017
@home-assistant home-assistant locked and limited conversation to collaborators Dec 11, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants