mirror of
https://github.com/home-assistant/core.git
synced 2025-12-19 18:38:58 +00:00
Bump go2rtc to 1.9.12 and go2rtc-client to 0.3.0 (#156948)
This commit is contained in:
2
Dockerfile
generated
2
Dockerfile
generated
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
2
requirements_all.txt
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -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
|
||||
|
||||
2
script/hassfest/docker/Dockerfile
generated
2
script/hassfest/docker/Dockerfile
generated
@@ -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 \
|
||||
|
||||
@@ -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(
|
||||
|
||||
23
tests/components/go2rtc/snapshots/test_server.ambr
Normal file
23
tests/components/go2rtc/snapshots/test_server.ambr
Normal 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({
|
||||
}),
|
||||
),
|
||||
])
|
||||
# ---
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user