mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Add support for Homekit battery service (#14288)
This commit is contained in:
parent
40c8f5f70e
commit
1533bc1e1f
@ -8,7 +8,8 @@ from pyhap.accessory import Accessory, Bridge
|
|||||||
from pyhap.accessory_driver import AccessoryDriver
|
from pyhap.accessory_driver import AccessoryDriver
|
||||||
from pyhap.const import CATEGORY_OTHER
|
from pyhap.const import CATEGORY_OTHER
|
||||||
|
|
||||||
from homeassistant.const import __version__
|
from homeassistant.const import (
|
||||||
|
__version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL)
|
||||||
from homeassistant.core import callback as ha_callback
|
from homeassistant.core import callback as ha_callback
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
@ -16,10 +17,11 @@ from homeassistant.helpers.event import (
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER,
|
BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL,
|
||||||
DEBOUNCE_TIMEOUT, MANUFACTURER)
|
CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, DEBOUNCE_TIMEOUT,
|
||||||
|
MANUFACTURER, SERV_BATTERY_SERVICE)
|
||||||
from .util import (
|
from .util import (
|
||||||
show_setup_message, dismiss_setup_message)
|
convert_to_float, show_setup_message, dismiss_setup_message)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -67,6 +69,23 @@ class HomeAccessory(Accessory):
|
|||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.debounce = {}
|
self.debounce = {}
|
||||||
|
self._support_battery_level = False
|
||||||
|
self._support_battery_charging = True
|
||||||
|
|
||||||
|
"""Add battery service if available"""
|
||||||
|
battery_level = self.hass.states.get(self.entity_id).attributes \
|
||||||
|
.get(ATTR_BATTERY_LEVEL)
|
||||||
|
if battery_level is None:
|
||||||
|
return
|
||||||
|
_LOGGER.debug('%s: Found battery level attribute', self.entity_id)
|
||||||
|
self._support_battery_level = True
|
||||||
|
serv_battery = self.add_preload_service(SERV_BATTERY_SERVICE)
|
||||||
|
self._char_battery = serv_battery.configure_char(
|
||||||
|
CHAR_BATTERY_LEVEL, value=0)
|
||||||
|
self._char_charging = serv_battery.configure_char(
|
||||||
|
CHAR_CHARGING_STATE, value=2)
|
||||||
|
self._char_low_battery = serv_battery.configure_char(
|
||||||
|
CHAR_STATUS_LOW_BATTERY, value=0)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Method called by accessory after driver is started.
|
"""Method called by accessory after driver is started.
|
||||||
@ -85,8 +104,32 @@ class HomeAccessory(Accessory):
|
|||||||
_LOGGER.debug('New_state: %s', new_state)
|
_LOGGER.debug('New_state: %s', new_state)
|
||||||
if new_state is None:
|
if new_state is None:
|
||||||
return
|
return
|
||||||
|
if self._support_battery_level:
|
||||||
|
self.hass.async_add_job(self.update_battery, new_state)
|
||||||
self.hass.async_add_job(self.update_state, new_state)
|
self.hass.async_add_job(self.update_state, new_state)
|
||||||
|
|
||||||
|
def update_battery(self, new_state):
|
||||||
|
"""Update battery service if available.
|
||||||
|
|
||||||
|
Only call this function if self._support_battery_level is True.
|
||||||
|
"""
|
||||||
|
battery_level = convert_to_float(
|
||||||
|
new_state.attributes.get(ATTR_BATTERY_LEVEL))
|
||||||
|
self._char_battery.set_value(battery_level)
|
||||||
|
self._char_low_battery.set_value(battery_level < 20)
|
||||||
|
_LOGGER.debug('%s: Updated battery level to %d', self.entity_id,
|
||||||
|
battery_level)
|
||||||
|
if not self._support_battery_charging:
|
||||||
|
return
|
||||||
|
charging = new_state.attributes.get(ATTR_BATTERY_CHARGING)
|
||||||
|
if charging is None:
|
||||||
|
self._support_battery_charging = False
|
||||||
|
return
|
||||||
|
hk_charging = 1 if charging is True else 0
|
||||||
|
self._char_charging.set_value(hk_charging)
|
||||||
|
_LOGGER.debug('%s: Updated battery charging to %d', self.entity_id,
|
||||||
|
hk_charging)
|
||||||
|
|
||||||
def update_state(self, new_state):
|
def update_state(self, new_state):
|
||||||
"""Method called on state change to update HomeKit value.
|
"""Method called on state change to update HomeKit value.
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ TYPE_SWITCH = 'switch'
|
|||||||
# #### Services ####
|
# #### Services ####
|
||||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||||
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
|
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
|
||||||
|
SERV_BATTERY_SERVICE = 'BatteryService'
|
||||||
SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor'
|
SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor'
|
||||||
SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor'
|
SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor'
|
||||||
SERV_CONTACT_SENSOR = 'ContactSensor'
|
SERV_CONTACT_SENSOR = 'ContactSensor'
|
||||||
@ -62,11 +63,13 @@ SERV_WINDOW_COVERING = 'WindowCovering'
|
|||||||
CHAR_ACTIVE = 'Active'
|
CHAR_ACTIVE = 'Active'
|
||||||
CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity'
|
CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity'
|
||||||
CHAR_AIR_QUALITY = 'AirQuality'
|
CHAR_AIR_QUALITY = 'AirQuality'
|
||||||
|
CHAR_BATTERY_LEVEL = 'BatteryLevel'
|
||||||
CHAR_BRIGHTNESS = 'Brightness'
|
CHAR_BRIGHTNESS = 'Brightness'
|
||||||
CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected'
|
CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected'
|
||||||
CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel'
|
CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel'
|
||||||
CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel'
|
CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel'
|
||||||
CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected'
|
CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected'
|
||||||
|
CHAR_CHARGING_STATE = 'ChargingState'
|
||||||
CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
|
CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
|
||||||
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
|
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
|
||||||
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
|
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
|
||||||
@ -96,6 +99,7 @@ CHAR_ROTATION_DIRECTION = 'RotationDirection'
|
|||||||
CHAR_SATURATION = 'Saturation'
|
CHAR_SATURATION = 'Saturation'
|
||||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||||
CHAR_SMOKE_DETECTED = 'SmokeDetected'
|
CHAR_SMOKE_DETECTED = 'SmokeDetected'
|
||||||
|
CHAR_STATUS_LOW_BATTERY = 'StatusLowBattery'
|
||||||
CHAR_SWING_MODE = 'SwingMode'
|
CHAR_SWING_MODE = 'SwingMode'
|
||||||
CHAR_TARGET_DOOR_STATE = 'TargetDoorState'
|
CHAR_TARGET_DOOR_STATE = 'TargetDoorState'
|
||||||
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
|
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
|
||||||
|
@ -254,6 +254,7 @@ ATTR_DISCOVERED = 'discovered'
|
|||||||
# Location of the device/sensor
|
# Location of the device/sensor
|
||||||
ATTR_LOCATION = 'location'
|
ATTR_LOCATION = 'location'
|
||||||
|
|
||||||
|
ATTR_BATTERY_CHARGING = 'battery_charging'
|
||||||
ATTR_BATTERY_LEVEL = 'battery_level'
|
ATTR_BATTERY_LEVEL = 'battery_level'
|
||||||
ATTR_WAKEUP = 'wake_up_interval'
|
ATTR_WAKEUP = 'wake_up_interval'
|
||||||
|
|
||||||
|
@ -13,7 +13,9 @@ from homeassistant.components.homekit.const import (
|
|||||||
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,
|
||||||
MANUFACTURER, SERV_ACCESSORY_INFO)
|
MANUFACTURER, SERV_ACCESSORY_INFO)
|
||||||
from homeassistant.const import __version__, ATTR_NOW, EVENT_TIME_CHANGED
|
from homeassistant.const import (
|
||||||
|
__version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_NOW,
|
||||||
|
EVENT_TIME_CHANGED)
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
|
||||||
@ -88,11 +90,60 @@ async def test_home_accessory(hass, hk_driver):
|
|||||||
|
|
||||||
# Test model name from domain
|
# Test model name from domain
|
||||||
entity_id = 'test_model.demo'
|
entity_id = 'test_model.demo'
|
||||||
acc = HomeAccessory('hass', hk_driver, 'test_name', entity_id, 2, None)
|
hass.states.async_set(entity_id, None)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = HomeAccessory(hass, hk_driver, 'test_name', entity_id, 2, None)
|
||||||
serv = acc.services[0] # SERV_ACCESSORY_INFO
|
serv = acc.services[0] # SERV_ACCESSORY_INFO
|
||||||
assert serv.get_characteristic(CHAR_MODEL).value == 'Test Model'
|
assert serv.get_characteristic(CHAR_MODEL).value == 'Test Model'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_battery_service(hass, hk_driver):
|
||||||
|
"""Test battery service."""
|
||||||
|
entity_id = 'homekit.accessory'
|
||||||
|
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)
|
||||||
|
assert acc._char_battery.value == 0
|
||||||
|
assert acc._char_low_battery.value == 0
|
||||||
|
assert acc._char_charging.value == 2
|
||||||
|
|
||||||
|
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(entity_id, None, {ATTR_BATTERY_LEVEL: 15})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc._char_battery.value == 15
|
||||||
|
assert acc._char_low_battery.value == 1
|
||||||
|
assert acc._char_charging.value == 2
|
||||||
|
|
||||||
|
# Test charging
|
||||||
|
hass.states.async_set(entity_id, None, {
|
||||||
|
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)
|
||||||
|
assert acc._char_battery.value == 0
|
||||||
|
assert acc._char_low_battery.value == 0
|
||||||
|
assert acc._char_charging.value == 2
|
||||||
|
|
||||||
|
await hass.async_add_job(acc.run)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc._char_battery.value == 10
|
||||||
|
assert acc._char_low_battery.value == 1
|
||||||
|
assert acc._char_charging.value == 1
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None, {
|
||||||
|
ATTR_BATTERY_LEVEL: 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
|
||||||
|
|
||||||
|
|
||||||
def test_home_bridge(hk_driver):
|
def test_home_bridge(hk_driver):
|
||||||
"""Test HomeBridge class."""
|
"""Test HomeBridge class."""
|
||||||
bridge = HomeBridge('hass', hk_driver)
|
bridge = HomeBridge('hass', hk_driver)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user