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:
parent
b09e54c961
commit
9c8a15cb64
5 changed files with 77 additions and 25 deletions
|
@ -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:
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue