Add go2rtc debug_ui yaml key to enable go2rtc ui (#129587)

* Add go2rtc debug_ui yaml key to enable go2rtc ui

* Apply suggestions from code review

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Order imports

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Robert Resch 2024-10-31 20:56:53 +01:00 committed by GitHub
parent b09e54c961
commit 9c8a15cb64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 25 deletions

View file

@ -37,7 +37,7 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from homeassistant.util.package import is_docker_env
from .const import DOMAIN
from .const import CONF_DEBUG_UI, DEBUG_UI_URL_MESSAGE, DOMAIN
from .server import Server
_LOGGER = logging.getLogger(__name__)
@ -72,9 +72,15 @@ _SUPPORTED_STREAMS = frozenset(
)
)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Optional(CONF_URL): cv.url})},
{
DOMAIN: vol.Schema(
{
vol.Exclusive(CONF_URL, DOMAIN, DEBUG_UI_URL_MESSAGE): cv.url,
vol.Exclusive(CONF_DEBUG_UI, DOMAIN, DEBUG_UI_URL_MESSAGE): cv.boolean,
}
)
},
extra=vol.ALLOW_EXTRA,
)
@ -104,7 +110,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return False
# HA will manage the binary
server = Server(hass, binary)
server = Server(
hass, binary, enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False)
)
await server.start()
async def on_stop(event: Event) -> None:

View file

@ -2,4 +2,5 @@
DOMAIN = "go2rtc"
CONF_BINARY = "binary"
CONF_DEBUG_UI = "debug_ui"
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."

View file

@ -10,15 +10,15 @@ from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
_TERMINATE_TIMEOUT = 5
_SETUP_TIMEOUT = 30
_SUCCESSFUL_BOOT_MESSAGE = "INF [api] listen addr=127.0.0.1:1984"
_SUCCESSFUL_BOOT_MESSAGE = "INF [api] listen addr="
_LOCALHOST_IP = "127.0.0.1"
# Default configuration for HA
# - Api is listening only on localhost
# - Disable rtsp listener
# - Clear default ice servers
_GO2RTC_CONFIG = """
_GO2RTC_CONFIG_FORMAT = r"""
api:
listen: "127.0.0.1:1984"
listen: "{api_ip}:1984"
rtsp:
# ffmpeg needs rtsp for opus audio transcoding
@ -29,29 +29,37 @@ webrtc:
"""
def _create_temp_file() -> str:
def _create_temp_file(api_ip: str) -> str:
"""Create temporary config file."""
# Set delete=False to prevent the file from being deleted when the file is closed
# Linux is clearing tmp folder on reboot, so no need to delete it manually
with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file:
file.write(_GO2RTC_CONFIG.encode())
file.write(_GO2RTC_CONFIG_FORMAT.format(api_ip=api_ip).encode())
return file.name
class Server:
"""Go2rtc server."""
def __init__(self, hass: HomeAssistant, binary: str) -> None:
def __init__(
self, hass: HomeAssistant, binary: str, *, enable_ui: bool = False
) -> None:
"""Initialize the server."""
self._hass = hass
self._binary = binary
self._process: asyncio.subprocess.Process | None = None
self._startup_complete = asyncio.Event()
self._api_ip = _LOCALHOST_IP
if enable_ui:
# Listen on all interfaces for allowing access from all ips
self._api_ip = ""
async def start(self) -> None:
"""Start the server."""
_LOGGER.debug("Starting go2rtc server")
config_file = await self._hass.async_add_executor_job(_create_temp_file)
config_file = await self._hass.async_add_executor_job(
_create_temp_file, self._api_ip
)
self._startup_complete.clear()
@ -84,9 +92,7 @@ class Server:
async for line in process.stdout:
msg = line[:-1].decode().strip()
_LOGGER.debug(msg)
if not self._startup_complete.is_set() and msg.endswith(
_SUCCESSFUL_BOOT_MESSAGE
):
if not self._startup_complete.is_set() and _SUCCESSFUL_BOOT_MESSAGE in msg:
self._startup_complete.set()
async def stop(self) -> None:

View file

@ -31,7 +31,11 @@ from homeassistant.components.camera import (
)
from homeassistant.components.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN
from homeassistant.components.go2rtc import WebRTCProvider
from homeassistant.components.go2rtc.const import DOMAIN
from homeassistant.components.go2rtc.const import (
CONF_DEBUG_UI,
DEBUG_UI_URL_MESSAGE,
DOMAIN,
)
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant
@ -265,7 +269,15 @@ async def _test_setup_and_signaling(
"mock_is_docker_env",
"mock_go2rtc_entry",
)
@pytest.mark.parametrize("config", [{DOMAIN: {}}, {DEFAULT_CONFIG_DOMAIN: {}}])
@pytest.mark.parametrize(
("config", "ui_enabled"),
[
({DOMAIN: {}}, False),
({DOMAIN: {CONF_DEBUG_UI: True}}, True),
({DEFAULT_CONFIG_DOMAIN: {}}, False),
({DEFAULT_CONFIG_DOMAIN: {}, DOMAIN: {CONF_DEBUG_UI: True}}, True),
],
)
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
async def test_setup_go_binary(
hass: HomeAssistant,
@ -277,12 +289,13 @@ async def test_setup_go_binary(
init_test_integration: MockCamera,
has_go2rtc_entry: bool,
config: ConfigType,
ui_enabled: bool,
) -> None:
"""Test the go2rtc config entry with binary."""
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
def after_setup() -> None:
server.assert_called_once_with(hass, "/usr/bin/go2rtc")
server.assert_called_once_with(hass, "/usr/bin/go2rtc", enable_ui=ui_enabled)
server_start.assert_called_once()
await _test_setup_and_signaling(
@ -468,7 +481,9 @@ ERR_CONNECT = "Could not connect to go2rtc instance"
ERR_CONNECT_RETRY = (
"Could not connect to go2rtc instance on http://localhost:1984/; Retrying"
)
ERR_INVALID_URL = "Invalid config for 'go2rtc': invalid url"
_INVALID_CONFIG = "Invalid config for 'go2rtc': "
ERR_INVALID_URL = _INVALID_CONFIG + "invalid url"
ERR_EXCLUSIVE = _INVALID_CONFIG + DEBUG_UI_URL_MESSAGE
ERR_URL_REQUIRED = "Go2rtc URL required in non-docker installs"
@ -501,6 +516,12 @@ async def test_non_user_setup_with_error(
({DOMAIN: {}}, None, False, ERR_URL_REQUIRED),
({DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
({DOMAIN: {CONF_URL: "invalid"}}, None, True, ERR_INVALID_URL),
(
{DOMAIN: {CONF_URL: "http://localhost:1984", CONF_DEBUG_UI: True}},
None,
True,
ERR_EXCLUSIVE,
),
],
)
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])

View file

@ -16,9 +16,15 @@ TEST_BINARY = "/bin/go2rtc"
@pytest.fixture
def server(hass: HomeAssistant) -> Server:
def enable_ui() -> bool:
"""Fixture to enable the UI."""
return False
@pytest.fixture
def server(hass: HomeAssistant, enable_ui: bool) -> Server:
"""Fixture to initialize the Server."""
return Server(hass, binary=TEST_BINARY)
return Server(hass, binary=TEST_BINARY, enable_ui=enable_ui)
@pytest.fixture
@ -32,12 +38,20 @@ def mock_tempfile() -> Generator[Mock]:
yield file
@pytest.mark.parametrize(
("enable_ui", "api_ip"),
[
(True, ""),
(False, "127.0.0.1"),
],
)
async def test_server_run_success(
mock_create_subprocess: AsyncMock,
server_stdout: list[str],
server: Server,
caplog: pytest.LogCaptureFixture,
mock_tempfile: Mock,
api_ip: str,
) -> None:
"""Test that the server runs successfully."""
await server.start()
@ -53,9 +67,10 @@ async def test_server_run_success(
)
# Verify that the config file was written
mock_tempfile.write.assert_called_once_with(b"""
mock_tempfile.write.assert_called_once_with(
f"""
api:
listen: "127.0.0.1:1984"
listen: "{api_ip}:1984"
rtsp:
# ffmpeg needs rtsp for opus audio transcoding
@ -63,7 +78,8 @@ rtsp:
webrtc:
ice_servers: []
""")
""".encode()
)
# Check that server read the log lines
for entry in server_stdout: