diff --git a/homeassistant/components/input_slider.py b/homeassistant/components/input_slider.py new file mode 100644 index 00000000000..bef270f1387 --- /dev/null +++ b/homeassistant/components/input_slider.py @@ -0,0 +1,145 @@ +""" +Component to offer a way to select a value from a slider. + +For more details about this component, please refer to the documentation +at https://home-assistant.io/components/input_slider/ +""" +import logging + +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.util import slugify + +DOMAIN = 'input_slider' +ENTITY_ID_FORMAT = DOMAIN + '.{}' +_LOGGER = logging.getLogger(__name__) + +CONF_NAME = 'name' +CONF_INITIAL = 'initial' +CONF_MIN = 'min' +CONF_MAX = 'max' +CONF_ICON = 'icon' +CONF_STEP = 'step' + +ATTR_VALUE = 'value' +ATTR_MIN = 'min' +ATTR_MAX = 'max' +ATTR_STEP = 'step' + +SERVICE_SELECT_VALUE = 'select_value' + + +def select_value(hass, entity_id, value): + """Set input_slider to value.""" + hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, { + ATTR_ENTITY_ID: entity_id, + ATTR_VALUE: value, + }) + + +def setup(hass, config): + """Set up input slider.""" + if not isinstance(config.get(DOMAIN), dict): + _LOGGER.error('Expected %s config to be a dictionary', DOMAIN) + return False + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + if object_id != slugify(object_id): + _LOGGER.warning("Found invalid key for boolean input: %s. " + "Use %s instead", object_id, slugify(object_id)) + continue + if not cfg: + _LOGGER.warning("No configuration specified for %s", object_id) + continue + + name = cfg.get(CONF_NAME) + minimum = cfg.get(CONF_MIN) + maximum = cfg.get(CONF_MAX) + state = cfg.get(CONF_INITIAL) + step = cfg.get(CONF_STEP) + icon = cfg.get(CONF_ICON) + + if state < minimum: + state = minimum + if state > maximum: + state = maximum + + entities.append( + InputSlider(object_id, name, state, minimum, maximum, step, icon) + ) + + if not entities: + return False + + def select_value_service(call): + """Handle a calls to the input slider services.""" + target_inputs = component.extract_from_service(call) + + for input_slider in target_inputs: + input_slider.select_value(call.data.get(ATTR_VALUE)) + + hass.services.register(DOMAIN, SERVICE_SELECT_VALUE, + select_value_service) + + component.add_entities(entities) + + return True + + +class InputSlider(Entity): + """Represent an slider.""" + + # pylint: disable=too-many-arguments + def __init__(self, object_id, name, state, minimum, maximum, step, icon): + """Initialize a select input.""" + self.entity_id = ENTITY_ID_FORMAT.format(object_id) + self._name = name + self._current_value = state + self._minimum = minimum + self._maximum = maximum + self._step = step + self._icon = icon + + @property + def should_poll(self): + """If entity should be polled.""" + return False + + @property + def name(self): + """Name of the select input.""" + return self._name + + @property + def icon(self): + """Icon to be used for this entity.""" + return self._icon + + @property + def state(self): + """State of the component.""" + return self._current_value + + @property + def state_attributes(self): + """State attributes.""" + return { + ATTR_MIN: self._minimum, + ATTR_MAX: self._maximum, + ATTR_STEP: self._step + } + + def select_value(self, value): + """Select new value.""" + num_value = int(value) + if num_value < self._minimum or num_value > self._maximum: + _LOGGER.warning('Invalid value: %s (range %s - %s)', + num_value, self._minimum, self._maximum) + return + self._current_value = num_value + self.update_ha_state() diff --git a/tests/components/test_input_slider.py b/tests/components/test_input_slider.py new file mode 100644 index 00000000000..f69553a21f7 --- /dev/null +++ b/tests/components/test_input_slider.py @@ -0,0 +1,64 @@ +"""The tests for the Input slider component.""" +# pylint: disable=too-many-public-methods,protected-access +import unittest + +from homeassistant.components import input_slider + +from tests.common import get_test_home_assistant + + +class TestInputSlider(unittest.TestCase): + """Test the input slider component.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Stop everything that was started.""" + self.hass.stop() + + def test_config(self): + """Test config.""" + self.assertFalse(input_slider.setup(self.hass, { + 'input_slider': None + })) + + self.assertFalse(input_slider.setup(self.hass, { + 'input_slider': { + } + })) + + self.assertFalse(input_slider.setup(self.hass, { + 'input_slider': { + 'name with space': None + } + })) + + def test_select_value(self): + """Test select_value method.""" + self.assertTrue(input_slider.setup(self.hass, { + 'input_slider': { + 'test_1': { + 'initial': 50, + 'min': 0, + 'max': 100, + }, + } + })) + entity_id = 'input_slider.test_1' + + state = self.hass.states.get(entity_id) + self.assertEqual('50', state.state) + + input_slider.select_value(self.hass, entity_id, '70') + self.hass.pool.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('70', state.state) + + input_slider.select_value(self.hass, entity_id, '110') + self.hass.pool.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('70', state.state)