Black
This commit is contained in:
parent
da05dfe708
commit
4de97abc3a
2676 changed files with 163166 additions and 140084 deletions
|
@ -3,66 +3,87 @@ from collections import OrderedDict
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice
|
||||
from homeassistant.const import (
|
||||
CONF_ABOVE, CONF_BELOW, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_NAME,
|
||||
CONF_PLATFORM, CONF_STATE, CONF_VALUE_TEMPLATE, STATE_UNKNOWN)
|
||||
CONF_ABOVE,
|
||||
CONF_BELOW,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
CONF_PLATFORM,
|
||||
CONF_STATE,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import condition
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
ATTR_OBSERVATIONS = 'observations'
|
||||
ATTR_PROBABILITY = 'probability'
|
||||
ATTR_PROBABILITY_THRESHOLD = 'probability_threshold'
|
||||
ATTR_OBSERVATIONS = "observations"
|
||||
ATTR_PROBABILITY = "probability"
|
||||
ATTR_PROBABILITY_THRESHOLD = "probability_threshold"
|
||||
|
||||
CONF_OBSERVATIONS = 'observations'
|
||||
CONF_PRIOR = 'prior'
|
||||
CONF_OBSERVATIONS = "observations"
|
||||
CONF_PRIOR = "prior"
|
||||
CONF_TEMPLATE = "template"
|
||||
CONF_PROBABILITY_THRESHOLD = 'probability_threshold'
|
||||
CONF_P_GIVEN_F = 'prob_given_false'
|
||||
CONF_P_GIVEN_T = 'prob_given_true'
|
||||
CONF_TO_STATE = 'to_state'
|
||||
CONF_PROBABILITY_THRESHOLD = "probability_threshold"
|
||||
CONF_P_GIVEN_F = "prob_given_false"
|
||||
CONF_P_GIVEN_T = "prob_given_true"
|
||||
CONF_TO_STATE = "to_state"
|
||||
|
||||
DEFAULT_NAME = "Bayesian Binary Sensor"
|
||||
DEFAULT_PROBABILITY_THRESHOLD = 0.5
|
||||
|
||||
NUMERIC_STATE_SCHEMA = vol.Schema({
|
||||
CONF_PLATFORM: 'numeric_state',
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(CONF_ABOVE): vol.Coerce(float),
|
||||
vol.Optional(CONF_BELOW): vol.Coerce(float),
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float)
|
||||
}, required=True)
|
||||
NUMERIC_STATE_SCHEMA = vol.Schema(
|
||||
{
|
||||
CONF_PLATFORM: "numeric_state",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(CONF_ABOVE): vol.Coerce(float),
|
||||
vol.Optional(CONF_BELOW): vol.Coerce(float),
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float),
|
||||
},
|
||||
required=True,
|
||||
)
|
||||
|
||||
STATE_SCHEMA = vol.Schema({
|
||||
CONF_PLATFORM: CONF_STATE,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_TO_STATE): cv.string,
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float)
|
||||
}, required=True)
|
||||
STATE_SCHEMA = vol.Schema(
|
||||
{
|
||||
CONF_PLATFORM: CONF_STATE,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_TO_STATE): cv.string,
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float),
|
||||
},
|
||||
required=True,
|
||||
)
|
||||
|
||||
TEMPLATE_SCHEMA = vol.Schema({
|
||||
CONF_PLATFORM: CONF_TEMPLATE,
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float)
|
||||
}, required=True)
|
||||
TEMPLATE_SCHEMA = vol.Schema(
|
||||
{
|
||||
CONF_PLATFORM: CONF_TEMPLATE,
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_P_GIVEN_T): vol.Coerce(float),
|
||||
vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float),
|
||||
},
|
||||
required=True,
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.string,
|
||||
vol.Required(CONF_OBSERVATIONS):
|
||||
vol.Schema(vol.All(cv.ensure_list,
|
||||
[vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA,
|
||||
TEMPLATE_SCHEMA)])),
|
||||
vol.Required(CONF_PRIOR): vol.Coerce(float),
|
||||
vol.Optional(CONF_PROBABILITY_THRESHOLD,
|
||||
default=DEFAULT_PROBABILITY_THRESHOLD): vol.Coerce(float),
|
||||
})
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.string,
|
||||
vol.Required(CONF_OBSERVATIONS): vol.Schema(
|
||||
vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA, TEMPLATE_SCHEMA)],
|
||||
)
|
||||
),
|
||||
vol.Required(CONF_PRIOR): vol.Coerce(float),
|
||||
vol.Optional(
|
||||
CONF_PROBABILITY_THRESHOLD, default=DEFAULT_PROBABILITY_THRESHOLD
|
||||
): vol.Coerce(float),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def update_probability(prior, prob_true, prob_false):
|
||||
|
@ -73,8 +94,7 @@ def update_probability(prior, prob_true, prob_false):
|
|||
return probability
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Bayesian Binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
observations = config.get(CONF_OBSERVATIONS)
|
||||
|
@ -82,17 +102,20 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||
probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
async_add_entities([
|
||||
BayesianBinarySensor(
|
||||
name, prior, observations, probability_threshold, device_class)
|
||||
], True)
|
||||
async_add_entities(
|
||||
[
|
||||
BayesianBinarySensor(
|
||||
name, prior, observations, probability_threshold, device_class
|
||||
)
|
||||
],
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class BayesianBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Bayesian sensor."""
|
||||
|
||||
def __init__(self, name, prior, observations, probability_threshold,
|
||||
device_class):
|
||||
def __init__(self, name, prior, observations, probability_threshold, device_class):
|
||||
"""Initialize the Bayesian sensor."""
|
||||
self._name = name
|
||||
self._observations = observations
|
||||
|
@ -106,32 +129,31 @@ class BayesianBinarySensor(BinarySensorDevice):
|
|||
|
||||
to_observe = set()
|
||||
for obs in self._observations:
|
||||
if 'entity_id' in obs:
|
||||
to_observe.update(set([obs.get('entity_id')]))
|
||||
if 'value_template' in obs:
|
||||
to_observe.update(
|
||||
set(obs.get(CONF_VALUE_TEMPLATE).extract_entities()))
|
||||
if "entity_id" in obs:
|
||||
to_observe.update(set([obs.get("entity_id")]))
|
||||
if "value_template" in obs:
|
||||
to_observe.update(set(obs.get(CONF_VALUE_TEMPLATE).extract_entities()))
|
||||
self.entity_obs = dict.fromkeys(to_observe, [])
|
||||
|
||||
for ind, obs in enumerate(self._observations):
|
||||
obs['id'] = ind
|
||||
if 'entity_id' in obs:
|
||||
self.entity_obs[obs['entity_id']].append(obs)
|
||||
if 'value_template' in obs:
|
||||
obs["id"] = ind
|
||||
if "entity_id" in obs:
|
||||
self.entity_obs[obs["entity_id"]].append(obs)
|
||||
if "value_template" in obs:
|
||||
for ent in obs.get(CONF_VALUE_TEMPLATE).extract_entities():
|
||||
self.entity_obs[ent].append(obs)
|
||||
|
||||
self.watchers = {
|
||||
'numeric_state': self._process_numeric_state,
|
||||
'state': self._process_state,
|
||||
'template': self._process_template
|
||||
"numeric_state": self._process_numeric_state,
|
||||
"state": self._process_state,
|
||||
"template": self._process_template,
|
||||
}
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity about to be added."""
|
||||
|
||||
@callback
|
||||
def async_threshold_sensor_state_listener(entity, old_state,
|
||||
new_state):
|
||||
def async_threshold_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Handle sensor state changes."""
|
||||
if new_state.state == STATE_UNKNOWN:
|
||||
return
|
||||
|
@ -139,33 +161,32 @@ class BayesianBinarySensor(BinarySensorDevice):
|
|||
entity_obs_list = self.entity_obs[entity]
|
||||
|
||||
for entity_obs in entity_obs_list:
|
||||
platform = entity_obs['platform']
|
||||
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'])
|
||||
prior = update_probability(prior, obs["prob_true"], obs["prob_false"])
|
||||
self.probability = prior
|
||||
|
||||
self.hass.async_add_job(self.async_update_ha_state, True)
|
||||
|
||||
async_track_state_change(
|
||||
self.hass, self.entity_obs, async_threshold_sensor_state_listener)
|
||||
self.hass, self.entity_obs, async_threshold_sensor_state_listener
|
||||
)
|
||||
|
||||
def _update_current_obs(self, entity_observation, should_trigger):
|
||||
"""Update current observation."""
|
||||
obs_id = entity_observation['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)
|
||||
prob_true = entity_observation["prob_given_true"]
|
||||
prob_false = entity_observation.get("prob_given_false", 1 - prob_true)
|
||||
|
||||
self.current_obs[obs_id] = {
|
||||
'prob_true': prob_true,
|
||||
'prob_false': prob_false
|
||||
"prob_true": prob_true,
|
||||
"prob_false": prob_false,
|
||||
}
|
||||
|
||||
else:
|
||||
|
@ -173,21 +194,26 @@ class BayesianBinarySensor(BinarySensorDevice):
|
|||
|
||||
def _process_numeric_state(self, entity_observation):
|
||||
"""Add entity to current_obs if numeric state conditions are met."""
|
||||
entity = entity_observation['entity_id']
|
||||
entity = entity_observation["entity_id"]
|
||||
|
||||
should_trigger = condition.async_numeric_state(
|
||||
self.hass, entity,
|
||||
entity_observation.get('below'),
|
||||
entity_observation.get('above'), None, entity_observation)
|
||||
self.hass,
|
||||
entity,
|
||||
entity_observation.get("below"),
|
||||
entity_observation.get("above"),
|
||||
None,
|
||||
entity_observation,
|
||||
)
|
||||
|
||||
self._update_current_obs(entity_observation, should_trigger)
|
||||
|
||||
def _process_state(self, entity_observation):
|
||||
"""Add entity to current observations if state conditions are met."""
|
||||
entity = entity_observation['entity_id']
|
||||
entity = entity_observation["entity_id"]
|
||||
|
||||
should_trigger = condition.state(
|
||||
self.hass, entity, entity_observation.get('to_state'))
|
||||
self.hass, entity, entity_observation.get("to_state")
|
||||
)
|
||||
|
||||
self._update_current_obs(entity_observation, should_trigger)
|
||||
|
||||
|
@ -196,7 +222,8 @@ class BayesianBinarySensor(BinarySensorDevice):
|
|||
template = entity_observation.get(CONF_VALUE_TEMPLATE)
|
||||
template.hass = self.hass
|
||||
should_trigger = condition.async_template(
|
||||
self.hass, template, entity_observation)
|
||||
self.hass, template, entity_observation
|
||||
)
|
||||
self._update_current_obs(entity_observation, should_trigger)
|
||||
|
||||
@property
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue