Skip to content
5 changes: 5 additions & 0 deletions homeassistant/components/generic/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
CONF_RTSP_TRANSPORT,
CONF_STILL_IMAGE_URL,
CONF_STREAM_SOURCE,
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
DEFAULT_NAME,
FFMPEG_OPTION_MAP,
GET_IMAGE_TIMEOUT,
Expand Down Expand Up @@ -160,6 +161,10 @@ def __init__(self, hass, device_info, identifier, title):
CONF_RTSP_TRANSPORT
]
self._auth = generate_auth(device_info)
if device_info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
self.stream_options[
FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS]
] = "1"

self._last_url = None
self._last_image = None
Expand Down
20 changes: 19 additions & 1 deletion homeassistant/components/generic/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
CONF_RTSP_TRANSPORT,
CONF_STILL_IMAGE_URL,
CONF_STREAM_SOURCE,
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
DEFAULT_NAME,
DOMAIN,
FFMPEG_OPTION_MAP,
Expand All @@ -64,6 +65,7 @@
def build_schema(
user_input: dict[str, Any] | MappingProxyType[str, Any],
is_options_flow: bool = False,
show_advanced_options=False,
):
"""Create schema for camera config setup."""
spec = {
Expand Down Expand Up @@ -106,6 +108,13 @@ def build_schema(
default=user_input.get(CONF_LIMIT_REFETCH_TO_URL_CHANGE, False),
)
] = bool
if show_advanced_options:
spec[
vol.Required(
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
default=user_input.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False),
)
] = bool
return vol.Schema(spec)


Expand Down Expand Up @@ -199,6 +208,8 @@ async def async_test_stream(hass, info) -> dict[str, str]:
}
if rtsp_transport := info.get(CONF_RTSP_TRANSPORT):
stream_options[FFMPEG_OPTION_MAP[CONF_RTSP_TRANSPORT]] = rtsp_transport
if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
stream_options[FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS]] = "1"
_LOGGER.debug("Attempting to open stream %s", stream_source)
container = await hass.async_add_executor_job(
partial(
Expand Down Expand Up @@ -356,13 +367,20 @@ async def async_step_init(
],
CONF_FRAMERATE: user_input[CONF_FRAMERATE],
CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
CONF_USE_WALLCLOCK_AS_TIMESTAMPS: user_input.get(
CONF_USE_WALLCLOCK_AS_TIMESTAMPS
),
}
return self.async_create_entry(
title=title,
data=data,
)
return self.async_show_form(
step_id="init",
data_schema=build_schema(user_input or self.config_entry.options, True),
data_schema=build_schema(
user_input or self.config_entry.options,
True,
self.show_advanced_options,
),
errors=errors,
)
6 changes: 5 additions & 1 deletion homeassistant/components/generic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
CONF_STREAM_SOURCE = "stream_source"
CONF_FRAMERATE = "framerate"
CONF_RTSP_TRANSPORT = "rtsp_transport"
FFMPEG_OPTION_MAP = {CONF_RTSP_TRANSPORT: "rtsp_transport"}
CONF_USE_WALLCLOCK_AS_TIMESTAMPS = "use_wallclock_as_timestamps"
FFMPEG_OPTION_MAP = {
CONF_RTSP_TRANSPORT: "rtsp_transport",
CONF_USE_WALLCLOCK_AS_TIMESTAMPS: "use_wallclock_as_timestamps",
}
RTSP_TRANSPORTS = {
"tcp": "TCP",
"udp": "UDP",
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/generic/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@
"authentication": "[%key:component::generic::config::step::user::data::authentication%]",
"limit_refetch_to_url_change": "[%key:component::generic::config::step::user::data::limit_refetch_to_url_change%]",
"password": "[%key:common::config_flow::data::password%]",
"use_wallclock_as_timestamps": "Use wallclock as timestamps",
"username": "[%key:common::config_flow::data::username%]",
"framerate": "[%key:component::generic::config::step::user::data::framerate%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"use_wallclock_as_timestamps": "This option may correct segmenting or crashing issues arising from buggy timestamp implementations on some cameras"
}
},
"content_type": {
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/generic/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"user": {
"data": {
"authentication": "Authentication",
"content_type": "Content Type",
"framerate": "Frame Rate (Hz)",
"limit_refetch_to_url_change": "Limit refetch to url change",
"password": "Password",
Expand Down Expand Up @@ -72,15 +71,18 @@
"init": {
"data": {
"authentication": "Authentication",
"content_type": "Content Type",
"framerate": "Frame Rate (Hz)",
"limit_refetch_to_url_change": "Limit refetch to url change",
"password": "Password",
"rtsp_transport": "RTSP transport protocol",
"still_image_url": "Still Image URL (e.g. http://...)",
"stream_source": "Stream Source URL (e.g. rtsp://...)",
"use_wallclock_as_timestamps": "Use wallclock as timestamps",
"username": "Username",
"verify_ssl": "Verify SSL certificate"
},
"data_description": {
"use_wallclock_as_timestamps": "This option may correct segmenting or crashing issues arising from buggy timestamp implementations on some cameras"
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions tests/components/generic/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CONF_RTSP_TRANSPORT,
CONF_STILL_IMAGE_URL,
CONF_STREAM_SOURCE,
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
DOMAIN,
)
from homeassistant.const import (
Expand Down Expand Up @@ -653,3 +654,32 @@ async def test_migrate_existing_ids(hass) -> None:

entity_entry = registry.async_get(entity_id)
assert entity_entry.unique_id == new_unique_id


@respx.mock
async def test_use_wallclock_as_timestamps_option(hass, fakeimg_png, mock_av_open):
"""Test the use_wallclock_as_timestamps option flow."""

mock_entry = MockConfigEntry(
title="Test Camera",
domain=DOMAIN,
data={},
options=TESTDATA,
)

with mock_av_open:
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()

result = await hass.config_entries.options.async_init(
mock_entry.entry_id, context={"show_advanced_options": True}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"

result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA},
)
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY