From 8b4c73099380a921822657dae385addbcf39656c Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 5 Oct 2025 12:59:51 +0300 Subject: [PATCH 01/16] Gemini: Use default model instead of recommended where applicable (#153676) --- .../components/google_generative_ai_conversation/entity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/entity.py b/homeassistant/components/google_generative_ai_conversation/entity.py index 74b76d9bb83464..54ef22bd1a58ed 100644 --- a/homeassistant/components/google_generative_ai_conversation/entity.py +++ b/homeassistant/components/google_generative_ai_conversation/entity.py @@ -456,6 +456,7 @@ def __init__( """Initialize the agent.""" self.entry = entry self.subentry = subentry + self.default_model = default_model self._attr_name = subentry.title self._genai_client = entry.runtime_data self._attr_unique_id = subentry.subentry_id @@ -489,7 +490,7 @@ async def _async_handle_chat_log( tools = tools or [] tools.append(Tool(google_search=GoogleSearch())) - model_name = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL) + model_name = options.get(CONF_CHAT_MODEL, self.default_model) # Avoid INVALID_ARGUMENT Developer instruction is not enabled for supports_system_instruction = ( "gemma" not in model_name @@ -620,7 +621,7 @@ async def _async_handle_chat_log( def create_generate_content_config(self) -> GenerateContentConfig: """Create the GenerateContentConfig for the LLM.""" options = self.subentry.data - model = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL) + model = options.get(CONF_CHAT_MODEL, self.default_model) thinking_config: ThinkingConfig | None = None if model.startswith("models/gemini-2.5") and not model.endswith( ("tts", "image", "image-preview") From 78e97428fd7a28dff6d56779a012c1d2a9fdeb87 Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Sun, 5 Oct 2025 12:31:45 +0200 Subject: [PATCH 02/16] Add debouncer to acaia (#153725) --- homeassistant/components/acaia/coordinator.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/acaia/coordinator.py b/homeassistant/components/acaia/coordinator.py index b42cbccaee50af..9f29c844235f4f 100644 --- a/homeassistant/components/acaia/coordinator.py +++ b/homeassistant/components/acaia/coordinator.py @@ -12,11 +12,13 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import CONF_IS_NEW_STYLE_SCALE SCAN_INTERVAL = timedelta(seconds=15) +UPDATE_DEBOUNCE_TIME = 0.2 _LOGGER = logging.getLogger(__name__) @@ -38,11 +40,19 @@ def __init__(self, hass: HomeAssistant, entry: AcaiaConfigEntry) -> None: config_entry=entry, ) + debouncer = Debouncer( + hass=hass, + logger=_LOGGER, + cooldown=UPDATE_DEBOUNCE_TIME, + immediate=True, + function=self.async_update_listeners, + ) + self._scale = AcaiaScale( address_or_ble_device=entry.data[CONF_ADDRESS], name=entry.title, is_new_style_scale=entry.data[CONF_IS_NEW_STYLE_SCALE], - notify_callback=self.async_update_listeners, + notify_callback=debouncer.async_schedule_call, scanner=async_get_scanner(hass), ) From ccf563437b4efd3e67e215280f7e5aa6370f367f Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:34:18 +0200 Subject: [PATCH 03/16] Bump aiontfy to v0.6.1 (#153738) --- homeassistant/components/ntfy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ntfy/manifest.json b/homeassistant/components/ntfy/manifest.json index 95e0a7857c9508..279d30d9f9fbc8 100644 --- a/homeassistant/components/ntfy/manifest.json +++ b/homeassistant/components/ntfy/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_push", "loggers": ["aionfty"], "quality_scale": "platinum", - "requirements": ["aiontfy==0.6.0"] + "requirements": ["aiontfy==0.6.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7d0fd6fb2f2c79..5675f496cb50ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -325,7 +325,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.ntfy -aiontfy==0.6.0 +aiontfy==0.6.1 # homeassistant.components.nut aionut==4.3.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9ef242b1b3d3de..3d076e78f26d08 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -307,7 +307,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.ntfy -aiontfy==0.6.0 +aiontfy==0.6.1 # homeassistant.components.nut aionut==4.3.4 From 6f9e6909cea02a4e5c61280f00b1620cc3b2479f Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 5 Oct 2025 12:43:43 +0200 Subject: [PATCH 04/16] Bump airOS to 0.5.5 using formdata for v6 firmware (#153736) --- homeassistant/components/airos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airos/manifest.json b/homeassistant/components/airos/manifest.json index a1aa96cff71a8d..02a1ca997fb2f2 100644 --- a/homeassistant/components/airos/manifest.json +++ b/homeassistant/components/airos/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/airos", "iot_class": "local_polling", "quality_scale": "bronze", - "requirements": ["airos==0.5.4"] + "requirements": ["airos==0.5.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5675f496cb50ff..2436088eea2e77 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -453,7 +453,7 @@ airgradient==0.9.2 airly==1.1.0 # homeassistant.components.airos -airos==0.5.4 +airos==0.5.5 # homeassistant.components.airthings_ble airthings-ble==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3d076e78f26d08..1236a4dfbff651 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -435,7 +435,7 @@ airgradient==0.9.2 airly==1.1.0 # homeassistant.components.airos -airos==0.5.4 +airos==0.5.5 # homeassistant.components.airthings_ble airthings-ble==1.1.1 From ca5c0a759fe8d03939173366ab8aa0d9514c3a72 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 5 Oct 2025 12:46:42 +0200 Subject: [PATCH 05/16] Remove Shelly `presencezone` component from `VIRTUAL_COMPONENTS` tuple (#153740) --- homeassistant/components/shelly/const.py | 10 +--------- homeassistant/components/shelly/utils.py | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 47a31163fe5f4f..5606d3a8ce9446 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -268,15 +268,7 @@ class BLEScannerMode(StrEnum): CONF_GEN = "gen" -VIRTUAL_COMPONENTS = ( - "boolean", - "button", - "enum", - "input", - "number", - "presencezone", - "text", -) +VIRTUAL_COMPONENTS = ("boolean", "button", "enum", "input", "number", "text") VIRTUAL_COMPONENTS_MAP = { "binary_sensor": {"types": ["boolean"], "modes": ["label"]}, "button": {"types": ["button"], "modes": ["button"]}, diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index bfdbcee74a1cb9..6cd90f1feb9d9a 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -402,7 +402,7 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: if key in device.config and key != "em:0": # workaround for Pro 3EM, we don't want to get name for em:0 if component_name := device.config[key].get("name"): - if component in (*VIRTUAL_COMPONENTS, "script"): + if component in (*VIRTUAL_COMPONENTS, "presencezone", "script"): return cast(str, component_name) return cast(str, component_name) if instances == 1 else None From 3601cff88ea56a9529b5c325f88e89a2ac154f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 5 Oct 2025 13:58:35 +0300 Subject: [PATCH 06/16] Upgrade upcloud-api to 2.9.0 (#153727) --- homeassistant/components/upcloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/upcloud/manifest.json b/homeassistant/components/upcloud/manifest.json index bca246ad9e54d3..ab79d3f5c1a17e 100644 --- a/homeassistant/components/upcloud/manifest.json +++ b/homeassistant/components/upcloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upcloud", "iot_class": "cloud_polling", - "requirements": ["upcloud-api==2.8.0"] + "requirements": ["upcloud-api==2.9.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2436088eea2e77..3b1f49b57a9927 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3063,7 +3063,7 @@ universal-silabs-flasher==0.0.35 upb-lib==0.6.1 # homeassistant.components.upcloud -upcloud-api==2.8.0 +upcloud-api==2.9.0 # homeassistant.components.huawei_lte # homeassistant.components.syncthru diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1236a4dfbff651..d85d947b67896e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2537,7 +2537,7 @@ universal-silabs-flasher==0.0.35 upb-lib==0.6.1 # homeassistant.components.upcloud -upcloud-api==2.8.0 +upcloud-api==2.9.0 # homeassistant.components.huawei_lte # homeassistant.components.syncthru From f560d2a05ee25ff30cdbf69ff59c5e7bc9fd37c4 Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:03:55 +0200 Subject: [PATCH 07/16] Update suggested display precision for ntfy attachment size to 2 (#153741) --- homeassistant/components/ntfy/sensor.py | 4 ++-- tests/components/ntfy/snapshots/test_sensor.ambr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ntfy/sensor.py b/homeassistant/components/ntfy/sensor.py index 0180d9fce72b53..8948dc3f5f61ec 100644 --- a/homeassistant/components/ntfy/sensor.py +++ b/homeassistant/components/ntfy/sensor.py @@ -163,7 +163,7 @@ class NtfySensor(StrEnum): device_class=SensorDeviceClass.DATA_SIZE, native_unit_of_measurement=UnitOfInformation.BYTES, suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES, - suggested_display_precision=0, + suggested_display_precision=2, ), NtfySensorEntityDescription( key=NtfySensor.ATTACHMENT_TOTAL_SIZE_REMAINING, @@ -172,7 +172,7 @@ class NtfySensor(StrEnum): device_class=SensorDeviceClass.DATA_SIZE, native_unit_of_measurement=UnitOfInformation.BYTES, suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES, - suggested_display_precision=0, + suggested_display_precision=2, entity_registry_enabled_default=False, ), NtfySensorEntityDescription( diff --git a/tests/components/ntfy/snapshots/test_sensor.ambr b/tests/components/ntfy/snapshots/test_sensor.ambr index fd0dd3c4bd410a..b475b1ee0bcd73 100644 --- a/tests/components/ntfy/snapshots/test_sensor.ambr +++ b/tests/components/ntfy/snapshots/test_sensor.ambr @@ -190,7 +190,7 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), 'sensor.private': dict({ 'suggested_unit_of_measurement': , @@ -302,7 +302,7 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), 'sensor.private': dict({ 'suggested_unit_of_measurement': , From cceee05c1558828fa8bd6dd100f7e2d23a91b074 Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Sun, 5 Oct 2025 13:04:28 +0200 Subject: [PATCH 08/16] Fix lamarzocco brewing start time sensor availability (#153732) --- homeassistant/components/lamarzocco/sensor.py | 11 +++++++++-- .../components/lamarzocco/snapshots/test_sensor.ambr | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lamarzocco/sensor.py b/homeassistant/components/lamarzocco/sensor.py index 1f4983a03a8f22..2e36db85fc4b35 100644 --- a/homeassistant/components/lamarzocco/sensor.py +++ b/homeassistant/components/lamarzocco/sensor.py @@ -5,7 +5,7 @@ from datetime import datetime from typing import cast -from pylamarzocco.const import BackFlushStatus, ModelName, WidgetType +from pylamarzocco.const import BackFlushStatus, MachineState, ModelName, WidgetType from pylamarzocco.models import ( BackFlush, BaseWidgetOutput, @@ -97,7 +97,14 @@ class LaMarzoccoSensorEntityDescription( ).brewing_start_time ), entity_category=EntityCategory.DIAGNOSTIC, - available_fn=(lambda coordinator: not coordinator.websocket_terminated), + available_fn=( + lambda coordinator: not coordinator.websocket_terminated + and cast( + MachineStatus, + coordinator.device.dashboard.config[WidgetType.CM_MACHINE_STATUS], + ).status + is MachineState.BREWING + ), ), LaMarzoccoSensorEntityDescription( key="steam_boiler_ready_time", diff --git a/tests/components/lamarzocco/snapshots/test_sensor.ambr b/tests/components/lamarzocco/snapshots/test_sensor.ambr index 3dd1ff9b6652cf..1ded7231287bc5 100644 --- a/tests/components/lamarzocco/snapshots/test_sensor.ambr +++ b/tests/components/lamarzocco/snapshots/test_sensor.ambr @@ -45,7 +45,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-05-07T18:04:20+00:00', + 'state': 'unavailable', }) # --- # name: test_sensors[sensor.gs012345_coffee_boiler_ready_time-entry] From dfd33fdab1d1f1ed840a0e527bcd0b11c57fdbd8 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 5 Oct 2025 14:26:16 +0200 Subject: [PATCH 09/16] Fix sensors availability check for Alexa Devices (#153743) --- homeassistant/components/alexa_devices/binary_sensor.py | 4 +++- homeassistant/components/alexa_devices/sensor.py | 8 +++++--- homeassistant/components/alexa_devices/switch.py | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/alexa_devices/binary_sensor.py b/homeassistant/components/alexa_devices/binary_sensor.py index 010a561fa77ea8..8347fa34423a62 100644 --- a/homeassistant/components/alexa_devices/binary_sensor.py +++ b/homeassistant/components/alexa_devices/binary_sensor.py @@ -51,7 +51,9 @@ class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription): ), is_supported=lambda device, key: device.sensors.get(key) is not None, is_available_fn=lambda device, key: ( - device.online and device.sensors[key].error is False + device.online + and (sensor := device.sensors.get(key)) is not None + and sensor.error is False ), ), ) diff --git a/homeassistant/components/alexa_devices/sensor.py b/homeassistant/components/alexa_devices/sensor.py index e6dbc251b9507a..57332b8ce3b146 100644 --- a/homeassistant/components/alexa_devices/sensor.py +++ b/homeassistant/components/alexa_devices/sensor.py @@ -32,7 +32,9 @@ class AmazonSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement_fn: Callable[[AmazonDevice, str], str] | None = None is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: ( - device.online and device.sensors[key].error is False + device.online + and (sensor := device.sensors.get(key)) is not None + and sensor.error is False ) @@ -40,9 +42,9 @@ class AmazonSensorEntityDescription(SensorEntityDescription): AmazonSensorEntityDescription( key="temperature", device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement_fn=lambda device, _key: ( + native_unit_of_measurement_fn=lambda device, key: ( UnitOfTemperature.CELSIUS - if device.sensors[_key].scale == "CELSIUS" + if key in device.sensors and device.sensors[key].scale == "CELSIUS" else UnitOfTemperature.FAHRENHEIT ), state_class=SensorStateClass.MEASUREMENT, diff --git a/homeassistant/components/alexa_devices/switch.py b/homeassistant/components/alexa_devices/switch.py index 2994ab777514dc..003f57620798c0 100644 --- a/homeassistant/components/alexa_devices/switch.py +++ b/homeassistant/components/alexa_devices/switch.py @@ -29,7 +29,9 @@ class AmazonSwitchEntityDescription(SwitchEntityDescription): is_on_fn: Callable[[AmazonDevice], bool] is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: ( - device.online and device.sensors[key].error is False + device.online + and (sensor := device.sensors.get(key)) is not None + and sensor.error is False ) method: str From c0fe4861f9138bf2c2d9f68029e24cfee020c619 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 5 Oct 2025 14:36:57 +0200 Subject: [PATCH 10/16] Align Shelly `presencezone` entity to the new API/firmware (#153737) --- homeassistant/components/shelly/binary_sensor.py | 2 +- tests/components/shelly/test_binary_sensor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index d292e2baf38b4b..3cce2f0183f555 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -319,7 +319,7 @@ def __init__( ), "presencezone_state": RpcBinarySensorDescription( key="presencezone", - sub_key="state", + sub_key="value", name="Occupancy", device_class=BinarySensorDeviceClass.OCCUPANCY, entity_class=RpcPresenceBinarySensor, diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index ed764ddf601ae5..090a0b47c3c19f 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -641,7 +641,7 @@ async def test_rpc_presencezone_component( monkeypatch.setattr(mock_rpc_device, "config", config) status = deepcopy(mock_rpc_device.status) - status["presencezone:200"] = {"state": True, "num_objects": 3} + status["presencezone:200"] = {"value": True, "num_objects": 3} monkeypatch.setattr(mock_rpc_device, "status", status) mock_config_entry = await init_integration(hass, 4) @@ -655,7 +655,7 @@ async def test_rpc_presencezone_component( assert entry.unique_id == "123456789ABC-presencezone:200-presencezone_state" mutate_rpc_device_status( - monkeypatch, mock_rpc_device, "presencezone:200", "state", False + monkeypatch, mock_rpc_device, "presencezone:200", "value", False ) mock_rpc_device.mock_update() From 618fe81207a02aff97e18a1fdc1ac07f94998689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Sun, 5 Oct 2025 14:49:34 +0200 Subject: [PATCH 11/16] Check if firmware is outdated when adding an Airthings BLE device (#153559) --- .../components/airthings_ble/config_flow.py | 9 +++ .../components/airthings_ble/strings.json | 1 + .../airthings_ble/test_config_flow.py | 69 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/homeassistant/components/airthings_ble/config_flow.py b/homeassistant/components/airthings_ble/config_flow.py index fa6a52a5a79834..c9b1ffbc81d58d 100644 --- a/homeassistant/components/airthings_ble/config_flow.py +++ b/homeassistant/components/airthings_ble/config_flow.py @@ -117,6 +117,12 @@ async def async_step_bluetooth_confirm( ) -> ConfigFlowResult: """Confirm discovery.""" if user_input is not None: + if ( + self._discovered_device is not None + and self._discovered_device.device.firmware.need_firmware_upgrade + ): + return self.async_abort(reason="firmware_upgrade_required") + return self.async_create_entry( title=self.context["title_placeholders"]["name"], data={} ) @@ -137,6 +143,9 @@ async def async_step_user( self._abort_if_unique_id_configured() discovery = self._discovered_devices[address] + if discovery.device.firmware.need_firmware_upgrade: + return self.async_abort(reason="firmware_upgrade_required") + self.context["title_placeholders"] = { "name": discovery.name, } diff --git a/homeassistant/components/airthings_ble/strings.json b/homeassistant/components/airthings_ble/strings.json index f73546bbe422e6..f5639e8da8f133 100644 --- a/homeassistant/components/airthings_ble/strings.json +++ b/homeassistant/components/airthings_ble/strings.json @@ -20,6 +20,7 @@ "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "firmware_upgrade_required": "Your device requires a firmware upgrade. Please use the Airthings app (Android/iOS) to upgrade it.", "unknown": "[%key:common::config_flow::error::unknown%]" } }, diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 42db22a99153ce..8203892adb92e1 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -281,3 +281,72 @@ async def test_unsupported_device(hass: HomeAssistant) -> None: ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "no_devices_found" + + +async def test_bluetooth_confirm_firmware_required(hass: HomeAssistant) -> None: + """Test discovery via bluetooth with a valid device.""" + device = AirthingsDevice( + manufacturer="Airthings AS", + model=AirthingsDeviceType.WAVE_ENHANCE_EU, + name="Airthings Wave Enhance", + identifier="123456", + ) + device.firmware.update_current_version("1.0.0") + device.firmware.update_required_version("2.6.1") + with ( + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble(device), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_BLUETOOTH}, + data=WAVE_SERVICE_INFO, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "bluetooth_confirm" + + with patch_async_setup_entry(): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"not": "empty"} + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "firmware_upgrade_required" + + +async def test_step_user_firmware_required(hass: HomeAssistant) -> None: + """Test the user has selected a device with a firmware upgrade required.""" + device = AirthingsDevice( + manufacturer="Airthings AS", + model=AirthingsDeviceType.WAVE_ENHANCE_EU, + name="Airthings Wave Enhance", + identifier="123456", + ) + device.firmware.update_current_version("1.0.0") + device.firmware.update_required_version("2.6.1") + + with ( + patch( + "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", + return_value=[WAVE_SERVICE_INFO], + ), + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble(device), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.airthings_ble.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_ADDRESS: "cc:cc:cc:cc:cc:cc"} + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "firmware_upgrade_required" From 2b370a0eca4bc8425c85eca28a5ff0913721e5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Sun, 5 Oct 2025 15:23:02 +0200 Subject: [PATCH 12/16] Use full serial number when adding an Airthings device (#153499) --- .../components/airthings_ble/config_flow.py | 2 +- tests/components/airthings_ble/test_config_flow.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/airthings_ble/config_flow.py b/homeassistant/components/airthings_ble/config_flow.py index c9b1ffbc81d58d..94660506b38572 100644 --- a/homeassistant/components/airthings_ble/config_flow.py +++ b/homeassistant/components/airthings_ble/config_flow.py @@ -44,7 +44,7 @@ def get_name(device: AirthingsDevice) -> str: name = device.friendly_name() if identifier := device.identifier: - name += f" ({identifier})" + name += f" ({device.model.value}{identifier})" return name diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 8203892adb92e1..49031a7840c23d 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -47,7 +47,7 @@ async def test_bluetooth_discovery(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["step_id"] == "bluetooth_confirm" assert result["description_placeholders"] == { - "name": "Airthings Wave Plus (123456)" + "name": "Airthings Wave Plus (2930123456)" } with patch_async_setup_entry(): @@ -56,7 +56,7 @@ async def test_bluetooth_discovery(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "Airthings Wave Plus (123456)" + assert result["title"] == "Airthings Wave Plus (2930123456)" assert result["result"].unique_id == "cc:cc:cc:cc:cc:cc" @@ -136,7 +136,7 @@ async def test_user_setup(hass: HomeAssistant) -> None: schema = result["data_schema"].schema assert schema.get(CONF_ADDRESS).container == { - "cc:cc:cc:cc:cc:cc": "Airthings Wave Plus (123456)" + "cc:cc:cc:cc:cc:cc": "Airthings Wave Plus (2930123456)" } with patch( @@ -149,7 +149,7 @@ async def test_user_setup(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "Airthings Wave Plus (123456)" + assert result["title"] == "Airthings Wave Plus (2930123456)" assert result["result"].unique_id == "cc:cc:cc:cc:cc:cc" @@ -186,7 +186,7 @@ async def test_user_setup_replaces_ignored_device(hass: HomeAssistant) -> None: schema = result["data_schema"].schema assert schema.get(CONF_ADDRESS).container == { - "cc:cc:cc:cc:cc:cc": "Airthings Wave Plus (123456)" + "cc:cc:cc:cc:cc:cc": "Airthings Wave Plus (2930123456)" } with patch( @@ -199,7 +199,7 @@ async def test_user_setup_replaces_ignored_device(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "Airthings Wave Plus (123456)" + assert result["title"] == "Airthings Wave Plus (2930123456)" assert result["result"].unique_id == "cc:cc:cc:cc:cc:cc" From 0d4737d360d777781a70187414af4bcfebb8d868 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Oct 2025 08:49:55 -0500 Subject: [PATCH 13/16] Bump aiohomekit to 3.2.20 (#153750) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 1acaae2b583426..09cd880a4929b7 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -14,6 +14,6 @@ "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "iot_class": "local_push", "loggers": ["aiohomekit", "commentjson"], - "requirements": ["aiohomekit==3.2.19"], + "requirements": ["aiohomekit==3.2.20"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 3b1f49b57a9927..09330429e91c52 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -271,7 +271,7 @@ aiohasupervisor==0.3.3 aiohomeconnect==0.20.0 # homeassistant.components.homekit_controller -aiohomekit==3.2.19 +aiohomekit==3.2.20 # homeassistant.components.mcp_server aiohttp_sse==2.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d85d947b67896e..0fd7e67198f2fe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -256,7 +256,7 @@ aiohasupervisor==0.3.3 aiohomeconnect==0.20.0 # homeassistant.components.homekit_controller -aiohomekit==3.2.19 +aiohomekit==3.2.20 # homeassistant.components.mcp_server aiohttp_sse==2.2.0 From b2a2868afde601856d63c9b9d8f99c434c88dad0 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 5 Oct 2025 16:51:46 +0300 Subject: [PATCH 14/16] AGENTS.md (#153680) --- .github/copilot-instructions.md => AGENTS.md | 0 CLAUDE.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/copilot-instructions.md => AGENTS.md (100%) diff --git a/.github/copilot-instructions.md b/AGENTS.md similarity index 100% rename from .github/copilot-instructions.md rename to AGENTS.md diff --git a/CLAUDE.md b/CLAUDE.md index 02dd134122ea96..47dc3e3d863cfb 120000 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -.github/copilot-instructions.md \ No newline at end of file +AGENTS.md \ No newline at end of file From 98f8f15e908c96224fb050a387c60a073b3497f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Sun, 5 Oct 2025 16:18:47 +0200 Subject: [PATCH 15/16] Fix crash when setting up Airthings BLE device (#153510) --- .../components/airthings_ble/config_flow.py | 13 ++++++++++--- tests/components/airthings_ble/test_config_flow.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/airthings_ble/config_flow.py b/homeassistant/components/airthings_ble/config_flow.py index 94660506b38572..6a6857d95b34fb 100644 --- a/homeassistant/components/airthings_ble/config_flow.py +++ b/homeassistant/components/airthings_ble/config_flow.py @@ -8,6 +8,7 @@ from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice from bleak import BleakError +from habluetooth import BluetoothServiceInfoBleak import voluptuous as vol from homeassistant.components import bluetooth @@ -155,21 +156,27 @@ async def async_step_user( return self.async_create_entry(title=discovery.name, data={}) current_addresses = self._async_current_ids(include_ignore=False) + devices: list[BluetoothServiceInfoBleak] = [] for discovery_info in async_discovered_service_info(self.hass): address = discovery_info.address if address in current_addresses or address in self._discovered_devices: continue - if MFCT_ID not in discovery_info.manufacturer_data: continue - if not any(uuid in SERVICE_UUIDS for uuid in discovery_info.service_uuids): continue + devices.append(discovery_info) + for discovery_info in devices: + address = discovery_info.address try: device = await self._get_device_data(discovery_info) except AirthingsDeviceUpdateError: - return self.async_abort(reason="cannot_connect") + _LOGGER.error( + "Error connecting to and getting data from %s", + discovery_info.address, + ) + continue except Exception: _LOGGER.exception("Unknown error occurred") return self.async_abort(reason="unknown") diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 49031a7840c23d..a65c51b3fd6fa0 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -267,7 +267,7 @@ async def test_user_setup_unable_to_connect(hass: HomeAssistant) -> None: ) assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == "no_devices_found" async def test_unsupported_device(hass: HomeAssistant) -> None: From 9209e419ec9bb608ee73f3ded5411e8c096a01b5 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Sun, 5 Oct 2025 16:36:42 +0200 Subject: [PATCH 16/16] Change style for critical number entities in ViCare integration (#153634) --- homeassistant/components/vicare/number.py | 16 +++++++ .../vicare/snapshots/test_number.ambr | 44 +++++++++---------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/vicare/number.py b/homeassistant/components/vicare/number.py index 04c4088bd3e55b..68e310c089e781 100644 --- a/homeassistant/components/vicare/number.py +++ b/homeassistant/components/vicare/number.py @@ -24,6 +24,7 @@ NumberDeviceClass, NumberEntity, NumberEntityDescription, + NumberMode, ) from homeassistant.const import EntityCategory, UnitOfTemperature from homeassistant.core import HomeAssistant @@ -59,6 +60,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDomesticHotWaterConfiguredTemperature(), value_setter=lambda api, value: api.setDomesticHotWaterTemperature(value), min_value_getter=lambda api: api.getDomesticHotWaterMinTemperature(), @@ -71,6 +73,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDomesticHotWaterConfiguredTemperature2(), value_setter=lambda api, value: api.setDomesticHotWaterTemperature2(value), # no getters for min, max, stepping exposed yet, using static values @@ -84,6 +87,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.KELVIN, + mode=NumberMode.BOX, value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOn(), value_setter=lambda api, value: api.setDomesticHotWaterHysteresisSwitchOn( value @@ -98,6 +102,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.KELVIN, + mode=NumberMode.BOX, value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOff(), value_setter=lambda api, value: api.setDomesticHotWaterHysteresisSwitchOff( value @@ -116,6 +121,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getHeatingCurveShift(), value_setter=lambda api, shift: ( api.setHeatingCurve(shift, api.getHeatingCurveSlope()) @@ -131,6 +137,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM key="heating curve slope", translation_key="heating_curve_slope", entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, value_getter=lambda api: api.getHeatingCurveSlope(), value_setter=lambda api, slope: ( api.setHeatingCurve(api.getHeatingCurveShift(), slope) @@ -148,6 +155,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.NORMAL ), @@ -168,6 +176,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.REDUCED ), @@ -188,6 +197,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.COMFORT ), @@ -208,6 +218,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.NORMAL_HEATING ), @@ -230,6 +241,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.REDUCED_HEATING ), @@ -252,6 +264,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.COMFORT_HEATING ), @@ -274,6 +287,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.NORMAL_COOLING ), @@ -296,6 +310,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.REDUCED_COOLING ), @@ -318,6 +333,7 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, + mode=NumberMode.BOX, value_getter=lambda api: api.getDesiredTemperatureForProgram( HeatingProgram.COMFORT_COOLING ), diff --git a/tests/components/vicare/snapshots/test_number.ambr b/tests/components/vicare/snapshots/test_number.ambr index 729d1403ad81a1..8a271d5d0f4051 100644 --- a/tests/components/vicare/snapshots/test_number.ambr +++ b/tests/components/vicare/snapshots/test_number.ambr @@ -7,7 +7,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -46,7 +46,7 @@ 'friendly_name': 'model0 Comfort temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }), @@ -66,7 +66,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -105,7 +105,7 @@ 'friendly_name': 'model0 Comfort temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }), @@ -125,7 +125,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1, }), 'config_entry_id': , @@ -164,7 +164,7 @@ 'friendly_name': 'model0 DHW temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1, 'unit_of_measurement': , }), @@ -184,7 +184,7 @@ 'capabilities': dict({ 'max': 40, 'min': -13, - 'mode': , + 'mode': , 'step': 1, }), 'config_entry_id': , @@ -223,7 +223,7 @@ 'friendly_name': 'model0 Heating curve shift', 'max': 40, 'min': -13, - 'mode': , + 'mode': , 'step': 1, 'unit_of_measurement': , }), @@ -243,7 +243,7 @@ 'capabilities': dict({ 'max': 40, 'min': -13, - 'mode': , + 'mode': , 'step': 1, }), 'config_entry_id': , @@ -282,7 +282,7 @@ 'friendly_name': 'model0 Heating curve shift', 'max': 40, 'min': -13, - 'mode': , + 'mode': , 'step': 1, 'unit_of_measurement': , }), @@ -302,7 +302,7 @@ 'capabilities': dict({ 'max': 3.5, 'min': 0.2, - 'mode': , + 'mode': , 'step': 0.1, }), 'config_entry_id': , @@ -340,7 +340,7 @@ 'friendly_name': 'model0 Heating curve slope', 'max': 3.5, 'min': 0.2, - 'mode': , + 'mode': , 'step': 0.1, }), 'context': , @@ -359,7 +359,7 @@ 'capabilities': dict({ 'max': 3.5, 'min': 0.2, - 'mode': , + 'mode': , 'step': 0.1, }), 'config_entry_id': , @@ -397,7 +397,7 @@ 'friendly_name': 'model0 Heating curve slope', 'max': 3.5, 'min': 0.2, - 'mode': , + 'mode': , 'step': 0.1, }), 'context': , @@ -416,7 +416,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -455,7 +455,7 @@ 'friendly_name': 'model0 Normal temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }), @@ -475,7 +475,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -514,7 +514,7 @@ 'friendly_name': 'model0 Normal temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }), @@ -534,7 +534,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -573,7 +573,7 @@ 'friendly_name': 'model0 Reduced temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }), @@ -593,7 +593,7 @@ 'capabilities': dict({ 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, }), 'config_entry_id': , @@ -632,7 +632,7 @@ 'friendly_name': 'model0 Reduced temperature', 'max': 100.0, 'min': 0.0, - 'mode': , + 'mode': , 'step': 1.0, 'unit_of_measurement': , }),