From 1eaad9bf8d3eaa3d0594888dfd690d354b9fe1ac Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 9 Feb 2026 11:53:59 -0700 Subject: [PATCH 1/4] Add options flow for IntelliFire read/control mode configuration Allow users to configure whether the integration uses local or cloud endpoints for reading data and controlling the fireplace through an options flow in the UI. Features: - Options flow to select read mode (local/cloud) and control mode - Validates connectivity before accepting mode changes - Shows error if selected mode is unavailable - Only updates modes that actually changed (no unnecessary reloads) - Add read_mode and control_mode diagnostic sensors Bug fix: - Convert string options to IntelliFireApiMode enum in _construct_common_data --- .../components/intellifire/__init__.py | 34 +++++- .../components/intellifire/config_flow.py | 89 ++++++++++++++- .../components/intellifire/coordinator.py | 17 +++ .../components/intellifire/sensor.py | 12 ++ .../components/intellifire/strings.json | 24 ++++ .../intellifire/snapshots/test_sensor.ambr | 100 ++++++++++++++++ .../intellifire/test_config_flow.py | 107 +++++++++++++++++- 7 files changed, 379 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index cc5da82ab92787..91aa783cc60e02 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -6,6 +6,7 @@ from intellifire4py import UnifiedFireplace from intellifire4py.cloud_interface import IntelliFireCloudInterface +from intellifire4py.const import IntelliFireApiMode from intellifire4py.model import IntelliFireCommonFireplaceData from homeassistant.const import ( @@ -55,8 +56,8 @@ def _construct_common_data( serial=entry.data[CONF_SERIAL], api_key=entry.data[CONF_API_KEY], ip_address=entry.data[CONF_IP_ADDRESS], - read_mode=entry.options[CONF_READ_MODE], - control_mode=entry.options[CONF_CONTROL_MODE], + read_mode=IntelliFireApiMode(entry.options[CONF_READ_MODE]), + control_mode=IntelliFireApiMode(entry.options[CONF_CONTROL_MODE]), ) @@ -139,9 +140,38 @@ async def async_setup_entry(hass: HomeAssistant, entry: IntellifireConfigEntry) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + entry.async_on_unload(entry.add_update_listener(async_update_options)) + return True +async def async_update_options( + hass: HomeAssistant, entry: IntellifireConfigEntry +) -> None: + """Handle options update.""" + coordinator: IntellifireDataUpdateCoordinator = entry.runtime_data + + new_read_mode = IntelliFireApiMode(entry.options[CONF_READ_MODE]) + new_control_mode = IntelliFireApiMode(entry.options[CONF_CONTROL_MODE]) + + current_read_mode = coordinator.get_read_mode() + current_control_mode = coordinator.get_control_mode() + + # Only update modes that actually changed + if new_read_mode != current_read_mode: + LOGGER.debug("Updating read mode: %s -> %s", current_read_mode, new_read_mode) + await coordinator.set_read_mode(new_read_mode) + + if new_control_mode != current_control_mode: + LOGGER.debug( + "Updating control mode: %s -> %s", current_control_mode, new_control_mode + ) + await coordinator.set_control_mode(new_control_mode) + + # Refresh data with new mode settings + await coordinator.async_request_refresh() + + async def _async_wait_for_initialization( fireplace: UnifiedFireplace, timeout=STARTUP_TIMEOUT ): diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index f6131ede00ac67..bdf53d28b6e397 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -13,7 +13,13 @@ from intellifire4py.model import IntelliFireCommonFireplaceData import voluptuous as vol -from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -21,9 +27,12 @@ CONF_PASSWORD, CONF_USERNAME, ) +from homeassistant.core import callback +from homeassistant.helpers import selector from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from .const import ( + API_MODE_CLOUD, API_MODE_LOCAL, CONF_AUTH_COOKIE, CONF_CONTROL_MODE, @@ -260,3 +269,81 @@ async def async_step_dhcp( return self.async_abort(reason="not_intellifire_device") return await self.async_step_cloud_api() + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + """Create the options flow.""" + return IntelliFireOptionsFlowHandler() + + +class IntelliFireOptionsFlowHandler(OptionsFlow): + """Options flow for IntelliFire component.""" + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Manage the options.""" + errors: dict[str, str] = {} + + if user_input is not None: + # Validate connectivity for requested modes + coordinator = self.config_entry.runtime_data + fireplace = coordinator.fireplace + + if ( + user_input[CONF_READ_MODE] == API_MODE_LOCAL + and not fireplace.local_connectivity + ): + errors[CONF_READ_MODE] = "local_disabled" + if ( + user_input[CONF_READ_MODE] == API_MODE_CLOUD + and not fireplace.cloud_connectivity + ): + errors[CONF_READ_MODE] = "cloud_disabled" + if ( + user_input[CONF_CONTROL_MODE] == API_MODE_LOCAL + and not fireplace.local_connectivity + ): + errors[CONF_CONTROL_MODE] = "local_disabled" + if ( + user_input[CONF_CONTROL_MODE] == API_MODE_CLOUD + and not fireplace.cloud_connectivity + ): + errors[CONF_CONTROL_MODE] = "cloud_disabled" + + if not errors: + return self.async_create_entry(title="", data=user_input) + + existing_read = self.config_entry.options.get(CONF_READ_MODE, API_MODE_LOCAL) + existing_control = self.config_entry.options.get( + CONF_CONTROL_MODE, API_MODE_LOCAL + ) + + cloud_local_options = selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value=API_MODE_LOCAL, label="Local"), + selector.SelectOptionDict(value=API_MODE_CLOUD, label="Cloud"), + ] + ) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_READ_MODE, + default=user_input.get(CONF_READ_MODE, existing_read) + if user_input + else existing_read, + ): selector.SelectSelector(cloud_local_options), + vol.Required( + CONF_CONTROL_MODE, + default=user_input.get(CONF_CONTROL_MODE, existing_control) + if user_input + else existing_control, + ): selector.SelectSelector(cloud_local_options), + } + ), + errors=errors, + ) diff --git a/homeassistant/components/intellifire/coordinator.py b/homeassistant/components/intellifire/coordinator.py index dc9aa45d58bcd0..37a45b92deac95 100644 --- a/homeassistant/components/intellifire/coordinator.py +++ b/homeassistant/components/intellifire/coordinator.py @@ -5,6 +5,7 @@ from datetime import timedelta from intellifire4py import UnifiedFireplace +from intellifire4py.const import IntelliFireApiMode from intellifire4py.control import IntelliFireController from intellifire4py.model import IntelliFirePollData from intellifire4py.read import IntelliFireDataProvider @@ -51,6 +52,22 @@ def control_api(self) -> IntelliFireController: """Return the control API.""" return self.fireplace.control_api + def get_read_mode(self) -> IntelliFireApiMode: + """Return the current read mode.""" + return self.fireplace.read_mode + + async def set_read_mode(self, mode: IntelliFireApiMode) -> None: + """Set the read mode between cloud/local.""" + await self.fireplace.set_read_mode(mode) + + def get_control_mode(self) -> IntelliFireApiMode: + """Return the current control mode.""" + return self.fireplace.control_mode + + async def set_control_mode(self, mode: IntelliFireApiMode) -> None: + """Set the control mode between cloud/local.""" + await self.fireplace.set_control_mode(mode) + async def _async_update_data(self) -> IntelliFirePollData: return self.fireplace.data diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index 82abc0d3797354..172f98971fc762 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -66,6 +66,18 @@ def _uptime_to_timestamp( INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( + IntellifireSensorEntityDescription( + key="read_mode", + translation_key="read_mode", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda coordinator: coordinator.get_read_mode().value, + ), + IntellifireSensorEntityDescription( + key="control_mode", + translation_key="control_mode", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda coordinator: coordinator.get_control_mode().value, + ), IntellifireSensorEntityDescription( key="flame_height", translation_key="flame_height", diff --git a/homeassistant/components/intellifire/strings.json b/homeassistant/components/intellifire/strings.json index 7c6c349b564de2..02813e94c63838 100644 --- a/homeassistant/components/intellifire/strings.json +++ b/homeassistant/components/intellifire/strings.json @@ -100,6 +100,9 @@ "connection_quality": { "name": "Connection quality" }, + "control_mode": { + "name": "Control mode" + }, "downtime": { "name": "Downtime" }, @@ -115,6 +118,9 @@ "ipv4_address": { "name": "IP address" }, + "read_mode": { + "name": "Read mode" + }, "target_temp": { "name": "Target temperature" }, @@ -133,5 +139,23 @@ "name": "Pilot light" } } + }, + "options": { + "error": { + "cloud_disabled": "Cloud connectivity is not available", + "local_disabled": "Local connectivity is not available" + }, + "step": { + "init": { + "data": { + "cloud_control": "Control mode", + "cloud_read": "Read mode" + }, + "data_description": { + "cloud_control": "Select endpoint to use for controlling device", + "cloud_read": "Select endpoint to use for reading data" + } + } + } } } diff --git a/tests/components/intellifire/snapshots/test_sensor.ambr b/tests/components/intellifire/snapshots/test_sensor.ambr index 6ec468ef1418b6..785de691a2e5a5 100644 --- a/tests/components/intellifire/snapshots/test_sensor.ambr +++ b/tests/components/intellifire/snapshots/test_sensor.ambr @@ -49,6 +49,56 @@ 'state': '988451', }) # --- +# name: test_all_sensor_entities[sensor.intellifire_control_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.intellifire_control_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Control mode', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Control mode', + 'platform': 'intellifire', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'control_mode', + 'unique_id': 'control_mode_mock_serial', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_sensor_entities[sensor.intellifire_control_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by unpublished Intellifire API', + 'friendly_name': 'IntelliFire Control mode', + }), + 'context': , + 'entity_id': 'sensor.intellifire_control_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'local', + }) +# --- # name: test_all_sensor_entities[sensor.intellifire_downtime-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -306,6 +356,56 @@ 'state': '192.168.2.108', }) # --- +# name: test_all_sensor_entities[sensor.intellifire_read_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.intellifire_read_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Read mode', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Read mode', + 'platform': 'intellifire', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'read_mode', + 'unique_id': 'read_mode_mock_serial', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_sensor_entities[sensor.intellifire_read_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by unpublished Intellifire API', + 'friendly_name': 'IntelliFire Read mode', + }), + 'context': , + 'entity_id': 'sensor.intellifire_read_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'local', + }) +# --- # name: test_all_sensor_entities[sensor.intellifire_target_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/intellifire/test_config_flow.py b/tests/components/intellifire/test_config_flow.py index 7ce4724ce3a6e6..02a4b7f13cc43b 100644 --- a/tests/components/intellifire/test_config_flow.py +++ b/tests/components/intellifire/test_config_flow.py @@ -5,7 +5,14 @@ from intellifire4py.exceptions import LoginError from homeassistant import config_entries -from homeassistant.components.intellifire.const import CONF_SERIAL, DOMAIN +from homeassistant.components.intellifire.const import ( + API_MODE_CLOUD, + API_MODE_LOCAL, + CONF_CONTROL_MODE, + CONF_READ_MODE, + CONF_SERIAL, + DOMAIN, +) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -227,3 +234,101 @@ async def test_reauth_flow( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reauth_successful" + + +async def test_options_flow( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test options flow for changing read/control modes.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Enable both connectivity for this test + mock_fp.local_connectivity = True + mock_fp.cloud_connectivity = True + + # Start options flow + result = await hass.config_entries.options.async_init( + mock_config_entry_current.entry_id + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "init" + + # Submit new options - both should succeed with connectivity enabled + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {CONF_READ_MODE: API_MODE_CLOUD, CONF_CONTROL_MODE: API_MODE_LOCAL}, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["data"] == { + CONF_READ_MODE: API_MODE_CLOUD, + CONF_CONTROL_MODE: API_MODE_LOCAL, + } + + +async def test_options_flow_local_unavailable( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test options flow shows error when local connectivity unavailable.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Disable local connectivity + mock_fp.local_connectivity = False + mock_fp.cloud_connectivity = True + + # Start options flow + result = await hass.config_entries.options.async_init( + mock_config_entry_current.entry_id + ) + + # Try to select local mode - should fail + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_CLOUD}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {CONF_READ_MODE: "local_disabled"} + + +async def test_options_flow_cloud_unavailable( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test options flow shows error when cloud connectivity unavailable.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Disable cloud connectivity (this is the default in mock, but be explicit) + mock_fp.local_connectivity = True + mock_fp.cloud_connectivity = False + + # Start options flow + result = await hass.config_entries.options.async_init( + mock_config_entry_current.entry_id + ) + + # Try to select cloud mode - should fail + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {CONF_READ_MODE: API_MODE_CLOUD, CONF_CONTROL_MODE: API_MODE_LOCAL}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {CONF_READ_MODE: "cloud_disabled"} From a1b43627bd6ef02cacd09db6eac3e1847189cbae Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 9 Feb 2026 12:29:56 -0700 Subject: [PATCH 2/4] feat: retest connectivity on options flow --- homeassistant/components/intellifire/config_flow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index bdf53d28b6e397..a262b3ac742e0c 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -291,6 +291,9 @@ async def async_step_init( coordinator = self.config_entry.runtime_data fireplace = coordinator.fireplace + # Refresh connectivity status before validating + await fireplace.async_validate_connectivity() + if ( user_input[CONF_READ_MODE] == API_MODE_LOCAL and not fireplace.local_connectivity From e2532c98f9a6838334a3647e48291d83ee32e288 Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 9 Feb 2026 12:34:30 -0700 Subject: [PATCH 3/4] test coverage --- .../intellifire/test_config_flow.py | 80 +++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/tests/components/intellifire/test_config_flow.py b/tests/components/intellifire/test_config_flow.py index 02a4b7f13cc43b..14e390b4bde986 100644 --- a/tests/components/intellifire/test_config_flow.py +++ b/tests/components/intellifire/test_config_flow.py @@ -272,12 +272,12 @@ async def test_options_flow( } -async def test_options_flow_local_unavailable( +async def test_options_flow_local_read_unavailable( hass: HomeAssistant, mock_config_entry_current: MockConfigEntry, mock_apis_single_fp, ) -> None: - """Test options flow shows error when local connectivity unavailable.""" + """Test options flow shows error when local connectivity unavailable for read mode.""" _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp mock_config_entry_current.add_to_hass(hass) @@ -293,7 +293,7 @@ async def test_options_flow_local_unavailable( mock_config_entry_current.entry_id ) - # Try to select local mode - should fail + # Try to select local read mode - should fail result = await hass.config_entries.options.async_configure( result["flow_id"], {CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_CLOUD}, @@ -301,21 +301,54 @@ async def test_options_flow_local_unavailable( assert result["type"] is FlowResultType.FORM assert result["errors"] == {CONF_READ_MODE: "local_disabled"} + # Verify connectivity was checked + mock_fp.async_validate_connectivity.assert_called_once() -async def test_options_flow_cloud_unavailable( +async def test_options_flow_local_control_unavailable( hass: HomeAssistant, mock_config_entry_current: MockConfigEntry, mock_apis_single_fp, ) -> None: - """Test options flow shows error when cloud connectivity unavailable.""" + """Test options flow shows error when local connectivity unavailable for control mode.""" _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp mock_config_entry_current.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry_current.entry_id) await hass.async_block_till_done() - # Disable cloud connectivity (this is the default in mock, but be explicit) + # Disable local connectivity + mock_fp.local_connectivity = False + mock_fp.cloud_connectivity = True + + # Start options flow + result = await hass.config_entries.options.async_init( + mock_config_entry_current.entry_id + ) + + # Try to select local control mode - should fail + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {CONF_READ_MODE: API_MODE_CLOUD, CONF_CONTROL_MODE: API_MODE_LOCAL}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {CONF_CONTROL_MODE: "local_disabled"} + + +async def test_options_flow_cloud_read_unavailable( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test options flow shows error when cloud connectivity unavailable for read mode.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Disable cloud connectivity mock_fp.local_connectivity = True mock_fp.cloud_connectivity = False @@ -324,7 +357,7 @@ async def test_options_flow_cloud_unavailable( mock_config_entry_current.entry_id ) - # Try to select cloud mode - should fail + # Try to select cloud read mode - should fail result = await hass.config_entries.options.async_configure( result["flow_id"], {CONF_READ_MODE: API_MODE_CLOUD, CONF_CONTROL_MODE: API_MODE_LOCAL}, @@ -332,3 +365,36 @@ async def test_options_flow_cloud_unavailable( assert result["type"] is FlowResultType.FORM assert result["errors"] == {CONF_READ_MODE: "cloud_disabled"} + # Verify connectivity was checked + mock_fp.async_validate_connectivity.assert_called_once() + + +async def test_options_flow_cloud_control_unavailable( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test options flow shows error when cloud connectivity unavailable for control mode.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Disable cloud connectivity + mock_fp.local_connectivity = True + mock_fp.cloud_connectivity = False + + # Start options flow + result = await hass.config_entries.options.async_init( + mock_config_entry_current.entry_id + ) + + # Try to select cloud control mode - should fail + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_CLOUD}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {CONF_CONTROL_MODE: "cloud_disabled"} From 0a19c0ed2ce14e21c8d26e9be8009d729f83fed6 Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 9 Feb 2026 13:19:55 -0700 Subject: [PATCH 4/4] feat: double polling fix --- homeassistant/components/intellifire/__init__.py | 3 ++- .../components/intellifire/coordinator.py | 1 + .../components/intellifire/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/intellifire/conftest.py | 2 ++ tests/components/intellifire/test_init.py | 16 ++++++++++++++++ 7 files changed, 24 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index 91aa783cc60e02..a527a59cac6712 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -118,7 +118,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: IntellifireConfigEntry) try: fireplace: UnifiedFireplace = ( await UnifiedFireplace.build_fireplace_from_common( - _construct_common_data(entry) + _construct_common_data(entry), + polling_enabled=False, ) ) LOGGER.debug("Waiting for Fireplace to Initialize") diff --git a/homeassistant/components/intellifire/coordinator.py b/homeassistant/components/intellifire/coordinator.py index 37a45b92deac95..5d3828f3a6507e 100644 --- a/homeassistant/components/intellifire/coordinator.py +++ b/homeassistant/components/intellifire/coordinator.py @@ -69,6 +69,7 @@ async def set_control_mode(self, mode: IntelliFireApiMode) -> None: await self.fireplace.set_control_mode(mode) async def _async_update_data(self) -> IntelliFirePollData: + await self.fireplace.perform_poll() return self.fireplace.data @property diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index b54ba47ce573fe..4feef90a7f7289 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -12,5 +12,5 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["intellifire4py"], - "requirements": ["intellifire4py==4.2.1"] + "requirements": ["intellifire4py==4.4.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 172a1decf2fa84..95fbe7c8ac26f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1306,7 +1306,7 @@ inkbird-ble==1.1.1 insteon-frontend-home-assistant==0.6.1 # homeassistant.components.intellifire -intellifire4py==4.2.1 +intellifire4py==4.4.0 # homeassistant.components.iometer iometer==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 87a272cdc9346a..8eacf6dbcdfbef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1152,7 +1152,7 @@ inkbird-ble==1.1.1 insteon-frontend-home-assistant==0.6.1 # homeassistant.components.intellifire -intellifire4py==4.2.1 +intellifire4py==4.4.0 # homeassistant.components.iometer iometer==0.4.0 diff --git a/tests/components/intellifire/conftest.py b/tests/components/intellifire/conftest.py index 0bd7073ee47f3b..2de26720f7d492 100644 --- a/tests/components/intellifire/conftest.py +++ b/tests/components/intellifire/conftest.py @@ -235,6 +235,8 @@ def mock_fp(mock_common_data_local) -> Generator[AsyncMock]: mock_instance.set_read_mode = AsyncMock() mock_instance.set_control_mode = AsyncMock() + mock_instance.perform_poll = AsyncMock() + mock_instance.async_validate_connectivity = AsyncMock( return_value=(True, False) ) diff --git a/tests/components/intellifire/test_init.py b/tests/components/intellifire/test_init.py index 6d08fda26c3a56..c63a210a0b9278 100644 --- a/tests/components/intellifire/test_init.py +++ b/tests/components/intellifire/test_init.py @@ -109,3 +109,19 @@ async def test_connectivity_bad( await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 + + +async def test_coordinator_performs_poll( + hass: HomeAssistant, + mock_config_entry_current: MockConfigEntry, + mock_apis_single_fp, +) -> None: + """Test that the coordinator uses perform_poll() for data refresh.""" + _mock_local, _mock_cloud, mock_fp = mock_apis_single_fp + + mock_config_entry_current.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry_current.entry_id) + await hass.async_block_till_done() + + # Verify perform_poll was called during initial setup + mock_fp.perform_poll.assert_called()