2024.1.6 (#109129)
This commit is contained in:
commit
1f7bf7c2a9
35 changed files with 170 additions and 86 deletions
|
@ -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]
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
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 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
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."""
|
||||
|
|
Loading…
Add table
Reference in a new issue