This commit is contained in:
Franck Nijhof 2024-01-30 19:38:38 +01:00 committed by GitHub
commit 1f7bf7c2a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 170 additions and 86 deletions

View file

@ -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]

View file

@ -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"]
}

View file

@ -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}}

View file

@ -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)

View file

@ -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
}

View file

@ -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(

View file

@ -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)

View file

@ -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

View file

@ -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"]
}

View file

@ -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}

View file

@ -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"
)

View file

@ -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)

View file

@ -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)

View file

@ -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"]
}

View file

@ -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%]"
}
}
}

View file

@ -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

View file

@ -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."

View file

@ -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"

View file

@ -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"]
}

View file

@ -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"]
}

View file

@ -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,
@ -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,

View file

@ -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",

View file

@ -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)))

View file

@ -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)

View file

@ -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

View file

@ -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"
@ -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",

View file

@ -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

View file

@ -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
@ -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
@ -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
@ -1413,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
@ -2842,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
@ -2887,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

View file

@ -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
@ -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
@ -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
@ -1110,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
@ -2150,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
@ -2186,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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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."""