Apply new customize format to Zwave (#5603)

pull/5611/head
andrey-git 2017-01-28 22:29:51 +02:00 committed by Johann Kellerman
parent 405b2fdfa0
commit 1fb372ffdb
9 changed files with 171 additions and 84 deletions

View File

@ -17,6 +17,7 @@ from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
color_temperature_mired_to_kelvin, color_temperature_to_rgb, \
color_rgb_to_rgbw, color_rgbw_to_rgb
from homeassistant.helpers import customize
_LOGGER = logging.getLogger(__name__)
@ -54,13 +55,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
customize = hass.data['zwave_customize']
name = '{}.{}'.format(DOMAIN, zwave.object_id(value))
node_config = customize.get(name, {})
node_config = customize.get_overrides(hass, zwave.DOMAIN, name)
refresh = node_config.get(zwave.CONF_REFRESH_VALUE)
delay = node_config.get(zwave.CONF_REFRESH_DELAY)
_LOGGER.debug('customize=%s name=%s node_config=%s CONF_REFRESH_VALUE=%s'
' CONF_REFRESH_DELAY=%s', customize, name, node_config,
_LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s'
' CONF_REFRESH_DELAY=%s', name, node_config,
refresh, delay)
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return

View File

@ -11,10 +11,10 @@ from pprint import pprint
import voluptuous as vol
from homeassistant.helpers import discovery
from homeassistant.helpers import discovery, customize
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_LOCATION, ATTR_ENTITY_ID, CONF_CUSTOMIZE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_ENTITY_ID)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_change
from homeassistant.util import convert, slugify
@ -150,9 +150,9 @@ CHANGE_ASSOCIATION_SCHEMA = vol.Schema({
vol.Optional(const.ATTR_INSTANCE, default=0x00): vol.Coerce(int)
})
CUSTOMIZE_SCHEMA = vol.Schema({
vol.Optional(CONF_POLLING_INTENSITY):
vol.All(cv.positive_int),
_ZWAVE_CUSTOMIZE_SCHEMA_ENTRY = vol.Schema({
vol.Required(CONF_ENTITY_ID): cv.match_all,
vol.Optional(CONF_POLLING_INTENSITY): cv.positive_int,
vol.Optional(CONF_IGNORED, default=DEFAULT_CONF_IGNORED): cv.boolean,
vol.Optional(CONF_REFRESH_VALUE, default=DEFAULT_CONF_REFRESH_VALUE):
cv.boolean,
@ -164,8 +164,9 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_AUTOHEAL, default=DEFAULT_CONF_AUTOHEAL): cv.boolean,
vol.Optional(CONF_CONFIG_PATH): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}):
vol.Schema({cv.string: CUSTOMIZE_SCHEMA}),
vol.Optional(CONF_CUSTOMIZE, default=[]):
vol.All(customize.CUSTOMIZE_SCHEMA,
[_ZWAVE_CUSTOMIZE_SCHEMA_ENTRY]),
vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean,
vol.Optional(CONF_POLLING_INTERVAL, default=DEFAULT_POLLING_INTERVAL):
cv.positive_int,
@ -268,8 +269,7 @@ def setup(hass, config):
# Load configuration
use_debug = config[DOMAIN].get(CONF_DEBUG)
hass.data['zwave_customize'] = config[DOMAIN].get(CONF_CUSTOMIZE)
customize = hass.data['zwave_customize']
customize.set_customize(hass, DOMAIN, config[DOMAIN].get(CONF_CUSTOMIZE))
autoheal = config[DOMAIN].get(CONF_AUTOHEAL)
# Setup options
@ -349,7 +349,7 @@ def setup(hass, config):
value.genre)
name = "{}.{}".format(component, object_id(value))
node_config = customize.get(name, {})
node_config = customize.get_overrides(hass, DOMAIN, name)
if node_config.get(CONF_IGNORED):
_LOGGER.info("Ignoring device %s", name)

View File

@ -13,7 +13,7 @@ from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC,
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
CONF_ENTITY_ID, __version__)
__version__)
from homeassistant.core import DOMAIN as CONF_CORE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import get_component
@ -21,7 +21,7 @@ from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as date_util, location as loc_util
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
from homeassistant.helpers.customize import set_customize
from homeassistant.helpers import customize
_LOGGER = logging.getLogger(__name__)
@ -86,27 +86,6 @@ tts:
"""
CUSTOMIZE_SCHEMA_ENTRY = vol.Schema({
vol.Required(CONF_ENTITY_ID): vol.All(
cv.ensure_list_csv, vol.Length(min=1), [cv.string], [vol.Lower])
}, extra=vol.ALLOW_EXTRA)
def _convert_old_config(inp: Any) -> List:
if not isinstance(inp, dict):
return cv.ensure_list(inp)
if CONF_ENTITY_ID in inp:
return [inp] # sigle entry
res = []
inp = vol.Schema({cv.match_all: dict})(inp)
for key, val in inp.items():
val = dict(val)
val[CONF_ENTITY_ID] = key
res.append(val)
return res
PACKAGES_CONFIG_SCHEMA = vol.Schema({
cv.slug: vol.Schema( # Package names are slugs
{cv.slug: vol.Any(dict, list)}) # Only slugs for component names
@ -120,8 +99,7 @@ CORE_CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit,
CONF_UNIT_SYSTEM: cv.unit_system,
CONF_TIME_ZONE: cv.time_zone,
vol.Optional(CONF_CUSTOMIZE, default=[]): vol.All(
_convert_old_config, [CUSTOMIZE_SCHEMA_ENTRY]),
vol.Optional(CONF_CUSTOMIZE, default=[]): customize.CUSTOMIZE_SCHEMA,
vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA,
})
@ -280,6 +258,7 @@ def async_process_ha_core_config(hass, config):
This method is a coroutine.
"""
print(CORE_CONFIG_SCHEMA)
config = CORE_CONFIG_SCHEMA(config)
hac = hass.config
@ -306,9 +285,9 @@ def async_process_ha_core_config(hass, config):
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
customize = merge_packages_customize(
merged_customize = merge_packages_customize(
config[CONF_CUSTOMIZE], config[CONF_PACKAGES])
set_customize(hass, customize)
customize.set_customize(hass, CONF_CORE, merged_customize)
if CONF_UNIT_SYSTEM in config:
if config[CONF_UNIT_SYSTEM] == CONF_UNIT_SYSTEM_IMPERIAL:
@ -463,15 +442,14 @@ def merge_packages_config(config, packages):
return config
def merge_packages_customize(customize, packages):
def merge_packages_customize(core_customize, packages):
"""Merge customize from packages."""
schema = vol.Schema({
vol.Optional(CONF_CORE): vol.Schema({
CONF_CUSTOMIZE: vol.All(
_convert_old_config, [CUSTOMIZE_SCHEMA_ENTRY])})
CONF_CUSTOMIZE: customize.CUSTOMIZE_SCHEMA}),
}, extra=vol.ALLOW_EXTRA)
cust = list(customize)
cust = list(core_customize)
for pkg in packages.values():
conf = schema(pkg)
cust.extend(conf.get(CONF_CORE, {}).get(CONF_CUSTOMIZE, []))

View File

@ -1,25 +1,51 @@
"""A helper module for customization."""
import collections
from typing import Dict, List
from typing import Any, Dict, List
import fnmatch
import voluptuous as vol
from homeassistant.const import CONF_ENTITY_ID
from homeassistant.core import HomeAssistant, split_entity_id
import homeassistant.helpers.config_validation as cv
_OVERWRITE_KEY = 'overwrite'
_OVERWRITE_CACHE_KEY = 'overwrite_cache'
_OVERWRITE_KEY_FORMAT = '{}.overwrite'
_OVERWRITE_CACHE_KEY_FORMAT = '{}.overwrite_cache'
_CUSTOMIZE_SCHEMA_ENTRY = vol.Schema({
vol.Required(CONF_ENTITY_ID): vol.All(
cv.ensure_list_csv, vol.Length(min=1), [vol.Schema(str)], [vol.Lower])
}, extra=vol.ALLOW_EXTRA)
def set_customize(hass: HomeAssistant, customize: List[Dict]) -> None:
def _convert_old_config(inp: Any) -> List:
if not isinstance(inp, dict):
return cv.ensure_list(inp)
if CONF_ENTITY_ID in inp:
return [inp] # sigle entry
res = []
inp = vol.Schema({cv.match_all: dict})(inp)
for key, val in inp.items():
val = dict(val)
val[CONF_ENTITY_ID] = key
res.append(val)
return res
CUSTOMIZE_SCHEMA = vol.All(_convert_old_config, [_CUSTOMIZE_SCHEMA_ENTRY])
def set_customize(
hass: HomeAssistant, domain: str, customize: List[Dict]) -> None:
"""Overwrite all current customize settings.
Async friendly.
"""
hass.data[_OVERWRITE_KEY] = customize
hass.data[_OVERWRITE_CACHE_KEY] = {}
hass.data[_OVERWRITE_KEY_FORMAT.format(domain)] = customize
hass.data[_OVERWRITE_CACHE_KEY_FORMAT.format(domain)] = {}
def get_overrides(hass: HomeAssistant, entity_id: str) -> Dict:
def get_overrides(hass: HomeAssistant, domain: str, entity_id: str) -> Dict:
"""Return a dictionary of overrides related to entity_id.
Whole-domain overrides are of lowest priorities,
@ -28,10 +54,11 @@ def get_overrides(hass: HomeAssistant, entity_id: str) -> Dict:
The lookups are cached.
"""
if _OVERWRITE_CACHE_KEY in hass.data and \
entity_id in hass.data[_OVERWRITE_CACHE_KEY]:
return hass.data[_OVERWRITE_CACHE_KEY][entity_id]
if _OVERWRITE_KEY not in hass.data:
cache_key = _OVERWRITE_CACHE_KEY_FORMAT.format(domain)
if cache_key in hass.data and entity_id in hass.data[cache_key]:
return hass.data[cache_key][entity_id]
overwrite_key = _OVERWRITE_KEY_FORMAT.format(domain)
if overwrite_key not in hass.data:
return {}
domain_result = {} # type: Dict[str, Any]
glob_result = {} # type: Dict[str, Any]
@ -57,7 +84,7 @@ def get_overrides(hass: HomeAssistant, entity_id: str) -> Dict:
else:
target[key] = source[key]
for rule in hass.data[_OVERWRITE_KEY]:
for rule in hass.data[overwrite_key]:
if CONF_ENTITY_ID in rule:
entities = rule[CONF_ENTITY_ID]
if domain in entities:
@ -74,7 +101,7 @@ def get_overrides(hass: HomeAssistant, entity_id: str) -> Dict:
deep_update(result, clean_entry(domain_result))
deep_update(result, clean_entry(glob_result))
deep_update(result, clean_entry(exact_result))
if _OVERWRITE_CACHE_KEY not in hass.data:
hass.data[_OVERWRITE_CACHE_KEY] = {}
hass.data[_OVERWRITE_CACHE_KEY][entity_id] = result
if cache_key not in hass.data:
hass.data[cache_key] = {}
hass.data[cache_key][entity_id] = result
return result

View File

@ -11,7 +11,7 @@ from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_ENTITY_PICTURE)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, DOMAIN as CORE_DOMAIN
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify
from homeassistant.util.async import (
@ -242,7 +242,7 @@ class Entity(object):
end - start)
# Overwrite properties that have been set in the config file.
attr.update(get_overrides(self.hass, self.entity_id))
attr.update(get_overrides(self.hass, CORE_DOMAIN, self.entity_id))
# Remove hidden property if false so it won't show up.
if not attr.get(ATTR_HIDDEN, True):

View File

@ -0,0 +1,68 @@
"""The tests for the zwave component."""
import unittest
from unittest.mock import MagicMock, patch
from homeassistant.bootstrap import setup_component
from homeassistant.components import zwave
from tests.common import get_test_home_assistant
class TestComponentZwave(unittest.TestCase):
"""Test the Zwave component."""
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
def _validate_config(self, validator, config):
libopenzwave = MagicMock()
libopenzwave.__file__ = 'test'
with patch.dict('sys.modules', {
'libopenzwave': libopenzwave,
'openzwave.option': MagicMock(),
'openzwave.network': MagicMock(),
'openzwave.group': MagicMock(),
}):
validator(setup_component(self.hass, zwave.DOMAIN, {
zwave.DOMAIN: config,
}))
def test_empty_config(self):
"""Test empty config."""
self._validate_config(self.assertTrue, {})
def test_empty_customize(self):
"""Test empty customize."""
self._validate_config(self.assertTrue, {'customize': {}})
self._validate_config(self.assertTrue, {'customize': []})
def test_empty_customize_content(self):
"""Test empty customize."""
self._validate_config(
self.assertTrue, {'customize': {'test.test': {}}})
def test_full_customize_dict(self):
"""Test full customize as dict."""
self._validate_config(self.assertTrue, {'customize': {'test.test': {
zwave.CONF_POLLING_INTENSITY: 10,
zwave.CONF_IGNORED: 1,
zwave.CONF_REFRESH_VALUE: 1,
zwave.CONF_REFRESH_DELAY: 10}}})
def test_full_customize_list(self):
"""Test full customize as list."""
self._validate_config(self.assertTrue, {'customize': [{
'entity_id': 'test.test',
zwave.CONF_POLLING_INTENSITY: 10,
zwave.CONF_IGNORED: 1,
zwave.CONF_REFRESH_VALUE: 1,
zwave.CONF_REFRESH_DELAY: 10}]})
def test_bad_customize(self):
"""Test customize with extra keys."""
self._validate_config(
self.assertFalse, {'customize': {'test.test': {'extra_key': 10}}})

View File

@ -1,5 +1,7 @@
"""Test the customize helper."""
import homeassistant.helpers.customize as customize
from voluptuous import MultipleInvalid
import pytest
class MockHass(object):
@ -17,8 +19,9 @@ class TestHelpersCustomize(object):
self.hass = MockHass()
def _get_overrides(self, overrides):
customize.set_customize(self.hass, overrides)
return customize.get_overrides(self.hass, self.entity_id)
test_domain = 'test.domain'
customize.set_customize(self.hass, test_domain, overrides)
return customize.get_overrides(self.hass, test_domain, self.entity_id)
def test_override_single_value(self):
"""Test entity customization through configuration."""
@ -75,7 +78,7 @@ class TestHelpersCustomize(object):
'key3': 'valueDomain'}
def test_override_deep_dict(self):
"""Test we can overwrite hidden property to True."""
"""Test we can deep-overwrite a dict."""
result = self._get_overrides(
[{'entity_id': [self.entity_id],
'test': {'key1': 'value1', 'key2': 'value2'}},
@ -85,3 +88,32 @@ class TestHelpersCustomize(object):
'key1': 'value1',
'key2': 'value22',
'key3': 'value3'}
def test_schema_bad_schema(self):
"""Test bad customize schemas."""
for value in (
{'test.test': 10},
{'test.test': ['hello']},
{'entity_id': {'a': 'b'}},
{'entity_id': 10},
[{'test.test': 'value'}],
):
with pytest.raises(
MultipleInvalid,
message="{} should have raised MultipleInvalid".format(
value)):
customize.CUSTOMIZE_SCHEMA(value)
def test_get_customize_schema_allow_extra(self):
"""Test schema with ALLOW_EXTRA."""
for value in (
{'test.test': {'hidden': True}},
{'test.test': {'key': ['value1', 'value2']}},
[{'entity_id': 'id1', 'key': 'value'}],
):
customize.CUSTOMIZE_SCHEMA(value)
def test_get_customize_schema_csv(self):
"""Test schema with comma separated entity IDs."""
assert [{'entity_id': ['id1', 'id2', 'id3']}] == \
customize.CUSTOMIZE_SCHEMA([{'entity_id': 'id1,ID2 , id3'}])

View File

@ -90,6 +90,7 @@ class TestHelpersEntity(object):
"""Test we can overwrite hidden property to True."""
set_customize(
self.hass,
entity.CORE_DOMAIN,
[{'entity_id': [self.entity.entity_id], ATTR_HIDDEN: True}])
self.entity.update_ha_state()

View File

@ -234,25 +234,6 @@ class TestConfig(unittest.TestCase):
assert state.attributes['hidden']
def test_entity_customization_comma_separated(self):
"""Test entity customization through configuration."""
config = {CONF_LATITUDE: 50,
CONF_LONGITUDE: 50,
CONF_NAME: 'Test',
CONF_CUSTOMIZE: [
{'entity_id': 'test.not_test,test,test.not_t*',
'key1': 'value1'},
{'entity_id': 'test.test,not_test,test.not_t*',
'key2': 'value2'},
{'entity_id': 'test.not_test,not_test,test.t*',
'key3': 'value3'}]}
state = self._compute_state(config)
assert state.attributes['key1'] == 'value1'
assert state.attributes['key2'] == 'value2'
assert state.attributes['key3'] == 'value3'
@mock.patch('homeassistant.config.shutil')
@mock.patch('homeassistant.config.os')
def test_remove_lib_on_upgrade(self, mock_os, mock_shutil):