Skip to content

Commit

Permalink
Use registry to find linked batteries for homekit
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Apr 2, 2020
1 parent 4e043b3 commit 79dcaf9
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 47 deletions.
4 changes: 4 additions & 0 deletions homeassistant/components/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
# On means low, Off means normal
DEVICE_CLASS_BATTERY = "battery"

# On means charging, Off means not charging
DEVICE_CLASS_BATTERY_CHARGING = "battery_charging"

# On means cold, Off means normal
DEVICE_CLASS_COLD = "cold"

Expand Down Expand Up @@ -91,6 +94,7 @@

DEVICE_CLASSES = [
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/binary_sensor/device_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from . import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
Expand Down Expand Up @@ -44,6 +45,8 @@

CONF_IS_BAT_LOW = "is_bat_low"
CONF_IS_NOT_BAT_LOW = "is_not_bat_low"
CONF_IS_CHARGING = "is_charging"
CONF_IS_NOT_CHARGING = "is_not_charging"
CONF_IS_COLD = "is_cold"
CONF_IS_NOT_COLD = "is_not_cold"
CONF_IS_CONNECTED = "is_connected"
Expand Down Expand Up @@ -85,6 +88,7 @@

IS_ON = [
CONF_IS_BAT_LOW,
CONF_IS_CHARGING,
CONF_IS_COLD,
CONF_IS_CONNECTED,
CONF_IS_GAS,
Expand All @@ -109,6 +113,7 @@

IS_OFF = [
CONF_IS_NOT_BAT_LOW,
CONF_IS_NOT_CHARGING,
CONF_IS_NOT_COLD,
CONF_IS_NOT_CONNECTED,
CONF_IS_NOT_HOT,
Expand Down Expand Up @@ -136,6 +141,10 @@
{CONF_TYPE: CONF_IS_BAT_LOW},
{CONF_TYPE: CONF_IS_NOT_BAT_LOW},
],
DEVICE_CLASS_BATTERY_CHARGING: [
{CONF_TYPE: CONF_IS_CHARGING},
{CONF_TYPE: CONF_IS_NOT_CHARGING},
],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_IS_COLD}, {CONF_TYPE: CONF_IS_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_IS_CONNECTED},
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/binary_sensor/device_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from . import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
Expand Down Expand Up @@ -44,6 +45,8 @@

CONF_BAT_LOW = "bat_low"
CONF_NOT_BAT_LOW = "not_bat_low"
CONF_CHARGING = "_charging"
CONF_NOT_CHARGING = "not_charging"
CONF_COLD = "cold"
CONF_NOT_COLD = "not_cold"
CONF_CONNECTED = "connected"
Expand Down Expand Up @@ -135,6 +138,10 @@

ENTITY_TRIGGERS = {
DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_BAT_LOW}, {CONF_TYPE: CONF_NOT_BAT_LOW}],
DEVICE_CLASS_BATTERY_CHARGING: [
{CONF_TYPE: CONF_CHARGING},
{CONF_TYPE: CONF_NOT_CHARGING},
],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_COLD}, {CONF_TYPE: CONF_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_CONNECTED},
Expand Down
84 changes: 76 additions & 8 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from homeassistant.components import cover
from homeassistant.components.media_player import DEVICE_CLASS_TV
from homeassistant.const import (
ATTR_BATTERY_CHARGING,
ATTR_BATTERY_LEVEL,
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
Expand All @@ -16,6 +18,7 @@
CONF_NAME,
CONF_PORT,
CONF_TYPE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
Expand All @@ -25,6 +28,8 @@
TEMP_FAHRENHEIT,
UNIT_PERCENTAGE,
)
from homeassistant.core import callback
from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.util import get_local_ip
Expand All @@ -37,6 +42,8 @@
CONF_ENTITY_CONFIG,
CONF_FEATURE_LIST,
CONF_FILTER,
CONF_LINKED_BATTERY_CHARGING_SENSOR,
CONF_LINKED_BATTERY_SENSOR,
CONF_SAFE_MODE,
DEFAULT_AUTO_START,
DEFAULT_PORT,
Expand All @@ -61,6 +68,11 @@
validate_media_player_features,
)

# TODO: switch the below once done testing outside of tree
# from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING
DEVICE_CLASS_BATTERY_CHARGING = "battery_charging"


_LOGGER = logging.getLogger(__name__)

MAX_DEVICES = 100
Expand Down Expand Up @@ -154,21 +166,21 @@ def handle_homekit_reset_accessory(service):
)

if auto_start:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.start)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.async_start)
return True

def handle_homekit_service_start(service):
async def async_handle_homekit_service_start(service):
"""Handle start HomeKit service call."""
if homekit.status != STATUS_READY:
_LOGGER.warning(
"HomeKit is not ready. Either it is already running or has "
"been stopped."
)
return
homekit.start()
await homekit.async_start()

hass.services.async_register(
DOMAIN, SERVICE_HOMEKIT_START, handle_homekit_service_start
DOMAIN, SERVICE_HOMEKIT_START, async_handle_homekit_service_start
)

return True
Expand Down Expand Up @@ -306,7 +318,7 @@ def setup(self):
# pylint: disable=import-outside-toplevel
from .accessories import HomeBridge, HomeDriver

self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)

ip_addr = self._ip_address or get_local_ip()
path = self.hass.config.path(HOMEKIT_FILE)
Expand Down Expand Up @@ -357,12 +369,24 @@ def remove_bridge_accessory(self, aid):
acc = self.bridge.accessories.pop(aid)
return acc

def start(self, *args):
async def async_start(self, *args):
"""Start the accessory driver."""
if self.status != STATUS_READY:
return
self.status = STATUS_WAIT

ent_reg = await entity_registry.async_get_registry(self.hass)

bridged_states = []
for state in self.hass.states.async_all():
if not state or not self._filter(state.entity_id):
continue
_async_configure_linked_battery_sensors(ent_reg, self._config, state)
bridged_states.append(state)

await self.hass.async_add_executor_job(self._start, bridged_states)

def _start(self, bridged_states):
from . import ( # noqa: F401 pylint: disable=unused-import, import-outside-toplevel
type_covers,
type_fans,
Expand All @@ -375,8 +399,9 @@ def start(self, *args):
type_thermostats,
)

for state in self.hass.states.all():
for state in bridged_states:
self.add_bridge_accessory(state)

self.driver.add_accessory(self.bridge)

if not self.driver.state.paired:
Expand All @@ -392,11 +417,54 @@ def start(self, *args):
self.hass.add_job(self.driver.start)
self.status = STATUS_RUNNING

def stop(self, *args):
async def async_stop(self, *args):
"""Stop the accessory driver."""
if self.status != STATUS_RUNNING:
return
self.status = STATUS_STOPPED

_LOGGER.debug("Driver stop")
self.hass.add_job(self.driver.stop)


@callback
def _async_configure_linked_battery_sensors(ent_reg, config, state):
entry = ent_reg.async_get(state.entity_id)

if entry is None or entry.device_id is None:
return

entries = entity_registry.async_entries_for_device(ent_reg, entry.device_id)

for entry in entries:
if entry.device_class in (
DEVICE_CLASS_BATTERY_CHARGING,
CONF_LINKED_BATTERY_SENSOR,
):
continue
if (
entry.domain == "binary_sensor"
and entry.device_class == DEVICE_CLASS_BATTERY_CHARGING
and ATTR_BATTERY_LEVEL not in state.attributes
):
_LOGGER.debug(
"Found linked charging sensor for: %s: %s",
state.entity_id,
entry.entity_id,
)
config.setdefault(state.entity_id, {}).setdefault(
CONF_LINKED_BATTERY_CHARGING_SENSOR, entry.entity_id
)
if (
entry.domain == "sensor"
and entry.device_class == DEVICE_CLASS_BATTERY
and ATTR_BATTERY_CHARGING not in state.attributes
):
_LOGGER.debug(
"Found linked battery sensor for: %s: %s",
state.entity_id,
entry.entity_id,
)
config.setdefault(state.entity_id, {}).setdefault(
CONF_LINKED_BATTERY_SENSOR, entry.entity_id
)
Loading

0 comments on commit 79dcaf9

Please sign in to comment.