Add linked battery sensor to HomeKit (#22788)
parent
6244a397b1
commit
c4e31bc4df
|
@ -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,
|
||||
DEBOUNCE_TIMEOUT, EVENT_HOMEKIT_CHANGED, MANUFACTURER,
|
||||
SERV_BATTERY_SERVICE)
|
||||
CONF_LINKED_BATTERY_SENSOR, 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__)
|
||||
|
@ -65,19 +65,25 @@ class HomeAccessory(Accessory):
|
|||
firmware_revision=__version__, manufacturer=MANUFACTURER,
|
||||
model=model, serial_number=entity_id)
|
||||
self.category = category
|
||||
self.config = config
|
||||
self.config = config or {}
|
||||
self.entity_id = entity_id
|
||||
self.hass = hass
|
||||
self.debounce = {}
|
||||
self._support_battery_level = False
|
||||
self._support_battery_charging = True
|
||||
self.linked_battery_sensor = \
|
||||
self.config.get(CONF_LINKED_BATTERY_SENSOR)
|
||||
|
||||
"""Add battery service if available"""
|
||||
battery_level = self.hass.states.get(self.entity_id).attributes \
|
||||
battery_found = self.hass.states.get(self.entity_id).attributes \
|
||||
.get(ATTR_BATTERY_LEVEL)
|
||||
if battery_level is None:
|
||||
if self.linked_battery_sensor:
|
||||
battery_found = self.hass.states.get(
|
||||
self.linked_battery_sensor).state
|
||||
|
||||
if battery_found is None:
|
||||
return
|
||||
_LOGGER.debug('%s: Found battery level attribute', self.entity_id)
|
||||
_LOGGER.debug('%s: Found battery level', self.entity_id)
|
||||
self._support_battery_level = True
|
||||
serv_battery = self.add_preload_service(SERV_BATTERY_SERVICE)
|
||||
self._char_battery = serv_battery.configure_char(
|
||||
|
@ -104,6 +110,14 @@ class HomeAccessory(Accessory):
|
|||
async_track_state_change(
|
||||
self.hass, self.entity_id, self.update_state_callback)
|
||||
|
||||
if self.linked_battery_sensor:
|
||||
battery_state = self.hass.states.get(self.linked_battery_sensor)
|
||||
self.hass.async_add_job(self.update_linked_battery, None, None,
|
||||
battery_state)
|
||||
async_track_state_change(
|
||||
self.hass, self.linked_battery_sensor,
|
||||
self.update_linked_battery)
|
||||
|
||||
@ha_callback
|
||||
def update_state_callback(self, entity_id=None, old_state=None,
|
||||
new_state=None):
|
||||
|
@ -111,10 +125,16 @@ class HomeAccessory(Accessory):
|
|||
_LOGGER.debug('New_state: %s', new_state)
|
||||
if new_state is None:
|
||||
return
|
||||
if self._support_battery_level:
|
||||
if self._support_battery_level and not self.linked_battery_sensor:
|
||||
self.hass.async_add_executor_job(self.update_battery, new_state)
|
||||
self.hass.async_add_executor_job(self.update_state, new_state)
|
||||
|
||||
@ha_callback
|
||||
def update_linked_battery(self, entity_id=None, old_state=None,
|
||||
new_state=None):
|
||||
"""Handle linked battery sensor state change listener callback."""
|
||||
self.hass.async_add_executor_job(self.update_battery, new_state)
|
||||
|
||||
def update_battery(self, new_state):
|
||||
"""Update battery service if available.
|
||||
|
||||
|
@ -122,6 +142,8 @@ class HomeAccessory(Accessory):
|
|||
"""
|
||||
battery_level = convert_to_float(
|
||||
new_state.attributes.get(ATTR_BATTERY_LEVEL))
|
||||
if self.linked_battery_sensor:
|
||||
battery_level = convert_to_float(new_state.state)
|
||||
if battery_level is None:
|
||||
return
|
||||
self._char_battery.set_value(battery_level)
|
||||
|
|
|
@ -15,6 +15,7 @@ CONF_ENTITY_CONFIG = 'entity_config'
|
|||
CONF_FEATURE = 'feature'
|
||||
CONF_FEATURE_LIST = 'feature_list'
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor'
|
||||
CONF_SAFE_MODE = 'safe_mode'
|
||||
|
||||
# #### Config Defaults ####
|
||||
|
|
|
@ -4,7 +4,7 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import fan, media_player
|
||||
from homeassistant.components import fan, media_player, sensor
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS)
|
||||
from homeassistant.core import split_entity_id
|
||||
|
@ -12,22 +12,23 @@ import homeassistant.helpers.config_validation as cv
|
|||
import homeassistant.util.temperature as temp_util
|
||||
|
||||
from .const import (
|
||||
CONF_FEATURE, CONF_FEATURE_LIST, 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)
|
||||
CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR,
|
||||
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)
|
||||
|
||||
_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),
|
||||
})
|
||||
|
||||
FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
||||
vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list,
|
||||
})
|
||||
|
||||
|
||||
CODE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
||||
vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string),
|
||||
})
|
||||
|
|
|
@ -13,7 +13,7 @@ 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,
|
||||
MANUFACTURER, SERV_ACCESSORY_INFO)
|
||||
CONF_LINKED_BATTERY_SENSOR, 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)
|
||||
|
@ -156,6 +156,61 @@ async def test_battery_service(hass, hk_driver, caplog):
|
|||
assert acc._char_charging.value == 0
|
||||
|
||||
|
||||
async def test_linked_battery_sensor(hass, hk_driver, caplog):
|
||||
"""Test battery service with linked_battery_sensor."""
|
||||
entity_id = 'homekit.accessory'
|
||||
linked_battery = 'sensor.battery'
|
||||
hass.states.async_set(entity_id, 'open', {ATTR_BATTERY_LEVEL: 100})
|
||||
hass.states.async_set(linked_battery, 50, None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2,
|
||||
{CONF_LINKED_BATTERY_SENSOR: linked_battery})
|
||||
acc.update_state = lambda x: None
|
||||
assert acc.linked_battery_sensor == linked_battery
|
||||
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc._char_battery.value == 50
|
||||
assert acc._char_low_battery.value == 0
|
||||
assert acc._char_charging.value == 2
|
||||
|
||||
hass.states.async_set(linked_battery, 10, None)
|
||||
await hass.async_block_till_done()
|
||||
assert acc._char_battery.value == 10
|
||||
assert acc._char_low_battery.value == 1
|
||||
|
||||
# Ignore battery change on entity if it has linked_battery
|
||||
hass.states.async_set(entity_id, 'open', {ATTR_BATTERY_LEVEL: 90})
|
||||
await hass.async_block_till_done()
|
||||
assert acc._char_battery.value == 10
|
||||
|
||||
# Test none numeric state for linked_battery
|
||||
hass.states.async_set(linked_battery, 'error', None)
|
||||
await hass.async_block_till_done()
|
||||
assert acc._char_battery.value == 10
|
||||
assert 'ERROR' not in caplog.text
|
||||
|
||||
# Test charging
|
||||
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})
|
||||
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_charging.value == 1
|
||||
|
||||
hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False})
|
||||
await hass.async_block_till_done()
|
||||
assert acc._char_battery.value == 100
|
||||
assert acc._char_low_battery.value == 0
|
||||
assert acc._char_charging.value == 0
|
||||
|
||||
|
||||
async def test_call_service(hass, hk_driver, events):
|
||||
"""Test call_service method."""
|
||||
entity_id = 'homekit.accessory'
|
||||
|
|
|
@ -3,9 +3,9 @@ import pytest
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homekit.const import (
|
||||
CONF_FEATURE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
|
||||
HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
|
||||
TYPE_SWITCH, TYPE_VALVE)
|
||||
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)
|
||||
from homeassistant.components.homekit.util import (
|
||||
HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality,
|
||||
dismiss_setup_message, show_setup_message, temperature_to_homekit,
|
||||
|
@ -25,6 +25,9 @@ def test_validate_entity_config():
|
|||
"""Test validate entities."""
|
||||
configs = [None, [], 'string', 12345,
|
||||
{'invalid_entity_id': {}}, {'demo.test': 1},
|
||||
{'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: None}},
|
||||
{'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
|
||||
'switch.demo'}},
|
||||
{'demo.test': 'test'}, {'demo.test': [1, 2]},
|
||||
{'demo.test': None}, {'demo.test': {CONF_NAME: None}},
|
||||
{'media_player.test': {CONF_FEATURE_LIST: [
|
||||
|
@ -42,6 +45,11 @@ def test_validate_entity_config():
|
|||
assert vec({'demo.test': {CONF_NAME: 'Name'}}) == \
|
||||
{'demo.test': {CONF_NAME: 'Name'}}
|
||||
|
||||
assert vec({'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
|
||||
'sensor.demo_battery'}}) == \
|
||||
{'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
|
||||
'sensor.demo_battery'}}
|
||||
|
||||
assert vec({'alarm_control_panel.demo': {}}) == \
|
||||
{'alarm_control_panel.demo': {ATTR_CODE: None}}
|
||||
assert vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}) == \
|
||||
|
|
Loading…
Reference in New Issue