diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py index 290820f3bd4..dd27ab3ebda 100644 --- a/homeassistant/components/input_boolean.py +++ b/homeassistant/components/input_boolean.py @@ -30,13 +30,11 @@ SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, }) -DEFAULT_CONFIG = {CONF_INITIAL: DEFAULT_INITIAL} - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ cv.slug: vol.Any({ vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): cv.boolean, + vol.Optional(CONF_INITIAL): cv.boolean, vol.Optional(CONF_ICON): cv.icon, }, None) }) @@ -72,13 +70,13 @@ def async_setup(hass, config): for object_id, cfg in config[DOMAIN].items(): if not cfg: - cfg = DEFAULT_CONFIG + cfg = {} name = cfg.get(CONF_NAME) - state = cfg.get(CONF_INITIAL) + initial = cfg.get(CONF_INITIAL) icon = cfg.get(CONF_ICON) - entities.append(InputBoolean(object_id, name, state, icon)) + entities.append(InputBoolean(object_id, name, initial, icon)) if not entities: return False @@ -113,11 +111,11 @@ def async_setup(hass, config): class InputBoolean(ToggleEntity): """Representation of a boolean input.""" - def __init__(self, object_id, name, state, icon): + def __init__(self, object_id, name, initial, icon): """Initialize a boolean input.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name - self._state = state + self._state = initial self._icon = icon @property @@ -143,10 +141,12 @@ class InputBoolean(ToggleEntity): @asyncio.coroutine def async_added_to_hass(self): """Called when entity about to be added to hass.""" - state = yield from async_get_last_state(self.hass, self.entity_id) - if not state: + # If not None, we got an initial value. + if self._state is not None: return - self._state = state.state == STATE_ON + + state = yield from async_get_last_state(self.hass, self.entity_id) + self._state = state and state.state == STATE_ON @asyncio.coroutine def async_turn_on(self, **kwargs): diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index 5d0ad043ccc..7247a8b1093 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -58,10 +58,10 @@ SERVICE_SET_OPTIONS_SCHEMA = vol.Schema({ def _cv_input_select(cfg): """Config validation helper for input select (Voluptuous).""" options = cfg[CONF_OPTIONS] - state = cfg.get(CONF_INITIAL, options[0]) - if state not in options: + initial = cfg.get(CONF_INITIAL) + if initial is not None and initial not in options: raise vol.Invalid('initial state "{}" is not part of the options: {}' - .format(state, ','.join(options))) + .format(initial, ','.join(options))) return cfg @@ -117,9 +117,9 @@ def async_setup(hass, config): for object_id, cfg in config[DOMAIN].items(): name = cfg.get(CONF_NAME) options = cfg.get(CONF_OPTIONS) - state = cfg.get(CONF_INITIAL, options[0]) + initial = cfg.get(CONF_INITIAL) icon = cfg.get(CONF_ICON) - entities.append(InputSelect(object_id, name, state, options, icon)) + entities.append(InputSelect(object_id, name, initial, options, icon)) if not entities: return False @@ -187,23 +187,25 @@ def async_setup(hass, config): class InputSelect(Entity): """Representation of a select input.""" - def __init__(self, object_id, name, state, options, icon): + def __init__(self, object_id, name, initial, options, icon): """Initialize a select input.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name - self._current_option = state + self._current_option = initial self._options = options self._icon = icon @asyncio.coroutine def async_added_to_hass(self): """Called when entity about to be added to hass.""" + if self._current_option is not None: + return + state = yield from async_get_last_state(self.hass, self.entity_id) - if not state: - return - if state.state not in self._options: - return - self._current_option = state.state + if not state or state.state not in self._options: + self._current_option = self._options[0] + else: + self._current_option = state.state @property def should_poll(self): diff --git a/homeassistant/components/input_slider.py b/homeassistant/components/input_slider.py index 9e4faaf3d78..d10120e673b 100644 --- a/homeassistant/components/input_slider.py +++ b/homeassistant/components/input_slider.py @@ -45,11 +45,10 @@ def _cv_input_slider(cfg): if minimum >= maximum: raise vol.Invalid('Maximum ({}) is not greater than minimum ({})' .format(minimum, maximum)) - state = cfg.get(CONF_INITIAL, minimum) - if state < minimum or state > maximum: + state = cfg.get(CONF_INITIAL) + if state is not None and (state < minimum or state > maximum): raise vol.Invalid('Initial value {} not in range {}-{}' .format(state, minimum, maximum)) - cfg[CONF_INITIAL] = state return cfg @@ -88,12 +87,12 @@ def async_setup(hass, config): name = cfg.get(CONF_NAME) minimum = cfg.get(CONF_MIN) maximum = cfg.get(CONF_MAX) - state = cfg.get(CONF_INITIAL, minimum) + initial = cfg.get(CONF_INITIAL) step = cfg.get(CONF_STEP) icon = cfg.get(CONF_ICON) unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT) - entities.append(InputSlider(object_id, name, state, minimum, maximum, + entities.append(InputSlider(object_id, name, initial, minimum, maximum, step, icon, unit)) if not entities: @@ -120,12 +119,12 @@ def async_setup(hass, config): class InputSlider(Entity): """Represent an slider.""" - def __init__(self, object_id, name, state, minimum, maximum, step, icon, + def __init__(self, object_id, name, initial, minimum, maximum, step, icon, unit): """Initialize a select input.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name - self._current_value = state + self._current_value = initial self._minimum = minimum self._maximum = maximum self._step = step @@ -169,14 +168,17 @@ class InputSlider(Entity): @asyncio.coroutine def async_added_to_hass(self): """Called when entity about to be added to hass.""" - state = yield from async_get_last_state(self.hass, self.entity_id) - if not state: + if self._current_value is not None: return - num_value = float(state.state) - if num_value < self._minimum or num_value > self._maximum: - return - self._current_value = num_value + state = yield from async_get_last_state(self.hass, self.entity_id) + value = state and float(state.state) + + # Check against False because value can be 0 + if value is not False and self._minimum < value < self._maximum: + self._current_value = value + else: + self._current_value = self._minimum @asyncio.coroutine def async_select_value(self, value): diff --git a/tests/common.py b/tests/common.py index 66460110449..01889af1bf1 100644 --- a/tests/common.py +++ b/tests/common.py @@ -12,7 +12,7 @@ from contextlib import contextmanager from aiohttp import web from homeassistant import core as ha, loader -from homeassistant.setup import setup_component, DATA_SETUP +from homeassistant.setup import setup_component from homeassistant.config import async_process_component_config from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import ToggleEntity @@ -271,15 +271,10 @@ def mock_mqtt_component(hass): def mock_component(hass, component): """Mock a component is setup.""" - setup_tasks = hass.data.get(DATA_SETUP) - if setup_tasks is None: - setup_tasks = hass.data[DATA_SETUP] = {} - - if component not in setup_tasks: + if component in hass.config.components: AssertionError("Component {} is already setup".format(component)) hass.config.components.add(component) - setup_tasks[component] = asyncio.Task(mock_coro(True), loop=hass.loop) class MockModule(object): @@ -499,4 +494,4 @@ def mock_restore_cache(hass, states): assert len(hass.data[DATA_RESTORE_CACHE]) == len(states), \ "Duplicate entity_id? {}".format(states) hass.state = ha.CoreState.starting - hass.config.components.add(recorder.DOMAIN) + mock_component(hass, recorder.DOMAIN) diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py index f9042b7de4c..e39b12481bc 100644 --- a/tests/components/test_input_boolean.py +++ b/tests/components/test_input_boolean.py @@ -4,15 +4,15 @@ import asyncio import unittest import logging -from tests.common import get_test_home_assistant, mock_component - from homeassistant.core import CoreState, State from homeassistant.setup import setup_component, async_setup_component from homeassistant.components.input_boolean import ( - DOMAIN, is_on, toggle, turn_off, turn_on) + DOMAIN, is_on, toggle, turn_off, turn_on, CONF_INITIAL) from homeassistant.const import ( STATE_ON, STATE_OFF, ATTR_ICON, ATTR_FRIENDLY_NAME) -from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE + +from tests.common import ( + get_test_home_assistant, mock_component, mock_restore_cache) _LOGGER = logging.getLogger(__name__) @@ -111,11 +111,11 @@ class TestInputBoolean(unittest.TestCase): @asyncio.coroutine def test_restore_state(hass): """Ensure states are restored on startup.""" - hass.data[DATA_RESTORE_CACHE] = { - 'input_boolean.b1': State('input_boolean.b1', 'on'), - 'input_boolean.b2': State('input_boolean.b2', 'off'), - 'input_boolean.b3': State('input_boolean.b3', 'on'), - } + mock_restore_cache(hass, ( + State('input_boolean.b1', 'on'), + State('input_boolean.b2', 'off'), + State('input_boolean.b3', 'on'), + )) hass.state = CoreState.starting mock_component(hass, 'recorder') @@ -133,3 +133,28 @@ def test_restore_state(hass): state = hass.states.get('input_boolean.b2') assert state assert state.state == 'off' + + +@asyncio.coroutine +def test_initial_state_overrules_restore_state(hass): + """Ensure states are restored on startup.""" + mock_restore_cache(hass, ( + State('input_boolean.b1', 'on'), + State('input_boolean.b2', 'off'), + )) + + hass.state = CoreState.starting + + yield from async_setup_component(hass, DOMAIN, { + DOMAIN: { + 'b1': {CONF_INITIAL: False}, + 'b2': {CONF_INITIAL: True}, + }}) + + state = hass.states.get('input_boolean.b1') + assert state + assert state.state == 'off' + + state = hass.states.get('input_boolean.b2') + assert state + assert state.state == 'on' diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py index e2549acd35f..20d9656d8b0 100644 --- a/tests/components/test_input_select.py +++ b/tests/components/test_input_select.py @@ -229,7 +229,6 @@ def test_restore_state(hass): 'middle option', 'last option', ], - 'initial': 'middle option', } yield from async_setup_component(hass, DOMAIN, { @@ -242,6 +241,38 @@ def test_restore_state(hass): assert state assert state.state == 'last option' + state = hass.states.get('input_select.s2') + assert state + assert state.state == 'first option' + + +@asyncio.coroutine +def test_initial_state_overrules_restore_state(hass): + """Ensure states are restored on startup.""" + mock_restore_cache(hass, ( + State('input_select.s1', 'last option'), + State('input_select.s2', 'bad option'), + )) + + options = { + 'options': [ + 'first option', + 'middle option', + 'last option', + ], + 'initial': 'middle option', + } + + yield from async_setup_component(hass, DOMAIN, { + DOMAIN: { + 's1': options, + 's2': options, + }}) + + state = hass.states.get('input_select.s1') + assert state + assert state.state == 'middle option' + state = hass.states.get('input_select.s2') assert state assert state.state == 'middle option' diff --git a/tests/components/test_input_slider.py b/tests/components/test_input_slider.py index f4f9efe687f..7097e87e646 100644 --- a/tests/components/test_input_slider.py +++ b/tests/components/test_input_slider.py @@ -3,12 +3,11 @@ import asyncio import unittest -from tests.common import get_test_home_assistant, mock_component - from homeassistant.core import CoreState, State from homeassistant.setup import setup_component, async_setup_component from homeassistant.components.input_slider import (DOMAIN, select_value) -from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE + +from tests.common import get_test_home_assistant, mock_restore_cache class TestInputSlider(unittest.TestCase): @@ -75,13 +74,43 @@ class TestInputSlider(unittest.TestCase): @asyncio.coroutine def test_restore_state(hass): """Ensure states are restored on startup.""" - hass.data[DATA_RESTORE_CACHE] = { - 'input_slider.b1': State('input_slider.b1', '70'), - 'input_slider.b2': State('input_slider.b2', '200'), - } + mock_restore_cache(hass, ( + State('input_slider.b1', '70'), + State('input_slider.b2', '200'), + )) + + hass.state = CoreState.starting + + yield from async_setup_component(hass, DOMAIN, { + DOMAIN: { + 'b1': { + 'min': 0, + 'max': 100, + }, + 'b2': { + 'min': 10, + 'max': 100, + }, + }}) + + state = hass.states.get('input_slider.b1') + assert state + assert float(state.state) == 70 + + state = hass.states.get('input_slider.b2') + assert state + assert float(state.state) == 10 + + +@asyncio.coroutine +def test_initial_state_overrules_restore_state(hass): + """Ensure states are restored on startup.""" + mock_restore_cache(hass, ( + State('input_slider.b1', '70'), + State('input_slider.b2', '200'), + )) hass.state = CoreState.starting - mock_component(hass, 'recorder') yield from async_setup_component(hass, DOMAIN, { DOMAIN: { @@ -99,7 +128,7 @@ def test_restore_state(hass): state = hass.states.get('input_slider.b1') assert state - assert float(state.state) == 70 + assert float(state.state) == 50 state = hass.states.get('input_slider.b2') assert state