From 3c0d02f057277a37dd9529bd613ccf244ff024ff Mon Sep 17 00:00:00 2001 From: BioSehnsucht Date: Tue, 3 Oct 2017 14:34:13 -0500 Subject: [PATCH] Rename input_slider to input_number and add numeric text box option (#9494) * * Rename input_slider to input_number * Update input_number to optionally display slider, input box, or both * input_number support either input box or slider mode, but not both * input_number : change service from select_value to set_value * input_number : add test for mode setting to tests --- homeassistant/components/demo.py | 6 +- .../{input_slider.py => input_number.py} | 63 ++++++++++------- tests/components/cover/test_template.py | 12 ++-- .../components/media_player/test_universal.py | 4 +- ...t_input_slider.py => test_input_number.py} | 70 ++++++++++++++----- tests/helpers/test_template.py | 4 +- 6 files changed, 101 insertions(+), 58 deletions(-) rename homeassistant/components/{input_slider.py => input_number.py} (77%) rename tests/components/{test_input_slider.py => test_input_number.py} (62%) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 2f1dde05bab..b85c2d9a53b 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -87,8 +87,8 @@ def async_setup(hass, config): # Set up input boolean tasks.append(bootstrap.async_setup_component( - hass, 'input_slider', - {'input_slider': { + hass, 'input_number', + {'input_number': { 'noise_allowance': {'icon': 'mdi:bell-ring', 'min': 0, 'max': 10, @@ -163,7 +163,7 @@ def async_setup(hass, config): 'scene.romantic_lights'])) tasks2.append(group.Group.async_create_group(hass, 'Bedroom', [ lights[0], switches[1], media_players[0], - 'input_slider.noise_allowance'])) + 'input_number.noise_allowance'])) tasks2.append(group.Group.async_create_group(hass, 'Kitchen', [ lights[2], 'cover.kitchen_window', 'lock.kitchen_door'])) tasks2.append(group.Group.async_create_group(hass, 'Doors', [ diff --git a/homeassistant/components/input_slider.py b/homeassistant/components/input_number.py similarity index 77% rename from homeassistant/components/input_slider.py rename to homeassistant/components/input_number.py index 5357878a0ce..598fb573904 100644 --- a/homeassistant/components/input_slider.py +++ b/homeassistant/components/input_number.py @@ -1,8 +1,8 @@ """ -Component to offer a way to select a value from a slider. +Component to offer a way to set a numeric value from a slider or text box. For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_slider/ +at https://home-assistant.io/components/input_number/ """ import asyncio import logging @@ -19,29 +19,34 @@ from homeassistant.helpers.restore_state import async_get_last_state _LOGGER = logging.getLogger(__name__) -DOMAIN = 'input_slider' +DOMAIN = 'input_number' ENTITY_ID_FORMAT = DOMAIN + '.{}' CONF_INITIAL = 'initial' CONF_MIN = 'min' CONF_MAX = 'max' +CONF_MODE = 'mode' CONF_STEP = 'step' +MODE_SLIDER = 'slider' +MODE_BOX = 'box' + ATTR_VALUE = 'value' ATTR_MIN = 'min' ATTR_MAX = 'max' ATTR_STEP = 'step' +ATTR_MODE = 'mode' -SERVICE_SELECT_VALUE = 'select_value' +SERVICE_SET_VALUE = 'set_value' -SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({ +SERVICE_SET_VALUE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_VALUE): vol.Coerce(float), }) -def _cv_input_slider(cfg): - """Configure validation helper for input slider (voluptuous).""" +def _cv_input_number(cfg): + """Configure validation helper for input number (voluptuous).""" minimum = cfg.get(CONF_MIN) maximum = cfg.get(CONF_MAX) if minimum >= maximum: @@ -64,16 +69,18 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_STEP, default=1): vol.All(vol.Coerce(float), vol.Range(min=1e-3)), vol.Optional(CONF_ICON): cv.icon, - vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string - }, _cv_input_slider) + vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_MODE, default=MODE_SLIDER): + vol.In([MODE_BOX, MODE_SLIDER]), + }, _cv_input_number) }) }, required=True, extra=vol.ALLOW_EXTRA) @bind_hass -def select_value(hass, entity_id, value): - """Set input_slider to value.""" - hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, { +def set_value(hass, entity_id, value): + """Set input_number to value.""" + hass.services.call(DOMAIN, SERVICE_SET_VALUE, { ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value, }) @@ -94,37 +101,39 @@ def async_setup(hass, config): step = cfg.get(CONF_STEP) icon = cfg.get(CONF_ICON) unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT) + mode = cfg.get(CONF_MODE) - entities.append(InputSlider( - object_id, name, initial, minimum, maximum, step, icon, unit)) + entities.append(InputNumber( + object_id, name, initial, minimum, maximum, step, icon, unit, + mode)) if not entities: return False @asyncio.coroutine - def async_select_value_service(call): + def async_set_value_service(call): """Handle a calls to the input slider services.""" target_inputs = component.async_extract_from_service(call) - tasks = [input_slider.async_select_value(call.data[ATTR_VALUE]) - for input_slider in target_inputs] + tasks = [input_number.async_set_value(call.data[ATTR_VALUE]) + for input_number in target_inputs] if tasks: yield from asyncio.wait(tasks, loop=hass.loop) hass.services.async_register( - DOMAIN, SERVICE_SELECT_VALUE, async_select_value_service, - schema=SERVICE_SELECT_VALUE_SCHEMA) + DOMAIN, SERVICE_SET_VALUE, async_set_value_service, + schema=SERVICE_SET_VALUE_SCHEMA) yield from component.async_add_entities(entities) return True -class InputSlider(Entity): +class InputNumber(Entity): """Represent an slider.""" def __init__(self, object_id, name, initial, minimum, maximum, step, icon, - unit): - """Initialize a select input.""" + unit, mode): + """Initialize an input number.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name self._current_value = initial @@ -133,6 +142,7 @@ class InputSlider(Entity): self._step = step self._icon = icon self._unit = unit + self._mode = mode @property def should_poll(self): @@ -141,7 +151,7 @@ class InputSlider(Entity): @property def name(self): - """Return the name of the select input slider.""" + """Return the name of the input slider.""" return self._name @property @@ -165,7 +175,8 @@ class InputSlider(Entity): return { ATTR_MIN: self._minimum, ATTR_MAX: self._maximum, - ATTR_STEP: self._step + ATTR_STEP: self._step, + ATTR_MODE: self._mode, } @asyncio.coroutine @@ -184,8 +195,8 @@ class InputSlider(Entity): self._current_value = self._minimum @asyncio.coroutine - def async_select_value(self, value): - """Select new value.""" + def async_set_value(self, value): + """Set new value.""" num_value = float(value) if num_value < self._minimum or num_value > self._maximum: _LOGGER.warning("Invalid value: %s (range %s - %s)", diff --git a/tests/components/cover/test_template.py b/tests/components/cover/test_template.py index 3c574bbf497..495508203b3 100644 --- a/tests/components/cover/test_template.py +++ b/tests/components/cover/test_template.py @@ -409,8 +409,8 @@ class TestTemplateCover(unittest.TestCase): def test_set_position(self): """Test the set_position command.""" with assert_setup_component(1, 'cover'): - assert setup.setup_component(self.hass, 'input_slider', { - 'input_slider': { + assert setup.setup_component(self.hass, 'input_number', { + 'input_number': { 'test': { 'min': '0', 'max': '100', @@ -424,10 +424,10 @@ class TestTemplateCover(unittest.TestCase): 'covers': { 'test_template_cover': { 'position_template': - "{{ states.input_slider.test.state | int }}", + "{{ states.input_number.test.state | int }}", 'set_cover_position': { - 'service': 'input_slider.select_value', - 'entity_id': 'input_slider.test', + 'service': 'input_number.set_value', + 'entity_id': 'input_number.test', 'data_template': { 'value': '{{ position }}' }, @@ -440,7 +440,7 @@ class TestTemplateCover(unittest.TestCase): self.hass.start() self.hass.block_till_done() - state = self.hass.states.set('input_slider.test', 42) + state = self.hass.states.set('input_number.test', 42) self.hass.block_till_done() state = self.hass.states.get('cover.test_template_cover') assert state.state == STATE_OPEN diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index d2cc874a541..01281d189b4 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -5,7 +5,7 @@ import unittest from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, STATE_PLAYING, STATE_PAUSED) import homeassistant.components.switch as switch -import homeassistant.components.input_slider as input_slider +import homeassistant.components.input_number as input_number import homeassistant.components.input_select as input_select import homeassistant.components.media_player as media_player import homeassistant.components.media_player.universal as universal @@ -166,7 +166,7 @@ class TestMediaPlayer(unittest.TestCase): self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state') self.hass.states.set(self.mock_state_switch_id, STATE_OFF) - self.mock_volume_id = input_slider.ENTITY_ID_FORMAT.format( + self.mock_volume_id = input_number.ENTITY_ID_FORMAT.format( 'volume_level') self.hass.states.set(self.mock_volume_id, 0) diff --git a/tests/components/test_input_slider.py b/tests/components/test_input_number.py similarity index 62% rename from tests/components/test_input_slider.py rename to tests/components/test_input_number.py index f550091e31f..7d11325dabb 100644 --- a/tests/components/test_input_slider.py +++ b/tests/components/test_input_number.py @@ -1,17 +1,17 @@ -"""The tests for the Input slider component.""" +"""The tests for the Input number component.""" # pylint: disable=protected-access import asyncio import unittest 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.components.input_number import (DOMAIN, set_value) from tests.common import get_test_home_assistant, mock_restore_cache -class TestInputSlider(unittest.TestCase): - """Test the input slider component.""" +class TestInputNumber(unittest.TestCase): + """Test the input number component.""" # pylint: disable=invalid-name def setUp(self): @@ -38,8 +38,8 @@ class TestInputSlider(unittest.TestCase): self.assertFalse( setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) - def test_select_value(self): - """Test select_value method.""" + def test_set_value(self): + """Test set_value method.""" self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'initial': 50, @@ -47,36 +47,68 @@ class TestInputSlider(unittest.TestCase): 'max': 100, }, }})) - entity_id = 'input_slider.test_1' + entity_id = 'input_number.test_1' state = self.hass.states.get(entity_id) self.assertEqual(50, float(state.state)) - select_value(self.hass, entity_id, '30.4') + set_value(self.hass, entity_id, '30.4') self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(30.4, float(state.state)) - select_value(self.hass, entity_id, '70') + set_value(self.hass, entity_id, '70') self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(70, float(state.state)) - select_value(self.hass, entity_id, '110') + set_value(self.hass, entity_id, '110') self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(70, float(state.state)) + def test_mode(self): + """Test mode settings.""" + self.assertTrue( + setup_component(self.hass, DOMAIN, {DOMAIN: { + 'test_default_slider': { + 'min': 0, + 'max': 100, + }, + 'test_explicit_box': { + 'min': 0, + 'max': 100, + 'mode': 'box', + }, + 'test_explicit_slider': { + 'min': 0, + 'max': 100, + 'mode': 'slider', + }, + }})) + + state = self.hass.states.get('input_number.test_default_slider') + assert state + self.assertEqual('slider', state.attributes['mode']) + + state = self.hass.states.get('input_number.test_explicit_box') + assert state + self.assertEqual('box', state.attributes['mode']) + + state = self.hass.states.get('input_number.test_explicit_slider') + assert state + self.assertEqual('slider', state.attributes['mode']) + @asyncio.coroutine def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache(hass, ( - State('input_slider.b1', '70'), - State('input_slider.b2', '200'), + State('input_number.b1', '70'), + State('input_number.b2', '200'), )) hass.state = CoreState.starting @@ -93,11 +125,11 @@ def test_restore_state(hass): }, }}) - state = hass.states.get('input_slider.b1') + state = hass.states.get('input_number.b1') assert state assert float(state.state) == 70 - state = hass.states.get('input_slider.b2') + state = hass.states.get('input_number.b2') assert state assert float(state.state) == 10 @@ -106,8 +138,8 @@ def test_restore_state(hass): 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'), + State('input_number.b1', '70'), + State('input_number.b2', '200'), )) hass.state = CoreState.starting @@ -126,11 +158,11 @@ def test_initial_state_overrules_restore_state(hass): }, }}) - state = hass.states.get('input_slider.b1') + state = hass.states.get('input_number.b1') assert state assert float(state.state) == 50 - state = hass.states.get('input_slider.b2') + state = hass.states.get('input_number.b2') assert state assert float(state.state) == 60 @@ -148,6 +180,6 @@ def test_no_initial_state_and_no_restore_state(hass): }, }}) - state = hass.states.get('input_slider.b1') + state = hass.states.get('input_number.b1') assert state assert float(state.state) == 0 diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index e668bd5b6cd..a32b2dc13a1 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -745,11 +745,11 @@ is_state_attr('device_tracker.phone_2', 'battery', 40) self.assertListEqual( sorted([ 'sensor.luftfeuchtigkeit_mean', - 'input_slider.luftfeuchtigkeit', + 'input_number.luftfeuchtigkeit', ]), sorted(template.extract_entities( "{% if (states('sensor.luftfeuchtigkeit_mean') | int)" - " > (states('input_slider.luftfeuchtigkeit') | int +1.5)" + " > (states('input_number.luftfeuchtigkeit') | int +1.5)" " %}true{% endif %}" )))