mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
2024.1.6 (#109129)
This commit is contained in:
commit
1f7bf7c2a9
@ -40,10 +40,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
f"Could not find Airthings device with address {address}"
|
f"Could not find Airthings device with address {address}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric)
|
||||||
|
|
||||||
async def _async_update_method() -> AirthingsDevice:
|
async def _async_update_method() -> AirthingsDevice:
|
||||||
"""Get data from Airthings BLE."""
|
"""Get data from Airthings BLE."""
|
||||||
ble_device = bluetooth.async_ble_device_from_address(hass, address)
|
ble_device = bluetooth.async_ble_device_from_address(hass, address)
|
||||||
airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await airthings.update_device(ble_device) # type: ignore[arg-type]
|
data = await airthings.update_device(ble_device) # type: ignore[arg-type]
|
||||||
|
@ -24,5 +24,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["airthings-ble==0.5.6-2"]
|
"requirements": ["airthings-ble==0.6.0"]
|
||||||
}
|
}
|
||||||
|
@ -860,8 +860,8 @@ class AlexaInputController(AlexaCapability):
|
|||||||
|
|
||||||
def inputs(self) -> list[dict[str, str]] | None:
|
def inputs(self) -> list[dict[str, str]] | None:
|
||||||
"""Return the list of valid supported inputs."""
|
"""Return the list of valid supported inputs."""
|
||||||
source_list: list[Any] = self.entity.attributes.get(
|
source_list: list[Any] = (
|
||||||
media_player.ATTR_INPUT_SOURCE_LIST, []
|
self.entity.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
|
||||||
)
|
)
|
||||||
return AlexaInputController.get_valid_inputs(source_list)
|
return AlexaInputController.get_valid_inputs(source_list)
|
||||||
|
|
||||||
@ -1196,7 +1196,7 @@ class AlexaThermostatController(AlexaCapability):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
supported_modes: list[str] = []
|
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:
|
for mode in hvac_modes:
|
||||||
if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
|
if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
|
||||||
supported_modes.append(thermostat_mode)
|
supported_modes.append(thermostat_mode)
|
||||||
@ -1422,18 +1422,22 @@ class AlexaModeController(AlexaCapability):
|
|||||||
|
|
||||||
# Humidifier mode
|
# Humidifier mode
|
||||||
if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
|
if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
|
||||||
mode = self.entity.attributes.get(humidifier.ATTR_MODE, None)
|
mode = self.entity.attributes.get(humidifier.ATTR_MODE)
|
||||||
if mode in self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, []):
|
modes: list[str] = (
|
||||||
|
self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES) or []
|
||||||
|
)
|
||||||
|
if mode in modes:
|
||||||
return f"{humidifier.ATTR_MODE}.{mode}"
|
return f"{humidifier.ATTR_MODE}.{mode}"
|
||||||
|
|
||||||
# Water heater operation mode
|
# Water heater operation mode
|
||||||
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
||||||
operation_mode = self.entity.attributes.get(
|
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(
|
operation_modes: list[str] = (
|
||||||
water_heater.ATTR_OPERATION_LIST, []
|
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}"
|
return f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}"
|
||||||
|
|
||||||
# Cover Position
|
# Cover Position
|
||||||
@ -1492,7 +1496,7 @@ class AlexaModeController(AlexaCapability):
|
|||||||
self._resource = AlexaModeResource(
|
self._resource = AlexaModeResource(
|
||||||
[AlexaGlobalCatalog.SETTING_PRESET], False
|
[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:
|
for preset_mode in preset_modes:
|
||||||
self._resource.add_mode(
|
self._resource.add_mode(
|
||||||
f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
|
f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
|
||||||
@ -1508,7 +1512,7 @@ class AlexaModeController(AlexaCapability):
|
|||||||
# Humidifier modes
|
# Humidifier modes
|
||||||
if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
|
if self.instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
|
||||||
self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
|
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:
|
for mode in modes:
|
||||||
self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
|
self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
|
||||||
# Humidifiers or Fans with a single mode completely break Alexa discovery,
|
# Humidifiers or Fans with a single mode completely break Alexa discovery,
|
||||||
@ -1522,8 +1526,8 @@ class AlexaModeController(AlexaCapability):
|
|||||||
# Water heater operation modes
|
# Water heater operation modes
|
||||||
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
||||||
self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
|
self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
|
||||||
operation_modes = self.entity.attributes.get(
|
operation_modes = (
|
||||||
water_heater.ATTR_OPERATION_LIST, []
|
self.entity.attributes.get(water_heater.ATTR_OPERATION_LIST) or []
|
||||||
)
|
)
|
||||||
for operation_mode in operation_modes:
|
for operation_mode in operation_modes:
|
||||||
self._resource.add_mode(
|
self._resource.add_mode(
|
||||||
@ -2368,7 +2372,7 @@ class AlexaEqualizerController(AlexaCapability):
|
|||||||
"""Return the sound modes supported in the configurations object."""
|
"""Return the sound modes supported in the configurations object."""
|
||||||
configurations = None
|
configurations = None
|
||||||
supported_sound_modes = self.get_valid_inputs(
|
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:
|
if supported_sound_modes:
|
||||||
configurations = {"modes": {"supported": supported_sound_modes}}
|
configurations = {"modes": {"supported": supported_sound_modes}}
|
||||||
|
@ -478,7 +478,7 @@ class ClimateCapabilities(AlexaEntity):
|
|||||||
if (
|
if (
|
||||||
self.entity.domain == climate.DOMAIN
|
self.entity.domain == climate.DOMAIN
|
||||||
and climate.HVACMode.OFF
|
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
|
or self.entity.domain == water_heater.DOMAIN
|
||||||
and (supported_features & water_heater.WaterHeaterEntityFeature.ON_OFF)
|
and (supported_features & water_heater.WaterHeaterEntityFeature.ON_OFF)
|
||||||
):
|
):
|
||||||
@ -742,7 +742,8 @@ class MediaPlayerCapabilities(AlexaEntity):
|
|||||||
and domain != "denonavr"
|
and domain != "denonavr"
|
||||||
):
|
):
|
||||||
inputs = AlexaEqualizerController.get_valid_inputs(
|
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:
|
if len(inputs) > 0:
|
||||||
yield AlexaEqualizerController(self.entity)
|
yield AlexaEqualizerController(self.entity)
|
||||||
|
@ -570,7 +570,7 @@ async def async_api_select_input(
|
|||||||
|
|
||||||
# Attempt to map the ALL UPPERCASE payload name to a source.
|
# Attempt to map the ALL UPPERCASE payload name to a source.
|
||||||
# Strips trailing 1 to match single input devices.
|
# 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:
|
for source in source_list:
|
||||||
formatted_source = (
|
formatted_source = (
|
||||||
source.lower().replace("-", "").replace("_", "").replace(" ", "")
|
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)
|
ha_preset = next((k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode), None)
|
||||||
|
|
||||||
if ha_preset:
|
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:
|
if ha_preset not in presets:
|
||||||
msg = f"The requested thermostat mode {ha_preset} is not supported"
|
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
|
data[climate.ATTR_PRESET_MODE] = ha_preset
|
||||||
|
|
||||||
elif mode == "CUSTOM":
|
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 = directive.payload["thermostatMode"]["customName"]
|
||||||
custom_mode = next(
|
custom_mode = next(
|
||||||
(k for k, v in API_THERMOSTAT_MODES_CUSTOM.items() if v == custom_mode),
|
(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
|
data[climate.ATTR_HVAC_MODE] = custom_mode
|
||||||
|
|
||||||
else:
|
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] = {
|
ha_modes: dict[str, str] = {
|
||||||
k: v for k, v in API_THERMOSTAT_MODES.items() if v == mode
|
k: v for k, v in API_THERMOSTAT_MODES.items() if v == mode
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,8 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
# Test the connection to the host and get the current status for serial number.
|
# Test the connection to the host and get the current status for serial number.
|
||||||
coordinator = APCUPSdCoordinator(self.hass, host, port)
|
coordinator = APCUPSdCoordinator(self.hass, host, port)
|
||||||
|
|
||||||
await coordinator.async_request_refresh()
|
await coordinator.async_request_refresh()
|
||||||
await self.hass.async_block_till_done()
|
|
||||||
if isinstance(coordinator.last_exception, (UpdateFailed, asyncio.TimeoutError)):
|
if isinstance(coordinator.last_exception, (UpdateFailed, asyncio.TimeoutError)):
|
||||||
errors = {"base": "cannot_connect"}
|
errors = {"base": "cannot_connect"}
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -386,7 +386,7 @@ class HueOneLightChangeView(HomeAssistantView):
|
|||||||
# Get the entity's supported features
|
# Get the entity's supported features
|
||||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if entity.domain == light.DOMAIN:
|
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
|
# Parse the request
|
||||||
parsed: dict[str, Any] = {
|
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]:
|
def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||||
"""Convert an entity to its Hue bridge JSON representation."""
|
"""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)
|
unique_id = _entity_unique_id(state.entity_id)
|
||||||
state_dict = get_entity_state_dict(config, state)
|
state_dict = get_entity_state_dict(config, state)
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
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.store import ScopedCalendarStore
|
||||||
from gcal_sync.sync import CalendarEventSyncManager
|
from gcal_sync.sync import CalendarEventSyncManager
|
||||||
from gcal_sync.timeline import Timeline
|
from gcal_sync.timeline import Timeline
|
||||||
|
from ical.iter import SortableItemValue
|
||||||
|
|
||||||
from homeassistant.components.calendar import (
|
from homeassistant.components.calendar import (
|
||||||
CREATE_EVENT_SCHEMA,
|
CREATE_EVENT_SCHEMA,
|
||||||
@ -76,6 +78,9 @@ from .const import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
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
|
# Avoid syncing super old data on initial syncs. Note that old but active
|
||||||
# recurring events are still included.
|
# 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]):
|
class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
|
||||||
"""Coordinator for calendar RPC calls that use an efficient sync."""
|
"""Coordinator for calendar RPC calls that use an efficient sync."""
|
||||||
|
|
||||||
@ -263,6 +284,7 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
|
|||||||
update_interval=MIN_TIME_BETWEEN_UPDATES,
|
update_interval=MIN_TIME_BETWEEN_UPDATES,
|
||||||
)
|
)
|
||||||
self.sync = sync
|
self.sync = sync
|
||||||
|
self._upcoming_timeline: Timeline | None = None
|
||||||
|
|
||||||
async def _async_update_data(self) -> Timeline:
|
async def _async_update_data(self) -> Timeline:
|
||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
@ -271,9 +293,11 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
|
|||||||
except ApiException as err:
|
except ApiException as err:
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from 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
|
dt_util.DEFAULT_TIME_ZONE
|
||||||
)
|
)
|
||||||
|
self._upcoming_timeline = _truncate_timeline(timeline, MAX_UPCOMING_EVENTS)
|
||||||
|
return timeline
|
||||||
|
|
||||||
async def async_get_events(
|
async def async_get_events(
|
||||||
self, start_date: datetime, end_date: datetime
|
self, start_date: datetime, end_date: datetime
|
||||||
@ -291,8 +315,8 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
|
|||||||
@property
|
@property
|
||||||
def upcoming(self) -> Iterable[Event] | None:
|
def upcoming(self) -> Iterable[Event] | None:
|
||||||
"""Return upcoming events if any."""
|
"""Return upcoming events if any."""
|
||||||
if self.data:
|
if self._upcoming_timeline:
|
||||||
return self.data.active_after(dt_util.now())
|
return self._upcoming_timeline.active_after(dt_util.now())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
|
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["googleapiclient"],
|
"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"]
|
||||||
}
|
}
|
||||||
|
@ -1152,12 +1152,12 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
modes = []
|
modes = []
|
||||||
attrs = self.state.attributes
|
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)
|
google_mode = self.hvac_to_google.get(mode)
|
||||||
if google_mode and google_mode not in modes:
|
if google_mode and google_mode not in modes:
|
||||||
modes.append(google_mode)
|
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)
|
google_mode = self.preset_to_google.get(preset)
|
||||||
if google_mode and google_mode not in modes:
|
if google_mode and google_mode not in modes:
|
||||||
modes.append(google_mode)
|
modes.append(google_mode)
|
||||||
@ -2094,9 +2094,10 @@ class InputSelectorTrait(_Trait):
|
|||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
"""Return mode attributes for a sync request."""
|
"""Return mode attributes for a sync request."""
|
||||||
attrs = self.state.attributes
|
attrs = self.state.attributes
|
||||||
|
sourcelist: list[str] = attrs.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
|
||||||
inputs = [
|
inputs = [
|
||||||
{"key": source, "names": [{"name_synonym": [source], "lang": "en"}]}
|
{"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}
|
payload = {"availableInputs": inputs, "orderedInputs": True}
|
||||||
|
@ -110,7 +110,7 @@ class SetModeHandler(intent.IntentHandler):
|
|||||||
intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes")
|
intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes")
|
||||||
mode = slots["mode"]["value"]
|
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(
|
raise intent.IntentHandleError(
|
||||||
f"Entity {state.name} does not support {mode} mode"
|
f"Entity {state.name} does not support {mode} mode"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,7 @@ from homeassistant.core import Event, HomeAssistant, callback
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util import slugify as util_slugify
|
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 .const import DOMAIN
|
||||||
from .coordinator import OctoprintDataUpdateCoordinator
|
from .coordinator import OctoprintDataUpdateCoordinator
|
||||||
@ -159,7 +160,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
connector = aiohttp.TCPConnector(
|
connector = aiohttp.TCPConnector(
|
||||||
force_close=True,
|
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)
|
session = aiohttp.ClientSession(connector=connector)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
@ -264,7 +265,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
connector = aiohttp.TCPConnector(
|
connector = aiohttp.TCPConnector(
|
||||||
force_close=True,
|
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)
|
session = aiohttp.ClientSession(connector=connector)
|
||||||
self._sessions.append(session)
|
self._sessions.append(session)
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/openerz",
|
"documentation": "https://www.home-assistant.io/integrations/openerz",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["openerz_api"],
|
"loggers": ["openerz_api"],
|
||||||
"requirements": ["openerz-api==0.2.0"]
|
"requirements": ["openerz-api==0.3.0"]
|
||||||
}
|
}
|
||||||
|
22
homeassistant/components/sensirion_ble/strings.json
Normal file
22
homeassistant/components/sensirion_ble/strings.json
Normal 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%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,13 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, cast
|
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.components.number import NumberEntity, NumberEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
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(
|
async def _async_set_foot_warmer_time(
|
||||||
foot_warmer: SleepIQFootWarmer, time: int
|
foot_warmer: SleepIQFootWarmer, time: int
|
||||||
) -> None:
|
) -> None:
|
||||||
|
temperature = FootWarmingTemps(foot_warmer.temperature)
|
||||||
|
if temperature != FootWarmingTemps.OFF:
|
||||||
|
await foot_warmer.turn_on(temperature, time)
|
||||||
|
|
||||||
foot_warmer.timer = time
|
foot_warmer.timer = time
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
"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": {
|
"create_entry": {
|
||||||
"default": "Successfully authenticated with Spotify."
|
"default": "Successfully authenticated with Spotify."
|
||||||
|
@ -141,52 +141,52 @@
|
|||||||
"name": "Heating gas consumption this year"
|
"name": "Heating gas consumption this year"
|
||||||
},
|
},
|
||||||
"gas_summary_consumption_heating_currentday": {
|
"gas_summary_consumption_heating_currentday": {
|
||||||
"name": "Heating gas consumption current day"
|
"name": "Heating gas consumption today"
|
||||||
},
|
},
|
||||||
"gas_summary_consumption_heating_currentmonth": {
|
"gas_summary_consumption_heating_currentmonth": {
|
||||||
"name": "Heating gas consumption current month"
|
"name": "Heating gas consumption this month"
|
||||||
},
|
},
|
||||||
"gas_summary_consumption_heating_currentyear": {
|
"gas_summary_consumption_heating_currentyear": {
|
||||||
"name": "Heating gas consumption current year"
|
"name": "Heating gas consumption this year"
|
||||||
},
|
},
|
||||||
"gas_summary_consumption_heating_lastsevendays": {
|
"gas_summary_consumption_heating_lastsevendays": {
|
||||||
"name": "Heating gas consumption last seven days"
|
"name": "Heating gas consumption last seven days"
|
||||||
},
|
},
|
||||||
"hotwater_gas_summary_consumption_heating_currentday": {
|
"hotwater_gas_summary_consumption_heating_currentday": {
|
||||||
"name": "DHW gas consumption current day"
|
"name": "DHW gas consumption today"
|
||||||
},
|
},
|
||||||
"hotwater_gas_summary_consumption_heating_currentmonth": {
|
"hotwater_gas_summary_consumption_heating_currentmonth": {
|
||||||
"name": "DHW gas consumption current month"
|
"name": "DHW gas consumption this month"
|
||||||
},
|
},
|
||||||
"hotwater_gas_summary_consumption_heating_currentyear": {
|
"hotwater_gas_summary_consumption_heating_currentyear": {
|
||||||
"name": "DHW gas consumption current year"
|
"name": "DHW gas consumption this year"
|
||||||
},
|
},
|
||||||
"hotwater_gas_summary_consumption_heating_lastsevendays": {
|
"hotwater_gas_summary_consumption_heating_lastsevendays": {
|
||||||
"name": "DHW gas consumption last seven days"
|
"name": "DHW gas consumption last seven days"
|
||||||
},
|
},
|
||||||
"energy_summary_consumption_heating_currentday": {
|
"energy_summary_consumption_heating_currentday": {
|
||||||
"name": "Energy consumption of gas heating current day"
|
"name": "Heating energy consumption today"
|
||||||
},
|
},
|
||||||
"energy_summary_consumption_heating_currentmonth": {
|
"energy_summary_consumption_heating_currentmonth": {
|
||||||
"name": "Energy consumption of gas heating current month"
|
"name": "Heating energy consumption this month"
|
||||||
},
|
},
|
||||||
"energy_summary_consumption_heating_currentyear": {
|
"energy_summary_consumption_heating_currentyear": {
|
||||||
"name": "Energy consumption of gas heating current year"
|
"name": "Heating energy consumption this year"
|
||||||
},
|
},
|
||||||
"energy_summary_consumption_heating_lastsevendays": {
|
"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": {
|
"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": {
|
"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": {
|
"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": {
|
"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": {
|
"power_production_current": {
|
||||||
"name": "Power production current"
|
"name": "Power production current"
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aiovodafone"],
|
"loggers": ["aiovodafone"],
|
||||||
"requirements": ["aiovodafone==0.4.3"]
|
"requirements": ["aiovodafone==0.5.4"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"dependencies": ["auth", "application_credentials"],
|
"dependencies": ["auth", "application_credentials"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"requirements": ["yolink-api==0.3.4"]
|
"requirements": ["yolink-api==0.3.6"]
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class Endpoint:
|
|||||||
if not cluster_handler_class.matches(cluster, self):
|
if not cluster_handler_class.matches(cluster, self):
|
||||||
cluster_handler_class = ClusterHandler
|
cluster_handler_class = ClusterHandler
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.debug(
|
||||||
"Creating cluster handler for cluster id: %s class: %s",
|
"Creating cluster handler for cluster id: %s class: %s",
|
||||||
cluster_id,
|
cluster_id,
|
||||||
cluster_handler_class,
|
cluster_handler_class,
|
||||||
@ -199,10 +199,10 @@ class Endpoint:
|
|||||||
results = await gather(*tasks, return_exceptions=True)
|
results = await gather(*tasks, return_exceptions=True)
|
||||||
for cluster_handler, outcome in zip(cluster_handlers, results):
|
for cluster_handler, outcome in zip(cluster_handlers, results):
|
||||||
if isinstance(outcome, Exception):
|
if isinstance(outcome, Exception):
|
||||||
cluster_handler.warning(
|
cluster_handler.debug(
|
||||||
"'%s' stage failed: %s", func_name, str(outcome), exc_info=outcome
|
"'%s' stage failed: %s", func_name, str(outcome), exc_info=outcome
|
||||||
)
|
)
|
||||||
continue
|
else:
|
||||||
cluster_handler.debug("'%s' stage succeeded", func_name)
|
cluster_handler.debug("'%s' stage succeeded", func_name)
|
||||||
|
|
||||||
def async_new_entity(
|
def async_new_entity(
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"pyserial-asyncio==0.6",
|
"pyserial-asyncio==0.6",
|
||||||
"zha-quirks==0.0.109",
|
"zha-quirks==0.0.109",
|
||||||
"zigpy-deconz==0.22.4",
|
"zigpy-deconz==0.22.4",
|
||||||
"zigpy==0.60.6",
|
"zigpy==0.60.7",
|
||||||
"zigpy-xbee==0.20.1",
|
"zigpy-xbee==0.20.1",
|
||||||
"zigpy-zigate==0.12.0",
|
"zigpy-zigate==0.12.0",
|
||||||
"zigpy-znp==0.12.1",
|
"zigpy-znp==0.12.1",
|
||||||
|
@ -481,8 +481,12 @@ class Illuminance(Sensor):
|
|||||||
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
|
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
|
||||||
_attr_native_unit_of_measurement = LIGHT_LUX
|
_attr_native_unit_of_measurement = LIGHT_LUX
|
||||||
|
|
||||||
def formatter(self, value: int) -> int:
|
def formatter(self, value: int) -> int | None:
|
||||||
"""Convert illumination data."""
|
"""Convert illumination data."""
|
||||||
|
if value == 0:
|
||||||
|
return 0
|
||||||
|
if value == 0xFFFF:
|
||||||
|
return None
|
||||||
return round(pow(10, ((value - 1) / 10000)))
|
return round(pow(10, ((value - 1) / 10000)))
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from .helpers.deprecation import (
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 1
|
MINOR_VERSION: Final = 1
|
||||||
PATCH_VERSION: Final = "5"
|
PATCH_VERSION: Final = "6"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
aiodiscover==1.6.0
|
aiodiscover==1.6.0
|
||||||
aiohttp-fast-url-dispatcher==0.3.0
|
aiohttp-fast-url-dispatcher==0.3.0
|
||||||
aiohttp-zlib-ng==0.1.3
|
aiohttp-zlib-ng==0.1.3
|
||||||
aiohttp==3.9.1
|
aiohttp==3.9.3
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
astral==2.2
|
astral==2.2
|
||||||
async-upnp-client==0.38.1
|
async-upnp-client==0.38.1
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.1.5"
|
version = "2024.1.6"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -23,7 +23,7 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
requires-python = ">=3.11.0"
|
requires-python = ">=3.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp==3.9.1",
|
"aiohttp==3.9.3",
|
||||||
"aiohttp_cors==0.7.0",
|
"aiohttp_cors==0.7.0",
|
||||||
"aiohttp-fast-url-dispatcher==0.3.0",
|
"aiohttp-fast-url-dispatcher==0.3.0",
|
||||||
"aiohttp-zlib-ng==0.1.3",
|
"aiohttp-zlib-ng==0.1.3",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
-c homeassistant/package_constraints.txt
|
-c homeassistant/package_constraints.txt
|
||||||
|
|
||||||
# Home Assistant Core
|
# Home Assistant Core
|
||||||
aiohttp==3.9.1
|
aiohttp==3.9.3
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiohttp-fast-url-dispatcher==0.3.0
|
aiohttp-fast-url-dispatcher==0.3.0
|
||||||
aiohttp-zlib-ng==0.1.3
|
aiohttp-zlib-ng==0.1.3
|
||||||
|
@ -383,7 +383,7 @@ aiounifi==69
|
|||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.vodafone_station
|
# homeassistant.components.vodafone_station
|
||||||
aiovodafone==0.4.3
|
aiovodafone==0.5.4
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==3.0.1
|
aiowaqi==3.0.1
|
||||||
@ -404,7 +404,7 @@ aioymaps==1.2.2
|
|||||||
airly==1.1.0
|
airly==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.airthings_ble
|
# homeassistant.components.airthings_ble
|
||||||
airthings-ble==0.5.6-2
|
airthings-ble==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.airthings
|
# homeassistant.components.airthings
|
||||||
airthings-cloud==0.1.0
|
airthings-cloud==0.1.0
|
||||||
@ -1076,6 +1076,7 @@ ibeacon-ble==1.0.1
|
|||||||
# homeassistant.components.watson_iot
|
# homeassistant.components.watson_iot
|
||||||
ibmiotf==0.3.4
|
ibmiotf==0.3.4
|
||||||
|
|
||||||
|
# homeassistant.components.google
|
||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
ical==6.1.1
|
ical==6.1.1
|
||||||
@ -1413,7 +1414,7 @@ openai==1.3.8
|
|||||||
# opencv-python-headless==4.6.0.66
|
# opencv-python-headless==4.6.0.66
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.2.0
|
openerz-api==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.openevse
|
# homeassistant.components.openevse
|
||||||
openevsewifi==1.1.2
|
openevsewifi==1.1.2
|
||||||
@ -2842,7 +2843,7 @@ yeelight==0.7.14
|
|||||||
yeelightsunflower==0.0.10
|
yeelightsunflower==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.yolink
|
# homeassistant.components.yolink
|
||||||
yolink-api==0.3.4
|
yolink-api==0.3.6
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==1.0.1
|
youless-api==1.0.1
|
||||||
@ -2887,7 +2888,7 @@ zigpy-zigate==0.12.0
|
|||||||
zigpy-znp==0.12.1
|
zigpy-znp==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.60.6
|
zigpy==0.60.7
|
||||||
|
|
||||||
# homeassistant.components.zoneminder
|
# homeassistant.components.zoneminder
|
||||||
zm-py==0.5.4
|
zm-py==0.5.4
|
||||||
|
@ -356,7 +356,7 @@ aiounifi==69
|
|||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.vodafone_station
|
# homeassistant.components.vodafone_station
|
||||||
aiovodafone==0.4.3
|
aiovodafone==0.5.4
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==3.0.1
|
aiowaqi==3.0.1
|
||||||
@ -377,7 +377,7 @@ aioymaps==1.2.2
|
|||||||
airly==1.1.0
|
airly==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.airthings_ble
|
# homeassistant.components.airthings_ble
|
||||||
airthings-ble==0.5.6-2
|
airthings-ble==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.airthings
|
# homeassistant.components.airthings
|
||||||
airthings-cloud==0.1.0
|
airthings-cloud==0.1.0
|
||||||
@ -860,6 +860,7 @@ iaqualink==0.5.0
|
|||||||
# homeassistant.components.ibeacon
|
# homeassistant.components.ibeacon
|
||||||
ibeacon-ble==1.0.1
|
ibeacon-ble==1.0.1
|
||||||
|
|
||||||
|
# homeassistant.components.google
|
||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
ical==6.1.1
|
ical==6.1.1
|
||||||
@ -1110,7 +1111,7 @@ open-meteo==0.3.1
|
|||||||
openai==1.3.8
|
openai==1.3.8
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.2.0
|
openerz-api==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.openhome
|
# homeassistant.components.openhome
|
||||||
openhomedevice==2.2.0
|
openhomedevice==2.2.0
|
||||||
@ -2150,7 +2151,7 @@ yalexs==1.10.0
|
|||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
|
||||||
# homeassistant.components.yolink
|
# homeassistant.components.yolink
|
||||||
yolink-api==0.3.4
|
yolink-api==0.3.6
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==1.0.1
|
youless-api==1.0.1
|
||||||
@ -2186,7 +2187,7 @@ zigpy-zigate==0.12.0
|
|||||||
zigpy-znp==0.12.1
|
zigpy-znp==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.60.6
|
zigpy==0.60.7
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.55.3
|
zwave-js-server-python==0.55.3
|
||||||
|
@ -188,7 +188,10 @@ async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None:
|
|||||||
assert len(mode_calls) == 0
|
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."""
|
"""Test the set mode intent for unsupported mode."""
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"humidifier.bedroom_humidifier",
|
"humidifier.bedroom_humidifier",
|
||||||
@ -196,8 +199,8 @@ async def test_intent_set_unknown_mode(hass: HomeAssistant) -> None:
|
|||||||
{
|
{
|
||||||
ATTR_HUMIDITY: 40,
|
ATTR_HUMIDITY: 40,
|
||||||
ATTR_SUPPORTED_FEATURES: 1,
|
ATTR_SUPPORTED_FEATURES: 1,
|
||||||
ATTR_AVAILABLE_MODES: ["home", "away"],
|
ATTR_AVAILABLE_MODES: available_modes,
|
||||||
ATTR_MODE: "home",
|
ATTR_MODE: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
mode_calls = async_mock_service(hass, DOMAIN, SERVICE_SET_MODE)
|
mode_calls = async_mock_service(hass, DOMAIN, SERVICE_SET_MODE)
|
||||||
|
@ -220,7 +220,7 @@ async def test_auth_close_after_revoke(
|
|||||||
await hass.auth.async_remove_refresh_token(refresh_token)
|
await hass.auth.async_remove_refresh_token(refresh_token)
|
||||||
|
|
||||||
msg = await websocket_client.receive()
|
msg = await websocket_client.receive()
|
||||||
assert msg.type == aiohttp.WSMsgType.CLOSE
|
assert msg.type == aiohttp.WSMsgType.CLOSED
|
||||||
assert websocket_client.closed
|
assert websocket_client.closed
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ async def test_pending_msg_overflow(
|
|||||||
for idx in range(10):
|
for idx in range(10):
|
||||||
await websocket_client.send_json({"id": idx + 1, "type": "ping"})
|
await websocket_client.send_json({"id": idx + 1, "type": "ping"})
|
||||||
msg = await websocket_client.receive()
|
msg = await websocket_client.receive()
|
||||||
assert msg.type == WSMsgType.close
|
assert msg.type == WSMsgType.CLOSED
|
||||||
|
|
||||||
|
|
||||||
async def test_cleanup_on_cancellation(
|
async def test_cleanup_on_cancellation(
|
||||||
@ -248,7 +248,7 @@ async def test_pending_msg_peak(
|
|||||||
)
|
)
|
||||||
|
|
||||||
msg = await websocket_client.receive()
|
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 "Client unable to keep up with pending messages" in caplog.text
|
||||||
assert "Stayed over 5 for 5 seconds" in caplog.text
|
assert "Stayed over 5 for 5 seconds" in caplog.text
|
||||||
assert "overload" in caplog.text
|
assert "overload" in caplog.text
|
||||||
@ -296,7 +296,7 @@ async def test_pending_msg_peak_recovery(
|
|||||||
msg = await websocket_client.receive()
|
msg = await websocket_client.receive()
|
||||||
assert msg.type == WSMsgType.TEXT
|
assert msg.type == WSMsgType.TEXT
|
||||||
msg = await websocket_client.receive()
|
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
|
assert "Client unable to keep up with pending messages" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ async def test_quiting_hass(hass: HomeAssistant, websocket_client) -> None:
|
|||||||
|
|
||||||
msg = await websocket_client.receive()
|
msg = await websocket_client.receive()
|
||||||
|
|
||||||
assert msg.type == WSMsgType.CLOSE
|
assert msg.type == WSMsgType.CLOSED
|
||||||
|
|
||||||
|
|
||||||
async def test_unknown_command(websocket_client) -> None:
|
async def test_unknown_command(websocket_client) -> None:
|
||||||
|
@ -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.call_count == 1
|
||||||
assert ch.async_configure.await_count == 1
|
assert ch.async_configure.await_count == 1
|
||||||
|
|
||||||
assert ch_3.warning.call_count == 2
|
assert ch_3.debug.call_count == 2
|
||||||
assert ch_5.warning.call_count == 2
|
assert ch_5.debug.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
async def test_poll_control_configure(poll_control_ch) -> None:
|
async def test_poll_control_configure(poll_control_ch) -> None:
|
||||||
|
@ -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})
|
await send_attributes_report(hass, cluster, {1: 1, 0: 10, 2: 20})
|
||||||
assert_state(hass, entity_id, "1", LIGHT_LUX)
|
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):
|
async def async_test_metering(hass, cluster, entity_id):
|
||||||
"""Test Smart Energy metering sensor."""
|
"""Test Smart Energy metering sensor."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user