mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
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
@ -37,7 +37,7 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
from homeassistant.util.package import is_docker_env
|
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
|
from .server import Server
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -72,9 +72,15 @@ _SUPPORTED_STREAMS = frozenset(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
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,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,7 +110,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# HA will manage the binary
|
# 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()
|
await server.start()
|
||||||
|
|
||||||
async def on_stop(event: Event) -> None:
|
async def on_stop(event: Event) -> None:
|
||||||
|
@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
DOMAIN = "go2rtc"
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_TERMINATE_TIMEOUT = 5
|
_TERMINATE_TIMEOUT = 5
|
||||||
_SETUP_TIMEOUT = 30
|
_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
|
# Default configuration for HA
|
||||||
# - Api is listening only on localhost
|
# - Api is listening only on localhost
|
||||||
# - Disable rtsp listener
|
# - Disable rtsp listener
|
||||||
# - Clear default ice servers
|
# - Clear default ice servers
|
||||||
_GO2RTC_CONFIG = """
|
_GO2RTC_CONFIG_FORMAT = r"""
|
||||||
api:
|
api:
|
||||||
listen: "127.0.0.1:1984"
|
listen: "{api_ip}:1984"
|
||||||
|
|
||||||
rtsp:
|
rtsp:
|
||||||
# ffmpeg needs rtsp for opus audio transcoding
|
# 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."""
|
"""Create temporary config file."""
|
||||||
# Set delete=False to prevent the file from being deleted when the file is closed
|
# 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
|
# Linux is clearing tmp folder on reboot, so no need to delete it manually
|
||||||
with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file:
|
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
|
return file.name
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
"""Go2rtc 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."""
|
"""Initialize the server."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._binary = binary
|
self._binary = binary
|
||||||
self._process: asyncio.subprocess.Process | None = None
|
self._process: asyncio.subprocess.Process | None = None
|
||||||
self._startup_complete = asyncio.Event()
|
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:
|
async def start(self) -> None:
|
||||||
"""Start the server."""
|
"""Start the server."""
|
||||||
_LOGGER.debug("Starting go2rtc 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()
|
self._startup_complete.clear()
|
||||||
|
|
||||||
@ -84,9 +92,7 @@ class Server:
|
|||||||
async for line in process.stdout:
|
async for line in process.stdout:
|
||||||
msg = line[:-1].decode().strip()
|
msg = line[:-1].decode().strip()
|
||||||
_LOGGER.debug(msg)
|
_LOGGER.debug(msg)
|
||||||
if not self._startup_complete.is_set() and msg.endswith(
|
if not self._startup_complete.is_set() and _SUCCESSFUL_BOOT_MESSAGE in msg:
|
||||||
_SUCCESSFUL_BOOT_MESSAGE
|
|
||||||
):
|
|
||||||
self._startup_complete.set()
|
self._startup_complete.set()
|
||||||
|
|
||||||
async def stop(self) -> None:
|
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.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN
|
||||||
from homeassistant.components.go2rtc import WebRTCProvider
|
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.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
|
||||||
from homeassistant.const import CONF_URL
|
from homeassistant.const import CONF_URL
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -265,7 +269,15 @@ async def _test_setup_and_signaling(
|
|||||||
"mock_is_docker_env",
|
"mock_is_docker_env",
|
||||||
"mock_go2rtc_entry",
|
"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])
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
async def test_setup_go_binary(
|
async def test_setup_go_binary(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -277,12 +289,13 @@ async def test_setup_go_binary(
|
|||||||
init_test_integration: MockCamera,
|
init_test_integration: MockCamera,
|
||||||
has_go2rtc_entry: bool,
|
has_go2rtc_entry: bool,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
|
ui_enabled: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the go2rtc config entry with binary."""
|
"""Test the go2rtc config entry with binary."""
|
||||||
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
||||||
|
|
||||||
def after_setup() -> None:
|
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()
|
server_start.assert_called_once()
|
||||||
|
|
||||||
await _test_setup_and_signaling(
|
await _test_setup_and_signaling(
|
||||||
@ -468,7 +481,9 @@ ERR_CONNECT = "Could not connect to go2rtc instance"
|
|||||||
ERR_CONNECT_RETRY = (
|
ERR_CONNECT_RETRY = (
|
||||||
"Could not connect to go2rtc instance on http://localhost:1984/; Retrying"
|
"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"
|
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, False, ERR_URL_REQUIRED),
|
||||||
({DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
({DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
||||||
({DOMAIN: {CONF_URL: "invalid"}}, None, True, ERR_INVALID_URL),
|
({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])
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
|
@ -16,9 +16,15 @@ TEST_BINARY = "/bin/go2rtc"
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@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."""
|
"""Fixture to initialize the Server."""
|
||||||
return Server(hass, binary=TEST_BINARY)
|
return Server(hass, binary=TEST_BINARY, enable_ui=enable_ui)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -32,12 +38,20 @@ def mock_tempfile() -> Generator[Mock]:
|
|||||||
yield file
|
yield file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("enable_ui", "api_ip"),
|
||||||
|
[
|
||||||
|
(True, ""),
|
||||||
|
(False, "127.0.0.1"),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_server_run_success(
|
async def test_server_run_success(
|
||||||
mock_create_subprocess: AsyncMock,
|
mock_create_subprocess: AsyncMock,
|
||||||
server_stdout: list[str],
|
server_stdout: list[str],
|
||||||
server: Server,
|
server: Server,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
mock_tempfile: Mock,
|
mock_tempfile: Mock,
|
||||||
|
api_ip: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that the server runs successfully."""
|
"""Test that the server runs successfully."""
|
||||||
await server.start()
|
await server.start()
|
||||||
@ -53,9 +67,10 @@ async def test_server_run_success(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Verify that the config file was written
|
# Verify that the config file was written
|
||||||
mock_tempfile.write.assert_called_once_with(b"""
|
mock_tempfile.write.assert_called_once_with(
|
||||||
|
f"""
|
||||||
api:
|
api:
|
||||||
listen: "127.0.0.1:1984"
|
listen: "{api_ip}:1984"
|
||||||
|
|
||||||
rtsp:
|
rtsp:
|
||||||
# ffmpeg needs rtsp for opus audio transcoding
|
# ffmpeg needs rtsp for opus audio transcoding
|
||||||
@ -63,7 +78,8 @@ rtsp:
|
|||||||
|
|
||||||
webrtc:
|
webrtc:
|
||||||
ice_servers: []
|
ice_servers: []
|
||||||
""")
|
""".encode()
|
||||||
|
)
|
||||||
|
|
||||||
# Check that server read the log lines
|
# Check that server read the log lines
|
||||||
for entry in server_stdout:
|
for entry in server_stdout:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user