From 8c6547f1b64f4a3d9f10090b97383353c9367892 Mon Sep 17 00:00:00 2001
From: vexofp <vexofp@gmail.com>
Date: Tue, 2 Jan 2024 05:59:40 -0500
Subject: [PATCH 01/21] Pass default SSLContext instances to Octoprint custom
 HTTP sessions (#105351)

---
 homeassistant/components/octoprint/__init__.py    | 5 ++++-
 homeassistant/components/octoprint/config_flow.py | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py
index 5fd2182ca00..50ba6c964f3 100644
--- a/homeassistant/components/octoprint/__init__.py
+++ b/homeassistant/components/octoprint/__init__.py
@@ -26,6 +26,7 @@ from homeassistant.core import Event, HomeAssistant, callback
 import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.typing import ConfigType
 from homeassistant.util import slugify as util_slugify
+from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
 
 from .const import DOMAIN
 from .coordinator import OctoprintDataUpdateCoordinator
@@ -159,7 +160,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 
     connector = aiohttp.TCPConnector(
         force_close=True,
-        ssl=False if not entry.data[CONF_VERIFY_SSL] else None,
+        ssl=get_default_no_verify_context()
+        if not entry.data[CONF_VERIFY_SSL]
+        else get_default_context(),
     )
     session = aiohttp.ClientSession(connector=connector)
 
diff --git a/homeassistant/components/octoprint/config_flow.py b/homeassistant/components/octoprint/config_flow.py
index 09ac53ecf5b..696898400bf 100644
--- a/homeassistant/components/octoprint/config_flow.py
+++ b/homeassistant/components/octoprint/config_flow.py
@@ -24,6 +24,7 @@ from homeassistant.const import (
 )
 from homeassistant.data_entry_flow import FlowResult
 import homeassistant.helpers.config_validation as cv
+from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
 
 from .const import DOMAIN
 
@@ -264,7 +265,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
 
         connector = aiohttp.TCPConnector(
             force_close=True,
-            ssl=False if not verify_ssl else None,
+            ssl=get_default_no_verify_context()
+            if not verify_ssl
+            else get_default_context(),
         )
         session = aiohttp.ClientSession(connector=connector)
         self._sessions.append(session)

From 18f663d4980cf2d07e81a247034fb9bbc0e2d0b4 Mon Sep 17 00:00:00 2001
From: Allen Porter <allen@thebends.org>
Date: Tue, 23 Jan 2024 01:50:00 -0800
Subject: [PATCH 02/21] Reduce overhead for google calendar state updates
 (#108133)

---
 homeassistant/components/google/calendar.py   | 30 +++++++++++++++++--
 homeassistant/components/google/manifest.json |  2 +-
 requirements_all.txt                          |  1 +
 requirements_test_all.txt                     |  1 +
 4 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py
index 3e34a7234a4..88f59ff44f7 100644
--- a/homeassistant/components/google/calendar.py
+++ b/homeassistant/components/google/calendar.py
@@ -4,6 +4,7 @@ from __future__ import annotations
 
 from collections.abc import Iterable
 from datetime import datetime, timedelta
+import itertools
 import logging
 from typing import Any, cast
 
@@ -18,6 +19,7 @@ from gcal_sync.model import AccessRole, DateOrDatetime, Event
 from gcal_sync.store import ScopedCalendarStore
 from gcal_sync.sync import CalendarEventSyncManager
 from gcal_sync.timeline import Timeline
+from ical.iter import SortableItemValue
 
 from homeassistant.components.calendar import (
     CREATE_EVENT_SCHEMA,
@@ -76,6 +78,9 @@ from .const import (
 _LOGGER = logging.getLogger(__name__)
 
 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
+# Maximum number of upcoming events to consider for state changes between
+# coordinator updates.
+MAX_UPCOMING_EVENTS = 20
 
 # Avoid syncing super old data on initial syncs. Note that old but active
 # recurring events are still included.
@@ -244,6 +249,22 @@ async def async_setup_entry(
         )
 
 
+def _truncate_timeline(timeline: Timeline, max_events: int) -> Timeline:
+    """Truncate the timeline to a maximum number of events.
+
+    This is used to avoid repeated expansion of recurring events during
+    state machine updates.
+    """
+    upcoming = timeline.active_after(dt_util.now())
+    truncated = list(itertools.islice(upcoming, max_events))
+    return Timeline(
+        [
+            SortableItemValue(event.timespan_of(dt_util.DEFAULT_TIME_ZONE), event)
+            for event in truncated
+        ]
+    )
+
+
 class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
     """Coordinator for calendar RPC calls that use an efficient sync."""
 
@@ -263,6 +284,7 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
             update_interval=MIN_TIME_BETWEEN_UPDATES,
         )
         self.sync = sync
+        self._upcoming_timeline: Timeline | None = None
 
     async def _async_update_data(self) -> Timeline:
         """Fetch data from API endpoint."""
@@ -271,9 +293,11 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
         except ApiException as err:
             raise UpdateFailed(f"Error communicating with API: {err}") from err
 
-        return await self.sync.store_service.async_get_timeline(
+        timeline = await self.sync.store_service.async_get_timeline(
             dt_util.DEFAULT_TIME_ZONE
         )
+        self._upcoming_timeline = _truncate_timeline(timeline, MAX_UPCOMING_EVENTS)
+        return timeline
 
     async def async_get_events(
         self, start_date: datetime, end_date: datetime
@@ -291,8 +315,8 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
     @property
     def upcoming(self) -> Iterable[Event] | None:
         """Return upcoming events if any."""
-        if self.data:
-            return self.data.active_after(dt_util.now())
+        if self._upcoming_timeline:
+            return self._upcoming_timeline.active_after(dt_util.now())
         return None
 
 
diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json
index 27e462a380e..d0705f9382a 100644
--- a/homeassistant/components/google/manifest.json
+++ b/homeassistant/components/google/manifest.json
@@ -7,5 +7,5 @@
   "documentation": "https://www.home-assistant.io/integrations/calendar.google",
   "iot_class": "cloud_polling",
   "loggers": ["googleapiclient"],
-  "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3"]
+  "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==6.1.1"]
 }
diff --git a/requirements_all.txt b/requirements_all.txt
index cff90b6a6cd..bbe4a6fd90c 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1076,6 +1076,7 @@ ibeacon-ble==1.0.1
 # homeassistant.components.watson_iot
 ibmiotf==0.3.4
 
+# homeassistant.components.google
 # homeassistant.components.local_calendar
 # homeassistant.components.local_todo
 ical==6.1.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 8877471660a..a2bbeea023a 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -860,6 +860,7 @@ iaqualink==0.5.0
 # homeassistant.components.ibeacon
 ibeacon-ble==1.0.1
 
+# homeassistant.components.google
 # homeassistant.components.local_calendar
 # homeassistant.components.local_todo
 ical==6.1.1

From 3a510f84a737a5d12a95f4b5b53b459eb75b7c13 Mon Sep 17 00:00:00 2001
From: jmwaldrip <jmwaldrip@gmail.com>
Date: Mon, 22 Jan 2024 12:22:54 -0800
Subject: [PATCH 03/21] Fix SleepIQ setting FootWarmer timer (#108433)

* Fixing foot warmer timer bug

* Fixing bug where temperature wasnt assigned to number entity causing tests to fail
---
 homeassistant/components/sleepiq/number.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/homeassistant/components/sleepiq/number.py b/homeassistant/components/sleepiq/number.py
index 520e11bb331..4f90ef7dbdc 100644
--- a/homeassistant/components/sleepiq/number.py
+++ b/homeassistant/components/sleepiq/number.py
@@ -5,7 +5,13 @@ from collections.abc import Callable, Coroutine
 from dataclasses import dataclass
 from typing import Any, cast
 
-from asyncsleepiq import SleepIQActuator, SleepIQBed, SleepIQFootWarmer, SleepIQSleeper
+from asyncsleepiq import (
+    FootWarmingTemps,
+    SleepIQActuator,
+    SleepIQBed,
+    SleepIQFootWarmer,
+    SleepIQSleeper,
+)
 
 from homeassistant.components.number import NumberEntity, NumberEntityDescription
 from homeassistant.config_entries import ConfigEntry
@@ -79,6 +85,10 @@ def _get_sleeper_unique_id(bed: SleepIQBed, sleeper: SleepIQSleeper) -> str:
 async def _async_set_foot_warmer_time(
     foot_warmer: SleepIQFootWarmer, time: int
 ) -> None:
+    temperature = FootWarmingTemps(foot_warmer.temperature)
+    if temperature != FootWarmingTemps.OFF:
+        await foot_warmer.turn_on(temperature, time)
+
     foot_warmer.timer = time
 
 

From 279f7264e74c67b584b429dc363ea831718e6062 Mon Sep 17 00:00:00 2001
From: Florian Kisser <github@kssr.net>
Date: Sun, 21 Jan 2024 02:37:13 +0100
Subject: [PATCH 04/21] Fix zha illuminance measured value mapping (#108547)

---
 homeassistant/components/zha/sensor.py | 6 +++++-
 tests/components/zha/test_sensor.py    | 6 ++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py
index ea5d09dd6f4..8bf8ca96d77 100644
--- a/homeassistant/components/zha/sensor.py
+++ b/homeassistant/components/zha/sensor.py
@@ -481,8 +481,12 @@ class Illuminance(Sensor):
     _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
     _attr_native_unit_of_measurement = LIGHT_LUX
 
-    def formatter(self, value: int) -> int:
+    def formatter(self, value: int) -> int | None:
         """Convert illumination data."""
+        if value == 0:
+            return 0
+        if value == 0xFFFF:
+            return None
         return round(pow(10, ((value - 1) / 10000)))
 
 
diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py
index d9a61b12357..0d3035f9717 100644
--- a/tests/components/zha/test_sensor.py
+++ b/tests/components/zha/test_sensor.py
@@ -136,6 +136,12 @@ async def async_test_illuminance(hass, cluster, entity_id):
     await send_attributes_report(hass, cluster, {1: 1, 0: 10, 2: 20})
     assert_state(hass, entity_id, "1", LIGHT_LUX)
 
+    await send_attributes_report(hass, cluster, {1: 0, 0: 0, 2: 20})
+    assert_state(hass, entity_id, "0", LIGHT_LUX)
+
+    await send_attributes_report(hass, cluster, {1: 0, 0: 0xFFFF, 2: 20})
+    assert_state(hass, entity_id, "unknown", LIGHT_LUX)
+
 
 async def async_test_metering(hass, cluster, entity_id):
     """Test Smart Energy metering sensor."""

From 4db6f7ce59e8372572d1b495ccc6b2f492f61fdd Mon Sep 17 00:00:00 2001
From: Matrix <justin@yosmart.com>
Date: Sun, 28 Jan 2024 13:38:42 +0800
Subject: [PATCH 05/21] Bump yolink-api to 0.3.6 fix aiomqtt breaking changes
 (#108555)

* bump yolink-api to 0.3.5

* bump yolink-api to 0.3.6
---
 homeassistant/components/yolink/manifest.json | 2 +-
 requirements_all.txt                          | 2 +-
 requirements_test_all.txt                     | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json
index a42687a3551..6fd62ce571c 100644
--- a/homeassistant/components/yolink/manifest.json
+++ b/homeassistant/components/yolink/manifest.json
@@ -6,5 +6,5 @@
   "dependencies": ["auth", "application_credentials"],
   "documentation": "https://www.home-assistant.io/integrations/yolink",
   "iot_class": "cloud_push",
-  "requirements": ["yolink-api==0.3.4"]
+  "requirements": ["yolink-api==0.3.6"]
 }
diff --git a/requirements_all.txt b/requirements_all.txt
index bbe4a6fd90c..beed6c6eb01 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2843,7 +2843,7 @@ yeelight==0.7.14
 yeelightsunflower==0.0.10
 
 # homeassistant.components.yolink
-yolink-api==0.3.4
+yolink-api==0.3.6
 
 # homeassistant.components.youless
 youless-api==1.0.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index a2bbeea023a..1ef4e6809a9 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -2151,7 +2151,7 @@ yalexs==1.10.0
 yeelight==0.7.14
 
 # homeassistant.components.yolink
-yolink-api==0.3.4
+yolink-api==0.3.6
 
 # homeassistant.components.youless
 youless-api==1.0.1

From bbe80c60491aca4971e4355235380ac62d3d5b1a Mon Sep 17 00:00:00 2001
From: Michal Ziemski <michal@terrestryal.com>
Date: Tue, 23 Jan 2024 15:14:41 +0100
Subject: [PATCH 06/21] Update openerz-api to 0.3.0 (#108575)

---
 homeassistant/components/openerz/manifest.json | 2 +-
 requirements_all.txt                           | 2 +-
 requirements_test_all.txt                      | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/openerz/manifest.json b/homeassistant/components/openerz/manifest.json
index 181e0bd870a..c7a5a202568 100644
--- a/homeassistant/components/openerz/manifest.json
+++ b/homeassistant/components/openerz/manifest.json
@@ -5,5 +5,5 @@
   "documentation": "https://www.home-assistant.io/integrations/openerz",
   "iot_class": "cloud_polling",
   "loggers": ["openerz_api"],
-  "requirements": ["openerz-api==0.2.0"]
+  "requirements": ["openerz-api==0.3.0"]
 }
diff --git a/requirements_all.txt b/requirements_all.txt
index beed6c6eb01..062423be67e 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1414,7 +1414,7 @@ openai==1.3.8
 # opencv-python-headless==4.6.0.66
 
 # homeassistant.components.openerz
-openerz-api==0.2.0
+openerz-api==0.3.0
 
 # homeassistant.components.openevse
 openevsewifi==1.1.2
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 1ef4e6809a9..acc7b389699 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1111,7 +1111,7 @@ open-meteo==0.3.1
 openai==1.3.8
 
 # homeassistant.components.openerz
-openerz-api==0.2.0
+openerz-api==0.3.0
 
 # homeassistant.components.openhome
 openhomedevice==2.2.0

From b17b4c3350518661f9278ecfffb148b825fe8edf Mon Sep 17 00:00:00 2001
From: Simone Chemelli <simone.chemelli@gmail.com>
Date: Mon, 22 Jan 2024 13:51:17 +0100
Subject: [PATCH 07/21] Bump aiovodafone to 0.5.4 (#108592)

---
 homeassistant/components/vodafone_station/manifest.json | 2 +-
 requirements_all.txt                                    | 2 +-
 requirements_test_all.txt                               | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/vodafone_station/manifest.json b/homeassistant/components/vodafone_station/manifest.json
index 20ea4db057e..ced871b7616 100644
--- a/homeassistant/components/vodafone_station/manifest.json
+++ b/homeassistant/components/vodafone_station/manifest.json
@@ -6,5 +6,5 @@
   "documentation": "https://www.home-assistant.io/integrations/vodafone_station",
   "iot_class": "local_polling",
   "loggers": ["aiovodafone"],
-  "requirements": ["aiovodafone==0.4.3"]
+  "requirements": ["aiovodafone==0.5.4"]
 }
diff --git a/requirements_all.txt b/requirements_all.txt
index 062423be67e..3d253b3a953 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -383,7 +383,7 @@ aiounifi==69
 aiovlc==0.1.0
 
 # homeassistant.components.vodafone_station
-aiovodafone==0.4.3
+aiovodafone==0.5.4
 
 # homeassistant.components.waqi
 aiowaqi==3.0.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index acc7b389699..7871af2ffb2 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -356,7 +356,7 @@ aiounifi==69
 aiovlc==0.1.0
 
 # homeassistant.components.vodafone_station
-aiovodafone==0.4.3
+aiovodafone==0.5.4
 
 # homeassistant.components.waqi
 aiowaqi==3.0.1

From 1b8c91dcb711a77a0340cabb8631af11f8be9f2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?=
 <stale.semb@airthings.com>
Date: Sun, 21 Jan 2024 22:20:07 +0100
Subject: [PATCH 08/21] Bump airthings-ble to 0.6.0 (#108612)

---
 homeassistant/components/airthings_ble/__init__.py   | 3 ++-
 homeassistant/components/airthings_ble/manifest.json | 2 +-
 requirements_all.txt                                 | 2 +-
 requirements_test_all.txt                            | 2 +-
 4 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/airthings_ble/__init__.py b/homeassistant/components/airthings_ble/__init__.py
index 1d62442f14d..3a97813741b 100644
--- a/homeassistant/components/airthings_ble/__init__.py
+++ b/homeassistant/components/airthings_ble/__init__.py
@@ -40,10 +40,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
             f"Could not find Airthings device with address {address}"
         )
 
+    airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric)
+
     async def _async_update_method() -> AirthingsDevice:
         """Get data from Airthings BLE."""
         ble_device = bluetooth.async_ble_device_from_address(hass, address)
-        airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric)
 
         try:
             data = await airthings.update_device(ble_device)  # type: ignore[arg-type]
diff --git a/homeassistant/components/airthings_ble/manifest.json b/homeassistant/components/airthings_ble/manifest.json
index cb7114ff8ff..03b42410d66 100644
--- a/homeassistant/components/airthings_ble/manifest.json
+++ b/homeassistant/components/airthings_ble/manifest.json
@@ -24,5 +24,5 @@
   "dependencies": ["bluetooth_adapters"],
   "documentation": "https://www.home-assistant.io/integrations/airthings_ble",
   "iot_class": "local_polling",
-  "requirements": ["airthings-ble==0.5.6-2"]
+  "requirements": ["airthings-ble==0.6.0"]
 }
diff --git a/requirements_all.txt b/requirements_all.txt
index 3d253b3a953..99bde907caa 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -404,7 +404,7 @@ aioymaps==1.2.2
 airly==1.1.0
 
 # homeassistant.components.airthings_ble
-airthings-ble==0.5.6-2
+airthings-ble==0.6.0
 
 # homeassistant.components.airthings
 airthings-cloud==0.1.0
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 7871af2ffb2..3262ea26b88 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -377,7 +377,7 @@ aioymaps==1.2.2
 airly==1.1.0
 
 # homeassistant.components.airthings_ble
-airthings-ble==0.5.6-2
+airthings-ble==0.6.0
 
 # homeassistant.components.airthings
 airthings-cloud==0.1.0

From 58c96ff79643e4fdd7d74234d60ef886c06de2fb Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Date: Tue, 23 Jan 2024 23:36:44 +0100
Subject: [PATCH 09/21] Fix alexa fails reporting the state in specific cases
 (#108743)

* Fix alexa fails reporting the state in specific cases

* More cases
---
 .../components/alexa/capabilities.py          | 32 +++++++++++--------
 homeassistant/components/alexa/entities.py    |  5 +--
 homeassistant/components/alexa/handlers.py    |  8 ++---
 3 files changed, 25 insertions(+), 20 deletions(-)

diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py
index ab3bd8591fd..d30f3f7376d 100644
--- a/homeassistant/components/alexa/capabilities.py
+++ b/homeassistant/components/alexa/capabilities.py
@@ -860,8 +860,8 @@ class AlexaInputController(AlexaCapability):
 
     def inputs(self) -> list[dict[str, str]] | None:
         """Return the list of valid supported inputs."""
-        source_list: list[Any] = self.entity.attributes.get(
-            media_player.ATTR_INPUT_SOURCE_LIST, []
+        source_list: list[Any] = (
+            self.entity.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
         )
         return AlexaInputController.get_valid_inputs(source_list)
 
@@ -1196,7 +1196,7 @@ class AlexaThermostatController(AlexaCapability):
             return None
 
         supported_modes: list[str] = []
-        hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES, [])
+        hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES) or []
         for mode in hvac_modes:
             if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
                 supported_modes.append(thermostat_mode)
@@ -1422,18 +1422,22 @@ class AlexaModeController(AlexaCapability):
 
         # Humidifier mode
         if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
-            mode = self.entity.attributes.get(humidifier.ATTR_MODE, None)
-            if mode in self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, []):
+            mode = self.entity.attributes.get(humidifier.ATTR_MODE)
+            modes: list[str] = (
+                self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES) or []
+            )
+            if mode in modes:
                 return f"{humidifier.ATTR_MODE}.{mode}"
 
         # Water heater operation mode
         if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
             operation_mode = self.entity.attributes.get(
-                water_heater.ATTR_OPERATION_MODE, None
+                water_heater.ATTR_OPERATION_MODE
             )
-            if operation_mode in self.entity.attributes.get(
-                water_heater.ATTR_OPERATION_LIST, []
-            ):
+            operation_modes: list[str] = (
+                self.entity.attributes.get(water_heater.ATTR_OPERATION_LIST) or []
+            )
+            if operation_mode in operation_modes:
                 return f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}"
 
         # Cover Position
@@ -1492,7 +1496,7 @@ class AlexaModeController(AlexaCapability):
             self._resource = AlexaModeResource(
                 [AlexaGlobalCatalog.SETTING_PRESET], False
             )
-            preset_modes = self.entity.attributes.get(fan.ATTR_PRESET_MODES, [])
+            preset_modes = self.entity.attributes.get(fan.ATTR_PRESET_MODES) or []
             for preset_mode in preset_modes:
                 self._resource.add_mode(
                     f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
@@ -1508,7 +1512,7 @@ class AlexaModeController(AlexaCapability):
         # Humidifier modes
         if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
             self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
-            modes = self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, [])
+            modes = self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES) or []
             for mode in modes:
                 self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
             # Humidifiers or Fans with a single mode completely break Alexa discovery,
@@ -1522,8 +1526,8 @@ class AlexaModeController(AlexaCapability):
         # Water heater operation modes
         if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
             self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
-            operation_modes = self.entity.attributes.get(
-                water_heater.ATTR_OPERATION_LIST, []
+            operation_modes = (
+                self.entity.attributes.get(water_heater.ATTR_OPERATION_LIST) or []
             )
             for operation_mode in operation_modes:
                 self._resource.add_mode(
@@ -2368,7 +2372,7 @@ class AlexaEqualizerController(AlexaCapability):
         """Return the sound modes supported in the configurations object."""
         configurations = None
         supported_sound_modes = self.get_valid_inputs(
-            self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST, [])
+            self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST) or []
         )
         if supported_sound_modes:
             configurations = {"modes": {"supported": supported_sound_modes}}
diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py
index d0e265b8454..70679f8dafb 100644
--- a/homeassistant/components/alexa/entities.py
+++ b/homeassistant/components/alexa/entities.py
@@ -478,7 +478,7 @@ class ClimateCapabilities(AlexaEntity):
         if (
             self.entity.domain == climate.DOMAIN
             and climate.HVACMode.OFF
-            in self.entity.attributes.get(climate.ATTR_HVAC_MODES, [])
+            in (self.entity.attributes.get(climate.ATTR_HVAC_MODES) or [])
             or self.entity.domain == water_heater.DOMAIN
             and (supported_features & water_heater.WaterHeaterEntityFeature.ON_OFF)
         ):
@@ -742,7 +742,8 @@ class MediaPlayerCapabilities(AlexaEntity):
             and domain != "denonavr"
         ):
             inputs = AlexaEqualizerController.get_valid_inputs(
-                self.entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST, [])
+                self.entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
+                or []
             )
             if len(inputs) > 0:
                 yield AlexaEqualizerController(self.entity)
diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py
index 68702bc0533..463693f7da6 100644
--- a/homeassistant/components/alexa/handlers.py
+++ b/homeassistant/components/alexa/handlers.py
@@ -570,7 +570,7 @@ async def async_api_select_input(
 
     # Attempt to map the ALL UPPERCASE payload name to a source.
     # Strips trailing 1 to match single input devices.
-    source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST, [])
+    source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST) or []
     for source in source_list:
         formatted_source = (
             source.lower().replace("-", "").replace("_", "").replace(" ", "")
@@ -987,7 +987,7 @@ async def async_api_set_thermostat_mode(
     ha_preset = next((k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode), None)
 
     if ha_preset:
-        presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
+        presets = entity.attributes.get(climate.ATTR_PRESET_MODES) or []
 
         if ha_preset not in presets:
             msg = f"The requested thermostat mode {ha_preset} is not supported"
@@ -997,7 +997,7 @@ async def async_api_set_thermostat_mode(
         data[climate.ATTR_PRESET_MODE] = ha_preset
 
     elif mode == "CUSTOM":
-        operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES, [])
+        operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) or []
         custom_mode = directive.payload["thermostatMode"]["customName"]
         custom_mode = next(
             (k for k, v in API_THERMOSTAT_MODES_CUSTOM.items() if v == custom_mode),
@@ -1013,7 +1013,7 @@ async def async_api_set_thermostat_mode(
         data[climate.ATTR_HVAC_MODE] = custom_mode
 
     else:
-        operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES, [])
+        operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) or []
         ha_modes: dict[str, str] = {
             k: v for k, v in API_THERMOSTAT_MODES.items() if v == mode
         }

From eeafb61b8f05c83890ed690a8cc45dfc829f0118 Mon Sep 17 00:00:00 2001
From: puddly <32534428+puddly@users.noreply.github.com>
Date: Tue, 23 Jan 2024 21:04:59 -0500
Subject: [PATCH 10/21] Reduce log level of ZHA endpoint handler init (#108749)

* Reduce the log level of endpoint handler init failure to debug

* Reduce log level in unit test
---
 homeassistant/components/zha/core/endpoint.py | 6 +++---
 tests/components/zha/test_cluster_handlers.py | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/homeassistant/components/zha/core/endpoint.py b/homeassistant/components/zha/core/endpoint.py
index 4dbfccf6f25..eb91ec96c59 100644
--- a/homeassistant/components/zha/core/endpoint.py
+++ b/homeassistant/components/zha/core/endpoint.py
@@ -199,11 +199,11 @@ class Endpoint:
         results = await gather(*tasks, return_exceptions=True)
         for cluster_handler, outcome in zip(cluster_handlers, results):
             if isinstance(outcome, Exception):
-                cluster_handler.warning(
+                cluster_handler.debug(
                     "'%s' stage failed: %s", func_name, str(outcome), exc_info=outcome
                 )
-                continue
-            cluster_handler.debug("'%s' stage succeeded", func_name)
+            else:
+                cluster_handler.debug("'%s' stage succeeded", func_name)
 
     def async_new_entity(
         self,
diff --git a/tests/components/zha/test_cluster_handlers.py b/tests/components/zha/test_cluster_handlers.py
index 39f201e668e..46efe306b91 100644
--- a/tests/components/zha/test_cluster_handlers.py
+++ b/tests/components/zha/test_cluster_handlers.py
@@ -591,8 +591,8 @@ async def test_ep_cluster_handlers_configure(cluster_handler) -> None:
         assert ch.async_configure.call_count == 1
         assert ch.async_configure.await_count == 1
 
-    assert ch_3.warning.call_count == 2
-    assert ch_5.warning.call_count == 2
+    assert ch_3.debug.call_count == 2
+    assert ch_5.debug.call_count == 2
 
 
 async def test_poll_control_configure(poll_control_ch) -> None:

From 12126ebfdafb659d3d980e4b577f2bd6eabacd23 Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Date: Wed, 24 Jan 2024 19:47:07 +0100
Subject: [PATCH 11/21] Fix google_assistant climate modes might be None
 (#108793)

---
 homeassistant/components/google_assistant/trait.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py
index 9b8a95f0b4a..189d1354e26 100644
--- a/homeassistant/components/google_assistant/trait.py
+++ b/homeassistant/components/google_assistant/trait.py
@@ -1152,12 +1152,12 @@ class TemperatureSettingTrait(_Trait):
         modes = []
         attrs = self.state.attributes
 
-        for mode in attrs.get(climate.ATTR_HVAC_MODES, []):
+        for mode in attrs.get(climate.ATTR_HVAC_MODES) or []:
             google_mode = self.hvac_to_google.get(mode)
             if google_mode and google_mode not in modes:
                 modes.append(google_mode)
 
-        for preset in attrs.get(climate.ATTR_PRESET_MODES, []):
+        for preset in attrs.get(climate.ATTR_PRESET_MODES) or []:
             google_mode = self.preset_to_google.get(preset)
             if google_mode and google_mode not in modes:
                 modes.append(google_mode)
@@ -2094,9 +2094,10 @@ class InputSelectorTrait(_Trait):
     def sync_attributes(self):
         """Return mode attributes for a sync request."""
         attrs = self.state.attributes
+        sourcelist: list[str] = attrs.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
         inputs = [
             {"key": source, "names": [{"name_synonym": [source], "lang": "en"}]}
-            for source in attrs.get(media_player.ATTR_INPUT_SOURCE_LIST, [])
+            for source in sourcelist
         ]
 
         payload = {"availableInputs": inputs, "orderedInputs": True}

From d220cb5102351b38ec37edc1527d832c135c55ff Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Date: Wed, 24 Jan 2024 22:14:15 +0100
Subject: [PATCH 12/21] Fix unhandled exception on humidifier intent when
 available_modes is None (#108802)

---
 homeassistant/components/humidifier/intent.py | 2 +-
 tests/components/humidifier/test_intent.py    | 9 ++++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py
index d949874cc67..103521aeb04 100644
--- a/homeassistant/components/humidifier/intent.py
+++ b/homeassistant/components/humidifier/intent.py
@@ -110,7 +110,7 @@ class SetModeHandler(intent.IntentHandler):
         intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes")
         mode = slots["mode"]["value"]
 
-        if mode not in state.attributes.get(ATTR_AVAILABLE_MODES, []):
+        if mode not in (state.attributes.get(ATTR_AVAILABLE_MODES) or []):
             raise intent.IntentHandleError(
                 f"Entity {state.name} does not support {mode} mode"
             )
diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py
index cbdd5c3da26..d8c9f199f57 100644
--- a/tests/components/humidifier/test_intent.py
+++ b/tests/components/humidifier/test_intent.py
@@ -188,7 +188,10 @@ async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None:
     assert len(mode_calls) == 0
 
 
-async def test_intent_set_unknown_mode(hass: HomeAssistant) -> None:
+@pytest.mark.parametrize("available_modes", (["home", "away"], None))
+async def test_intent_set_unknown_mode(
+    hass: HomeAssistant, available_modes: list[str] | None
+) -> None:
     """Test the set mode intent for unsupported mode."""
     hass.states.async_set(
         "humidifier.bedroom_humidifier",
@@ -196,8 +199,8 @@ async def test_intent_set_unknown_mode(hass: HomeAssistant) -> None:
         {
             ATTR_HUMIDITY: 40,
             ATTR_SUPPORTED_FEATURES: 1,
-            ATTR_AVAILABLE_MODES: ["home", "away"],
-            ATTR_MODE: "home",
+            ATTR_AVAILABLE_MODES: available_modes,
+            ATTR_MODE: None,
         },
     )
     mode_calls = async_mock_service(hass, DOMAIN, SERVICE_SET_MODE)

From d697fd23b77a4e2f45cedb9df898f10ab98c52c3 Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Date: Wed, 24 Jan 2024 22:15:00 +0100
Subject: [PATCH 13/21] Fix processing supported color modes for emulated_hue
 (#108803)

---
 homeassistant/components/emulated_hue/hue_api.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py
index 0730eced60c..94ac97b6b36 100644
--- a/homeassistant/components/emulated_hue/hue_api.py
+++ b/homeassistant/components/emulated_hue/hue_api.py
@@ -386,7 +386,7 @@ class HueOneLightChangeView(HomeAssistantView):
         # Get the entity's supported features
         entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
         if entity.domain == light.DOMAIN:
-            color_modes = entity.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
+            color_modes = entity.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) or []
 
         # Parse the request
         parsed: dict[str, Any] = {
@@ -765,7 +765,7 @@ def _entity_unique_id(entity_id: str) -> str:
 
 def state_to_json(config: Config, state: State) -> dict[str, Any]:
     """Convert an entity to its Hue bridge JSON representation."""
-    color_modes = state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
+    color_modes = state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) or []
     unique_id = _entity_unique_id(state.entity_id)
     state_dict = get_entity_state_dict(config, state)
 

From b66339dbfe49e7d79b9989821dbe5ced45c35339 Mon Sep 17 00:00:00 2001
From: TheJulianJES <TheJulianJES@users.noreply.github.com>
Date: Thu, 25 Jan 2024 14:45:11 +0100
Subject: [PATCH 14/21] Reduce log level for creating ZHA cluster handler
 (#108809)

---
 homeassistant/components/zha/core/endpoint.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/homeassistant/components/zha/core/endpoint.py b/homeassistant/components/zha/core/endpoint.py
index eb91ec96c59..490a4e05ea2 100644
--- a/homeassistant/components/zha/core/endpoint.py
+++ b/homeassistant/components/zha/core/endpoint.py
@@ -132,7 +132,7 @@ class Endpoint:
             if not cluster_handler_class.matches(cluster, self):
                 cluster_handler_class = ClusterHandler
 
-            _LOGGER.info(
+            _LOGGER.debug(
                 "Creating cluster handler for cluster id: %s class: %s",
                 cluster_id,
                 cluster_handler_class,

From 0532c4343a34906d197972368bef328895e480f5 Mon Sep 17 00:00:00 2001
From: Yuxin Wang <yuxinwang.dev@gmail.com>
Date: Sat, 27 Jan 2024 02:34:29 -0500
Subject: [PATCH 15/21] Fix stalls in config flow of APCUPSD (#108931)

Fix deadlock in config flow of APCUPSD
---
 homeassistant/components/apcupsd/config_flow.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/homeassistant/components/apcupsd/config_flow.py b/homeassistant/components/apcupsd/config_flow.py
index 57002d7a2b2..99c78fd5d33 100644
--- a/homeassistant/components/apcupsd/config_flow.py
+++ b/homeassistant/components/apcupsd/config_flow.py
@@ -52,9 +52,8 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
 
         # Test the connection to the host and get the current status for serial number.
         coordinator = APCUPSdCoordinator(self.hass, host, port)
-
         await coordinator.async_request_refresh()
-        await self.hass.async_block_till_done()
+
         if isinstance(coordinator.last_exception, (UpdateFailed, asyncio.TimeoutError)):
             errors = {"base": "cannot_connect"}
             return self.async_show_form(

From 5903ec084b29f378a61957b033eccef7b6136a1b Mon Sep 17 00:00:00 2001
From: Joost Lekkerkerker <joostlek@outlook.com>
Date: Sun, 28 Jan 2024 19:18:22 +0100
Subject: [PATCH 16/21] Add strings to Sensirion BLE (#109001)

---
 .../components/sensirion_ble/strings.json     | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 homeassistant/components/sensirion_ble/strings.json

diff --git a/homeassistant/components/sensirion_ble/strings.json b/homeassistant/components/sensirion_ble/strings.json
new file mode 100644
index 00000000000..d1d544c2381
--- /dev/null
+++ b/homeassistant/components/sensirion_ble/strings.json
@@ -0,0 +1,22 @@
+{
+  "config": {
+    "flow_title": "[%key:component::bluetooth::config::flow_title%]",
+    "step": {
+      "user": {
+        "description": "[%key:component::bluetooth::config::step::user::description%]",
+        "data": {
+          "address": "[%key:common::config_flow::data::device%]"
+        }
+      },
+      "bluetooth_confirm": {
+        "description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
+      }
+    },
+    "abort": {
+      "not_supported": "Device not supported",
+      "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
+      "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
+      "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
+    }
+  }
+}

From 923259a8c63188cedf31a77b9479496155e95a11 Mon Sep 17 00:00:00 2001
From: Christopher Fenner <9592452+CFenner@users.noreply.github.com>
Date: Sun, 28 Jan 2024 19:07:14 +0100
Subject: [PATCH 17/21] Fix entity naming for heatpump heatings in ViCare
 (#109013)

Update strings.json
---
 homeassistant/components/vicare/strings.json | 28 ++++++++++----------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/homeassistant/components/vicare/strings.json b/homeassistant/components/vicare/strings.json
index 6c08215a9c1..87b5bb6cc14 100644
--- a/homeassistant/components/vicare/strings.json
+++ b/homeassistant/components/vicare/strings.json
@@ -141,52 +141,52 @@
         "name": "Heating gas consumption this year"
       },
       "gas_summary_consumption_heating_currentday": {
-        "name": "Heating gas consumption current day"
+        "name": "Heating gas consumption today"
       },
       "gas_summary_consumption_heating_currentmonth": {
-        "name": "Heating gas consumption current month"
+        "name": "Heating gas consumption this month"
       },
       "gas_summary_consumption_heating_currentyear": {
-        "name": "Heating gas consumption current year"
+        "name": "Heating gas consumption this year"
       },
       "gas_summary_consumption_heating_lastsevendays": {
         "name": "Heating gas consumption last seven days"
       },
       "hotwater_gas_summary_consumption_heating_currentday": {
-        "name": "DHW gas consumption current day"
+        "name": "DHW gas consumption today"
       },
       "hotwater_gas_summary_consumption_heating_currentmonth": {
-        "name": "DHW gas consumption current month"
+        "name": "DHW gas consumption this month"
       },
       "hotwater_gas_summary_consumption_heating_currentyear": {
-        "name": "DHW gas consumption current year"
+        "name": "DHW gas consumption this year"
       },
       "hotwater_gas_summary_consumption_heating_lastsevendays": {
         "name": "DHW gas consumption last seven days"
       },
       "energy_summary_consumption_heating_currentday": {
-        "name": "Energy consumption of gas heating current day"
+        "name": "Heating energy consumption today"
       },
       "energy_summary_consumption_heating_currentmonth": {
-        "name": "Energy consumption of gas heating current month"
+        "name": "Heating energy consumption this month"
       },
       "energy_summary_consumption_heating_currentyear": {
-        "name": "Energy consumption of gas heating current year"
+        "name": "Heating energy consumption this year"
       },
       "energy_summary_consumption_heating_lastsevendays": {
-        "name": "Energy consumption of gas heating last seven days"
+        "name": "Heating energy consumption last seven days"
       },
       "energy_dhw_summary_consumption_heating_currentday": {
-        "name": "Energy consumption of hot water gas heating current day"
+        "name": "DHW energy consumption today"
       },
       "energy_dhw_summary_consumption_heating_currentmonth": {
-        "name": "Energy consumption of hot water gas heating current month"
+        "name": "DHW energy consumption this month"
       },
       "energy_dhw_summary_consumption_heating_currentyear": {
-        "name": "Energy consumption of hot water gas heating current year"
+        "name": "DHW energy consumption this year"
       },
       "energy_summary_dhw_consumption_heating_lastsevendays": {
-        "name": "Energy consumption of hot water gas heating last seven days"
+        "name": "DHW energy consumption last seven days"
       },
       "power_production_current": {
         "name": "Power production current"

From 8429a7979661098498df71d34076e6c71a50579b Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Mon, 29 Jan 2024 12:15:18 -1000
Subject: [PATCH 18/21] Bump aiohttp to 3.9.3 (#109025)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
---
 homeassistant/package_constraints.txt       | 2 +-
 pyproject.toml                              | 2 +-
 requirements.txt                            | 2 +-
 tests/components/websocket_api/test_auth.py | 2 +-
 tests/components/websocket_api/test_http.py | 6 +++---
 tests/components/websocket_api/test_init.py | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt
index 821b6fdf141..9d030118dae 100644
--- a/homeassistant/package_constraints.txt
+++ b/homeassistant/package_constraints.txt
@@ -3,7 +3,7 @@
 aiodiscover==1.6.0
 aiohttp-fast-url-dispatcher==0.3.0
 aiohttp-zlib-ng==0.1.3
-aiohttp==3.9.1
+aiohttp==3.9.3
 aiohttp_cors==0.7.0
 astral==2.2
 async-upnp-client==0.38.1
diff --git a/pyproject.toml b/pyproject.toml
index f678d14d214..82162ff02d6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,7 +23,7 @@ classifiers = [
 ]
 requires-python = ">=3.11.0"
 dependencies    = [
-    "aiohttp==3.9.1",
+    "aiohttp==3.9.3",
     "aiohttp_cors==0.7.0",
     "aiohttp-fast-url-dispatcher==0.3.0",
     "aiohttp-zlib-ng==0.1.3",
diff --git a/requirements.txt b/requirements.txt
index e1878a33584..eaf5b8a9e22 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
 -c homeassistant/package_constraints.txt
 
 # Home Assistant Core
-aiohttp==3.9.1
+aiohttp==3.9.3
 aiohttp_cors==0.7.0
 aiohttp-fast-url-dispatcher==0.3.0
 aiohttp-zlib-ng==0.1.3
diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py
index d5ff879de78..dd18342abec 100644
--- a/tests/components/websocket_api/test_auth.py
+++ b/tests/components/websocket_api/test_auth.py
@@ -220,7 +220,7 @@ async def test_auth_close_after_revoke(
     await hass.auth.async_remove_refresh_token(refresh_token)
 
     msg = await websocket_client.receive()
-    assert msg.type == aiohttp.WSMsgType.CLOSE
+    assert msg.type == aiohttp.WSMsgType.CLOSED
     assert websocket_client.closed
 
 
diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py
index e69b5629b63..f6723f0a592 100644
--- a/tests/components/websocket_api/test_http.py
+++ b/tests/components/websocket_api/test_http.py
@@ -42,7 +42,7 @@ async def test_pending_msg_overflow(
     for idx in range(10):
         await websocket_client.send_json({"id": idx + 1, "type": "ping"})
     msg = await websocket_client.receive()
-    assert msg.type == WSMsgType.close
+    assert msg.type == WSMsgType.CLOSED
 
 
 async def test_cleanup_on_cancellation(
@@ -248,7 +248,7 @@ async def test_pending_msg_peak(
     )
 
     msg = await websocket_client.receive()
-    assert msg.type == WSMsgType.close
+    assert msg.type == WSMsgType.CLOSED
     assert "Client unable to keep up with pending messages" in caplog.text
     assert "Stayed over 5 for 5 seconds" in caplog.text
     assert "overload" in caplog.text
@@ -296,7 +296,7 @@ async def test_pending_msg_peak_recovery(
     msg = await websocket_client.receive()
     assert msg.type == WSMsgType.TEXT
     msg = await websocket_client.receive()
-    assert msg.type == WSMsgType.close
+    assert msg.type == WSMsgType.CLOSED
     assert "Client unable to keep up with pending messages" not in caplog.text
 
 
diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py
index 468b35fef51..c4c83925311 100644
--- a/tests/components/websocket_api/test_init.py
+++ b/tests/components/websocket_api/test_init.py
@@ -40,7 +40,7 @@ async def test_quiting_hass(hass: HomeAssistant, websocket_client) -> None:
 
     msg = await websocket_client.receive()
 
-    assert msg.type == WSMsgType.CLOSE
+    assert msg.type == WSMsgType.CLOSED
 
 
 async def test_unknown_command(websocket_client) -> None:

From b7410fecb82cfab7c3f2c5e106fdf251be98bbb8 Mon Sep 17 00:00:00 2001
From: puddly <32534428+puddly@users.noreply.github.com>
Date: Mon, 29 Jan 2024 17:59:56 -0500
Subject: [PATCH 19/21] Bump ZHA dependency zigpy to 0.60.7 (#109082)

---
 homeassistant/components/zha/manifest.json | 2 +-
 requirements_all.txt                       | 2 +-
 requirements_test_all.txt                  | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json
index de429b299c0..024fea9227a 100644
--- a/homeassistant/components/zha/manifest.json
+++ b/homeassistant/components/zha/manifest.json
@@ -26,7 +26,7 @@
     "pyserial-asyncio==0.6",
     "zha-quirks==0.0.109",
     "zigpy-deconz==0.22.4",
-    "zigpy==0.60.6",
+    "zigpy==0.60.7",
     "zigpy-xbee==0.20.1",
     "zigpy-zigate==0.12.0",
     "zigpy-znp==0.12.1",
diff --git a/requirements_all.txt b/requirements_all.txt
index 99bde907caa..22d8e110fe7 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2888,7 +2888,7 @@ zigpy-zigate==0.12.0
 zigpy-znp==0.12.1
 
 # homeassistant.components.zha
-zigpy==0.60.6
+zigpy==0.60.7
 
 # homeassistant.components.zoneminder
 zm-py==0.5.4
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 3262ea26b88..cb89514bf1d 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -2187,7 +2187,7 @@ zigpy-zigate==0.12.0
 zigpy-znp==0.12.1
 
 # homeassistant.components.zha
-zigpy==0.60.6
+zigpy==0.60.7
 
 # homeassistant.components.zwave_js
 zwave-js-server-python==0.55.3

From 1c6c925a2b39d0942eaf3c34fdbca535854daccb Mon Sep 17 00:00:00 2001
From: Joost Lekkerkerker <joostlek@outlook.com>
Date: Tue, 30 Jan 2024 10:07:23 +0100
Subject: [PATCH 20/21] Add missing abort message for Spotify (#109102)

* Shield for unregistered Spotify users

* Shield for unregistered Spotify users
---
 homeassistant/components/spotify/strings.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/homeassistant/components/spotify/strings.json b/homeassistant/components/spotify/strings.json
index 02077cbdb43..e58d2098bde 100644
--- a/homeassistant/components/spotify/strings.json
+++ b/homeassistant/components/spotify/strings.json
@@ -17,7 +17,8 @@
       "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
       "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
       "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
-      "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
+      "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
+      "connection_error": "Could not fetch account information. Is the user registered in the Spotify Developer Dashboard?"
     },
     "create_entry": {
       "default": "Successfully authenticated with Spotify."

From 1dff998a25de8d3c8d8f80ecb493aa277fd0edca Mon Sep 17 00:00:00 2001
From: Franck Nijhof <git@frenck.dev>
Date: Tue, 30 Jan 2024 15:22:32 +0100
Subject: [PATCH 21/21] Bump version to 2024.1.6

---
 homeassistant/const.py | 2 +-
 pyproject.toml         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/homeassistant/const.py b/homeassistant/const.py
index f9d250c6732..0f483da47d8 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -16,7 +16,7 @@ from .helpers.deprecation import (
 APPLICATION_NAME: Final = "HomeAssistant"
 MAJOR_VERSION: Final = 2024
 MINOR_VERSION: Final = 1
-PATCH_VERSION: Final = "5"
+PATCH_VERSION: Final = "6"
 __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
 __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
 REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
diff --git a/pyproject.toml b/pyproject.toml
index 82162ff02d6..f6d936fb637 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name        = "homeassistant"
-version     = "2024.1.5"
+version     = "2024.1.6"
 license     = {text = "Apache-2.0"}
 description = "Open-source home automation platform running on Python 3."
 readme      = "README.rst"