Skip to content

Commit 847afab

Browse files
authored
2024.11.2 (#130713)
2 parents ab05562 + ac270e1 commit 847afab

File tree

118 files changed

+1030
-741
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+1030
-741
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ repos:
9090
pass_filenames: false
9191
language: script
9292
types: [text]
93-
files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|pyproject\.toml)$
93+
files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|pyproject\.toml|homeassistant/components/go2rtc/const\.py)$
9494
- id: hassfest-mypy-config
9595
name: hassfest-mypy-config
9696
entry: script/run-in-env.sh python3 -m script.hassfest -p mypy_config

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ RUN \
5555
"armv7") go2rtc_suffix='arm' ;; \
5656
*) go2rtc_suffix=${BUILD_ARCH} ;; \
5757
esac \
58-
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.6/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
58+
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.7/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
5959
&& chmod +x /bin/go2rtc \
6060
# Verify go2rtc can be executed
6161
&& go2rtc --version

homeassistant/components/alarm_control_panel/__init__.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from datetime import timedelta
77
from functools import partial
88
import logging
9-
from typing import Any, Final, final
9+
from typing import TYPE_CHECKING, Any, Final, final
1010

1111
from propcache import cached_property
1212
import voluptuous as vol
@@ -221,9 +221,15 @@ def _report_deprecated_alarm_state_handling(self) -> None:
221221
@property
222222
def state(self) -> str | None:
223223
"""Return the current state."""
224-
if (alarm_state := self.alarm_state) is None:
225-
return None
226-
return alarm_state
224+
if (alarm_state := self.alarm_state) is not None:
225+
return alarm_state
226+
if self._attr_state is not None:
227+
# Backwards compatibility for integrations that set state directly
228+
# Should be removed in 2025.11
229+
if TYPE_CHECKING:
230+
assert isinstance(self._attr_state, str)
231+
return self._attr_state
232+
return None
227233

228234
@cached_property
229235
def alarm_state(self) -> AlarmControlPanelState | None:

homeassistant/components/cambridge_audio/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
"integration_type": "device",
88
"iot_class": "local_push",
99
"loggers": ["aiostreammagic"],
10-
"requirements": ["aiostreammagic==2.8.4"],
10+
"requirements": ["aiostreammagic==2.8.5"],
1111
"zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."]
1212
}

homeassistant/components/cambridge_audio/select.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ def _audio_output_value_fn(client: StreamMagicClient) -> str | None:
5151
CambridgeAudioSelectEntityDescription(
5252
key="display_brightness",
5353
translation_key="display_brightness",
54-
options=[x.value for x in DisplayBrightness],
54+
options=[
55+
DisplayBrightness.BRIGHT.value,
56+
DisplayBrightness.DIM.value,
57+
DisplayBrightness.OFF.value,
58+
],
5559
entity_category=EntityCategory.CONFIG,
60+
load_fn=lambda client: client.display.brightness != DisplayBrightness.NONE,
5661
value_fn=lambda client: client.display.brightness,
5762
set_value_fn=lambda client, value: client.set_display_brightness(
5863
DisplayBrightness(value)

homeassistant/components/co2signal/config_flow.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ async def _validate_and_create(
168168
)
169169

170170
return self.async_create_entry(
171-
title=get_extra_name(data) or "CO2 Signal",
171+
title=get_extra_name(data) or "Electricity Maps",
172172
data=data,
173173
)
174174

homeassistant/components/conversation/default_agent.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ async def async_process(self, user_input: ConversationInput) -> ConversationResu
294294
self.hass, language, DOMAIN, [DOMAIN]
295295
)
296296
response_text = translations.get(
297-
f"component.{DOMAIN}.agent.done", "Done"
297+
f"component.{DOMAIN}.conversation.agent.done", "Done"
298298
)
299299

300300
response.async_set_speech(response_text)

homeassistant/components/emulated_kasa/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"iot_class": "local_push",
77
"loggers": ["sense_energy"],
88
"quality_scale": "internal",
9-
"requirements": ["sense-energy==0.13.3"]
9+
"requirements": ["sense-energy==0.13.4"]
1010
}

homeassistant/components/file/strings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
"data_description": {
2020
"file_path": "The local file path to retrieve the sensor value from",
21-
"value_template": "A template to render the the sensors value based on the file content",
21+
"value_template": "A template to render the sensors value based on the file content",
2222
"unit_of_measurement": "Unit of measurement for the sensor"
2323
}
2424
},

homeassistant/components/generic/config_flow.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ async def async_test_stream(
282282
return {CONF_STREAM_SOURCE: "timeout"}
283283
await stream.stop()
284284
except StreamWorkerError as err:
285-
return {CONF_STREAM_SOURCE: str(err)}
285+
return {CONF_STREAM_SOURCE: "unknown_with_details", "error_details": str(err)}
286286
except PermissionError:
287287
return {CONF_STREAM_SOURCE: "stream_not_permitted"}
288288
except OSError as err:
@@ -339,6 +339,7 @@ async def async_step_user(
339339
) -> ConfigFlowResult:
340340
"""Handle the start of the config flow."""
341341
errors = {}
342+
description_placeholders = {}
342343
hass = self.hass
343344
if user_input:
344345
# Secondary validation because serialised vol can't seem to handle this complexity:
@@ -372,13 +373,16 @@ async def async_step_user(
372373
# temporary preview for user to check the image
373374
self.preview_cam = user_input
374375
return await self.async_step_user_confirm_still()
376+
if "error_details" in errors:
377+
description_placeholders["error"] = errors.pop("error_details")
375378
elif self.user_input:
376379
user_input = self.user_input
377380
else:
378381
user_input = DEFAULT_DATA.copy()
379382
return self.async_show_form(
380383
step_id="user",
381384
data_schema=build_schema(user_input),
385+
description_placeholders=description_placeholders,
382386
errors=errors,
383387
)
384388

homeassistant/components/generic/strings.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"config": {
44
"error": {
55
"unknown": "[%key:common::config_flow::error::unknown%]",
6+
"unknown_with_details": "An unknown error occurred: {error}",
67
"already_exists": "A camera with these URL settings already exists.",
78
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
89
"unable_still_load_auth": "Unable to load valid image from still image URL: The camera may require a user name and password, or they are not correct.",

homeassistant/components/generic_thermostat/strings.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"config": {
44
"step": {
55
"user": {
6-
"title": "Add generic thermostat helper",
6+
"title": "Add generic thermostat",
77
"description": "Create a climate entity that controls the temperature via a switch and sensor.",
88
"data": {
99
"ac_mode": "Cooling mode",
@@ -17,8 +17,8 @@
1717
"data_description": {
1818
"ac_mode": "Set the actuator specified to be treated as a cooling device instead of a heating device.",
1919
"heater": "Switch entity used to cool or heat depending on A/C mode.",
20-
"target_sensor": "Temperature sensor that reflect the current temperature.",
21-
"min_cycle_duration": "Set a minimum amount of time that the switch specified must be in its current state prior to being switched either off or on. This option will be ignored if the keep alive option is set.",
20+
"target_sensor": "Temperature sensor that reflects the current temperature.",
21+
"min_cycle_duration": "Set a minimum amount of time that the switch specified must be in its current state prior to being switched either off or on.",
2222
"cold_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched on. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will start when the sensor equals or goes below 24.5.",
2323
"hot_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched off. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will stop when the sensor equals or goes above 25.5."
2424
}

homeassistant/components/go2rtc/__init__.py

+43-57
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
"""The go2rtc component."""
22

3-
from __future__ import annotations
4-
5-
from dataclasses import dataclass
63
import logging
74
import shutil
85

96
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
7+
from awesomeversion import AwesomeVersion
108
from go2rtc_client import Go2RtcRestClient
119
from go2rtc_client.exceptions import Go2RtcClientError, Go2RtcVersionError
1210
from go2rtc_client.ws import (
@@ -35,7 +33,11 @@
3533
from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP
3634
from homeassistant.core import Event, HomeAssistant, callback
3735
from homeassistant.exceptions import ConfigEntryNotReady
38-
from homeassistant.helpers import config_validation as cv, discovery_flow
36+
from homeassistant.helpers import (
37+
config_validation as cv,
38+
discovery_flow,
39+
issue_registry as ir,
40+
)
3941
from homeassistant.helpers.aiohttp_client import async_get_clientsession
4042
from homeassistant.helpers.typing import ConfigType
4143
from homeassistant.util.hass_dict import HassKey
@@ -45,8 +47,8 @@
4547
CONF_DEBUG_UI,
4648
DEBUG_UI_URL_MESSAGE,
4749
DOMAIN,
48-
HA_MANAGED_RTSP_PORT,
4950
HA_MANAGED_URL,
51+
RECOMMENDED_VERSION,
5052
)
5153
from .server import Server
5254

@@ -94,22 +96,13 @@
9496
extra=vol.ALLOW_EXTRA,
9597
)
9698

97-
_DATA_GO2RTC: HassKey[Go2RtcData] = HassKey(DOMAIN)
99+
_DATA_GO2RTC: HassKey[str] = HassKey(DOMAIN)
98100
_RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError)
99101

100102

101-
@dataclass(frozen=True)
102-
class Go2RtcData:
103-
"""Data for go2rtc."""
104-
105-
url: str
106-
managed: bool
107-
108-
109103
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
110104
"""Set up WebRTC."""
111105
url: str | None = None
112-
managed = False
113106
if DOMAIN not in config and DEFAULT_CONFIG_DOMAIN not in config:
114107
await _remove_go2rtc_entries(hass)
115108
return True
@@ -144,9 +137,8 @@ async def on_stop(event: Event) -> None:
144137
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, on_stop)
145138

146139
url = HA_MANAGED_URL
147-
managed = True
148140

149-
hass.data[_DATA_GO2RTC] = Go2RtcData(url, managed)
141+
hass.data[_DATA_GO2RTC] = url
150142
discovery_flow.async_create_flow(
151143
hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={}
152144
)
@@ -161,32 +153,42 @@ async def _remove_go2rtc_entries(hass: HomeAssistant) -> None:
161153

162154
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
163155
"""Set up go2rtc from a config entry."""
164-
data = hass.data[_DATA_GO2RTC]
156+
url = hass.data[_DATA_GO2RTC]
165157

166158
# Validate the server URL
167159
try:
168-
client = Go2RtcRestClient(async_get_clientsession(hass), data.url)
169-
await client.validate_server_version()
160+
client = Go2RtcRestClient(async_get_clientsession(hass), url)
161+
version = await client.validate_server_version()
162+
if version < AwesomeVersion(RECOMMENDED_VERSION):
163+
ir.async_create_issue(
164+
hass,
165+
DOMAIN,
166+
"recommended_version",
167+
is_fixable=False,
168+
is_persistent=False,
169+
severity=ir.IssueSeverity.WARNING,
170+
translation_key="recommended_version",
171+
translation_placeholders={
172+
"recommended_version": RECOMMENDED_VERSION,
173+
"current_version": str(version),
174+
},
175+
)
170176
except Go2RtcClientError as err:
171177
if isinstance(err.__cause__, _RETRYABLE_ERRORS):
172178
raise ConfigEntryNotReady(
173-
f"Could not connect to go2rtc instance on {data.url}"
179+
f"Could not connect to go2rtc instance on {url}"
174180
) from err
175-
_LOGGER.warning(
176-
"Could not connect to go2rtc instance on %s (%s)", data.url, err
177-
)
181+
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
178182
return False
179183
except Go2RtcVersionError as err:
180184
raise ConfigEntryNotReady(
181185
f"The go2rtc server version is not supported, {err}"
182186
) from err
183187
except Exception as err: # noqa: BLE001
184-
_LOGGER.warning(
185-
"Could not connect to go2rtc instance on %s (%s)", data.url, err
186-
)
188+
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
187189
return False
188190

189-
provider = WebRTCProvider(hass, data)
191+
provider = WebRTCProvider(hass, url)
190192
async_register_webrtc_provider(hass, provider)
191193
return True
192194

@@ -204,12 +206,12 @@ async def _get_binary(hass: HomeAssistant) -> str | None:
204206
class WebRTCProvider(CameraWebRTCProvider):
205207
"""WebRTC provider."""
206208

207-
def __init__(self, hass: HomeAssistant, data: Go2RtcData) -> None:
209+
def __init__(self, hass: HomeAssistant, url: str) -> None:
208210
"""Initialize the WebRTC provider."""
209211
self._hass = hass
210-
self._data = data
212+
self._url = url
211213
self._session = async_get_clientsession(hass)
212-
self._rest_client = Go2RtcRestClient(self._session, data.url)
214+
self._rest_client = Go2RtcRestClient(self._session, url)
213215
self._sessions: dict[str, Go2RtcWsClient] = {}
214216

215217
@property
@@ -231,7 +233,7 @@ async def async_handle_async_webrtc_offer(
231233
) -> None:
232234
"""Handle the WebRTC offer and return the answer via the provided callback."""
233235
self._sessions[session_id] = ws_client = Go2RtcWsClient(
234-
self._session, self._data.url, source=camera.entity_id
236+
self._session, self._url, source=camera.entity_id
235237
)
236238

237239
if not (stream_source := await camera.stream_source()):
@@ -242,34 +244,18 @@ async def async_handle_async_webrtc_offer(
242244

243245
streams = await self._rest_client.streams.list()
244246

245-
if self._data.managed:
246-
# HA manages the go2rtc instance
247-
stream_org_name = camera.entity_id + "_orginal"
248-
stream_redirect_sources = [
249-
f"rtsp://127.0.0.1:{HA_MANAGED_RTSP_PORT}/{stream_org_name}",
250-
f"ffmpeg:{stream_org_name}#audio=opus",
251-
]
252-
253-
if (
254-
(stream_org := streams.get(stream_org_name)) is None
255-
or not any(
256-
stream_source == producer.url for producer in stream_org.producers
257-
)
258-
or (stream_redirect := streams.get(camera.entity_id)) is None
259-
or stream_redirect_sources != [p.url for p in stream_redirect.producers]
260-
):
261-
await self._rest_client.streams.add(stream_org_name, stream_source)
262-
await self._rest_client.streams.add(
263-
camera.entity_id, stream_redirect_sources
264-
)
265-
266-
# go2rtc instance is managed outside HA
267-
elif (stream_org := streams.get(camera.entity_id)) is None or not any(
268-
stream_source == producer.url for producer in stream_org.producers
247+
if (stream := streams.get(camera.entity_id)) is None or not any(
248+
stream_source == producer.url for producer in stream.producers
269249
):
270250
await self._rest_client.streams.add(
271251
camera.entity_id,
272-
[stream_source, f"ffmpeg:{camera.entity_id}#audio=opus"],
252+
[
253+
stream_source,
254+
# We are setting any ffmpeg rtsp related logs to debug
255+
# Connection problems to the camera will be logged by the first stream
256+
# Therefore setting it to debug will not hide any important logs
257+
f"ffmpeg:{camera.entity_id}#audio=opus#query=log_level=debug",
258+
],
273259
)
274260

275261
@callback

homeassistant/components/go2rtc/const.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
77
HA_MANAGED_API_PORT = 11984
88
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
9-
HA_MANAGED_RTSP_PORT = 18554
9+
RECOMMENDED_VERSION = "1.9.7"

homeassistant/components/go2rtc/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
"documentation": "https://www.home-assistant.io/integrations/go2rtc",
88
"integration_type": "system",
99
"iot_class": "local_polling",
10-
"requirements": ["go2rtc-client==0.1.0"],
10+
"requirements": ["go2rtc-client==0.1.1"],
1111
"single_config_entry": true
1212
}

0 commit comments

Comments
 (0)