From 80cefc74ecd6f8d059eb51b3080d55efb59f3cf9 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 25 May 2026 17:17:53 +0200 Subject: [PATCH] Update rf-protocols to 4.0.0 (#172131) --- .../components/novy_cooker_hood/commands.py | 7 ---- .../novy_cooker_hood/config_flow.py | 7 ++-- .../components/novy_cooker_hood/fan.py | 16 ++++----- .../components/novy_cooker_hood/light.py | 15 ++++----- .../components/radio_frequency/manifest.json | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- tests/components/novy_cooker_hood/conftest.py | 33 +------------------ .../novy_cooker_hood/test_config_flow.py | 28 ++++++++-------- .../components/novy_cooker_hood/test_light.py | 12 +++---- 10 files changed, 40 insertions(+), 84 deletions(-) delete mode 100644 homeassistant/components/novy_cooker_hood/commands.py diff --git a/homeassistant/components/novy_cooker_hood/commands.py b/homeassistant/components/novy_cooker_hood/commands.py deleted file mode 100644 index 6e422f9ac28..00000000000 --- a/homeassistant/components/novy_cooker_hood/commands.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Command names for the Novy Cooker Hood RF codes.""" - -from typing import Final - -COMMAND_LIGHT: Final = "light" -COMMAND_PLUS: Final = "plus" -COMMAND_MINUS: Final = "minus" diff --git a/homeassistant/components/novy_cooker_hood/config_flow.py b/homeassistant/components/novy_cooker_hood/config_flow.py index 503033e7e69..4d7d9b919e1 100644 --- a/homeassistant/components/novy_cooker_hood/config_flow.py +++ b/homeassistant/components/novy_cooker_hood/config_flow.py @@ -3,7 +3,7 @@ import asyncio from typing import Any -from rf_protocols.codes.novy.cooker_hood import get_codes_for_code +from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton import voluptuous as vol from homeassistant.components.radio_frequency import ( @@ -19,7 +19,6 @@ from homeassistant.const import CONF_CODE from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er, selector -from .commands import COMMAND_LIGHT from .const import ( CODE_MAX, CODE_MIN, @@ -128,10 +127,8 @@ class NovyCookerHoodConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Toggle the hood light on then off so it ends in its starting state.""" assert self._transmitter_entity_id is not None + command = NovyCookerHoodButton.LIGHT.to_command(channel=self._code) try: - command = await get_codes_for_code(self._code).async_load_command( - COMMAND_LIGHT - ) await async_send_command(self.hass, self._transmitter_entity_id, command) await asyncio.sleep(_TOGGLE_GAP) await async_send_command(self.hass, self._transmitter_entity_id, command) diff --git a/homeassistant/components/novy_cooker_hood/fan.py b/homeassistant/components/novy_cooker_hood/fan.py index a6ef15e013e..3243f8a7113 100644 --- a/homeassistant/components/novy_cooker_hood/fan.py +++ b/homeassistant/components/novy_cooker_hood/fan.py @@ -3,7 +3,8 @@ import math from typing import Any -from rf_protocols.codes.novy.cooker_hood import get_codes_for_code +from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton +from rf_protocols.commands.novy import NovyCookerHoodCommand from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntity, FanEntityFeature from homeassistant.components.radio_frequency import async_send_command @@ -17,7 +18,6 @@ from homeassistant.util.percentage import ( ranged_value_to_percentage, ) -from .commands import COMMAND_MINUS, COMMAND_PLUS from .const import SPEED_COUNT from .entity import NovyCookerHoodEntity @@ -49,7 +49,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity): def __init__(self, entry: ConfigEntry) -> None: """Initialize the fan.""" super().__init__(entry) - self._codes = get_codes_for_code(entry.data[CONF_CODE]) + self._code: int = entry.data[CONF_CODE] self._level = 0 self._attr_unique_id = entry.entry_id @@ -103,7 +103,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity): async def async_increase_speed(self, percentage_step: int | None = None) -> None: """Bump speed up by N hardware levels (no recalibration).""" steps = self._steps_from_percentage(percentage_step) - plus = await self._codes.async_load_command(COMMAND_PLUS) + plus = NovyCookerHoodButton.PLUS.to_command(channel=self._code) for _ in range(steps): await self._async_send(plus) self._level = min(SPEED_COUNT, self._level + steps) @@ -112,7 +112,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity): async def async_decrease_speed(self, percentage_step: int | None = None) -> None: """Bump speed down by N hardware levels (no recalibration).""" steps = self._steps_from_percentage(percentage_step) - minus = await self._codes.async_load_command(COMMAND_MINUS) + minus = NovyCookerHoodButton.MINUS.to_command(channel=self._code) for _ in range(steps): await self._async_send(minus) self._level = max(0, self._level - steps) @@ -127,17 +127,17 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity): async def _async_set_level(self, level: int) -> None: """Reset to off with `SPEED_COUNT` minus presses, then climb to level.""" - minus = await self._codes.async_load_command(COMMAND_MINUS) + minus = NovyCookerHoodButton.MINUS.to_command(channel=self._code) for _ in range(SPEED_COUNT): await self._async_send(minus) if level > 0: - plus = await self._codes.async_load_command(COMMAND_PLUS) + plus = NovyCookerHoodButton.PLUS.to_command(channel=self._code) for _ in range(level): await self._async_send(plus) self._level = level self.async_write_ha_state() - async def _async_send(self, command: Any) -> None: + async def _async_send(self, command: NovyCookerHoodCommand) -> None: """Send a single RF command via the configured transmitter.""" await async_send_command( self.hass, self._transmitter, command, context=self._context diff --git a/homeassistant/components/novy_cooker_hood/light.py b/homeassistant/components/novy_cooker_hood/light.py index 17f5ca6f045..db31075de8c 100644 --- a/homeassistant/components/novy_cooker_hood/light.py +++ b/homeassistant/components/novy_cooker_hood/light.py @@ -2,7 +2,7 @@ from typing import Any -from rf_protocols.codes.novy.cooker_hood import get_codes_for_code +from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton from homeassistant.components.light import ColorMode, LightEntity from homeassistant.components.radio_frequency import async_send_command @@ -12,7 +12,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity -from .commands import COMMAND_LIGHT from .entity import NovyCookerHoodEntity PARALLEL_UPDATES = 1 @@ -37,7 +36,7 @@ class NovyCookerHoodLight(NovyCookerHoodEntity, LightEntity, RestoreEntity): def __init__(self, entry: ConfigEntry) -> None: """Initialize the light.""" super().__init__(entry) - self._codes = get_codes_for_code(entry.data[CONF_CODE]) + self._code = entry.data[CONF_CODE] self._attr_unique_id = entry.entry_id async def async_added_to_hass(self) -> None: @@ -48,19 +47,19 @@ class NovyCookerHoodLight(NovyCookerHoodEntity, LightEntity, RestoreEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the light on by sending the toggle command.""" - await self._async_send_command(COMMAND_LIGHT) + await self._async_send_light() self._attr_is_on = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off by sending the toggle command.""" - await self._async_send_command(COMMAND_LIGHT) + await self._async_send_light() self._attr_is_on = False self.async_write_ha_state() - async def _async_send_command(self, name: str) -> None: - """Load the named command and send it via the configured transmitter.""" - command = await self._codes.async_load_command(name) + async def _async_send_light(self) -> None: + """Send the light toggle command via the configured transmitter.""" + command = NovyCookerHoodButton.LIGHT.to_command(channel=self._code) await async_send_command( self.hass, self._transmitter, command, context=self._context ) diff --git a/homeassistant/components/radio_frequency/manifest.json b/homeassistant/components/radio_frequency/manifest.json index ca1cb17cf5f..ede2f718b4d 100644 --- a/homeassistant/components/radio_frequency/manifest.json +++ b/homeassistant/components/radio_frequency/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/radio_frequency", "integration_type": "entity", "quality_scale": "internal", - "requirements": ["rf-protocols==3.2.0"] + "requirements": ["rf-protocols==4.0.0"] } diff --git a/requirements.txt b/requirements.txt index e411de7039c..3be402f06d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ python-slugify==8.0.4 PyTurboJPEG==1.8.3 PyYAML==6.0.3 requests==2.34.2 -rf-protocols==3.2.0 +rf-protocols==4.0.0 securetar==2026.4.1 SQLAlchemy==2.0.49 standard-aifc==3.13.0 diff --git a/requirements_all.txt b/requirements_all.txt index c377fe7f494..9ccaf4f7738 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2874,7 +2874,7 @@ renson-endura-delta==1.7.2 reolink-aio==0.20.0 # homeassistant.components.radio_frequency -rf-protocols==3.2.0 +rf-protocols==4.0.0 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/tests/components/novy_cooker_hood/conftest.py b/tests/components/novy_cooker_hood/conftest.py index 9c4a99434ca..f9a0b62cf3b 100644 --- a/tests/components/novy_cooker_hood/conftest.py +++ b/tests/components/novy_cooker_hood/conftest.py @@ -1,10 +1,6 @@ """Common fixtures for the Novy Cooker Hood tests.""" -from collections.abc import Iterator -from unittest.mock import AsyncMock, MagicMock, patch - import pytest -from rf_protocols.loader import CodeCollection from homeassistant.components.novy_cooker_hood.const import CONF_TRANSMITTER, DOMAIN from homeassistant.const import CONF_CODE @@ -12,38 +8,11 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -from tests.components.radio_frequency.common import ( - MockRadioFrequencyCommand, - MockRadioFrequencyEntity, -) +from tests.components.radio_frequency.common import MockRadioFrequencyEntity TRANSMITTER_ENTITY_ID = "radio_frequency.test_rf_transmitter" -@pytest.fixture(autouse=True) -def mock_get_codes() -> Iterator[MagicMock]: - """Patch the bundled-codes loader so tests don't hit the filesystem.""" - fake_collection = MagicMock(spec=CodeCollection) - fake_collection.async_load_command = AsyncMock( - side_effect=lambda name: MockRadioFrequencyCommand() - ) - with ( - patch( - "homeassistant.components.novy_cooker_hood.light.get_codes_for_code", - return_value=fake_collection, - ), - patch( - "homeassistant.components.novy_cooker_hood.fan.get_codes_for_code", - return_value=fake_collection, - ), - patch( - "homeassistant.components.novy_cooker_hood.config_flow.get_codes_for_code", - return_value=fake_collection, - ), - ): - yield fake_collection - - @pytest.fixture def mock_config_entry( mock_rf_entity: MockRadioFrequencyEntity, diff --git a/tests/components/novy_cooker_hood/test_config_flow.py b/tests/components/novy_cooker_hood/test_config_flow.py index d74dd55cf5d..85bc94d2f9d 100644 --- a/tests/components/novy_cooker_hood/test_config_flow.py +++ b/tests/components/novy_cooker_hood/test_config_flow.py @@ -1,11 +1,11 @@ """Test the Novy Hood config flow.""" from collections.abc import Iterator -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest +from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton -from homeassistant.components.novy_cooker_hood.commands import COMMAND_LIGHT from homeassistant.components.novy_cooker_hood.const import CONF_TRANSMITTER, DOMAIN from homeassistant.components.radio_frequency import DATA_COMPONENT, DOMAIN as RF_DOMAIN from homeassistant.config_entries import SOURCE_USER @@ -49,7 +49,6 @@ async def _start_user_flow(hass: HomeAssistant, code: str = "1") -> dict: async def test_user_flow_test_then_finish( hass: HomeAssistant, - mock_get_codes: MagicMock, mock_rf_entity: MockRadioFrequencyEntity, entity_registry: er.EntityRegistry, ) -> None: @@ -58,8 +57,10 @@ async def test_user_flow_test_then_finish( assert result["type"] is FlowResultType.MENU assert result["step_id"] == "test_light" - mock_get_codes.async_load_command.assert_awaited_with(COMMAND_LIGHT) assert len(mock_rf_entity.send_command_calls) == 2 + sent = mock_rf_entity.send_command_calls[0].command + assert sent.key == NovyCookerHoodButton.LIGHT.code + assert sent.channel == 3 result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "finish"} @@ -77,7 +78,6 @@ async def test_user_flow_test_then_finish( async def test_user_flow_retry_picks_different_code( hass: HomeAssistant, - mock_get_codes: MagicMock, mock_rf_entity: MockRadioFrequencyEntity, entity_registry: er.EntityRegistry, ) -> None: @@ -99,9 +99,13 @@ async def test_user_flow_retry_picks_different_code( }, ) assert result["type"] is FlowResultType.MENU - # One load per test x two tests; two sends per test x two tests. - assert mock_get_codes.async_load_command.await_count == 2 assert len(mock_rf_entity.send_command_calls) == 4 + assert [c.command.channel for c in mock_rf_entity.send_command_calls] == [ + 1, + 1, + 7, + 7, + ] result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "finish"} @@ -127,7 +131,6 @@ async def test_user_flow_test_transmit_failure( async def test_recover_after_transmit_failure( hass: HomeAssistant, - mock_get_codes: MagicMock, mock_rf_entity: MockRadioFrequencyEntity, ) -> None: """The user can Retry from test_failed and complete the flow.""" @@ -183,7 +186,6 @@ async def test_unique_id_already_configured( async def test_same_transmitter_different_code_is_allowed( hass: HomeAssistant, - mock_get_codes: MagicMock, mock_config_entry: MockConfigEntry, mock_rf_entity: MockRadioFrequencyEntity, entity_registry: er.EntityRegistry, @@ -205,7 +207,6 @@ async def test_same_transmitter_different_code_is_allowed( async def test_reconfigure_updates_entry( hass: HomeAssistant, - mock_get_codes: MagicMock, init_novy_cooker_hood: MockConfigEntry, mock_rf_entity: MockRadioFrequencyEntity, entity_registry: er.EntityRegistry, @@ -224,7 +225,9 @@ async def test_reconfigure_updates_entry( ) assert result["type"] is FlowResultType.MENU assert result["step_id"] == "test_light" - mock_get_codes.async_load_command.assert_awaited_with(COMMAND_LIGHT) + sent = mock_rf_entity.send_command_calls[-1].command + assert sent.key == NovyCookerHoodButton.LIGHT.code + assert sent.channel == 4 result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "finish"} @@ -239,7 +242,6 @@ async def test_reconfigure_updates_entry( async def test_reconfigure_frees_old_unique_id( hass: HomeAssistant, - mock_get_codes: MagicMock, init_novy_cooker_hood: MockConfigEntry, mock_rf_entity: MockRadioFrequencyEntity, ) -> None: @@ -295,7 +297,6 @@ async def test_reconfigure_aborts_on_collision( async def test_reconfigure_retry_returns_to_picker( hass: HomeAssistant, - mock_get_codes: MagicMock, init_novy_cooker_hood: MockConfigEntry, mock_rf_entity: MockRadioFrequencyEntity, ) -> None: @@ -326,7 +327,6 @@ async def test_no_transmitters(hass: HomeAssistant) -> None: async def test_recover_after_no_transmitters( hass: HomeAssistant, - mock_get_codes: MagicMock, ) -> None: """User can re-init the flow after the radio_frequency integration loads.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/novy_cooker_hood/test_light.py b/tests/components/novy_cooker_hood/test_light.py index 6fc34e20c2f..f117f76b1f2 100644 --- a/tests/components/novy_cooker_hood/test_light.py +++ b/tests/components/novy_cooker_hood/test_light.py @@ -1,13 +1,12 @@ """Tests for the Novy Hood light platform.""" -from unittest.mock import MagicMock, call +from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton from homeassistant.components.light import ( DOMAIN as LIGHT_DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.components.novy_cooker_hood.commands import COMMAND_LIGHT from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, @@ -28,7 +27,6 @@ ENTITY_ID = "light.novy_cooker_hood_light" async def test_turn_on_and_off_send_light_once_each( hass: HomeAssistant, - mock_get_codes: MagicMock, mock_rf_entity: MockRadioFrequencyEntity, init_novy_cooker_hood: MockConfigEntry, ) -> None: @@ -66,11 +64,11 @@ async def test_turn_on_and_off_send_light_once_each( state = hass.states.get(ENTITY_ID) assert state is not None assert state.state == STATE_OFF - assert mock_get_codes.async_load_command.await_args_list == [ - call(COMMAND_LIGHT), - call(COMMAND_LIGHT), - ] assert len(mock_rf_entity.send_command_calls) == 2 + assert [c.command.key for c in mock_rf_entity.send_command_calls] == [ + NovyCookerHoodButton.LIGHT.code, + NovyCookerHoodButton.LIGHT.code, + ] async def test_restore_state(