Support HomeKit accessory mode (#41679)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
db7c16162d
commit
3ffa050905
8 changed files with 261 additions and 69 deletions
|
@ -5,6 +5,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from pyhap.const import STANDALONE_AID
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
|
@ -56,6 +57,7 @@ from .const import (
|
||||||
CONF_ENTITY_CONFIG,
|
CONF_ENTITY_CONFIG,
|
||||||
CONF_ENTRY_INDEX,
|
CONF_ENTRY_INDEX,
|
||||||
CONF_FILTER,
|
CONF_FILTER,
|
||||||
|
CONF_HOMEKIT_MODE,
|
||||||
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
||||||
CONF_LINKED_BATTERY_SENSOR,
|
CONF_LINKED_BATTERY_SENSOR,
|
||||||
CONF_LINKED_DOORBELL_SENSOR,
|
CONF_LINKED_DOORBELL_SENSOR,
|
||||||
|
@ -65,10 +67,13 @@ from .const import (
|
||||||
CONF_ZEROCONF_DEFAULT_INTERFACE,
|
CONF_ZEROCONF_DEFAULT_INTERFACE,
|
||||||
CONFIG_OPTIONS,
|
CONFIG_OPTIONS,
|
||||||
DEFAULT_AUTO_START,
|
DEFAULT_AUTO_START,
|
||||||
|
DEFAULT_HOMEKIT_MODE,
|
||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HOMEKIT,
|
HOMEKIT,
|
||||||
|
HOMEKIT_MODE_ACCESSORY,
|
||||||
|
HOMEKIT_MODES,
|
||||||
HOMEKIT_PAIRING_QR,
|
HOMEKIT_PAIRING_QR,
|
||||||
HOMEKIT_PAIRING_QR_SECRET,
|
HOMEKIT_PAIRING_QR_SECRET,
|
||||||
MANUFACTURER,
|
MANUFACTURER,
|
||||||
|
@ -111,6 +116,9 @@ BRIDGE_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_ZEROCONF_DEFAULT_INTERFACE),
|
cv.deprecated(CONF_ZEROCONF_DEFAULT_INTERFACE),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
|
vol.Optional(CONF_HOMEKIT_MODE, default=DEFAULT_HOMEKIT_MODE): vol.In(
|
||||||
|
HOMEKIT_MODES
|
||||||
|
),
|
||||||
vol.Optional(CONF_NAME, default=BRIDGE_NAME): vol.All(
|
vol.Optional(CONF_NAME, default=BRIDGE_NAME): vol.All(
|
||||||
cv.string, vol.Length(min=3, max=25)
|
cv.string, vol.Length(min=3, max=25)
|
||||||
),
|
),
|
||||||
|
@ -228,7 +236,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
# ip_address and advertise_ip are yaml only
|
# ip_address and advertise_ip are yaml only
|
||||||
ip_address = conf.get(CONF_IP_ADDRESS)
|
ip_address = conf.get(CONF_IP_ADDRESS)
|
||||||
advertise_ip = conf.get(CONF_ADVERTISE_IP)
|
advertise_ip = conf.get(CONF_ADVERTISE_IP)
|
||||||
|
homekit_mode = options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE)
|
||||||
entity_config = options.get(CONF_ENTITY_CONFIG, {}).copy()
|
entity_config = options.get(CONF_ENTITY_CONFIG, {}).copy()
|
||||||
auto_start = options.get(CONF_AUTO_START, DEFAULT_AUTO_START)
|
auto_start = options.get(CONF_AUTO_START, DEFAULT_AUTO_START)
|
||||||
safe_mode = options.get(CONF_SAFE_MODE, DEFAULT_SAFE_MODE)
|
safe_mode = options.get(CONF_SAFE_MODE, DEFAULT_SAFE_MODE)
|
||||||
|
@ -242,6 +250,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
entity_filter,
|
entity_filter,
|
||||||
entity_config,
|
entity_config,
|
||||||
safe_mode,
|
safe_mode,
|
||||||
|
homekit_mode,
|
||||||
advertise_ip,
|
advertise_ip,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -404,6 +413,7 @@ class HomeKit:
|
||||||
entity_filter,
|
entity_filter,
|
||||||
entity_config,
|
entity_config,
|
||||||
safe_mode,
|
safe_mode,
|
||||||
|
homekit_mode,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=None,
|
entry_id=None,
|
||||||
):
|
):
|
||||||
|
@ -417,6 +427,7 @@ class HomeKit:
|
||||||
self._safe_mode = safe_mode
|
self._safe_mode = safe_mode
|
||||||
self._advertise_ip = advertise_ip
|
self._advertise_ip = advertise_ip
|
||||||
self._entry_id = entry_id
|
self._entry_id = entry_id
|
||||||
|
self._homekit_mode = homekit_mode
|
||||||
self.status = STATUS_READY
|
self.status = STATUS_READY
|
||||||
|
|
||||||
self.bridge = None
|
self.bridge = None
|
||||||
|
@ -425,7 +436,7 @@ class HomeKit:
|
||||||
def setup(self, zeroconf_instance):
|
def setup(self, zeroconf_instance):
|
||||||
"""Set up bridge and accessory driver."""
|
"""Set up bridge and accessory driver."""
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
from .accessories import HomeBridge, HomeDriver
|
from .accessories import HomeDriver
|
||||||
|
|
||||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
||||||
ip_addr = self._ip_address or get_local_ip()
|
ip_addr = self._ip_address or get_local_ip()
|
||||||
|
@ -450,13 +461,16 @@ class HomeKit:
|
||||||
else:
|
else:
|
||||||
self.driver.persist()
|
self.driver.persist()
|
||||||
|
|
||||||
self.bridge = HomeBridge(self.hass, self.driver, self._name)
|
|
||||||
if self._safe_mode:
|
if self._safe_mode:
|
||||||
_LOGGER.debug("Safe_mode selected for %s", self._name)
|
_LOGGER.debug("Safe_mode selected for %s", self._name)
|
||||||
self.driver.safe_mode = True
|
self.driver.safe_mode = True
|
||||||
|
|
||||||
def reset_accessories(self, entity_ids):
|
def reset_accessories(self, entity_ids):
|
||||||
"""Reset the accessory to load the latest configuration."""
|
"""Reset the accessory to load the latest configuration."""
|
||||||
|
if not self.bridge:
|
||||||
|
self.driver.config_changed()
|
||||||
|
return
|
||||||
|
|
||||||
aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE]
|
aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE]
|
||||||
removed = []
|
removed = []
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
|
@ -602,7 +616,8 @@ class HomeKit:
|
||||||
dev_reg.async_remove_device(device_id)
|
dev_reg.async_remove_device(device_id)
|
||||||
|
|
||||||
def _start(self, bridged_states):
|
def _start(self, bridged_states):
|
||||||
from . import ( # noqa: F401 pylint: disable=unused-import, import-outside-toplevel
|
# pylint: disable=unused-import, import-outside-toplevel
|
||||||
|
from . import ( # noqa: F401
|
||||||
type_cameras,
|
type_cameras,
|
||||||
type_covers,
|
type_covers,
|
||||||
type_fans,
|
type_fans,
|
||||||
|
@ -616,10 +631,18 @@ class HomeKit:
|
||||||
type_thermostats,
|
type_thermostats,
|
||||||
)
|
)
|
||||||
|
|
||||||
for state in bridged_states:
|
if self._homekit_mode == HOMEKIT_MODE_ACCESSORY:
|
||||||
self.add_bridge_accessory(state)
|
state = bridged_states[0]
|
||||||
|
conf = self._config.pop(state.entity_id, {})
|
||||||
|
acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf)
|
||||||
|
self.driver.add_accessory(acc)
|
||||||
|
else:
|
||||||
|
from .accessories import HomeBridge
|
||||||
|
|
||||||
self.driver.add_accessory(self.bridge)
|
self.bridge = HomeBridge(self.hass, self.driver, self._name)
|
||||||
|
for state in bridged_states:
|
||||||
|
self.add_bridge_accessory(state)
|
||||||
|
self.driver.add_accessory(self.bridge)
|
||||||
|
|
||||||
if not self.driver.state.paired:
|
if not self.driver.state.paired:
|
||||||
show_setup_message(
|
show_setup_message(
|
||||||
|
@ -627,7 +650,7 @@ class HomeKit:
|
||||||
self._entry_id,
|
self._entry_id,
|
||||||
self._name,
|
self._name,
|
||||||
self.driver.state.pincode,
|
self.driver.state.pincode,
|
||||||
self.bridge.xhm_uri(),
|
self.driver.accessory.xhm_uri(),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_stop(self, *args):
|
async def async_stop(self, *args):
|
||||||
|
@ -637,8 +660,11 @@ class HomeKit:
|
||||||
self.status = STATUS_STOPPED
|
self.status = STATUS_STOPPED
|
||||||
_LOGGER.debug("Driver stop for %s", self._name)
|
_LOGGER.debug("Driver stop for %s", self._name)
|
||||||
await self.driver.async_stop()
|
await self.driver.async_stop()
|
||||||
for acc in self.bridge.accessories.values():
|
if self.bridge:
|
||||||
acc.async_stop()
|
for acc in self.bridge.accessories.values():
|
||||||
|
acc.async_stop()
|
||||||
|
else:
|
||||||
|
self.driver.accessory.async_stop()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state):
|
def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state):
|
||||||
|
|
|
@ -20,11 +20,15 @@ from .const import (
|
||||||
CONF_AUTO_START,
|
CONF_AUTO_START,
|
||||||
CONF_ENTITY_CONFIG,
|
CONF_ENTITY_CONFIG,
|
||||||
CONF_FILTER,
|
CONF_FILTER,
|
||||||
|
CONF_HOMEKIT_MODE,
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
CONF_VIDEO_CODEC,
|
CONF_VIDEO_CODEC,
|
||||||
DEFAULT_AUTO_START,
|
DEFAULT_AUTO_START,
|
||||||
DEFAULT_CONFIG_FLOW_PORT,
|
DEFAULT_CONFIG_FLOW_PORT,
|
||||||
|
DEFAULT_HOMEKIT_MODE,
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_ACCESSORY,
|
||||||
|
HOMEKIT_MODES,
|
||||||
SHORT_BRIDGE_NAME,
|
SHORT_BRIDGE_NAME,
|
||||||
VIDEO_CODEC_COPY,
|
VIDEO_CODEC_COPY,
|
||||||
)
|
)
|
||||||
|
@ -292,14 +296,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
CONF_INCLUDE_ENTITIES: [],
|
CONF_INCLUDE_ENTITIES: [],
|
||||||
CONF_EXCLUDE_ENTITIES: [],
|
CONF_EXCLUDE_ENTITIES: [],
|
||||||
}
|
}
|
||||||
|
if isinstance(user_input[CONF_ENTITIES], list):
|
||||||
|
entities = user_input[CONF_ENTITIES]
|
||||||
|
else:
|
||||||
|
entities = [user_input[CONF_ENTITIES]]
|
||||||
|
|
||||||
if user_input[CONF_INCLUDE_EXCLUDE_MODE] == MODE_INCLUDE:
|
if (
|
||||||
entity_filter[CONF_INCLUDE_ENTITIES] = user_input[CONF_ENTITIES]
|
self.homekit_options[CONF_HOMEKIT_MODE] == HOMEKIT_MODE_ACCESSORY
|
||||||
|
or user_input[CONF_INCLUDE_EXCLUDE_MODE] == MODE_INCLUDE
|
||||||
|
):
|
||||||
|
entity_filter[CONF_INCLUDE_ENTITIES] = entities
|
||||||
# Include all of the domain if there are no entities
|
# Include all of the domain if there are no entities
|
||||||
# explicitly included as the user selected the domain
|
# explicitly included as the user selected the domain
|
||||||
domains_with_entities_selected = _domains_set_from_entities(
|
domains_with_entities_selected = _domains_set_from_entities(entities)
|
||||||
user_input[CONF_ENTITIES]
|
|
||||||
)
|
|
||||||
entity_filter[CONF_INCLUDE_DOMAINS] = [
|
entity_filter[CONF_INCLUDE_DOMAINS] = [
|
||||||
domain
|
domain
|
||||||
for domain in self.homekit_options[CONF_DOMAINS]
|
for domain in self.homekit_options[CONF_DOMAINS]
|
||||||
|
@ -307,12 +316,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
]
|
]
|
||||||
|
|
||||||
for entity_id in list(self.included_cameras):
|
for entity_id in list(self.included_cameras):
|
||||||
if entity_id not in user_input[CONF_ENTITIES]:
|
if entity_id not in entities:
|
||||||
self.included_cameras.remove(entity_id)
|
self.included_cameras.remove(entity_id)
|
||||||
else:
|
else:
|
||||||
entity_filter[CONF_INCLUDE_DOMAINS] = self.homekit_options[CONF_DOMAINS]
|
entity_filter[CONF_INCLUDE_DOMAINS] = self.homekit_options[CONF_DOMAINS]
|
||||||
entity_filter[CONF_EXCLUDE_ENTITIES] = user_input[CONF_ENTITIES]
|
entity_filter[CONF_EXCLUDE_ENTITIES] = entities
|
||||||
for entity_id in user_input[CONF_ENTITIES]:
|
for entity_id in entities:
|
||||||
if entity_id in self.included_cameras:
|
if entity_id in self.included_cameras:
|
||||||
self.included_cameras.remove(entity_id)
|
self.included_cameras.remove(entity_id)
|
||||||
|
|
||||||
|
@ -335,26 +344,28 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
if entity_id.startswith("camera.")
|
if entity_id.startswith("camera.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data_schema = {}
|
||||||
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
||||||
if entities:
|
if self.homekit_options[CONF_HOMEKIT_MODE] == HOMEKIT_MODE_ACCESSORY:
|
||||||
include_exclude_mode = MODE_INCLUDE
|
entity_schema = vol.In
|
||||||
else:
|
else:
|
||||||
include_exclude_mode = MODE_EXCLUDE
|
if entities:
|
||||||
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
|
include_exclude_mode = MODE_INCLUDE
|
||||||
|
else:
|
||||||
|
include_exclude_mode = MODE_EXCLUDE
|
||||||
|
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
|
||||||
|
data_schema[
|
||||||
|
vol.Required(CONF_INCLUDE_EXCLUDE_MODE, default=include_exclude_mode)
|
||||||
|
] = vol.In(INCLUDE_EXCLUDE_MODES)
|
||||||
|
entity_schema = cv.multi_select
|
||||||
|
|
||||||
data_schema = vol.Schema(
|
data_schema[vol.Optional(CONF_ENTITIES, default=entities)] = entity_schema(
|
||||||
{
|
all_supported_entities
|
||||||
vol.Required(
|
)
|
||||||
CONF_INCLUDE_EXCLUDE_MODE,
|
|
||||||
default=include_exclude_mode,
|
return self.async_show_form(
|
||||||
): vol.In(INCLUDE_EXCLUDE_MODES),
|
step_id="include_exclude", data_schema=vol.Schema(data_schema)
|
||||||
vol.Optional(
|
|
||||||
CONF_ENTITIES,
|
|
||||||
default=entities,
|
|
||||||
): cv.multi_select(all_supported_entities),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return self.async_show_form(step_id="include_exclude", data_schema=data_schema)
|
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Handle options flow."""
|
"""Handle options flow."""
|
||||||
|
@ -368,6 +379,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
self.homekit_options = dict(self.config_entry.options)
|
self.homekit_options = dict(self.config_entry.options)
|
||||||
entity_filter = self.homekit_options.get(CONF_FILTER, {})
|
entity_filter = self.homekit_options.get(CONF_FILTER, {})
|
||||||
|
|
||||||
|
homekit_mode = self.homekit_options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE)
|
||||||
domains = entity_filter.get(CONF_INCLUDE_DOMAINS, [])
|
domains = entity_filter.get(CONF_INCLUDE_DOMAINS, [])
|
||||||
include_entities = entity_filter.get(CONF_INCLUDE_ENTITIES)
|
include_entities = entity_filter.get(CONF_INCLUDE_ENTITIES)
|
||||||
if include_entities:
|
if include_entities:
|
||||||
|
@ -375,10 +387,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{
|
{
|
||||||
|
vol.Optional(CONF_HOMEKIT_MODE, default=homekit_mode): vol.In(
|
||||||
|
HOMEKIT_MODES
|
||||||
|
),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_DOMAINS,
|
CONF_DOMAINS,
|
||||||
default=domains,
|
default=domains,
|
||||||
): cv.multi_select(SUPPORTED_DOMAINS)
|
): cv.multi_select(SUPPORTED_DOMAINS),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return self.async_show_form(step_id="init", data_schema=data_schema)
|
return self.async_show_form(step_id="init", data_schema=data_schema)
|
||||||
|
|
|
@ -30,6 +30,7 @@ ATTR_SOFTWARE_VERSION = "sw_version"
|
||||||
ATTR_KEY_NAME = "key_name"
|
ATTR_KEY_NAME = "key_name"
|
||||||
|
|
||||||
# #### Config ####
|
# #### Config ####
|
||||||
|
CONF_HOMEKIT_MODE = "mode"
|
||||||
CONF_ADVERTISE_IP = "advertise_ip"
|
CONF_ADVERTISE_IP = "advertise_ip"
|
||||||
CONF_AUDIO_CODEC = "audio_codec"
|
CONF_AUDIO_CODEC = "audio_codec"
|
||||||
CONF_AUDIO_MAP = "audio_map"
|
CONF_AUDIO_MAP = "audio_map"
|
||||||
|
@ -86,6 +87,12 @@ FEATURE_TOGGLE_MUTE = "toggle_mute"
|
||||||
EVENT_HOMEKIT_CHANGED = "homekit_state_change"
|
EVENT_HOMEKIT_CHANGED = "homekit_state_change"
|
||||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED = "homekit_tv_remote_key_pressed"
|
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED = "homekit_tv_remote_key_pressed"
|
||||||
|
|
||||||
|
# #### HomeKit Modes ####
|
||||||
|
HOMEKIT_MODE_ACCESSORY = "accessory"
|
||||||
|
HOMEKIT_MODE_BRIDGE = "bridge"
|
||||||
|
DEFAULT_HOMEKIT_MODE = HOMEKIT_MODE_BRIDGE
|
||||||
|
HOMEKIT_MODES = [HOMEKIT_MODE_BRIDGE, HOMEKIT_MODE_ACCESSORY]
|
||||||
|
|
||||||
# #### HomeKit Component Services ####
|
# #### HomeKit Component Services ####
|
||||||
SERVICE_HOMEKIT_START = "start"
|
SERVICE_HOMEKIT_START = "start"
|
||||||
SERVICE_HOMEKIT_RESET_ACCESSORY = "reset_accessory"
|
SERVICE_HOMEKIT_RESET_ACCESSORY = "reset_accessory"
|
||||||
|
@ -283,4 +290,5 @@ CONFIG_OPTIONS = [
|
||||||
CONF_AUTO_START,
|
CONF_AUTO_START,
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
CONF_ENTITY_CONFIG,
|
CONF_ENTITY_CONFIG,
|
||||||
|
CONF_HOMEKIT_MODE,
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
{
|
{
|
||||||
"title": "HomeKit Bridge",
|
|
||||||
"options": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"title": "Adjust HomeKit Bridge Options",
|
"title": "Adjust HomeKit Options",
|
||||||
"description": "This entry is controlled via YAML"
|
"description": "This entry is controlled via YAML"
|
||||||
},
|
},
|
||||||
"init": {
|
"init": {
|
||||||
"data": {
|
"data": {
|
||||||
|
"mode": "Mode",
|
||||||
"include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]"
|
"include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]"
|
||||||
},
|
},
|
||||||
"description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
"description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV device class to function properly. Entities in the \u201cDomains to include\u201d will be exposed to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
||||||
"title": "Select domains to bridge."
|
"title": "Select domains to expose."
|
||||||
},
|
},
|
||||||
"include_exclude": {
|
"include_exclude": {
|
||||||
"data": {
|
"data": {
|
||||||
"mode": "Mode",
|
"mode": "Mode",
|
||||||
"entities": "Entities"
|
"entities": "Entities"
|
||||||
},
|
},
|
||||||
"description": "Choose the entities to be bridged. In include mode, all entities in the domain will be bridged unless specific entities are selected. In exclude mode, all entities in the domain will be bridged except for the excluded entities.",
|
"description": "Choose the entities to be exposed. In accessory mode, only a single entity is exposed. In bridge include mode, all entities in the domain will be exposed unless specific entities are selected. In bridge exclude mode, all entities in the domain will be exposed except for the excluded entities.",
|
||||||
"title": "Select entities to be bridged"
|
"title": "Select entities to be exposed"
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
"auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]",
|
"auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]",
|
||||||
"safe_mode": "Safe Mode (enable only if pairing fails)"
|
"safe_mode": "Safe Mode (enable only if pairing fails)"
|
||||||
},
|
},
|
||||||
"description": "These settings only need to be adjusted if the HomeKit bridge is not functional.",
|
"description": "These settings only need to be adjusted if HomeKit is not functional.",
|
||||||
"title": "Advanced Configuration"
|
"title": "Advanced Configuration"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,16 +45,16 @@
|
||||||
"auto_start": "Autostart (disable if using Z-Wave or other delayed start system)",
|
"auto_start": "Autostart (disable if using Z-Wave or other delayed start system)",
|
||||||
"include_domains": "Domains to include"
|
"include_domains": "Domains to include"
|
||||||
},
|
},
|
||||||
"description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.",
|
"description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.",
|
||||||
"title": "Activate HomeKit Bridge"
|
"title": "Activate HomeKit"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"title": "Pair HomeKit Bridge",
|
"title": "Pair HomeKit",
|
||||||
"description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d."
|
"description": "As soon as the {name} is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"port_name_in_use": "A bridge with the same name or port is already configured."
|
"port_name_in_use": "An accessory or bridge with the same name or port is already configured."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
{
|
{
|
||||||
"title": "HomeKit Bridge",
|
"title": "HomeKit",
|
||||||
"options": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"title": "Adjust HomeKit Bridge Options",
|
"title": "Adjust HomeKit Options",
|
||||||
"description": "This entry is controlled via YAML"
|
"description": "This entry is controlled via YAML"
|
||||||
},
|
},
|
||||||
"init": {
|
"init": {
|
||||||
"data": {
|
"data": {
|
||||||
|
"mode": "Mode",
|
||||||
"include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]"
|
"include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]"
|
||||||
},
|
},
|
||||||
"description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
"description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV device class to function properly. Entities in the \u201cDomains to include\u201d will be exposed to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
||||||
"title": "Select domains to bridge."
|
"title": "Select domains to expose."
|
||||||
},
|
},
|
||||||
"include_exclude": {
|
"include_exclude": {
|
||||||
"data": {
|
"data": {
|
||||||
"mode": "Mode",
|
"mode": "Mode",
|
||||||
"entities": "Entities"
|
"entities": "Entities"
|
||||||
},
|
},
|
||||||
"description": "Choose the entities to be bridged. In include mode, all entities in the domain will be bridged unless specific entities are selected. In exclude mode, all entities in the domain will be bridged except for the excluded entities.",
|
"description": "Choose the entities to be exposed. In accessory mode, only a single entity is exposed. In bridge include mode, all entities in the domain will be exposed unless specific entities are selected. In bridge exclude mode, all entities in the domain will be exposed except for the excluded entities.",
|
||||||
"title": "Select entities to be bridged"
|
"title": "Select entities to be exposed"
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
"auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]",
|
"auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]",
|
||||||
"safe_mode": "Safe Mode (enable only if pairing fails)"
|
"safe_mode": "Safe Mode (enable only if pairing fails)"
|
||||||
},
|
},
|
||||||
"description": "These settings only need to be adjusted if the HomeKit bridge is not functional.",
|
"description": "These settings only need to be adjusted if HomeKit is not functional.",
|
||||||
"title": "Advanced Configuration"
|
"title": "Advanced Configuration"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,16 +46,16 @@
|
||||||
"auto_start": "Autostart (disable if using Z-Wave or other delayed start system)",
|
"auto_start": "Autostart (disable if using Z-Wave or other delayed start system)",
|
||||||
"include_domains": "Domains to include"
|
"include_domains": "Domains to include"
|
||||||
},
|
},
|
||||||
"description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.",
|
"description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.",
|
||||||
"title": "Activate HomeKit Bridge"
|
"title": "Activate HomeKit"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"title": "Pair HomeKit Bridge",
|
"title": "Pair HomeKit",
|
||||||
"description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d."
|
"description": "As soon as the {name} is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"port_name_in_use": "A bridge with the same name or port is already configured."
|
"port_name_in_use": "An accessory or bridge with the same name or port is already configured."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,6 +280,7 @@ class TelevisionMediaPlayer(HomeAccessory):
|
||||||
|
|
||||||
serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv)
|
serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv)
|
||||||
self.set_primary_service(serv_tv)
|
self.set_primary_service(serv_tv)
|
||||||
|
serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name)
|
||||||
serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True)
|
serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True)
|
||||||
self.char_active = serv_tv.configure_char(
|
self.char_active = serv_tv.configure_char(
|
||||||
CHAR_ACTIVE, setter_callback=self.set_on_off
|
CHAR_ACTIVE, setter_callback=self.set_on_off
|
||||||
|
|
|
@ -163,6 +163,7 @@ async def test_options_flow_exclude_mode_advanced(hass):
|
||||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": True,
|
"auto_start": True,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": ["climate.old"],
|
"exclude_entities": ["climate.old"],
|
||||||
|
@ -213,6 +214,7 @@ async def test_options_flow_exclude_mode_basic(hass):
|
||||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": ["climate.old"],
|
"exclude_entities": ["climate.old"],
|
||||||
|
@ -265,6 +267,7 @@ async def test_options_flow_include_mode_basic(hass):
|
||||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": [],
|
"exclude_entities": [],
|
||||||
|
@ -330,6 +333,7 @@ async def test_options_flow_exclude_mode_with_cameras(hass):
|
||||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": ["climate.old", "camera.excluded"],
|
"exclude_entities": ["climate.old", "camera.excluded"],
|
||||||
|
@ -384,6 +388,7 @@ async def test_options_flow_exclude_mode_with_cameras(hass):
|
||||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": ["climate.old", "camera.excluded"],
|
"exclude_entities": ["climate.old", "camera.excluded"],
|
||||||
|
@ -450,6 +455,7 @@ async def test_options_flow_include_mode_with_cameras(hass):
|
||||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": [],
|
"exclude_entities": [],
|
||||||
|
@ -504,6 +510,7 @@ async def test_options_flow_include_mode_with_cameras(hass):
|
||||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert config_entry.options == {
|
assert config_entry.options == {
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"mode": "bridge",
|
||||||
"filter": {
|
"filter": {
|
||||||
"exclude_domains": [],
|
"exclude_domains": [],
|
||||||
"exclude_entities": ["climate.old", "camera.excluded"],
|
"exclude_entities": ["climate.old", "camera.excluded"],
|
||||||
|
@ -553,3 +560,56 @@ async def test_options_flow_blocked_when_from_yaml(hass):
|
||||||
user_input={},
|
user_input={},
|
||||||
)
|
)
|
||||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow_include_mode_basic_accessory(hass):
|
||||||
|
"""Test config flow options in include mode with a single accessory."""
|
||||||
|
|
||||||
|
config_entry = _mock_config_entry_with_options_populated()
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
hass.states.async_set("media_player.tv", "off")
|
||||||
|
hass.states.async_set("media_player.sonos", "off")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(
|
||||||
|
config_entry.entry_id, context={"show_advanced_options": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={"domains": ["media_player"], "mode": "accessory"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "include_exclude"
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={"entities": "media_player.tv"},
|
||||||
|
)
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["step_id"] == "advanced"
|
||||||
|
|
||||||
|
with patch("homeassistant.components.homekit.async_setup_entry", return_value=True):
|
||||||
|
result3 = await hass.config_entries.options.async_configure(
|
||||||
|
result2["flow_id"],
|
||||||
|
user_input={"safe_mode": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
"auto_start": False,
|
||||||
|
"mode": "accessory",
|
||||||
|
"filter": {
|
||||||
|
"exclude_domains": [],
|
||||||
|
"exclude_entities": [],
|
||||||
|
"include_domains": [],
|
||||||
|
"include_entities": ["media_player.tv"],
|
||||||
|
},
|
||||||
|
"safe_mode": False,
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import os
|
import os
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
from pyhap.accessory import Accessory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config as hass_config
|
from homeassistant import config as hass_config
|
||||||
|
@ -31,6 +32,8 @@ from homeassistant.components.homekit.const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HOMEKIT,
|
HOMEKIT,
|
||||||
HOMEKIT_FILE,
|
HOMEKIT_FILE,
|
||||||
|
HOMEKIT_MODE_ACCESSORY,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
SERVICE_HOMEKIT_START,
|
SERVICE_HOMEKIT_START,
|
||||||
)
|
)
|
||||||
|
@ -118,6 +121,7 @@ async def test_setup_min(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -154,6 +158,7 @@ async def test_setup_auto_start_disabled(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -200,10 +205,13 @@ async def test_homekit_setup(hass, hk_driver):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.states.async_set("light.demo", "on")
|
||||||
|
hass.states.async_set("light.demo2", "on")
|
||||||
zeroconf_mock = MagicMock()
|
zeroconf_mock = MagicMock()
|
||||||
with patch(
|
with patch(
|
||||||
f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver
|
f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver
|
||||||
|
@ -212,7 +220,6 @@ async def test_homekit_setup(hass, hk_driver):
|
||||||
await hass.async_add_executor_job(homekit.setup, zeroconf_mock)
|
await hass.async_add_executor_job(homekit.setup, zeroconf_mock)
|
||||||
|
|
||||||
path = get_persist_fullpath_for_entry_id(hass, entry.entry_id)
|
path = get_persist_fullpath_for_entry_id(hass, entry.entry_id)
|
||||||
assert isinstance(homekit.bridge, HomeBridge)
|
|
||||||
mock_driver.assert_called_with(
|
mock_driver.assert_called_with(
|
||||||
hass,
|
hass,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
|
@ -245,6 +252,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
None,
|
None,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -283,6 +291,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
None,
|
None,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
"192.168.1.100",
|
"192.168.1.100",
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -321,6 +330,7 @@ async def test_homekit_setup_safe_mode(hass, hk_driver):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
True,
|
True,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -342,6 +352,7 @@ async def test_homekit_add_accessory(hass):
|
||||||
lambda entity_id: True,
|
lambda entity_id: True,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -378,6 +389,7 @@ async def test_homekit_remove_accessory(hass):
|
||||||
lambda entity_id: True,
|
lambda entity_id: True,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -403,6 +415,7 @@ async def test_homekit_entity_filter(hass):
|
||||||
entity_filter,
|
entity_filter,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -439,6 +452,7 @@ async def test_homekit_entity_glob_filter(hass):
|
||||||
entity_filter,
|
entity_filter,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -478,6 +492,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -486,6 +501,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher):
|
||||||
homekit.driver = hk_driver
|
homekit.driver = hk_driver
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
homekit._filter = Mock(return_value=True)
|
homekit._filter = Mock(return_value=True)
|
||||||
|
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||||
|
|
||||||
connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF")
|
connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF")
|
||||||
bridge_with_wrong_mac = device_reg.async_get_or_create(
|
bridge_with_wrong_mac = device_reg.async_get_or_create(
|
||||||
|
@ -497,6 +513,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher):
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.states.async_set("light.demo", "on")
|
hass.states.async_set("light.demo", "on")
|
||||||
|
hass.states.async_set("light.demo2", "on")
|
||||||
state = hass.states.async_all()[0]
|
state = hass.states.async_all()[0]
|
||||||
|
|
||||||
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
||||||
|
@ -509,7 +526,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher):
|
||||||
await homekit.async_start()
|
await homekit.async_start()
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_add_acc.assert_called_with(state)
|
mock_add_acc.assert_any_call(state)
|
||||||
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
|
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
|
||||||
hk_driver_add_acc.assert_called_with(homekit.bridge)
|
hk_driver_add_acc.assert_called_with(homekit.bridge)
|
||||||
assert hk_driver_start.called
|
assert hk_driver_start.called
|
||||||
|
@ -568,6 +585,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p
|
||||||
entity_filter,
|
entity_filter,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -575,6 +593,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p
|
||||||
homekit.bridge = Mock()
|
homekit.bridge = Mock()
|
||||||
homekit.bridge.accessories = []
|
homekit.bridge.accessories = []
|
||||||
homekit.driver = hk_driver
|
homekit.driver = hk_driver
|
||||||
|
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||||
|
|
||||||
hass.states.async_set("light.demo", "on")
|
hass.states.async_set("light.demo", "on")
|
||||||
hass.states.async_set("light.broken", "on")
|
hass.states.async_set("light.broken", "on")
|
||||||
|
@ -613,6 +632,7 @@ async def test_homekit_stop(hass):
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -653,6 +673,7 @@ async def test_homekit_reset_accessories(hass):
|
||||||
{},
|
{},
|
||||||
{entity_id: {}},
|
{entity_id: {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -687,7 +708,7 @@ async def test_homekit_reset_accessories(hass):
|
||||||
homekit.status = STATUS_READY
|
homekit.status = STATUS_READY
|
||||||
|
|
||||||
|
|
||||||
async def test_homekit_too_many_accessories(hass, hk_driver):
|
async def test_homekit_too_many_accessories(hass, hk_driver, caplog):
|
||||||
"""Test adding too many accessories to HomeKit."""
|
"""Test adding too many accessories to HomeKit."""
|
||||||
entry = await async_init_integration(hass)
|
entry = await async_init_integration(hass)
|
||||||
|
|
||||||
|
@ -701,24 +722,32 @@ async def test_homekit_too_many_accessories(hass, hk_driver):
|
||||||
entity_filter,
|
entity_filter,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
homekit.bridge = Mock()
|
|
||||||
# The bridge itself counts as an accessory
|
def _mock_bridge(*_):
|
||||||
homekit.bridge.accessories = range(MAX_DEVICES)
|
mock_bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||||
|
# The bridge itself counts as an accessory
|
||||||
|
mock_bridge.accessories = range(MAX_DEVICES)
|
||||||
|
return mock_bridge
|
||||||
|
|
||||||
homekit.driver = hk_driver
|
homekit.driver = hk_driver
|
||||||
|
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||||
|
|
||||||
hass.states.async_set("light.demo", "on")
|
hass.states.async_set("light.demo", "on")
|
||||||
|
hass.states.async_set("light.demo2", "on")
|
||||||
|
hass.states.async_set("light.demo3", "on")
|
||||||
|
|
||||||
with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch(
|
with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch(
|
||||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||||
), patch("homeassistant.components.homekit._LOGGER.warning") as mock_warn, patch(
|
), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch(
|
||||||
f"{PATH_HOMEKIT}.show_setup_message"
|
f"{PATH_HOMEKIT}.accessories.HomeBridge", _mock_bridge
|
||||||
):
|
):
|
||||||
await homekit.async_start()
|
await homekit.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_warn.called is True
|
assert "would exceeded" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_homekit_finds_linked_batteries(
|
async def test_homekit_finds_linked_batteries(
|
||||||
|
@ -735,6 +764,7 @@ async def test_homekit_finds_linked_batteries(
|
||||||
{},
|
{},
|
||||||
{"light.demo": {}},
|
{"light.demo": {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -821,6 +851,7 @@ async def test_homekit_async_get_integration_fails(
|
||||||
{},
|
{},
|
||||||
{"light.demo": {}},
|
{"light.demo": {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -927,6 +958,7 @@ async def test_setup_imported(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -981,6 +1013,7 @@ async def test_yaml_updates_update_config_entry_for_name(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1053,6 +1086,7 @@ async def test_homekit_ignored_missing_devices(
|
||||||
{},
|
{},
|
||||||
{"light.demo": {}},
|
{"light.demo": {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1094,6 +1128,7 @@ async def test_homekit_ignored_missing_devices(
|
||||||
device_reg.async_remove_device(device_entry.id)
|
device_reg.async_remove_device(device_entry.id)
|
||||||
|
|
||||||
hass.states.async_set(light.entity_id, STATE_ON)
|
hass.states.async_set(light.entity_id, STATE_ON)
|
||||||
|
hass.states.async_set("light.two", STATE_ON)
|
||||||
|
|
||||||
def _mock_get_accessory(*args, **kwargs):
|
def _mock_get_accessory(*args, **kwargs):
|
||||||
return [None, "acc", None]
|
return [None, "acc", None]
|
||||||
|
@ -1106,7 +1141,7 @@ async def test_homekit_ignored_missing_devices(
|
||||||
await homekit.async_start()
|
await homekit.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_get_acc.assert_called_with(
|
mock_get_acc.assert_any_call(
|
||||||
hass,
|
hass,
|
||||||
hk_driver,
|
hk_driver,
|
||||||
ANY,
|
ANY,
|
||||||
|
@ -1133,6 +1168,7 @@ async def test_homekit_finds_linked_motion_sensors(
|
||||||
{},
|
{},
|
||||||
{"camera.camera_demo": {}},
|
{"camera.camera_demo": {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1208,6 +1244,7 @@ async def test_homekit_finds_linked_humidity_sensors(
|
||||||
{},
|
{},
|
||||||
{"humidifier.humidifier": {}},
|
{"humidifier.humidifier": {}},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
advertise_ip=None,
|
advertise_ip=None,
|
||||||
entry_id=entry.entry_id,
|
entry_id=entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1297,6 +1334,7 @@ async def test_reload(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1333,6 +1371,7 @@ async def test_reload(hass):
|
||||||
ANY,
|
ANY,
|
||||||
{},
|
{},
|
||||||
DEFAULT_SAFE_MODE,
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_BRIDGE,
|
||||||
None,
|
None,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
)
|
)
|
||||||
|
@ -1341,3 +1380,45 @@ async def test_reload(hass):
|
||||||
|
|
||||||
def _get_fixtures_base_path():
|
def _get_fixtures_base_path():
|
||||||
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_start_in_accessory_mode(
|
||||||
|
hass, hk_driver, device_reg, debounce_patcher
|
||||||
|
):
|
||||||
|
"""Test HomeKit start method in accessory mode."""
|
||||||
|
entry = await async_init_integration(hass)
|
||||||
|
|
||||||
|
pin = b"123-45-678"
|
||||||
|
homekit = HomeKit(
|
||||||
|
hass,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
DEFAULT_SAFE_MODE,
|
||||||
|
HOMEKIT_MODE_ACCESSORY,
|
||||||
|
advertise_ip=None,
|
||||||
|
entry_id=entry.entry_id,
|
||||||
|
)
|
||||||
|
homekit.bridge = Mock()
|
||||||
|
homekit.bridge.accessories = []
|
||||||
|
homekit.driver = hk_driver
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
homekit._filter = Mock(return_value=True)
|
||||||
|
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||||
|
|
||||||
|
hass.states.async_set("light.demo", "on")
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
||||||
|
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||||
|
), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch(
|
||||||
|
"pyhap.accessory_driver.AccessoryDriver.start_service"
|
||||||
|
) as hk_driver_start:
|
||||||
|
await homekit.async_start()
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_add_acc.assert_not_called()
|
||||||
|
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
|
||||||
|
assert hk_driver_start.called
|
||||||
|
assert homekit.status == STATUS_RUNNING
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue