From 533684545222420f7330d80dcfb836988e4806a6 Mon Sep 17 00:00:00 2001 From: enegaard Date: Wed, 10 Nov 2021 08:14:16 +0100 Subject: [PATCH 1/8] Fix rpi_camera setup hanging on initialization (#59316) --- homeassistant/components/rpi_camera/camera.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py index 980586d4def..89fe7fe55d8 100644 --- a/homeassistant/components/rpi_camera/camera.py +++ b/homeassistant/components/rpi_camera/camera.py @@ -119,10 +119,13 @@ class RaspberryCamera(Camera): cmd_args.append("-a") cmd_args.append(str(device_info[CONF_OVERLAY_TIMESTAMP])) - with subprocess.Popen( + # The raspistill process started below must run "forever" in + # the background until killed when Home Assistant is stopped. + # Therefore it must not be wrapped with "with", since that + # waits for the subprocess to exit before continuing. + subprocess.Popen( # pylint: disable=consider-using-with cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT - ): - pass + ) def camera_image( self, width: int | None = None, height: int | None = None From aacc0edde756e67feb614f6ee3cd551ce78a2d08 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Thu, 11 Nov 2021 02:49:07 -0500 Subject: [PATCH 2/8] Fix state of sense net_production sensor (#59391) --- homeassistant/components/sense/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index a8695e32b57..08677cda8d0 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -1,7 +1,7 @@ """Support for monitoring a Sense energy sensor.""" from homeassistant.components.sensor import ( STATE_CLASS_MEASUREMENT, - STATE_CLASS_TOTAL_INCREASING, + STATE_CLASS_TOTAL, SensorEntity, ) from homeassistant.const import ( @@ -251,7 +251,7 @@ class SenseTrendsSensor(CoordinatorEntity, SensorEntity): """Implementation of a Sense energy sensor.""" _attr_device_class = DEVICE_CLASS_ENERGY - _attr_state_class = STATE_CLASS_TOTAL_INCREASING + _attr_state_class = STATE_CLASS_TOTAL _attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR _attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} _attr_icon = ICON From 66c5d75fbb9bf101817dc3554db8c32be91d15f0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 9 Nov 2021 23:40:21 +0100 Subject: [PATCH 3/8] Update frontend to 20211109.0 (#59451) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 43e0e7e86bc..d2db6138171 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20211108.0" + "home-assistant-frontend==20211109.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a290aaadd39..1f29737cdf3 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==3.4.8 emoji==1.5.0 hass-nabucasa==0.50.0 -home-assistant-frontend==20211108.0 +home-assistant-frontend==20211109.0 httpx==0.19.0 ifaddr==0.1.7 jinja2==3.0.2 diff --git a/requirements_all.txt b/requirements_all.txt index 3cb1b05e7e2..9aef5a12b5b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -813,7 +813,7 @@ hole==0.5.1 holidays==0.11.3.1 # homeassistant.components.frontend -home-assistant-frontend==20211108.0 +home-assistant-frontend==20211109.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 401e872c6d3..26d9eba1d2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -500,7 +500,7 @@ hole==0.5.1 holidays==0.11.3.1 # homeassistant.components.frontend -home-assistant-frontend==20211108.0 +home-assistant-frontend==20211109.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From c2f227bf1687ceb55c38dc06b08c899e4974fda6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Nov 2021 00:31:08 -0600 Subject: [PATCH 4/8] Fix zeroconf with sonos v1 firmware (#59460) --- homeassistant/components/sonos/config_flow.py | 2 +- homeassistant/components/sonos/helpers.py | 7 ++- tests/components/sonos/test_config_flow.py | 50 +++++++++++++++++++ tests/components/sonos/test_helpers.py | 6 +++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sonos/config_flow.py b/homeassistant/components/sonos/config_flow.py index 745d3db3890..98e1194ebd0 100644 --- a/homeassistant/components/sonos/config_flow.py +++ b/homeassistant/components/sonos/config_flow.py @@ -30,7 +30,7 @@ class SonosDiscoveryFlowHandler(DiscoveryFlowHandler): ) -> FlowResult: """Handle a flow initialized by zeroconf.""" hostname = discovery_info["hostname"] - if hostname is None or not hostname.startswith("Sonos-"): + if hostname is None or not hostname.lower().startswith("sonos"): return self.async_abort(reason="not_sonos_device") await self.async_set_unique_id(self._domain, raise_on_progress=False) host = discovery_info[CONF_HOST] diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index 01a75eb7747..490bcdefba5 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -44,5 +44,10 @@ def soco_error(errorcodes: list[str] | None = None) -> Callable: def hostname_to_uid(hostname: str) -> str: """Convert a Sonos hostname to a uid.""" - baseuid = hostname.split("-")[1].replace(".local.", "") + if hostname.startswith("Sonos-"): + baseuid = hostname.split("-")[1].replace(".local.", "") + elif hostname.startswith("sonos"): + baseuid = hostname[5:].replace(".local.", "") + else: + raise ValueError(f"{hostname} is not a sonos device.") return f"{UID_PREFIX}{baseuid}{UID_POSTFIX}" diff --git a/tests/components/sonos/test_config_flow.py b/tests/components/sonos/test_config_flow.py index 39f3966e2ce..7d6fd02f51d 100644 --- a/tests/components/sonos/test_config_flow.py +++ b/tests/components/sonos/test_config_flow.py @@ -75,6 +75,56 @@ async def test_zeroconf_form(hass: core.HomeAssistant): assert len(mock_manager.mock_calls) == 2 +async def test_zeroconf_sonos_v1(hass: core.HomeAssistant): + """Test we pass sonos devices to the discovery manager with v1 firmware devices.""" + + mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock() + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={ + "host": "192.168.1.107", + "port": 1443, + "hostname": "sonos5CAAFDE47AC8.local.", + "type": "_sonos._tcp.local.", + "name": "Sonos-5CAAFDE47AC8._sonos._tcp.local.", + "properties": { + "_raw": { + "info": b"/api/v1/players/RINCON_5CAAFDE47AC801400/info", + "vers": b"1", + "protovers": b"1.18.9", + }, + "info": "/api/v1/players/RINCON_5CAAFDE47AC801400/info", + "vers": "1", + "protovers": "1.18.9", + }, + }, + ) + assert result["type"] == "form" + assert result["errors"] is None + + with patch( + "homeassistant.components.sonos.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.sonos.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Sonos" + assert result2["data"] == {} + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_manager.mock_calls) == 2 + + async def test_zeroconf_form_not_sonos(hass: core.HomeAssistant): """Test we abort on non-sonos devices.""" mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock() diff --git a/tests/components/sonos/test_helpers.py b/tests/components/sonos/test_helpers.py index a52337f9455..be32d3a190b 100644 --- a/tests/components/sonos/test_helpers.py +++ b/tests/components/sonos/test_helpers.py @@ -1,9 +1,15 @@ """Test the sonos config flow.""" from __future__ import annotations +import pytest + from homeassistant.components.sonos.helpers import hostname_to_uid async def test_uid_to_hostname(): """Test we can convert a hostname to a uid.""" assert hostname_to_uid("Sonos-347E5C0CF1E3.local.") == "RINCON_347E5C0CF1E301400" + assert hostname_to_uid("sonos5CAAFDE47AC8.local.") == "RINCON_5CAAFDE47AC801400" + + with pytest.raises(ValueError): + assert hostname_to_uid("notsonos5CAAFDE47AC8.local.") From 9cb4a5ca39f1f052650f0ec5dc0bae51414083df Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 11 Nov 2021 06:31:56 +0000 Subject: [PATCH 5/8] Ignore None state in state_change_event (#59485) --- homeassistant/components/integration/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index a2fd77fb4e1..6c0e09af0c5 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -156,6 +156,7 @@ class IntegrationSensor(RestoreEntity, SensorEntity): if ( old_state is None + or new_state is None or old_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE) or new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE) ): From 04e1dc3a106f4d87c85b0b52e9e7e44abc5e3401 Mon Sep 17 00:00:00 2001 From: Sergiy Maysak Date: Thu, 11 Nov 2021 03:21:29 +0200 Subject: [PATCH 6/8] Fix wirelesstag switch arm/disarm (#59515) --- homeassistant/components/wirelesstag/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 24afb6b0465..b7de56d8eff 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -73,16 +73,14 @@ class WirelessTagPlatform: def arm(self, switch): """Arm entity sensor monitoring.""" - func_name = f"arm_{switch.sensor_type}" - arm_func = getattr(self.api, func_name) - if arm_func is not None: + func_name = f"arm_{switch.entity_description.key}" + if (arm_func := getattr(self.api, func_name)) is not None: arm_func(switch.tag_id, switch.tag_manager_mac) def disarm(self, switch): """Disarm entity sensor monitoring.""" - func_name = f"disarm_{switch.sensor_type}" - disarm_func = getattr(self.api, func_name) - if disarm_func is not None: + func_name = f"disarm_{switch.entity_description.key}" + if (disarm_func := getattr(self.api, func_name)) is not None: disarm_func(switch.tag_id, switch.tag_manager_mac) def start_monitoring(self): From 0f0ca36aa8b05dbfb409a6570230a0cf01fb3f4f Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 12 Nov 2021 00:59:13 +0800 Subject: [PATCH 7/8] Remove incomplete segment on stream restart (#59532) --- homeassistant/components/stream/hls.py | 10 ++++++++ homeassistant/components/stream/worker.py | 5 ++++ tests/components/stream/conftest.py | 10 ++++---- tests/components/stream/test_hls.py | 30 +++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index e1a0e6a8f67..44b19d2cc85 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -77,6 +77,16 @@ class HlsStreamOutput(StreamOutput): or self.stream_settings.min_segment_duration ) + def discontinuity(self) -> None: + """Remove incomplete segment from deque.""" + self._hass.loop.call_soon_threadsafe(self._async_discontinuity) + + @callback + def _async_discontinuity(self) -> None: + """Remove incomplete segment from deque in event loop.""" + if self._segments and not self._segments[-1].complete: + self._segments.pop() + class HlsMasterPlaylistView(StreamView): """Stream view used only for Chromecast compatibility.""" diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index e4be3168393..a0ab48290f5 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -18,6 +18,7 @@ from .const import ( ATTR_SETTINGS, AUDIO_CODECS, DOMAIN, + HLS_PROVIDER, MAX_MISSING_DTS, MAX_TIMESTAMP_GAP, PACKETS_TO_WAIT_FOR_AUDIO, @@ -25,6 +26,7 @@ from .const import ( SOURCE_TIMEOUT, ) from .core import Part, Segment, StreamOutput, StreamSettings +from .hls import HlsStreamOutput _LOGGER = logging.getLogger(__name__) @@ -279,6 +281,9 @@ class SegmentBuffer: # the discontinuity sequence number. self._stream_id += 1 self._start_time = datetime.datetime.utcnow() + # Call discontinuity to remove incomplete segment from the HLS output + if hls_output := self._outputs_callback().get(HLS_PROVIDER): + cast(HlsStreamOutput, hls_output).discontinuity() def close(self) -> None: """Close stream buffer.""" diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index f5f66258f70..b5d68ffaba5 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -22,8 +22,8 @@ from aiohttp import web import async_timeout import pytest -from homeassistant.components.stream import Stream from homeassistant.components.stream.core import Segment, StreamOutput +from homeassistant.components.stream.worker import SegmentBuffer TEST_TIMEOUT = 7.0 # Lower than 9s home assistant timeout @@ -34,7 +34,7 @@ class WorkerSync: def __init__(self): """Initialize WorkerSync.""" self._event = None - self._original = Stream._worker_finished + self._original = SegmentBuffer.discontinuity def pause(self): """Pause the worker before it finalizes the stream.""" @@ -45,7 +45,7 @@ class WorkerSync: logging.debug("waking blocked worker") self._event.set() - def blocking_finish(self, stream: Stream): + def blocking_discontinuity(self, stream: SegmentBuffer): """Intercept call to pause stream worker.""" # Worker is ending the stream, which clears all output buffers. # Block the worker thread until the test has a chance to verify @@ -63,8 +63,8 @@ def stream_worker_sync(hass): """Patch StreamOutput to allow test to synchronize worker stream end.""" sync = WorkerSync() with patch( - "homeassistant.components.stream.Stream._worker_finished", - side_effect=sync.blocking_finish, + "homeassistant.components.stream.worker.SegmentBuffer.discontinuity", + side_effect=sync.blocking_discontinuity, autospec=True, ): yield sync diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 07c8cc88a65..3bff13a936b 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -448,3 +448,33 @@ async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sy stream_worker_sync.resume() stream.stop() + + +async def test_remove_incomplete_segment_on_exit(hass, stream_worker_sync): + """Test that the incomplete segment gets removed when the worker thread quits.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE, {}) + stream_worker_sync.pause() + stream.start() + hls = stream.add_provider(HLS_PROVIDER) + + segment = Segment(sequence=0, stream_id=0, duration=SEGMENT_DURATION) + hls.put(segment) + segment = Segment(sequence=1, stream_id=0, duration=SEGMENT_DURATION) + hls.put(segment) + segment = Segment(sequence=2, stream_id=0, duration=0) + hls.put(segment) + await hass.async_block_till_done() + + segments = hls._segments + assert len(segments) == 3 + assert not segments[-1].complete + stream_worker_sync.resume() + stream._thread_quit.set() + stream._thread.join() + stream._thread = None + await hass.async_block_till_done() + assert segments[-1].complete + assert len(segments) == 2 + stream.stop() From 4cae92a5337d3ad4b4e78e5b2a3152ec34aad132 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 11 Nov 2021 10:33:14 -0800 Subject: [PATCH 8/8] Bumped version to 2021.11.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index dcd68543068..7b9d8e4165d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 11 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)