From bfdcee3287fa9f7ccf5c63c32b8d5cdd91aefb4c Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 4 Dec 2016 13:21:54 -0800 Subject: [PATCH 1/2] Add support for limiting which entities are recorded For privacy reasons, it may be desirable to either exclude some entities or domains from being recorded or limit the recorded devices to a specific list. This patch adds support. The exclude list allows for domains and entities to be blacklisted such that they will never be recorded. The include list, if present, will avoid recording all entities and domains unless they appear in the list. --- homeassistant/components/recorder/__init__.py | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 8de8925e093fb..7d12cd3504769 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -17,7 +17,8 @@ import voluptuous as vol from homeassistant.core import HomeAssistant, callback -from homeassistant.const import (EVENT_HOMEASSISTANT_START, +from homeassistant.const import (ATTR_ENTITY_ID, ATTR_DOMAIN, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) import homeassistant.helpers.config_validation as cv @@ -34,6 +35,10 @@ CONF_DB_URL = 'db_url' CONF_PURGE_DAYS = 'purge_days' +CONF_EXCLUDE = 'exclude' +CONF_INCLUDE = 'include' +CONF_ENTITIES = 'entities' +CONF_DOMAINS = 'domains' RETRIES = 3 CONNECT_RETRY_WAIT = 10 @@ -44,6 +49,16 @@ vol.Optional(CONF_PURGE_DAYS): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_DB_URL): cv.string, + CONF_EXCLUDE: vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) + }), + CONF_INCLUDE: vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) + }) }) }, extra=vol.ALLOW_EXTRA) @@ -110,7 +125,10 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: db_url = DEFAULT_URL.format( hass_config_path=hass.config.path(DEFAULT_DB_FILE)) - _INSTANCE = Recorder(hass, purge_days=purge_days, uri=db_url) + include = config.get(DOMAIN, {}).get(CONF_INCLUDE) + exclude = config.get(DOMAIN, {}).get(CONF_EXCLUDE) + _INSTANCE = Recorder(hass, purge_days=purge_days, uri=db_url, + include=include, exclude=exclude) return True @@ -153,7 +171,8 @@ def log_error(e: Exception, retry_wait: Optional[float]=0, class Recorder(threading.Thread): """A threaded recorder class.""" - def __init__(self, hass: HomeAssistant, purge_days: int, uri: str) -> None: + def __init__(self, hass: HomeAssistant, purge_days: int, uri: str, + include: list, exclude: list) -> None: """Initialize the recorder.""" threading.Thread.__init__(self) @@ -165,6 +184,20 @@ def __init__(self, hass: HomeAssistant, purge_days: int, uri: str) -> None: self.db_ready = threading.Event() self.engine = None # type: Any self._run = None # type: Any + self.include = include + self.exclude = exclude + if self.include: + self.include_entities = include[CONF_ENTITIES] + self.include_domains = include[CONF_DOMAINS] + else: + self.include_entities = [] + self.include_domains = [] + if self.exclude: + self.exclude_entities = exclude[CONF_ENTITIES] + self.exclude_domains = exclude[CONF_DOMAINS] + else: + self.exclude_entities = [] + self.exclude_domains = [] def start_recording(event): """Start recording.""" @@ -210,6 +243,19 @@ def purge_ticker(event): self.queue.task_done() continue + entity_id = event.data.get(ATTR_ENTITY_ID) + domain = event.data.get(ATTR_DOMAIN) + + if self.exclude and entity_id in self.exclude_entities or \ + domain in self.exclude_domains: + self.queue.task_done() + continue + + if self.include and entity_id not in self.include_entities and \ + domain not in self.include_domains: + self.queue.task_done() + continue + dbevent = Events.from_event(event) self._commit(dbevent) From 1e671e0bf7d2aedeca576b03ed5b041a72afe6f0 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Tue, 3 Jan 2017 23:19:39 +0200 Subject: [PATCH 2/2] Feedback --- homeassistant/components/history.py | 8 +-- homeassistant/components/recorder/__init__.py | 50 +++++++------------ homeassistant/const.py | 4 ++ 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index eee0570c9bc07..a077ad09ec79e 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -10,7 +10,8 @@ from itertools import groupby import voluptuous as vol -from homeassistant.const import HTTP_BAD_REQUEST +from homeassistant.const import ( + HTTP_BAD_REQUEST, CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE) import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components import recorder, script @@ -21,11 +22,6 @@ DOMAIN = 'history' DEPENDENCIES = ['recorder', 'http'] -CONF_EXCLUDE = 'exclude' -CONF_INCLUDE = 'include' -CONF_ENTITIES = 'entities' -CONF_DOMAINS = 'domains' - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ CONF_EXCLUDE: vol.Schema({ diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 7d12cd3504769..41a7991c32fb2 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -12,15 +12,15 @@ import threading import time from datetime import timedelta, datetime -from typing import Any, Union, Optional, List +from typing import Any, Union, Optional, List, Dict import voluptuous as vol from homeassistant.core import HomeAssistant, callback -from homeassistant.const import (ATTR_ENTITY_ID, ATTR_DOMAIN, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_DOMAIN, CONF_ENTITIES, CONF_EXCLUDE, CONF_DOMAINS, + CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.typing import ConfigType, QueryType @@ -35,10 +35,6 @@ CONF_DB_URL = 'db_url' CONF_PURGE_DAYS = 'purge_days' -CONF_EXCLUDE = 'exclude' -CONF_INCLUDE = 'include' -CONF_ENTITIES = 'entities' -CONF_DOMAINS = 'domains' RETRIES = 3 CONNECT_RETRY_WAIT = 10 @@ -49,12 +45,12 @@ vol.Optional(CONF_PURGE_DAYS): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_DB_URL): cv.string, - CONF_EXCLUDE: vol.Schema({ + vol.Optional(CONF_EXCLUDE, default={}): vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]) }), - CONF_INCLUDE: vol.Schema({ + vol.Optional(CONF_INCLUDE, default={}): vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]) @@ -125,8 +121,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: db_url = DEFAULT_URL.format( hass_config_path=hass.config.path(DEFAULT_DB_FILE)) - include = config.get(DOMAIN, {}).get(CONF_INCLUDE) - exclude = config.get(DOMAIN, {}).get(CONF_EXCLUDE) + include = config.get(DOMAIN, {}).get(CONF_INCLUDE, {}) + exclude = config.get(DOMAIN, {}).get(CONF_EXCLUDE, {}) _INSTANCE = Recorder(hass, purge_days=purge_days, uri=db_url, include=include, exclude=exclude) @@ -172,7 +168,7 @@ class Recorder(threading.Thread): """A threaded recorder class.""" def __init__(self, hass: HomeAssistant, purge_days: int, uri: str, - include: list, exclude: list) -> None: + include: Dict, exclude: Dict) -> None: """Initialize the recorder.""" threading.Thread.__init__(self) @@ -184,20 +180,11 @@ def __init__(self, hass: HomeAssistant, purge_days: int, uri: str, self.db_ready = threading.Event() self.engine = None # type: Any self._run = None # type: Any - self.include = include - self.exclude = exclude - if self.include: - self.include_entities = include[CONF_ENTITIES] - self.include_domains = include[CONF_DOMAINS] - else: - self.include_entities = [] - self.include_domains = [] - if self.exclude: - self.exclude_entities = exclude[CONF_ENTITIES] - self.exclude_domains = exclude[CONF_DOMAINS] - else: - self.exclude_entities = [] - self.exclude_domains = [] + + self.include = include.get(CONF_ENTITIES, []) + \ + include.get(CONF_DOMAINS, []) + self.exclude = exclude.get(CONF_ENTITIES, []) + \ + exclude.get(CONF_DOMAINS, []) def start_recording(event): """Start recording.""" @@ -246,13 +233,12 @@ def purge_ticker(event): entity_id = event.data.get(ATTR_ENTITY_ID) domain = event.data.get(ATTR_DOMAIN) - if self.exclude and entity_id in self.exclude_entities or \ - domain in self.exclude_domains: + if entity_id in self.exclude or domain in self.exclude: self.queue.task_done() continue - if self.include and entity_id not in self.include_entities and \ - domain not in self.include_domains: + if (self.include and entity_id not in self.include and + domain not in self.include): self.queue.task_done() continue diff --git a/homeassistant/const.py b/homeassistant/const.py index 0789531e9a333..d266a3aae557d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -81,11 +81,14 @@ CONF_DISARM_AFTER_TRIGGER = 'disarm_after_trigger' CONF_DISCOVERY = 'discovery' CONF_DISPLAY_OPTIONS = 'display_options' +CONF_DOMAINS = 'domains' CONF_ELEVATION = 'elevation' CONF_EMAIL = 'email' +CONF_ENTITIES = 'entities' CONF_ENTITY_ID = 'entity_id' CONF_ENTITY_NAMESPACE = 'entity_namespace' CONF_EVENT = 'event' +CONF_EXCLUDE = 'exclude' CONF_FILE_PATH = 'file_path' CONF_FILENAME = 'filename' CONF_FRIENDLY_NAME = 'friendly_name' @@ -93,6 +96,7 @@ CONF_HOST = 'host' CONF_HOSTS = 'hosts' CONF_ICON = 'icon' +CONF_INCLUDE = 'include' CONF_ID = 'id' CONF_LATITUDE = 'latitude' CONF_LONGITUDE = 'longitude'