From 6c39e1ef19b1ed56d53f6f05c0c05cfb9b02f2ce Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Wed, 25 Oct 2017 15:25:33 +0200 Subject: [PATCH] Added increment + decrement to input_number (#9870) * Added increment + decrement to input_number * Lint * Fix tests * Another lint * Additional testing * Added service descriptions * Consolidated service registration * Shortened service registration * Fixed service descriptions * Fix Lint --- homeassistant/components/input_number.py | 93 +++++++++++++++++++++--- homeassistant/components/services.yaml | 28 +++++++ tests/components/test_input_number.py | 55 +++++++++++++- 3 files changed, 165 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/input_number.py b/homeassistant/components/input_number.py index 598fb573904..27aaa752950 100644 --- a/homeassistant/components/input_number.py +++ b/homeassistant/components/input_number.py @@ -4,11 +4,13 @@ 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_number/ """ +import os import asyncio import logging import voluptuous as vol +from homeassistant.config import load_yaml_config_file import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_NAME) @@ -38,6 +40,12 @@ ATTR_STEP = 'step' ATTR_MODE = 'mode' SERVICE_SET_VALUE = 'set_value' +SERVICE_INCREMENT = 'increment' +SERVICE_DECREMENT = 'decrement' + +SERVICE_DEFAULT_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids +}) SERVICE_SET_VALUE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, @@ -77,6 +85,19 @@ CONFIG_SCHEMA = vol.Schema({ }, required=True, extra=vol.ALLOW_EXTRA) +SERVICE_TO_METHOD = { + SERVICE_SET_VALUE: { + 'method': 'async_set_value', + 'schema': SERVICE_SET_VALUE_SCHEMA}, + SERVICE_INCREMENT: { + 'method': 'async_increment', + 'schema': SERVICE_DEFAULT_SCHEMA}, + SERVICE_DECREMENT: { + 'method': 'async_decrement', + 'schema': SERVICE_DEFAULT_SCHEMA}, +} + + @bind_hass def set_value(hass, entity_id, value): """Set input_number to value.""" @@ -86,6 +107,22 @@ def set_value(hass, entity_id, value): }) +@bind_hass +def increment(hass, entity_id): + """Increment value of entity.""" + hass.services.call(DOMAIN, SERVICE_INCREMENT, { + ATTR_ENTITY_ID: entity_id + }) + + +@bind_hass +def decrement(hass, entity_id): + """Decrement value of entity.""" + hass.services.call(DOMAIN, SERVICE_DECREMENT, { + ATTR_ENTITY_ID: entity_id + }) + + @asyncio.coroutine def async_setup(hass, config): """Set up an input slider.""" @@ -111,18 +148,32 @@ def async_setup(hass, config): return False @asyncio.coroutine - def async_set_value_service(call): - """Handle a calls to the input slider services.""" - target_inputs = component.async_extract_from_service(call) + def async_handle_service(service): + """Handle calls to input_number services.""" + target_inputs = component.async_extract_from_service(service) + method = SERVICE_TO_METHOD.get(service.service) + params = service.data.copy() + params.pop(ATTR_ENTITY_ID, None) - 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) + # call method + update_tasks = [] + for target_input in target_inputs: + yield from getattr(target_input, method['method'])(**params) + if not target_input.should_poll: + continue + update_tasks.append(target_input.async_update_ha_state(True)) - hass.services.async_register( - DOMAIN, SERVICE_SET_VALUE, async_set_value_service, - schema=SERVICE_SET_VALUE_SCHEMA) + if update_tasks: + yield from asyncio.wait(update_tasks, loop=hass.loop) + + descriptions = yield from hass.async_add_job( + load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + for service, data in SERVICE_TO_METHOD.items(): + hass.services.async_register( + DOMAIN, service, async_handle_service, + description=descriptions[DOMAIN][service], schema=data['schema']) yield from component.async_add_entities(entities) return True @@ -204,3 +255,25 @@ class InputNumber(Entity): return self._current_value = num_value yield from self.async_update_ha_state() + + @asyncio.coroutine + def async_increment(self): + """Increment value.""" + new_value = self._current_value + self._step + if new_value > self._maximum: + _LOGGER.warning("Invalid value: %s (range %s - %s)", + new_value, self._minimum, self._maximum) + return + self._current_value = new_value + yield from self.async_update_ha_state() + + @asyncio.coroutine + def async_decrement(self): + """Decrement value.""" + new_value = self._current_value - self._step + if new_value < self._minimum: + _LOGGER.warning("Invalid value: %s (range %s - %s)", + new_value, self._minimum, self._maximum) + return + self._current_value = new_value + yield from self.async_update_ha_state() diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 1c7b3123c64..1aa6e760b66 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -543,6 +543,34 @@ input_boolean: description: Entity id of the input boolean to turn on example: 'input_boolean.notify_alerts' +input_number: + set_value: + description: Set the value of an input_number entity. + + fields: + entity_id: + description: Entity id of the input_number to set the new value. + example: 'input_number.threshold' + value: + description: The target value the entity should be set to. + example: 42 + + increment: + description: Increment the value of an input_number entity by its stepping. + + fields: + entity_id: + description: Entity id of the input_number the should be incremented. + example: 'input_number.threshold' + + decrement: + description: Decrement the value of an input_number entity by its stepping. + + fields: + entity_id: + description: Entity id of the input_number the should be decremented. + example: 'input_number.threshold' + homeassistant: check_config: description: Check the Home Assistant configuration files for errors. Errors will be displayed in the Home Assistant log. diff --git a/tests/components/test_input_number.py b/tests/components/test_input_number.py index 7d11325dabb..fde940efa1a 100644 --- a/tests/components/test_input_number.py +++ b/tests/components/test_input_number.py @@ -5,7 +5,8 @@ import unittest from homeassistant.core import CoreState, State from homeassistant.setup import setup_component, async_setup_component -from homeassistant.components.input_number import (DOMAIN, set_value) +from homeassistant.components.input_number import ( + DOMAIN, set_value, increment, decrement) from tests.common import get_test_home_assistant, mock_restore_cache @@ -70,6 +71,58 @@ class TestInputNumber(unittest.TestCase): state = self.hass.states.get(entity_id) self.assertEqual(70, float(state.state)) + def test_increment(self): + """Test increment method.""" + self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + 'test_2': { + 'initial': 50, + 'min': 0, + 'max': 51, + }, + }})) + entity_id = 'input_number.test_2' + + state = self.hass.states.get(entity_id) + self.assertEqual(50, float(state.state)) + + increment(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual(51, float(state.state)) + + increment(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual(51, float(state.state)) + + def test_decrement(self): + """Test decrement method.""" + self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + 'test_3': { + 'initial': 50, + 'min': 49, + 'max': 100, + }, + }})) + entity_id = 'input_number.test_3' + + state = self.hass.states.get(entity_id) + self.assertEqual(50, float(state.state)) + + decrement(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual(49, float(state.state)) + + decrement(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual(49, float(state.state)) + def test_mode(self): """Test mode settings.""" self.assertTrue(