diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 8b0e70f616e..a6d7c3f642e 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -19,8 +19,8 @@ from homeassistant.util import dt as dt_util from .const import ( ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, - CONF_LINKED_BATTERY_SENSOR, DEBOUNCE_TIMEOUT, EVENT_HOMEKIT_CHANGED, - MANUFACTURER, SERV_BATTERY_SERVICE) + CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEBOUNCE_TIMEOUT, + EVENT_HOMEKIT_CHANGED, MANUFACTURER, SERV_BATTERY_SERVICE) from .util import convert_to_float, dismiss_setup_message, show_setup_message _LOGGER = logging.getLogger(__name__) @@ -73,6 +73,8 @@ class HomeAccessory(Accessory): self._support_battery_charging = True self.linked_battery_sensor = \ self.config.get(CONF_LINKED_BATTERY_SENSOR) + self.low_battery_threshold = \ + self.config.get(CONF_LOW_BATTERY_THRESHOLD) """Add battery service if available""" battery_found = self.hass.states.get(self.entity_id).attributes \ @@ -147,7 +149,8 @@ class HomeAccessory(Accessory): if battery_level is None: return self._char_battery.set_value(battery_level) - self._char_low_battery.set_value(battery_level < 20) + self._char_low_battery.set_value( + battery_level < self.low_battery_threshold) _LOGGER.debug('%s: Updated battery level to %d', self.entity_id, battery_level) if not self._support_battery_charging: diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 0a2b7a0fd5d..a88cd7fc430 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -16,10 +16,12 @@ CONF_FEATURE = 'feature' CONF_FEATURE_LIST = 'feature_list' CONF_FILTER = 'filter' CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor' +CONF_LOW_BATTERY_THRESHOLD = 'low_battery_threshold' CONF_SAFE_MODE = 'safe_mode' # #### Config Defaults #### DEFAULT_AUTO_START = True +DEFAULT_LOW_BATTERY_THRESHOLD = 20 DEFAULT_PORT = 51827 DEFAULT_SAFE_MODE = False diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 1bf57f1b1f9..b3c90ae6cbe 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -13,7 +13,8 @@ import homeassistant.util.temperature as temp_util from .const import ( CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, - FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, + CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) @@ -23,6 +24,8 @@ _LOGGER = logging.getLogger(__name__) BASIC_INFO_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN), + vol.Optional(CONF_LOW_BATTERY_THRESHOLD, + default=DEFAULT_LOW_BATTERY_THRESHOLD): cv.positive_int, }) FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({ diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index c9798f6302a..e87d38e27a4 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -13,7 +13,8 @@ from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_FIRMWARE_REVISION, CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER, - CONF_LINKED_BATTERY_SENSOR, MANUFACTURER, SERV_ACCESSORY_INFO) + CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, + DEFAULT_LOW_BATTERY_THRESHOLD, MANUFACTURER, SERV_ACCESSORY_INFO) from homeassistant.const import ( __version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, ATTR_SERVICE, ATTR_NOW, EVENT_TIME_CHANGED) @@ -106,7 +107,9 @@ async def test_battery_service(hass, hk_driver, caplog): hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 50}) await hass.async_block_till_done() - acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, None) + acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, + {CONF_LOW_BATTERY_THRESHOLD: + DEFAULT_LOW_BATTERY_THRESHOLD}) acc.update_state = lambda x: None assert acc._char_battery.value == 0 assert acc._char_low_battery.value == 0 @@ -136,7 +139,9 @@ async def test_battery_service(hass, hk_driver, caplog): ATTR_BATTERY_LEVEL: 10, ATTR_BATTERY_CHARGING: True}) await hass.async_block_till_done() - acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, None) + acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, + {CONF_LOW_BATTERY_THRESHOLD: + DEFAULT_LOW_BATTERY_THRESHOLD}) acc.update_state = lambda x: None assert acc._char_battery.value == 0 assert acc._char_low_battery.value == 0 @@ -165,7 +170,9 @@ async def test_linked_battery_sensor(hass, hk_driver, caplog): await hass.async_block_till_done() acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, - {CONF_LINKED_BATTERY_SENSOR: linked_battery}) + {CONF_LINKED_BATTERY_SENSOR: linked_battery, + CONF_LOW_BATTERY_THRESHOLD: + DEFAULT_LOW_BATTERY_THRESHOLD}) acc.update_state = lambda x: None assert acc.linked_battery_sensor == linked_battery @@ -191,17 +198,18 @@ async def test_linked_battery_sensor(hass, hk_driver, caplog): assert acc._char_battery.value == 10 assert 'ERROR' not in caplog.text - # Test charging + # Test charging & low battery threshold hass.states.async_set(linked_battery, 20, {ATTR_BATTERY_CHARGING: True}) await hass.async_block_till_done() acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, - {CONF_LINKED_BATTERY_SENSOR: linked_battery}) + {CONF_LINKED_BATTERY_SENSOR: linked_battery, + CONF_LOW_BATTERY_THRESHOLD: 50}) acc.update_state = lambda x: None await hass.async_add_job(acc.run) await hass.async_block_till_done() assert acc._char_battery.value == 20 - assert acc._char_low_battery.value == 0 + assert acc._char_low_battery.value == 1 assert acc._char_charging.value == 1 hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False}) diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 9ffcfe5c01e..f35194608c6 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -4,8 +4,9 @@ import voluptuous as vol from homeassistant.components.homekit.const import ( CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, - FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET, - TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) + CONF_LOW_BATTERY_THRESHOLD, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, + HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, + TYPE_SWITCH, TYPE_VALVE) from homeassistant.components.homekit.util import ( HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality, dismiss_setup_message, show_setup_message, temperature_to_homekit, @@ -28,6 +29,9 @@ def test_validate_entity_config(): {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: None}}, {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: 'switch.demo'}}, + {'binary_sensor.demo': {CONF_LOW_BATTERY_THRESHOLD: + 'switch.demo'}}, + {'binary_sensor.demo': {CONF_LOW_BATTERY_THRESHOLD: -10}}, {'demo.test': 'test'}, {'demo.test': [1, 2]}, {'demo.test': None}, {'demo.test': {CONF_NAME: None}}, {'media_player.test': {CONF_FEATURE_LIST: [ @@ -43,42 +47,56 @@ def test_validate_entity_config(): assert vec({}) == {} assert vec({'demo.test': {CONF_NAME: 'Name'}}) == \ - {'demo.test': {CONF_NAME: 'Name'}} + {'demo.test': {CONF_NAME: 'Name', CONF_LOW_BATTERY_THRESHOLD: 20}} assert vec({'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: 'sensor.demo_battery'}}) == \ {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: - 'sensor.demo_battery'}} + 'sensor.demo_battery', + CONF_LOW_BATTERY_THRESHOLD: 20}} + assert vec({'binary_sensor.demo': {CONF_LOW_BATTERY_THRESHOLD: 50}}) == \ + {'binary_sensor.demo': {CONF_LOW_BATTERY_THRESHOLD: 50}} assert vec({'alarm_control_panel.demo': {}}) == \ - {'alarm_control_panel.demo': {ATTR_CODE: None}} + {'alarm_control_panel.demo': {ATTR_CODE: None, + CONF_LOW_BATTERY_THRESHOLD: 20}} assert vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}) == \ - {'alarm_control_panel.demo': {ATTR_CODE: '1234'}} + {'alarm_control_panel.demo': {ATTR_CODE: '1234', + CONF_LOW_BATTERY_THRESHOLD: 20}} - assert vec({'lock.demo': {}}) == {'lock.demo': {ATTR_CODE: None}} + assert vec({'lock.demo': {}}) == \ + {'lock.demo': {ATTR_CODE: None, CONF_LOW_BATTERY_THRESHOLD: 20}} assert vec({'lock.demo': {ATTR_CODE: '1234'}}) == \ - {'lock.demo': {ATTR_CODE: '1234'}} + {'lock.demo': {ATTR_CODE: '1234', CONF_LOW_BATTERY_THRESHOLD: 20}} assert vec({'media_player.demo': {}}) == \ - {'media_player.demo': {CONF_FEATURE_LIST: {}}} + {'media_player.demo': {CONF_FEATURE_LIST: {}, + CONF_LOW_BATTERY_THRESHOLD: 20}} config = {CONF_FEATURE_LIST: [{CONF_FEATURE: FEATURE_ON_OFF}, {CONF_FEATURE: FEATURE_PLAY_PAUSE}]} assert vec({'media_player.demo': config}) == \ {'media_player.demo': {CONF_FEATURE_LIST: - {FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}} + {FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}, + CONF_LOW_BATTERY_THRESHOLD: 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_FAUCET}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_FAUCET}} + {'switch.demo': {CONF_TYPE: TYPE_FAUCET, CONF_LOW_BATTERY_THRESHOLD: + 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_OUTLET}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_OUTLET}} + {'switch.demo': {CONF_TYPE: TYPE_OUTLET, CONF_LOW_BATTERY_THRESHOLD: + 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_SHOWER}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_SHOWER}} + {'switch.demo': {CONF_TYPE: TYPE_SHOWER, CONF_LOW_BATTERY_THRESHOLD: + 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}} + {'switch.demo': {CONF_TYPE: TYPE_SPRINKLER, CONF_LOW_BATTERY_THRESHOLD: + 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_SWITCH}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_SWITCH}} + {'switch.demo': {CONF_TYPE: TYPE_SWITCH, CONF_LOW_BATTERY_THRESHOLD: + 20}} assert vec({'switch.demo': {CONF_TYPE: TYPE_VALVE}}) == \ - {'switch.demo': {CONF_TYPE: TYPE_VALVE}} + {'switch.demo': {CONF_TYPE: TYPE_VALVE, CONF_LOW_BATTERY_THRESHOLD: + 20}} def test_validate_media_player_features():