Add audio support option to HomeKit camera UI config flow (#56107)
This commit is contained in:
parent
eff59e8b00
commit
371aa03bca
4 changed files with 155 additions and 4 deletions
|
@ -41,6 +41,7 @@ from .const import (
|
||||||
CONF_EXCLUDE_ACCESSORY_MODE,
|
CONF_EXCLUDE_ACCESSORY_MODE,
|
||||||
CONF_FILTER,
|
CONF_FILTER,
|
||||||
CONF_HOMEKIT_MODE,
|
CONF_HOMEKIT_MODE,
|
||||||
|
CONF_SUPPORT_AUDIO,
|
||||||
CONF_VIDEO_CODEC,
|
CONF_VIDEO_CODEC,
|
||||||
DEFAULT_AUTO_START,
|
DEFAULT_AUTO_START,
|
||||||
DEFAULT_CONFIG_FLOW_PORT,
|
DEFAULT_CONFIG_FLOW_PORT,
|
||||||
|
@ -54,6 +55,7 @@ from .const import (
|
||||||
)
|
)
|
||||||
from .util import async_find_next_available_port, state_needs_accessory_mode
|
from .util import async_find_next_available_port, state_needs_accessory_mode
|
||||||
|
|
||||||
|
CONF_CAMERA_AUDIO = "camera_audio"
|
||||||
CONF_CAMERA_COPY = "camera_copy"
|
CONF_CAMERA_COPY = "camera_copy"
|
||||||
CONF_INCLUDE_EXCLUDE_MODE = "include_exclude_mode"
|
CONF_INCLUDE_EXCLUDE_MODE = "include_exclude_mode"
|
||||||
|
|
||||||
|
@ -362,14 +364,24 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
and CONF_VIDEO_CODEC in entity_config[entity_id]
|
and CONF_VIDEO_CODEC in entity_config[entity_id]
|
||||||
):
|
):
|
||||||
del entity_config[entity_id][CONF_VIDEO_CODEC]
|
del entity_config[entity_id][CONF_VIDEO_CODEC]
|
||||||
|
if entity_id in user_input[CONF_CAMERA_AUDIO]:
|
||||||
|
entity_config.setdefault(entity_id, {})[CONF_SUPPORT_AUDIO] = True
|
||||||
|
elif (
|
||||||
|
entity_id in entity_config
|
||||||
|
and CONF_SUPPORT_AUDIO in entity_config[entity_id]
|
||||||
|
):
|
||||||
|
del entity_config[entity_id][CONF_SUPPORT_AUDIO]
|
||||||
return await self.async_step_advanced()
|
return await self.async_step_advanced()
|
||||||
|
|
||||||
|
cameras_with_audio = []
|
||||||
cameras_with_copy = []
|
cameras_with_copy = []
|
||||||
entity_config = self.hk_options.setdefault(CONF_ENTITY_CONFIG, {})
|
entity_config = self.hk_options.setdefault(CONF_ENTITY_CONFIG, {})
|
||||||
for entity in self.included_cameras:
|
for entity in self.included_cameras:
|
||||||
hk_entity_config = entity_config.get(entity, {})
|
hk_entity_config = entity_config.get(entity, {})
|
||||||
if hk_entity_config.get(CONF_VIDEO_CODEC) == VIDEO_CODEC_COPY:
|
if hk_entity_config.get(CONF_VIDEO_CODEC) == VIDEO_CODEC_COPY:
|
||||||
cameras_with_copy.append(entity)
|
cameras_with_copy.append(entity)
|
||||||
|
if hk_entity_config.get(CONF_SUPPORT_AUDIO):
|
||||||
|
cameras_with_audio.append(entity)
|
||||||
|
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -377,6 +389,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
CONF_CAMERA_COPY,
|
CONF_CAMERA_COPY,
|
||||||
default=cameras_with_copy,
|
default=cameras_with_copy,
|
||||||
): cv.multi_select(self.included_cameras),
|
): cv.multi_select(self.included_cameras),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_CAMERA_AUDIO,
|
||||||
|
default=cameras_with_audio,
|
||||||
|
): cv.multi_select(self.included_cameras),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return self.async_show_form(step_id="cameras", data_schema=data_schema)
|
return self.async_show_form(step_id="cameras", data_schema=data_schema)
|
||||||
|
|
|
@ -23,10 +23,11 @@
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"data": {
|
"data": {
|
||||||
"camera_copy": "Cameras that support native H.264 streams"
|
"camera_copy": "Cameras that support native H.264 streams",
|
||||||
|
"camera_audio": "Cameras that support audio"
|
||||||
},
|
},
|
||||||
"description": "Check all cameras that support native H.264 streams. If the camera does not output a H.264 stream, the system will transcode the video to H.264 for HomeKit. Transcoding requires a performant CPU and is unlikely to work on single board computers.",
|
"description": "Check all cameras that support native H.264 streams. If the camera does not output a H.264 stream, the system will transcode the video to H.264 for HomeKit. Transcoding requires a performant CPU and is unlikely to work on single board computers.",
|
||||||
"title": "Select camera video codec."
|
"title": "Camera Configuration"
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"data": {
|
"data": {
|
||||||
|
|
|
@ -29,10 +29,11 @@
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"data": {
|
"data": {
|
||||||
|
"camera_audio": "Cameras that support audio",
|
||||||
"camera_copy": "Cameras that support native H.264 streams"
|
"camera_copy": "Cameras that support native H.264 streams"
|
||||||
},
|
},
|
||||||
"description": "Check all cameras that support native H.264 streams. If the camera does not output a H.264 stream, the system will transcode the video to H.264 for HomeKit. Transcoding requires a performant CPU and is unlikely to work on single board computers.",
|
"description": "Check all cameras that support native H.264 streams. If the camera does not output a H.264 stream, the system will transcode the video to H.264 for HomeKit. Transcoding requires a performant CPU and is unlikely to work on single board computers.",
|
||||||
"title": "Select camera video codec."
|
"title": "Camera Configuration"
|
||||||
},
|
},
|
||||||
"include_exclude": {
|
"include_exclude": {
|
||||||
"data": {
|
"data": {
|
||||||
|
|
|
@ -753,7 +753,10 @@ async def test_options_flow_include_mode_with_cameras(hass, mock_get_source_ip):
|
||||||
)
|
)
|
||||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result2["step_id"] == "cameras"
|
assert result2["step_id"] == "cameras"
|
||||||
assert result2["data_schema"]({}) == {"camera_copy": ["camera.native_h264"]}
|
assert result2["data_schema"]({}) == {
|
||||||
|
"camera_copy": ["camera.native_h264"],
|
||||||
|
"camera_audio": [],
|
||||||
|
}
|
||||||
schema = result2["data_schema"].schema
|
schema = result2["data_schema"].schema
|
||||||
assert _get_schema_default(schema, "camera_copy") == ["camera.native_h264"]
|
assert _get_schema_default(schema, "camera_copy") == ["camera.native_h264"]
|
||||||
|
|
||||||
|
@ -776,6 +779,136 @@ async def test_options_flow_include_mode_with_cameras(hass, mock_get_source_ip):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow_with_camera_audio(hass, mock_get_source_ip):
|
||||||
|
"""Test config flow options with cameras that support audio."""
|
||||||
|
|
||||||
|
config_entry = _mock_config_entry_with_options_populated()
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
hass.states.async_set("climate.old", "off")
|
||||||
|
hass.states.async_set("camera.audio", "off")
|
||||||
|
hass.states.async_set("camera.no_audio", "off")
|
||||||
|
hass.states.async_set("camera.excluded", "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": ["fan", "vacuum", "climate", "camera"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
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": ["camera.audio", "camera.no_audio"],
|
||||||
|
"include_exclude_mode": "include",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["step_id"] == "cameras"
|
||||||
|
|
||||||
|
result3 = await hass.config_entries.options.async_configure(
|
||||||
|
result2["flow_id"],
|
||||||
|
user_input={"camera_audio": ["camera.audio"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
"auto_start": True,
|
||||||
|
"mode": "bridge",
|
||||||
|
"filter": {
|
||||||
|
"exclude_domains": [],
|
||||||
|
"exclude_entities": [],
|
||||||
|
"include_domains": ["fan", "vacuum", "climate"],
|
||||||
|
"include_entities": ["camera.audio", "camera.no_audio"],
|
||||||
|
},
|
||||||
|
"entity_config": {"camera.audio": {"support_audio": True}},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Now run though again and verify we can turn off audio
|
||||||
|
|
||||||
|
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"
|
||||||
|
assert result["data_schema"]({}) == {
|
||||||
|
"domains": ["fan", "vacuum", "climate", "camera"],
|
||||||
|
"mode": "bridge",
|
||||||
|
}
|
||||||
|
schema = result["data_schema"].schema
|
||||||
|
assert _get_schema_default(schema, "domains") == [
|
||||||
|
"fan",
|
||||||
|
"vacuum",
|
||||||
|
"climate",
|
||||||
|
"camera",
|
||||||
|
]
|
||||||
|
assert _get_schema_default(schema, "mode") == "bridge"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={"domains": ["fan", "vacuum", "climate", "camera"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "include_exclude"
|
||||||
|
assert result["data_schema"]({}) == {
|
||||||
|
"entities": ["camera.audio", "camera.no_audio"],
|
||||||
|
"include_exclude_mode": "include",
|
||||||
|
}
|
||||||
|
schema = result["data_schema"].schema
|
||||||
|
assert _get_schema_default(schema, "entities") == [
|
||||||
|
"camera.audio",
|
||||||
|
"camera.no_audio",
|
||||||
|
]
|
||||||
|
assert _get_schema_default(schema, "include_exclude_mode") == "include"
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
"entities": ["climate.old", "camera.excluded"],
|
||||||
|
"include_exclude_mode": "exclude",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["step_id"] == "cameras"
|
||||||
|
assert result2["data_schema"]({}) == {
|
||||||
|
"camera_copy": [],
|
||||||
|
"camera_audio": ["camera.audio"],
|
||||||
|
}
|
||||||
|
schema = result2["data_schema"].schema
|
||||||
|
assert _get_schema_default(schema, "camera_audio") == ["camera.audio"]
|
||||||
|
|
||||||
|
result3 = await hass.config_entries.options.async_configure(
|
||||||
|
result2["flow_id"],
|
||||||
|
user_input={"camera_audio": []},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
"auto_start": True,
|
||||||
|
"entity_config": {"camera.audio": {}},
|
||||||
|
"filter": {
|
||||||
|
"exclude_domains": [],
|
||||||
|
"exclude_entities": ["climate.old", "camera.excluded"],
|
||||||
|
"include_domains": ["fan", "vacuum", "climate", "camera"],
|
||||||
|
"include_entities": [],
|
||||||
|
},
|
||||||
|
"mode": "bridge",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_options_flow_blocked_when_from_yaml(hass, mock_get_source_ip):
|
async def test_options_flow_blocked_when_from_yaml(hass, mock_get_source_ip):
|
||||||
"""Test config flow options."""
|
"""Test config flow options."""
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue