This commit is contained in:
Franck Nijhof 2023-10-21 12:00:26 +02:00 committed by GitHub
commit aa5c4d8786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 4879 additions and 102 deletions

View File

@ -1,5 +1,6 @@
"""The AEMET OpenData component.""" """The AEMET OpenData component."""
import asyncio
import logging import logging
from aemet_opendata.exceptions import TownNotFound from aemet_opendata.exceptions import TownNotFound
@ -8,6 +9,7 @@ from aemet_opendata.interface import AEMET, ConnectionOptions
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import ( from .const import (
@ -37,6 +39,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except TownNotFound as err: except TownNotFound as err:
_LOGGER.error(err) _LOGGER.error(err)
return False return False
except asyncio.TimeoutError as err:
raise ConfigEntryNotReady("AEMET OpenData API timed out") from err
weather_coordinator = WeatherUpdateCoordinator(hass, aemet) weather_coordinator = WeatherUpdateCoordinator(hass, aemet)
await weather_coordinator.async_config_entry_first_refresh() await weather_coordinator.async_config_entry_first_refresh()

View File

@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone", "documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["aioairzone"], "loggers": ["aioairzone"],
"requirements": ["aioairzone==0.6.8"] "requirements": ["aioairzone==0.6.9"]
} }

View File

@ -18,7 +18,7 @@
"bleak-retry-connector==3.2.1", "bleak-retry-connector==3.2.1",
"bluetooth-adapters==0.16.1", "bluetooth-adapters==0.16.1",
"bluetooth-auto-recovery==1.2.3", "bluetooth-auto-recovery==1.2.3",
"bluetooth-data-tools==1.12.0", "bluetooth-data-tools==1.13.0",
"dbus-fast==2.11.1" "dbus-fast==2.12.0"
] ]
} }

View File

@ -483,7 +483,7 @@ class CalendarEntity(Entity):
_entity_component_unrecorded_attributes = frozenset({"description"}) _entity_component_unrecorded_attributes = frozenset({"description"})
_alarm_unsubs: list[CALLBACK_TYPE] = [] _alarm_unsubs: list[CALLBACK_TYPE] | None = None
@property @property
def event(self) -> CalendarEvent | None: def event(self) -> CalendarEvent | None:
@ -528,6 +528,8 @@ class CalendarEntity(Entity):
the current or upcoming event. the current or upcoming event.
""" """
super().async_write_ha_state() super().async_write_ha_state()
if self._alarm_unsubs is None:
self._alarm_unsubs = []
_LOGGER.debug( _LOGGER.debug(
"Clearing %s alarms (%s)", self.entity_id, len(self._alarm_unsubs) "Clearing %s alarms (%s)", self.entity_id, len(self._alarm_unsubs)
) )
@ -571,9 +573,9 @@ class CalendarEntity(Entity):
To be extended by integrations. To be extended by integrations.
""" """
for unsub in self._alarm_unsubs: for unsub in self._alarm_unsubs or ():
unsub() unsub()
self._alarm_unsubs.clear() self._alarm_unsubs = None
async def async_get_events( async def async_get_events(
self, self,

View File

@ -5,5 +5,5 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/duotecno", "documentation": "https://www.home-assistant.io/integrations/duotecno",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["pyDuotecno==2023.10.0"] "requirements": ["pyDuotecno==2023.10.1"]
} }

View File

@ -25,8 +25,11 @@ from aioesphomeapi import (
BluetoothProxyFeature, BluetoothProxyFeature,
DeviceInfo, DeviceInfo,
) )
from aioesphomeapi.connection import APIConnectionError, TimeoutAPIError from aioesphomeapi.core import (
from aioesphomeapi.core import BluetoothGATTAPIError APIConnectionError,
BluetoothGATTAPIError,
TimeoutAPIError,
)
from async_interrupt import interrupt from async_interrupt import interrupt
from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.client import BaseBleakClient, NotifyCallback from bleak.backends.client import BaseBleakClient, NotifyCallback
@ -389,8 +392,8 @@ class ESPHomeClient(BaseBleakClient):
return await self._disconnect() return await self._disconnect()
async def _disconnect(self) -> bool: async def _disconnect(self) -> bool:
self._async_disconnected_cleanup()
await self._client.bluetooth_device_disconnect(self._address_as_int) await self._client.bluetooth_device_disconnect(self._address_as_int)
self._async_ble_device_disconnected()
await self._wait_for_free_connection_slot(DISCONNECT_TIMEOUT) await self._wait_for_free_connection_slot(DISCONNECT_TIMEOUT)
return True return True

View File

@ -538,7 +538,7 @@ class ESPHomeManager:
on_connect=self.on_connect, on_connect=self.on_connect,
on_disconnect=self.on_disconnect, on_disconnect=self.on_disconnect,
zeroconf_instance=self.zeroconf_instance, zeroconf_instance=self.zeroconf_instance,
name=self.host, name=entry.data.get(CONF_DEVICE_NAME, self.host),
on_connect_error=self.on_connect_error, on_connect_error=self.on_connect_error,
) )
self.reconnect_logic = reconnect_logic self.reconnect_logic = reconnect_logic

View File

@ -16,8 +16,8 @@
"loggers": ["aioesphomeapi", "noiseprotocol"], "loggers": ["aioesphomeapi", "noiseprotocol"],
"requirements": [ "requirements": [
"async-interrupt==1.1.1", "async-interrupt==1.1.1",
"aioesphomeapi==17.0.1", "aioesphomeapi==18.0.7",
"bluetooth-data-tools==1.12.0", "bluetooth-data-tools==1.13.0",
"esphome-dashboard-api==1.2.3" "esphome-dashboard-api==1.2.3"
], ],
"zeroconf": ["_esphomelib._tcp.local."] "zeroconf": ["_esphomelib._tcp.local."]

View File

@ -55,6 +55,8 @@ _VOICE_ASSISTANT_EVENT_TYPES: EsphomeEnumMapper[
VoiceAssistantEventType.VOICE_ASSISTANT_TTS_END: PipelineEventType.TTS_END, VoiceAssistantEventType.VOICE_ASSISTANT_TTS_END: PipelineEventType.TTS_END,
VoiceAssistantEventType.VOICE_ASSISTANT_WAKE_WORD_START: PipelineEventType.WAKE_WORD_START, VoiceAssistantEventType.VOICE_ASSISTANT_WAKE_WORD_START: PipelineEventType.WAKE_WORD_START,
VoiceAssistantEventType.VOICE_ASSISTANT_WAKE_WORD_END: PipelineEventType.WAKE_WORD_END, VoiceAssistantEventType.VOICE_ASSISTANT_WAKE_WORD_END: PipelineEventType.WAKE_WORD_END,
VoiceAssistantEventType.VOICE_ASSISTANT_STT_VAD_START: PipelineEventType.STT_VAD_START,
VoiceAssistantEventType.VOICE_ASSISTANT_STT_VAD_END: PipelineEventType.STT_VAD_END,
} }
) )
@ -161,7 +163,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
try: try:
event_type = _VOICE_ASSISTANT_EVENT_TYPES.from_hass(event.type) event_type = _VOICE_ASSISTANT_EVENT_TYPES.from_hass(event.type)
except KeyError: except KeyError:
_LOGGER.warning("Received unknown pipeline event type: %s", event.type) _LOGGER.debug("Received unknown pipeline event type: %s", event.type)
return return
data_to_send = None data_to_send = None
@ -296,6 +298,10 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
if self.transport is None: if self.transport is None:
return return
self.handle_event(
VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {}
)
_extension, audio_bytes = await tts.async_get_media_source_audio( _extension, audio_bytes = await tts.async_get_media_source_audio(
self.hass, self.hass,
media_id, media_id,
@ -321,4 +327,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
sample_offset += samples_in_chunk sample_offset += samples_in_chunk
finally: finally:
self.handle_event(
VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_END, {}
)
self._tts_done.set() self._tts_done.set()

View File

@ -118,9 +118,7 @@ class GoogleMapsScanner:
) )
_LOGGER.debug("%s < %s", last_seen, self._prev_seen[dev_id]) _LOGGER.debug("%s < %s", last_seen, self._prev_seen[dev_id])
continue continue
if last_seen == self._prev_seen.get(dev_id, last_seen) and hasattr( if last_seen == self._prev_seen.get(dev_id):
self, "success_init"
):
_LOGGER.debug( _LOGGER.debug(
"Ignoring %s update because timestamp " "Ignoring %s update because timestamp "
"is the same as the last timestamp %s", "is the same as the last timestamp %s",

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble", "documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
"integration_type": "device", "integration_type": "device",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["bluetooth-data-tools==1.12.0", "ld2410-ble==0.1.1"] "requirements": ["bluetooth-data-tools==1.13.0", "ld2410-ble==0.1.1"]
} }

View File

@ -32,5 +32,5 @@
"dependencies": ["bluetooth_adapters"], "dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/led_ble", "documentation": "https://www.home-assistant.io/integrations/led_ble",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["bluetooth-data-tools==1.12.0", "led-ble==1.0.1"] "requirements": ["bluetooth-data-tools==1.13.0", "led-ble==1.0.1"]
} }

View File

@ -250,9 +250,9 @@ class MatterClimate(MatterEntity, ClimateEntity):
self._attr_min_temp = DEFAULT_MIN_TEMP self._attr_min_temp = DEFAULT_MIN_TEMP
# update max_temp # update max_temp
if self._attr_hvac_mode in (HVACMode.COOL, HVACMode.HEAT_COOL): if self._attr_hvac_mode in (HVACMode.COOL, HVACMode.HEAT_COOL):
attribute = clusters.Thermostat.Attributes.AbsMaxHeatSetpointLimit
else:
attribute = clusters.Thermostat.Attributes.AbsMaxCoolSetpointLimit attribute = clusters.Thermostat.Attributes.AbsMaxCoolSetpointLimit
else:
attribute = clusters.Thermostat.Attributes.AbsMaxHeatSetpointLimit
if (value := self._get_temperature_in_degrees(attribute)) is not None: if (value := self._get_temperature_in_degrees(attribute)) is not None:
self._attr_max_temp = value self._attr_max_temp = value
else: else:

View File

@ -176,7 +176,13 @@ async def async_subscribe(
raise HomeAssistantError( raise HomeAssistantError(
f"Cannot subscribe to topic '{topic}', MQTT is not enabled" f"Cannot subscribe to topic '{topic}', MQTT is not enabled"
) )
mqtt_data = get_mqtt_data(hass) try:
mqtt_data = get_mqtt_data(hass)
except KeyError as ex:
raise HomeAssistantError(
f"Cannot subscribe to topic '{topic}', "
"make sure MQTT is set up correctly"
) from ex
async_remove = await mqtt_data.client.async_subscribe( async_remove = await mqtt_data.client.async_subscribe(
topic, topic,
catch_log_exception( catch_log_exception(

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nina", "documentation": "https://www.home-assistant.io/integrations/nina",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["pynina"], "loggers": ["pynina"],
"requirements": ["PyNINA==0.3.2"] "requirements": ["PyNINA==0.3.3"]
} }

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower", "documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["opower"], "loggers": ["opower"],
"requirements": ["opower==0.0.35"] "requirements": ["opower==0.0.36"]
} }

View File

@ -6,5 +6,5 @@
"dependencies": ["bluetooth_adapters"], "dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/private_ble_device", "documentation": "https://www.home-assistant.io/integrations/private_ble_device",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["bluetooth-data-tools==1.12.0"] "requirements": ["bluetooth-data-tools==1.13.0"]
} }

View File

@ -7,5 +7,5 @@
"integration_type": "service", "integration_type": "service",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["vehicle==1.0.1"] "requirements": ["vehicle==2.0.0"]
} }

View File

@ -15,5 +15,5 @@
"documentation": "https://www.home-assistant.io/integrations/screenlogic", "documentation": "https://www.home-assistant.io/integrations/screenlogic",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["screenlogicpy"], "loggers": ["screenlogicpy"],
"requirements": ["screenlogicpy==0.9.2"] "requirements": ["screenlogicpy==0.9.3"]
} }

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
from asyncio import run_coroutine_threadsafe from asyncio import run_coroutine_threadsafe
import datetime as dt
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
@ -27,7 +26,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utcnow
from . import HomeAssistantSpotifyData from . import HomeAssistantSpotifyData
from .browse_media import async_browse_media_internal from .browse_media import async_browse_media_internal
@ -199,13 +198,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
return None return None
return self._currently_playing["progress_ms"] / 1000 return self._currently_playing["progress_ms"] / 1000
@property
def media_position_updated_at(self) -> dt.datetime | None:
"""When was the position of the current playing media valid."""
if not self._currently_playing:
return None
return utc_from_timestamp(self._currently_playing["timestamp"] / 1000)
@property @property
def media_image_url(self) -> str | None: def media_image_url(self) -> str | None:
"""Return the media image URL.""" """Return the media image URL."""
@ -413,6 +405,9 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
additional_types=[MediaType.EPISODE] additional_types=[MediaType.EPISODE]
) )
self._currently_playing = current or {} self._currently_playing = current or {}
# Record the last updated time, because Spotify's timestamp property is unreliable
# and doesn't actually return the fetch time as is mentioned in the API description
self._attr_media_position_updated_at = utcnow() if current is not None else None
context = self._currently_playing.get("context") or {} context = self._currently_playing.get("context") or {}

View File

@ -20,9 +20,14 @@ from homeassistant.const import (
CONF_PORT, CONF_PORT,
CONF_USERNAME, CONF_USERNAME,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
Platform,
) )
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
from homeassistant.helpers import aiohttp_client, device_registry as dr from homeassistant.helpers import (
aiohttp_client,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
DeviceEntry, DeviceEntry,
DeviceEntryType, DeviceEntryType,
@ -33,6 +38,7 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.helpers.event import async_call_later, async_track_time_interval from homeassistant.helpers.event import async_call_later, async_track_time_interval
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -131,7 +137,7 @@ class UniFiController:
# Client control options # Client control options
# Config entry option with list of clients to control network access. # Config entry option with list of clients to control network access.
self.option_block_clients = options.get(CONF_BLOCK_CLIENT, []) self.option_block_clients: list[str] = options.get(CONF_BLOCK_CLIENT, [])
# Config entry option to control DPI restriction groups. # Config entry option to control DPI restriction groups.
self.option_dpi_restrictions: bool = options.get( self.option_dpi_restrictions: bool = options.get(
CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS
@ -244,7 +250,16 @@ class UniFiController:
assert self.config_entry.unique_id is not None assert self.config_entry.unique_id is not None
self.is_admin = self.api.sites[self.config_entry.unique_id].role == "admin" self.is_admin = self.api.sites[self.config_entry.unique_id].role == "admin"
for mac in self.option_block_clients: # Restore device tracker clients that are not a part of active clients list.
macs: list[str] = []
entity_registry = er.async_get(self.hass)
for entry in async_entries_for_config_entry(
entity_registry, self.config_entry.entry_id
):
if entry.domain == Platform.DEVICE_TRACKER:
macs.append(entry.unique_id.split("-", 1)[0])
for mac in self.option_block_clients + macs:
if mac not in self.api.clients and mac in self.api.clients_all: if mac not in self.api.clients and mac in self.api.clients_all:
self.api.clients.process_raw([dict(self.api.clients_all[mac].raw)]) self.api.clients.process_raw([dict(self.api.clients_all[mac].raw)])

View File

@ -13,7 +13,7 @@
"velbus-packet", "velbus-packet",
"velbus-protocol" "velbus-protocol"
], ],
"requirements": ["velbus-aio==2023.2.0"], "requirements": ["velbus-aio==2023.10.1"],
"usb": [ "usb": [
{ {
"vid": "10CF", "vid": "10CF",

View File

@ -45,25 +45,18 @@ class VelbusSensor(VelbusEntity, SensorEntity):
"""Initialize a sensor Velbus entity.""" """Initialize a sensor Velbus entity."""
super().__init__(channel) super().__init__(channel)
self._is_counter: bool = counter self._is_counter: bool = counter
# define the unique id
if self._is_counter: if self._is_counter:
self._attr_unique_id = f"{self._attr_unique_id}-counter"
# define the name
if self._is_counter:
self._attr_name = f"{self._attr_name}-counter"
# define the device class
if self._is_counter:
self._attr_device_class = SensorDeviceClass.POWER
elif channel.is_counter_channel():
self._attr_device_class = SensorDeviceClass.ENERGY self._attr_device_class = SensorDeviceClass.ENERGY
self._attr_icon = "mdi:counter"
self._attr_name = f"{self._attr_name}-counter"
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
self._attr_unique_id = f"{self._attr_unique_id}-counter"
elif channel.is_counter_channel():
self._attr_device_class = SensorDeviceClass.POWER
self._attr_state_class = SensorStateClass.MEASUREMENT
elif channel.is_temperature(): elif channel.is_temperature():
self._attr_device_class = SensorDeviceClass.TEMPERATURE self._attr_device_class = SensorDeviceClass.TEMPERATURE
# define the icon self._attr_state_class = SensorStateClass.MEASUREMENT
if self._is_counter:
self._attr_icon = "mdi:counter"
# the state class
if self._is_counter:
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
else: else:
self._attr_state_class = SensorStateClass.MEASUREMENT self._attr_state_class = SensorStateClass.MEASUREMENT
# unit # unit

View File

@ -146,6 +146,7 @@ class VenstarSensor(VenstarEntity, SensorEntity):
super().__init__(coordinator, config) super().__init__(coordinator, config)
self.entity_description = entity_description self.entity_description = entity_description
self.sensor_name = sensor_name self.sensor_name = sensor_name
self._attr_name = entity_description.name_fn(sensor_name)
self._config = config self._config = config
@property @property
@ -153,11 +154,6 @@ class VenstarSensor(VenstarEntity, SensorEntity):
"""Return the unique id.""" """Return the unique id."""
return f"{self._config.entry_id}_{self.sensor_name.replace(' ', '_')}_{self.entity_description.key}" return f"{self._config.entry_id}_{self.sensor_name.replace(' ', '_')}_{self.entity_description.key}"
@property
def name(self):
"""Return the name of the device."""
return self.entity_description.name_fn(self.sensor_name)
@property @property
def native_value(self) -> int: def native_value(self) -> int:
"""Return state of the sensor.""" """Return state of the sensor."""

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/waqi", "documentation": "https://www.home-assistant.io/integrations/waqi",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aiowaqi"], "loggers": ["aiowaqi"],
"requirements": ["aiowaqi==2.0.0"] "requirements": ["aiowaqi==2.1.0"]
} }

View File

@ -21,15 +21,15 @@
"universal_silabs_flasher" "universal_silabs_flasher"
], ],
"requirements": [ "requirements": [
"bellows==0.36.5", "bellows==0.36.7",
"pyserial==3.5", "pyserial==3.5",
"pyserial-asyncio==0.6", "pyserial-asyncio==0.6",
"zha-quirks==0.0.104", "zha-quirks==0.0.105",
"zigpy-deconz==0.21.1", "zigpy-deconz==0.21.1",
"zigpy==0.57.2", "zigpy==0.57.2",
"zigpy-xbee==0.18.3", "zigpy-xbee==0.18.3",
"zigpy-zigate==0.11.0", "zigpy-zigate==0.11.0",
"zigpy-znp==0.11.5", "zigpy-znp==0.11.6",
"universal-silabs-flasher==0.0.14", "universal-silabs-flasher==0.0.14",
"pyserial-asyncio-fast==0.11" "pyserial-asyncio-fast==0.11"
], ],

View File

@ -259,9 +259,11 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType]: def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType]:
"""Return the list of enums that are relevant to the current thermostat mode.""" """Return the list of enums that are relevant to the current thermostat mode."""
if self._current_mode is None or self._current_mode.value is None: if self._current_mode is None or self._current_mode.value is None:
# Thermostat(valve) with no support for setting a mode # Thermostat with no support for setting a mode is just a setpoint
# is considered heating-only if self.info.primary_value.property_key is None:
return [ThermostatSetpointType.HEATING] return []
return [ThermostatSetpointType(int(self.info.primary_value.property_key))]
return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), [])
@property @property

View File

@ -163,12 +163,12 @@
} }
}, },
"device_config_file_changed": { "device_config_file_changed": {
"title": "Z-Wave device configuration file changed: {device_name}", "title": "Device configuration file changed: {device_name}",
"fix_flow": { "fix_flow": {
"step": { "step": {
"confirm": { "confirm": {
"title": "Z-Wave device configuration file changed: {device_name}", "title": "Device configuration file changed: {device_name}",
"description": "Z-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you'd like to proceed, click on SUBMIT below. The re-interview will take place in the background." "description": "The device configuration file for {device_name} has changed.\n\nZ-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you'd like to proceed, click on SUBMIT below. The re-interview will take place in the background."
} }
}, },
"abort": { "abort": {

View File

@ -7,7 +7,7 @@ from typing import Final
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2023 MAJOR_VERSION: Final = 2023
MINOR_VERSION: Final = 10 MINOR_VERSION: Final = 10
PATCH_VERSION: Final = "3" PATCH_VERSION: Final = "4"
__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)

View File

@ -11,11 +11,11 @@ bleak-retry-connector==3.2.1
bleak==0.21.1 bleak==0.21.1
bluetooth-adapters==0.16.1 bluetooth-adapters==0.16.1
bluetooth-auto-recovery==1.2.3 bluetooth-auto-recovery==1.2.3
bluetooth-data-tools==1.12.0 bluetooth-data-tools==1.13.0
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.3.0 ciso8601==2.3.0
cryptography==41.0.4 cryptography==41.0.4
dbus-fast==2.11.1 dbus-fast==2.12.0
fnv-hash-fast==0.4.1 fnv-hash-fast==0.4.1
ha-av==10.1.1 ha-av==10.1.1
hass-nabucasa==0.71.0 hass-nabucasa==0.71.0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2023.10.3" version = "2023.10.4"
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"

View File

@ -80,7 +80,7 @@ PyMetno==0.11.0
PyMicroBot==0.0.9 PyMicroBot==0.0.9
# homeassistant.components.nina # homeassistant.components.nina
PyNINA==0.3.2 PyNINA==0.3.3
# homeassistant.components.mobile_app # homeassistant.components.mobile_app
# homeassistant.components.owntracks # homeassistant.components.owntracks
@ -189,7 +189,7 @@ aioairq==0.2.4
aioairzone-cloud==0.2.3 aioairzone-cloud==0.2.3
# homeassistant.components.airzone # homeassistant.components.airzone
aioairzone==0.6.8 aioairzone==0.6.9
# homeassistant.components.ambient_station # homeassistant.components.ambient_station
aioambient==2023.04.0 aioambient==2023.04.0
@ -231,7 +231,7 @@ aioecowitt==2023.5.0
aioemonitor==1.0.5 aioemonitor==1.0.5
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==17.0.1 aioesphomeapi==18.0.7
# homeassistant.components.flo # homeassistant.components.flo
aioflo==2021.11.0 aioflo==2021.11.0
@ -372,7 +372,7 @@ aiovlc==0.1.0
aiovodafone==0.3.1 aiovodafone==0.3.1
# homeassistant.components.waqi # homeassistant.components.waqi
aiowaqi==2.0.0 aiowaqi==2.1.0
# homeassistant.components.watttime # homeassistant.components.watttime
aiowatttime==0.1.1 aiowatttime==0.1.1
@ -512,7 +512,7 @@ beautifulsoup4==4.12.2
# beewi-smartclim==0.0.10 # beewi-smartclim==0.0.10
# homeassistant.components.zha # homeassistant.components.zha
bellows==0.36.5 bellows==0.36.7
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer-connected==0.14.1 bimmer-connected==0.14.1
@ -553,7 +553,7 @@ bluetooth-auto-recovery==1.2.3
# homeassistant.components.ld2410_ble # homeassistant.components.ld2410_ble
# homeassistant.components.led_ble # homeassistant.components.led_ble
# homeassistant.components.private_ble_device # homeassistant.components.private_ble_device
bluetooth-data-tools==1.12.0 bluetooth-data-tools==1.13.0
# homeassistant.components.bond # homeassistant.components.bond
bond-async==0.2.1 bond-async==0.2.1
@ -645,7 +645,7 @@ datadog==0.15.0
datapoint==0.9.8 datapoint==0.9.8
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
dbus-fast==2.11.1 dbus-fast==2.12.0
# homeassistant.components.debugpy # homeassistant.components.debugpy
debugpy==1.8.0 debugpy==1.8.0
@ -1383,7 +1383,7 @@ openwrt-luci-rpc==1.1.16
openwrt-ubus-rpc==0.0.2 openwrt-ubus-rpc==0.0.2
# homeassistant.components.opower # homeassistant.components.opower
opower==0.0.35 opower==0.0.36
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6
@ -1538,7 +1538,7 @@ pyCEC==0.5.2
pyControl4==1.1.0 pyControl4==1.1.0
# homeassistant.components.duotecno # homeassistant.components.duotecno
pyDuotecno==2023.10.0 pyDuotecno==2023.10.1
# homeassistant.components.eight_sleep # homeassistant.components.eight_sleep
pyEight==0.3.2 pyEight==0.3.2
@ -2372,7 +2372,7 @@ satel-integra==0.3.7
scapy==2.5.0 scapy==2.5.0
# homeassistant.components.screenlogic # homeassistant.components.screenlogic
screenlogicpy==0.9.2 screenlogicpy==0.9.3
# homeassistant.components.scsgate # homeassistant.components.scsgate
scsgate==0.1.0 scsgate==0.1.0
@ -2648,10 +2648,10 @@ uvcclient==0.11.0
vallox-websocket-api==3.3.0 vallox-websocket-api==3.3.0
# homeassistant.components.rdw # homeassistant.components.rdw
vehicle==1.0.1 vehicle==2.0.0
# homeassistant.components.velbus # homeassistant.components.velbus
velbus-aio==2023.2.0 velbus-aio==2023.10.1
# homeassistant.components.venstar # homeassistant.components.venstar
venstarcolortouch==0.19 venstarcolortouch==0.19
@ -2787,7 +2787,7 @@ zeroconf==0.115.2
zeversolar==0.3.1 zeversolar==0.3.1
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.104 zha-quirks==0.0.105
# homeassistant.components.zhong_hong # homeassistant.components.zhong_hong
zhong-hong-hvac==1.0.9 zhong-hong-hvac==1.0.9
@ -2805,7 +2805,7 @@ zigpy-xbee==0.18.3
zigpy-zigate==0.11.0 zigpy-zigate==0.11.0
# homeassistant.components.zha # homeassistant.components.zha
zigpy-znp==0.11.5 zigpy-znp==0.11.6
# homeassistant.components.zha # homeassistant.components.zha
zigpy==0.57.2 zigpy==0.57.2

View File

@ -70,7 +70,7 @@ PyMetno==0.11.0
PyMicroBot==0.0.9 PyMicroBot==0.0.9
# homeassistant.components.nina # homeassistant.components.nina
PyNINA==0.3.2 PyNINA==0.3.3
# homeassistant.components.mobile_app # homeassistant.components.mobile_app
# homeassistant.components.owntracks # homeassistant.components.owntracks
@ -170,7 +170,7 @@ aioairq==0.2.4
aioairzone-cloud==0.2.3 aioairzone-cloud==0.2.3
# homeassistant.components.airzone # homeassistant.components.airzone
aioairzone==0.6.8 aioairzone==0.6.9
# homeassistant.components.ambient_station # homeassistant.components.ambient_station
aioambient==2023.04.0 aioambient==2023.04.0
@ -212,7 +212,7 @@ aioecowitt==2023.5.0
aioemonitor==1.0.5 aioemonitor==1.0.5
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==17.0.1 aioesphomeapi==18.0.7
# homeassistant.components.flo # homeassistant.components.flo
aioflo==2021.11.0 aioflo==2021.11.0
@ -347,7 +347,7 @@ aiovlc==0.1.0
aiovodafone==0.3.1 aiovodafone==0.3.1
# homeassistant.components.waqi # homeassistant.components.waqi
aiowaqi==2.0.0 aiowaqi==2.1.0
# homeassistant.components.watttime # homeassistant.components.watttime
aiowatttime==0.1.1 aiowatttime==0.1.1
@ -436,7 +436,7 @@ base36==0.1.1
beautifulsoup4==4.12.2 beautifulsoup4==4.12.2
# homeassistant.components.zha # homeassistant.components.zha
bellows==0.36.5 bellows==0.36.7
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer-connected==0.14.1 bimmer-connected==0.14.1
@ -467,7 +467,7 @@ bluetooth-auto-recovery==1.2.3
# homeassistant.components.ld2410_ble # homeassistant.components.ld2410_ble
# homeassistant.components.led_ble # homeassistant.components.led_ble
# homeassistant.components.private_ble_device # homeassistant.components.private_ble_device
bluetooth-data-tools==1.12.0 bluetooth-data-tools==1.13.0
# homeassistant.components.bond # homeassistant.components.bond
bond-async==0.2.1 bond-async==0.2.1
@ -528,7 +528,7 @@ datadog==0.15.0
datapoint==0.9.8 datapoint==0.9.8
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
dbus-fast==2.11.1 dbus-fast==2.12.0
# homeassistant.components.debugpy # homeassistant.components.debugpy
debugpy==1.8.0 debugpy==1.8.0
@ -1061,7 +1061,7 @@ openerz-api==0.2.0
openhomedevice==2.2.0 openhomedevice==2.2.0
# homeassistant.components.opower # homeassistant.components.opower
opower==0.0.35 opower==0.0.36
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6
@ -1171,7 +1171,7 @@ pyCEC==0.5.2
pyControl4==1.1.0 pyControl4==1.1.0
# homeassistant.components.duotecno # homeassistant.components.duotecno
pyDuotecno==2023.10.0 pyDuotecno==2023.10.1
# homeassistant.components.eight_sleep # homeassistant.components.eight_sleep
pyEight==0.3.2 pyEight==0.3.2
@ -1759,7 +1759,7 @@ samsungtvws[async,encrypted]==2.6.0
scapy==2.5.0 scapy==2.5.0
# homeassistant.components.screenlogic # homeassistant.components.screenlogic
screenlogicpy==0.9.2 screenlogicpy==0.9.3
# homeassistant.components.backup # homeassistant.components.backup
securetar==2023.3.0 securetar==2023.3.0
@ -1966,10 +1966,10 @@ uvcclient==0.11.0
vallox-websocket-api==3.3.0 vallox-websocket-api==3.3.0
# homeassistant.components.rdw # homeassistant.components.rdw
vehicle==1.0.1 vehicle==2.0.0
# homeassistant.components.velbus # homeassistant.components.velbus
velbus-aio==2023.2.0 velbus-aio==2023.10.1
# homeassistant.components.venstar # homeassistant.components.venstar
venstarcolortouch==0.19 venstarcolortouch==0.19
@ -2081,7 +2081,7 @@ zeroconf==0.115.2
zeversolar==0.3.1 zeversolar==0.3.1
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.104 zha-quirks==0.0.105
# homeassistant.components.zha # homeassistant.components.zha
zigpy-deconz==0.21.1 zigpy-deconz==0.21.1
@ -2093,7 +2093,7 @@ zigpy-xbee==0.18.3
zigpy-zigate==0.11.0 zigpy-zigate==0.11.0
# homeassistant.components.zha # homeassistant.components.zha
zigpy-znp==0.11.5 zigpy-znp==0.11.6
# homeassistant.components.zha # homeassistant.components.zha
zigpy==0.57.2 zigpy==0.57.2

View File

@ -1,4 +1,5 @@
"""Define tests for the AEMET OpenData init.""" """Define tests for the AEMET OpenData init."""
import asyncio
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
@ -70,3 +71,29 @@ async def test_init_town_not_found(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) is False assert await hass.config_entries.async_setup(config_entry.entry_id) is False
async def test_init_api_timeout(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test API timeouts when loading the AEMET integration."""
hass.config.set_time_zone("UTC")
freezer.move_to("2021-01-09 12:00:00+00:00")
with patch(
"homeassistant.components.aemet.AEMET.api_call",
side_effect=asyncio.TimeoutError,
):
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_API_KEY: "api-key",
CONF_LATITUDE: "0.0",
CONF_LONGITUDE: "0.0",
CONF_NAME: "AEMET",
},
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) is False

View File

@ -129,6 +129,7 @@ def mock_client(mock_device_info) -> APIClient:
mock_client.connect = AsyncMock() mock_client.connect = AsyncMock()
mock_client.disconnect = AsyncMock() mock_client.disconnect = AsyncMock()
mock_client.list_entities_services = AsyncMock(return_value=([], [])) mock_client.list_entities_services = AsyncMock(return_value=([], []))
mock_client.address = "127.0.0.1"
mock_client.api_version = APIVersion(99, 99) mock_client.api_version = APIVersion(99, 99)
with patch("homeassistant.components.esphome.APIClient", mock_client), patch( with patch("homeassistant.components.esphome.APIClient", mock_client), patch(

View File

@ -959,6 +959,17 @@ async def test_subscribe_topic(
unsub() unsub()
async def test_subscribe_topic_not_initialize(
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
) -> None:
"""Test the subscription of a topic when MQTT was not initialized."""
with pytest.raises(
HomeAssistantError, match=r".*make sure MQTT is set up correctly"
):
await mqtt.async_subscribe(hass, "test-topic", record_calls)
@patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0)
@patch("homeassistant.components.mqtt.client.UNSUBSCRIBE_COOLDOWN", 0.2) @patch("homeassistant.components.mqtt.client.UNSUBSCRIBE_COOLDOWN", 0.2)
async def test_subscribe_and_resubscribe( async def test_subscribe_and_resubscribe(

View File

@ -662,6 +662,12 @@ def logic_group_zdb5100_state_fixture():
return json.loads(load_fixture("zwave_js/logic_group_zdb5100_state.json")) return json.loads(load_fixture("zwave_js/logic_group_zdb5100_state.json"))
@pytest.fixture(name="climate_intermatic_pe653_state", scope="session")
def climate_intermatic_pe653_state_fixture():
"""Load Intermatic PE653 Pool Control node state fixture data."""
return json.loads(load_fixture("zwave_js/climate_intermatic_pe653_state.json"))
# model fixtures # model fixtures
@ -1290,3 +1296,11 @@ def logic_group_zdb5100_fixture(client, logic_group_zdb5100_state):
node = Node(client, copy.deepcopy(logic_group_zdb5100_state)) node = Node(client, copy.deepcopy(logic_group_zdb5100_state))
client.driver.controller.nodes[node.node_id] = node client.driver.controller.nodes[node.node_id] = node
return node return node
@pytest.fixture(name="climate_intermatic_pe653")
def climate_intermatic_pe653_fixture(client, climate_intermatic_pe653_state):
"""Mock an Intermatic PE653 node."""
node = Node(client, copy.deepcopy(climate_intermatic_pe653_state))
client.driver.controller.nodes[node.node_id] = node
return node

File diff suppressed because it is too large Load Diff

View File

@ -792,3 +792,196 @@ async def test_thermostat_raise_repair_issue_and_warning_when_setting_fan_preset
"Dry and Fan preset modes are deprecated and will be removed in Home Assistant 2024.2. Please use the corresponding Dry and Fan HVAC modes instead" "Dry and Fan preset modes are deprecated and will be removed in Home Assistant 2024.2. Please use the corresponding Dry and Fan HVAC modes instead"
in caplog.text in caplog.text
) )
async def test_multi_setpoint_thermostat(
hass: HomeAssistant, client, climate_intermatic_pe653, integration
) -> None:
"""Test a thermostat with multiple setpoints."""
node = climate_intermatic_pe653
heating_entity_id = "climate.pool_control_2"
heating = hass.states.get(heating_entity_id)
assert heating
assert heating.state == HVACMode.HEAT
assert heating.attributes[ATTR_TEMPERATURE] == 3.9
assert heating.attributes[ATTR_HVAC_MODES] == [HVACMode.HEAT]
assert (
heating.attributes[ATTR_SUPPORTED_FEATURES]
== ClimateEntityFeature.TARGET_TEMPERATURE
)
furnace_entity_id = "climate.pool_control"
furnace = hass.states.get(furnace_entity_id)
assert furnace
assert furnace.state == HVACMode.HEAT
assert furnace.attributes[ATTR_TEMPERATURE] == 15.6
assert furnace.attributes[ATTR_HVAC_MODES] == [HVACMode.HEAT]
assert (
furnace.attributes[ATTR_SUPPORTED_FEATURES]
== ClimateEntityFeature.TARGET_TEMPERATURE
)
client.async_send_command_no_wait.reset_mock()
# Test setting temperature of heating setpoint
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: heating_entity_id,
ATTR_TEMPERATURE: 20.0,
},
blocking=True,
)
# Test setting temperature of furnace setpoint
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: furnace_entity_id,
ATTR_TEMPERATURE: 2.0,
},
blocking=True,
)
# Test setting illegal mode raises an error
with pytest.raises(ValueError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: heating_entity_id,
ATTR_HVAC_MODE: HVACMode.COOL,
},
blocking=True,
)
with pytest.raises(ValueError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: furnace_entity_id,
ATTR_HVAC_MODE: HVACMode.COOL,
},
blocking=True,
)
# this is a no-op since there's no mode
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: heating_entity_id,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
)
# this is a no-op since there's no mode
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: furnace_entity_id,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 2
args = client.async_send_command.call_args_list[0][0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 19
assert args["valueId"] == {
"endpoint": 1,
"commandClass": 67,
"property": "setpoint",
"propertyKey": 1,
}
assert args["value"] == 68.0
args = client.async_send_command.call_args_list[1][0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 19
assert args["valueId"] == {
"endpoint": 0,
"commandClass": 67,
"property": "setpoint",
"propertyKey": 7,
}
assert args["value"] == 35.6
client.async_send_command.reset_mock()
# Test heating setpoint value update from value updated event
event = Event(
type="value updated",
data={
"source": "node",
"event": "value updated",
"nodeId": 19,
"args": {
"commandClassName": "Thermostat Setpoint",
"commandClass": 67,
"endpoint": 1,
"property": "setpoint",
"propertyKey": 1,
"propertyKeyName": "Heating",
"propertyName": "setpoint",
"newValue": 23,
"prevValue": 21.5,
},
},
)
node.receive_event(event)
state = hass.states.get(heating_entity_id)
assert state
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == -5
# furnace not changed
state = hass.states.get(furnace_entity_id)
assert state
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == 15.6
client.async_send_command.reset_mock()
# Test furnace setpoint value update from value updated event
event = Event(
type="value updated",
data={
"source": "node",
"event": "value updated",
"nodeId": 19,
"args": {
"commandClassName": "Thermostat Setpoint",
"commandClass": 67,
"endpoint": 0,
"property": "setpoint",
"propertyKey": 7,
"propertyKeyName": "Furnace",
"propertyName": "setpoint",
"newValue": 68,
"prevValue": 21.5,
},
},
)
node.receive_event(event)
# heating not changed
state = hass.states.get(heating_entity_id)
assert state
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == -5
state = hass.states.get(furnace_entity_id)
assert state
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == 20
client.async_send_command.reset_mock()