Add HomeKit low battery threshold config (#23363)

This commit is contained in:
Austin Drummond 2019-04-29 20:02:54 -04:00 committed by cdce8p
parent 2aee31ec6a
commit b4e2a0ef84
5 changed files with 61 additions and 27 deletions

View file

@ -19,8 +19,8 @@ from homeassistant.util import dt as dt_util
from .const import ( from .const import (
ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER, ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER,
CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY,
CONF_LINKED_BATTERY_SENSOR, DEBOUNCE_TIMEOUT, EVENT_HOMEKIT_CHANGED, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEBOUNCE_TIMEOUT,
MANUFACTURER, SERV_BATTERY_SERVICE) EVENT_HOMEKIT_CHANGED, MANUFACTURER, SERV_BATTERY_SERVICE)
from .util import convert_to_float, dismiss_setup_message, show_setup_message from .util import convert_to_float, dismiss_setup_message, show_setup_message
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -73,6 +73,8 @@ class HomeAccessory(Accessory):
self._support_battery_charging = True self._support_battery_charging = True
self.linked_battery_sensor = \ self.linked_battery_sensor = \
self.config.get(CONF_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""" """Add battery service if available"""
battery_found = self.hass.states.get(self.entity_id).attributes \ battery_found = self.hass.states.get(self.entity_id).attributes \
@ -147,7 +149,8 @@ class HomeAccessory(Accessory):
if battery_level is None: if battery_level is None:
return return
self._char_battery.set_value(battery_level) 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, _LOGGER.debug('%s: Updated battery level to %d', self.entity_id,
battery_level) battery_level)
if not self._support_battery_charging: if not self._support_battery_charging:

View file

@ -16,10 +16,12 @@ CONF_FEATURE = 'feature'
CONF_FEATURE_LIST = 'feature_list' CONF_FEATURE_LIST = 'feature_list'
CONF_FILTER = 'filter' CONF_FILTER = 'filter'
CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor' CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor'
CONF_LOW_BATTERY_THRESHOLD = 'low_battery_threshold'
CONF_SAFE_MODE = 'safe_mode' CONF_SAFE_MODE = 'safe_mode'
# #### Config Defaults #### # #### Config Defaults ####
DEFAULT_AUTO_START = True DEFAULT_AUTO_START = True
DEFAULT_LOW_BATTERY_THRESHOLD = 20
DEFAULT_PORT = 51827 DEFAULT_PORT = 51827
DEFAULT_SAFE_MODE = False DEFAULT_SAFE_MODE = False

View file

@ -13,7 +13,8 @@ import homeassistant.util.temperature as temp_util
from .const import ( from .const import (
CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, 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, HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
TYPE_SWITCH, TYPE_VALVE) TYPE_SWITCH, TYPE_VALVE)
@ -23,6 +24,8 @@ _LOGGER = logging.getLogger(__name__)
BASIC_INFO_SCHEMA = vol.Schema({ BASIC_INFO_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN), 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({ FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({

View file

@ -13,7 +13,8 @@ from homeassistant.components.homekit.const import (
ATTR_DISPLAY_NAME, ATTR_VALUE, ATTR_DISPLAY_NAME, ATTR_VALUE,
BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_FIRMWARE_REVISION, BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_FIRMWARE_REVISION,
CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER, 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 ( from homeassistant.const import (
__version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, __version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID,
ATTR_SERVICE, ATTR_NOW, EVENT_TIME_CHANGED) 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}) hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 50})
await hass.async_block_till_done() 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 acc.update_state = lambda x: None
assert acc._char_battery.value == 0 assert acc._char_battery.value == 0
assert acc._char_low_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}) ATTR_BATTERY_LEVEL: 10, ATTR_BATTERY_CHARGING: True})
await hass.async_block_till_done() 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 acc.update_state = lambda x: None
assert acc._char_battery.value == 0 assert acc._char_battery.value == 0
assert acc._char_low_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() await hass.async_block_till_done()
acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, 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 acc.update_state = lambda x: None
assert acc.linked_battery_sensor == linked_battery 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 acc._char_battery.value == 10
assert 'ERROR' not in caplog.text assert 'ERROR' not in caplog.text
# Test charging # Test charging & low battery threshold
hass.states.async_set(linked_battery, 20, {ATTR_BATTERY_CHARGING: True}) hass.states.async_set(linked_battery, 20, {ATTR_BATTERY_CHARGING: True})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2, 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 acc.update_state = lambda x: None
await hass.async_add_job(acc.run) await hass.async_add_job(acc.run)
await hass.async_block_till_done() await hass.async_block_till_done()
assert acc._char_battery.value == 20 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 assert acc._char_charging.value == 1
hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False}) hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False})

View file

@ -4,8 +4,9 @@ import voluptuous as vol
from homeassistant.components.homekit.const import ( from homeassistant.components.homekit.const import (
CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR,
FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET, CONF_LOW_BATTERY_THRESHOLD, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
TYPE_SWITCH, TYPE_VALVE)
from homeassistant.components.homekit.util import ( from homeassistant.components.homekit.util import (
HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality, HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality,
dismiss_setup_message, show_setup_message, temperature_to_homekit, 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: None}},
{'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
'switch.demo'}}, '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': 'test'}, {'demo.test': [1, 2]},
{'demo.test': None}, {'demo.test': {CONF_NAME: None}}, {'demo.test': None}, {'demo.test': {CONF_NAME: None}},
{'media_player.test': {CONF_FEATURE_LIST: [ {'media_player.test': {CONF_FEATURE_LIST: [
@ -43,42 +47,56 @@ def test_validate_entity_config():
assert vec({}) == {} assert vec({}) == {}
assert vec({'demo.test': {CONF_NAME: 'Name'}}) == \ 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: assert vec({'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
'sensor.demo_battery'}}) == \ 'sensor.demo_battery'}}) == \
{'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: {'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': {}}) == \ 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'}}) == \ 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'}}) == \ 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': {}}) == \ 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}, config = {CONF_FEATURE_LIST: [{CONF_FEATURE: FEATURE_ON_OFF},
{CONF_FEATURE: FEATURE_PLAY_PAUSE}]} {CONF_FEATURE: FEATURE_PLAY_PAUSE}]}
assert vec({'media_player.demo': config}) == \ assert vec({'media_player.demo': config}) == \
{'media_player.demo': {CONF_FEATURE_LIST: {'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}}) == \ 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}}) == \ 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}}) == \ 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}}) == \ 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}}) == \ 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}}) == \ 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(): def test_validate_media_player_features():