1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Bump go2rtc to 1.9.12 and go2rtc-client to 0.3.0 (#156948)

This commit is contained in:
Robert Resch
2025-11-20 19:41:40 +01:00
committed by GitHub
parent 0327b0e1ec
commit 126fd217e7
13 changed files with 134 additions and 68 deletions

2
Dockerfile generated
View File

@@ -25,7 +25,7 @@ RUN \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.11/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.12/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version

View File

@@ -60,35 +60,6 @@ from .server import Server
_LOGGER = logging.getLogger(__name__)
_FFMPEG = "ffmpeg"
_SUPPORTED_STREAMS = frozenset(
(
"bubble",
"dvrip",
"expr",
_FFMPEG,
"gopro",
"homekit",
"http",
"https",
"httpx",
"isapi",
"ivideon",
"kasa",
"nest",
"onvif",
"roborock",
"rtmp",
"rtmps",
"rtmpx",
"rtsp",
"rtsps",
"rtspx",
"tapo",
"tcp",
"webrtc",
"webtorrent",
)
)
CONFIG_SCHEMA = vol.Schema(
{
@@ -197,6 +168,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: Go2RtcConfigEntry) -> bo
return False
provider = entry.runtime_data = WebRTCProvider(hass, url, session, client)
await provider.initialize()
entry.async_on_unload(async_register_webrtc_provider(hass, provider))
return True
@@ -228,16 +200,21 @@ class WebRTCProvider(CameraWebRTCProvider):
self._session = session
self._rest_client = rest_client
self._sessions: dict[str, Go2RtcWsClient] = {}
self._supported_schemes: set[str] = set()
@property
def domain(self) -> str:
"""Return the integration domain of the provider."""
return DOMAIN
async def initialize(self) -> None:
"""Initialize the provider."""
self._supported_schemes = await self._rest_client.schemes.list()
@callback
def async_is_supported(self, stream_source: str) -> bool:
"""Return if this provider is supports the Camera as source."""
return stream_source.partition(":")[0] in _SUPPORTED_STREAMS
return stream_source.partition(":")[0] in self._supported_schemes
async def async_handle_async_webrtc_offer(
self,

View File

@@ -6,4 +6,4 @@ CONF_DEBUG_UI = "debug_ui"
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
HA_MANAGED_API_PORT = 11984
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
RECOMMENDED_VERSION = "1.9.11"
RECOMMENDED_VERSION = "1.9.12"

View File

@@ -8,6 +8,6 @@
"integration_type": "system",
"iot_class": "local_polling",
"quality_scale": "internal",
"requirements": ["go2rtc-client==0.2.1"],
"requirements": ["go2rtc-client==0.3.0"],
"single_config_entry": true
}

View File

@@ -29,8 +29,18 @@ _RESPAWN_COOLDOWN = 1
_GO2RTC_CONFIG_FORMAT = r"""# This file is managed by Home Assistant
# Do not edit it manually
app:
modules: {app_modules}
api:
listen: "{api_ip}:{api_port}"
allow_paths: {api_allow_paths}
# ffmpeg needs the exec module
# Restrict execution to only ffmpeg binary
exec:
allow_paths:
- ffmpeg
rtsp:
listen: "127.0.0.1:18554"
@@ -40,6 +50,43 @@ webrtc:
ice_servers: []
"""
_APP_MODULES = (
"api",
"exec", # Execution module for ffmpeg
"ffmpeg",
"http",
"mjpeg",
"onvif",
"rtmp",
"rtsp",
"srtp",
"webrtc",
"ws",
)
_API_ALLOW_PATHS = (
"/", # UI static page and version control
"/api", # Main API path
"/api/frame.jpeg", # Snapshot functionality
"/api/schemes", # Supported stream schemes
"/api/streams", # Stream management
"/api/webrtc", # Webrtc functionality
"/api/ws", # Websocket functionality (e.g. webrtc candidates)
)
# Additional modules when UI is enabled
_UI_APP_MODULES = (
*_APP_MODULES,
"debug",
)
# Additional api paths when UI is enabled
_UI_API_ALLOW_PATHS = (
*_API_ALLOW_PATHS,
"/api/config", # UI config view
"/api/log", # UI log view
"/api/streams.dot", # UI network view
)
_LOG_LEVEL_MAP = {
"TRC": logging.DEBUG,
"DBG": logging.DEBUG,
@@ -61,14 +108,34 @@ class Go2RTCWatchdogError(HomeAssistantError):
"""Raised on watchdog error."""
def _create_temp_file(api_ip: str) -> str:
def _format_list_for_yaml(items: tuple[str, ...]) -> str:
"""Format a list of strings for yaml config."""
if not items:
return "[]"
formatted_items = ",".join(f'"{item}"' for item in items)
return f"[{formatted_items}]"
def _create_temp_file(enable_ui: bool) -> str:
"""Create temporary config file."""
app_modules: tuple[str, ...] = _APP_MODULES
api_paths: tuple[str, ...] = _API_ALLOW_PATHS
api_ip = _LOCALHOST_IP
if enable_ui:
app_modules = _UI_APP_MODULES
api_paths = _UI_API_ALLOW_PATHS
# Listen on all interfaces for allowing access from all ips
api_ip = ""
# 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_FORMAT.format(
api_ip=api_ip, api_port=HA_MANAGED_API_PORT
api_ip=api_ip,
api_port=HA_MANAGED_API_PORT,
app_modules=_format_list_for_yaml(app_modules),
api_allow_paths=_format_list_for_yaml(api_paths),
).encode()
)
return file.name
@@ -86,10 +153,7 @@ class Server:
self._log_buffer: deque[str] = deque(maxlen=_LOG_BUFFER_SIZE)
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 = ""
self._enable_ui = enable_ui
self._watchdog_task: asyncio.Task | None = None
self._watchdog_tasks: list[asyncio.Task] = []
@@ -104,7 +168,7 @@ class Server:
"""Start the server."""
_LOGGER.debug("Starting go2rtc server")
config_file = await self._hass.async_add_executor_job(
_create_temp_file, self._api_ip
_create_temp_file, self._enable_ui
)
self._startup_complete.clear()

View File

@@ -33,7 +33,7 @@ cryptography==46.0.2
dbus-fast==3.0.0
file-read-backwards==2.0.0
fnv-hash-fast==1.6.0
go2rtc-client==0.2.1
go2rtc-client==0.3.0
ha-ffmpeg==3.2.2
habluetooth==5.7.0
hass-nabucasa==1.5.1

2
requirements_all.txt generated
View File

@@ -1049,7 +1049,7 @@ gitterpy==0.1.7
glances-api==0.8.0
# homeassistant.components.go2rtc
go2rtc-client==0.2.1
go2rtc-client==0.3.0
# homeassistant.components.goalzero
goalzero==0.2.2

View File

@@ -10,7 +10,7 @@
astroid==4.0.1
coverage==7.10.6
freezegun==1.5.2
go2rtc-client==0.2.1
go2rtc-client==0.3.0
# librt is an internal mypy dependency
librt==0.2.1
license-expression==30.4.3

View File

@@ -916,7 +916,7 @@ gios==6.1.2
glances-api==0.8.0
# homeassistant.components.go2rtc
go2rtc-client==0.2.1
go2rtc-client==0.3.0
# homeassistant.components.goalzero
goalzero==0.2.2

View File

@@ -29,7 +29,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.9.6,source=/uv,target=/bin/uv \
tqdm==4.67.1 \
ruff==0.13.0 \
PyTurboJPEG==1.8.0 \
go2rtc-client==0.2.1 \
go2rtc-client==0.3.0 \
ha-ffmpeg==3.2.2 \
hassil==3.4.0 \
home-assistant-intents==2025.11.7 \

View File

@@ -4,7 +4,7 @@ from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch
from awesomeversion import AwesomeVersion
from go2rtc_client.rest import _StreamClient, _WebRTCClient
from go2rtc_client.rest import _SchemesClient, _StreamClient, _WebRTCClient
import pytest
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
@@ -39,6 +39,23 @@ def rest_client() -> Generator[AsyncMock]:
patch("homeassistant.components.go2rtc.server.Go2RtcRestClient", mock_client),
):
client = mock_client.return_value
client.schemes = schemes = Mock(spec_set=_SchemesClient)
schemes.list.return_value = {
"onvif",
"exec",
"http",
"rtmps",
"https",
"rtmpx",
"httpx",
"rtsps",
"webrtc",
"rtmp",
"tcp",
"rtsp",
"rtspx",
"ffmpeg",
}
client.streams = streams = Mock(spec_set=_StreamClient)
streams.list.return_value = {}
client.validate_server_version = AsyncMock(

View File

@@ -0,0 +1,23 @@
# serializer version: 1
# name: test_server_run_success[False]
_CallList([
_Call(
tuple(
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws"]\n\napi:\n listen: "127.0.0.1:11984"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
),
dict({
}),
),
])
# ---
# name: test_server_run_success[True]
_CallList([
_Call(
tuple(
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws","debug"]\n\napi:\n listen: ":11984"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws","/api/config","/api/log","/api/streams.dot"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
),
dict({
}),
),
])
# ---

View File

@@ -7,6 +7,7 @@ import subprocess
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.go2rtc.server import Server
from homeassistant.core import HomeAssistant
@@ -75,20 +76,17 @@ def assert_server_output_not_logged(
@pytest.mark.parametrize(
("enable_ui", "api_ip"),
[
(True, ""),
(False, "127.0.0.1"),
],
"enable_ui",
[True, False],
)
@pytest.mark.usefixtures("rest_client")
async def test_server_run_success(
mock_create_subprocess: AsyncMock,
rest_client: AsyncMock,
server_stdout: list[str],
server: Server,
caplog: pytest.LogCaptureFixture,
mock_tempfile: Mock,
api_ip: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test that the server runs successfully."""
await server.start()
@@ -104,21 +102,8 @@ async def test_server_run_success(
)
# Verify that the config file was written
mock_tempfile.write.assert_called_once_with(
f"""# This file is managed by Home Assistant
# Do not edit it manually
api:
listen: "{api_ip}:11984"
rtsp:
listen: "127.0.0.1:18554"
webrtc:
listen: ":18555/tcp"
ice_servers: []
""".encode()
)
calls = mock_tempfile.write.call_args_list
assert calls == snapshot()
# Verify go2rtc binary stdout was logged with debug level
assert_server_output_logged(server_stdout, caplog, logging.DEBUG)