mirror of
https://github.com/home-assistant/core.git
synced 2025-07-31 17:18:23 +00:00
2025.7.4 (#149526)
This commit is contained in:
commit
777b3128bb
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioamazondevices"],
|
"loggers": ["aioamazondevices"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["aioamazondevices==3.5.0"]
|
"requirements": ["aioamazondevices==3.5.1"]
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ from homeassistant.components.climate import (
|
|||||||
FAN_HIGH,
|
FAN_HIGH,
|
||||||
FAN_LOW,
|
FAN_LOW,
|
||||||
FAN_MEDIUM,
|
FAN_MEDIUM,
|
||||||
|
FAN_OFF,
|
||||||
SWING_BOTH,
|
SWING_BOTH,
|
||||||
SWING_HORIZONTAL,
|
SWING_HORIZONTAL,
|
||||||
SWING_OFF,
|
SWING_OFF,
|
||||||
@ -31,6 +32,7 @@ from .coordinator import FGLairConfigEntry, FGLairCoordinator
|
|||||||
from .entity import FGLairEntity
|
from .entity import FGLairEntity
|
||||||
|
|
||||||
HA_TO_FUJI_FAN = {
|
HA_TO_FUJI_FAN = {
|
||||||
|
FAN_OFF: FanSpeed.QUIET,
|
||||||
FAN_LOW: FanSpeed.LOW,
|
FAN_LOW: FanSpeed.LOW,
|
||||||
FAN_MEDIUM: FanSpeed.MEDIUM,
|
FAN_MEDIUM: FanSpeed.MEDIUM,
|
||||||
FAN_HIGH: FanSpeed.HIGH,
|
FAN_HIGH: FanSpeed.HIGH,
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["habiticalib"],
|
"loggers": ["habiticalib"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["habiticalib==0.4.0"]
|
"requirements": ["habiticalib==0.4.1"]
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,9 @@ import aiolifx_effects as aiolifx_effects_module
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_BRIGHTNESS_STEP,
|
||||||
|
ATTR_BRIGHTNESS_STEP_PCT,
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
LIGHT_TURN_ON_SCHEMA,
|
LIGHT_TURN_ON_SCHEMA,
|
||||||
@ -234,6 +237,20 @@ class LIFXLight(LIFXEntity, LightEntity):
|
|||||||
else:
|
else:
|
||||||
fade = 0
|
fade = 0
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS_STEP in kwargs or ATTR_BRIGHTNESS_STEP_PCT in kwargs:
|
||||||
|
brightness = self.brightness if self.is_on and self.brightness else 0
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS_STEP in kwargs:
|
||||||
|
brightness += kwargs.pop(ATTR_BRIGHTNESS_STEP)
|
||||||
|
|
||||||
|
else:
|
||||||
|
brightness_pct = round(brightness / 255 * 100)
|
||||||
|
brightness = round(
|
||||||
|
(brightness_pct + kwargs.pop(ATTR_BRIGHTNESS_STEP_PCT)) / 100 * 255
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs[ATTR_BRIGHTNESS] = max(0, min(255, brightness))
|
||||||
|
|
||||||
# These are both False if ATTR_POWER is not set
|
# These are both False if ATTR_POWER is not set
|
||||||
power_on = kwargs.get(ATTR_POWER, False)
|
power_on = kwargs.get(ATTR_POWER, False)
|
||||||
power_off = not kwargs.get(ATTR_POWER, True)
|
power_off = not kwargs.get(ATTR_POWER, True)
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from chip.clusters import Objects as clusters
|
from chip.clusters import Objects as clusters
|
||||||
|
from chip.clusters.Objects import NullValue
|
||||||
from matter_server.client.models import device_types
|
from matter_server.client.models import device_types
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
@ -241,7 +242,7 @@ class MatterLight(MatterEntity, LightEntity):
|
|||||||
|
|
||||||
return int(color_temp)
|
return int(color_temp)
|
||||||
|
|
||||||
def _get_brightness(self) -> int:
|
def _get_brightness(self) -> int | None:
|
||||||
"""Get brightness from matter."""
|
"""Get brightness from matter."""
|
||||||
|
|
||||||
level_control = self._endpoint.get_cluster(clusters.LevelControl)
|
level_control = self._endpoint.get_cluster(clusters.LevelControl)
|
||||||
@ -255,6 +256,10 @@ class MatterLight(MatterEntity, LightEntity):
|
|||||||
self.entity_id,
|
self.entity_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if level_control.currentLevel is NullValue:
|
||||||
|
# currentLevel is a nullable value.
|
||||||
|
return None
|
||||||
|
|
||||||
return round(
|
return round(
|
||||||
renormalize(
|
renormalize(
|
||||||
level_control.currentLevel,
|
level_control.currentLevel,
|
||||||
|
@ -218,6 +218,9 @@ def _async_fix_device_id(
|
|||||||
for device_entry in device_entries:
|
for device_entry in device_entries:
|
||||||
unique_id = str(next(iter(device_entry.identifiers))[1])
|
unique_id = str(next(iter(device_entry.identifiers))[1])
|
||||||
device_entry_map[unique_id] = device_entry
|
device_entry_map[unique_id] = device_entry
|
||||||
|
if unique_id.startswith(mac_address):
|
||||||
|
# Already in the correct format
|
||||||
|
continue
|
||||||
if (suffix := unique_id.removeprefix(str(serial_number))) != unique_id:
|
if (suffix := unique_id.removeprefix(str(serial_number))) != unique_id:
|
||||||
migrations[unique_id] = f"{mac_address}{suffix}"
|
migrations[unique_id] = f"{mac_address}{suffix}"
|
||||||
|
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/schlage",
|
"documentation": "https://www.home-assistant.io/integrations/schlage",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["pyschlage==2025.4.0"]
|
"requirements": ["pyschlage==2025.7.2"]
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pysuez", "regex"],
|
"loggers": ["pysuez", "regex"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pysuezV2==2.0.5"]
|
"requirements": ["pysuezV2==2.0.7"]
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ class PushBot(BaseTelegramBot):
|
|||||||
self.base_url = config.data.get(CONF_URL) or get_url(
|
self.base_url = config.data.get(CONF_URL) or get_url(
|
||||||
hass, require_ssl=True, allow_internal=False
|
hass, require_ssl=True, allow_internal=False
|
||||||
)
|
)
|
||||||
self.webhook_url = f"{self.base_url}{TELEGRAM_WEBHOOK_URL}"
|
self.webhook_url = self.base_url + _get_webhook_url(bot)
|
||||||
|
|
||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
"""Shutdown the app."""
|
"""Shutdown the app."""
|
||||||
@ -98,9 +98,11 @@ class PushBot(BaseTelegramBot):
|
|||||||
api_kwargs={"secret_token": self.secret_token},
|
api_kwargs={"secret_token": self.secret_token},
|
||||||
connect_timeout=5,
|
connect_timeout=5,
|
||||||
)
|
)
|
||||||
except TelegramError:
|
except TelegramError as err:
|
||||||
retry_num += 1
|
retry_num += 1
|
||||||
_LOGGER.warning("Error trying to set webhook (retry #%d)", retry_num)
|
_LOGGER.warning(
|
||||||
|
"Error trying to set webhook (retry #%d)", retry_num, exc_info=err
|
||||||
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -143,7 +145,6 @@ class PushBotView(HomeAssistantView):
|
|||||||
"""View for handling webhook calls from Telegram."""
|
"""View for handling webhook calls from Telegram."""
|
||||||
|
|
||||||
requires_auth = False
|
requires_auth = False
|
||||||
url = TELEGRAM_WEBHOOK_URL
|
|
||||||
name = "telegram_webhooks"
|
name = "telegram_webhooks"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -160,6 +161,7 @@ class PushBotView(HomeAssistantView):
|
|||||||
self.application = application
|
self.application = application
|
||||||
self.trusted_networks = trusted_networks
|
self.trusted_networks = trusted_networks
|
||||||
self.secret_token = secret_token
|
self.secret_token = secret_token
|
||||||
|
self.url = _get_webhook_url(bot)
|
||||||
|
|
||||||
async def post(self, request: HomeAssistantRequest) -> Response | None:
|
async def post(self, request: HomeAssistantRequest) -> Response | None:
|
||||||
"""Accept the POST from telegram."""
|
"""Accept the POST from telegram."""
|
||||||
@ -183,3 +185,7 @@ class PushBotView(HomeAssistantView):
|
|||||||
await self.application.process_update(update)
|
await self.application.process_update(update)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_webhook_url(bot: Bot) -> str:
|
||||||
|
return f"{TELEGRAM_WEBHOOK_URL}_{bot.id}"
|
||||||
|
@ -14,9 +14,8 @@ CONF_REFRESH_TOKEN = "refresh_token"
|
|||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
CLIENT_ID = "71b813eb-4a2e-483a-b831-4dec5cb9bf0d"
|
AUTHORIZE_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/authorize"
|
||||||
AUTHORIZE_URL = "https://auth.tesla.com/oauth2/v3/authorize"
|
TOKEN_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token"
|
||||||
TOKEN_URL = "https://auth.tesla.com/oauth2/v3/token"
|
|
||||||
|
|
||||||
SCOPES = [
|
SCOPES = [
|
||||||
Scope.OPENID,
|
Scope.OPENID,
|
||||||
|
@ -23,7 +23,7 @@ async def async_setup_entry(
|
|||||||
entities: list[WebControlProGenericEntity] = [
|
entities: list[WebControlProGenericEntity] = [
|
||||||
WebControlProIdentifyButton(config_entry.entry_id, dest)
|
WebControlProIdentifyButton(config_entry.entry_id, dest)
|
||||||
for dest in hub.dests.values()
|
for dest in hub.dests.values()
|
||||||
if dest.action(WMS_WebControl_pro_API_actionDescription.Identify)
|
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.Identify)
|
||||||
]
|
]
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@ -32,9 +32,9 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
entities: list[WebControlProGenericEntity] = []
|
entities: list[WebControlProGenericEntity] = []
|
||||||
for dest in hub.dests.values():
|
for dest in hub.dests.values():
|
||||||
if dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive):
|
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.AwningDrive):
|
||||||
entities.append(WebControlProAwning(config_entry.entry_id, dest))
|
entities.append(WebControlProAwning(config_entry.entry_id, dest))
|
||||||
elif dest.action(
|
elif dest.hasAction(
|
||||||
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
|
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
|
||||||
):
|
):
|
||||||
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))
|
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))
|
||||||
|
@ -33,9 +33,9 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
entities: list[WebControlProGenericEntity] = []
|
entities: list[WebControlProGenericEntity] = []
|
||||||
for dest in hub.dests.values():
|
for dest in hub.dests.values():
|
||||||
if dest.action(WMS_WebControl_pro_API_actionDescription.LightDimming):
|
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.LightDimming):
|
||||||
entities.append(WebControlProDimmer(config_entry.entry_id, dest))
|
entities.append(WebControlProDimmer(config_entry.entry_id, dest))
|
||||||
elif dest.action(WMS_WebControl_pro_API_actionDescription.LightSwitch):
|
elif dest.hasAction(WMS_WebControl_pro_API_actionDescription.LightSwitch):
|
||||||
entities.append(WebControlProLight(config_entry.entry_id, dest))
|
entities.append(WebControlProLight(config_entry.entry_id, dest))
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@ -14,5 +14,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/wmspro",
|
"documentation": "https://www.home-assistant.io/integrations/wmspro",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["pywmspro==0.3.0"]
|
"requirements": ["pywmspro==0.3.2"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pyasn1", "slixmpp"],
|
"loggers": ["pyasn1", "slixmpp"],
|
||||||
"quality_scale": "legacy",
|
"quality_scale": "legacy",
|
||||||
"requirements": ["slixmpp==1.8.5", "emoji==2.8.0"]
|
"requirements": ["slixmpp==1.10.0", "emoji==2.8.0"]
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,8 @@ async def async_send_message( # noqa: C901
|
|||||||
|
|
||||||
self.loop = hass.loop
|
self.loop = hass.loop
|
||||||
|
|
||||||
self.force_starttls = use_tls
|
self.enable_starttls = use_tls
|
||||||
|
self.enable_direct_tls = use_tls
|
||||||
self.use_ipv6 = False
|
self.use_ipv6 = False
|
||||||
self.add_event_handler("failed_all_auth", self.disconnect_on_login_fail)
|
self.add_event_handler("failed_all_auth", self.disconnect_on_login_fail)
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
@ -163,7 +164,7 @@ async def async_send_message( # noqa: C901
|
|||||||
self.register_plugin("xep_0128") # Service Discovery
|
self.register_plugin("xep_0128") # Service Discovery
|
||||||
self.register_plugin("xep_0363") # HTTP upload
|
self.register_plugin("xep_0363") # HTTP upload
|
||||||
|
|
||||||
self.connect(force_starttls=self.force_starttls, use_ssl=False)
|
self.connect()
|
||||||
|
|
||||||
async def start(self, event):
|
async def start(self, event):
|
||||||
"""Start the communication and sends the message."""
|
"""Start the communication and sends the message."""
|
||||||
|
@ -495,10 +495,23 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
self._usb_discovery = True
|
self._usb_discovery = True
|
||||||
if current_config_entries:
|
if current_config_entries:
|
||||||
return await self.async_step_intent_migrate()
|
return await self.async_step_confirm_usb_migration()
|
||||||
|
|
||||||
return await self.async_step_installation_type()
|
return await self.async_step_installation_type()
|
||||||
|
|
||||||
|
async def async_step_confirm_usb_migration(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Confirm USB migration."""
|
||||||
|
if user_input is not None:
|
||||||
|
return await self.async_step_intent_migrate()
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="confirm_usb_migration",
|
||||||
|
description_placeholders={
|
||||||
|
"usb_title": self.context["title_placeholders"][CONF_NAME],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_manual(
|
async def async_step_manual(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from zwave_js_server.const import NodeStatus
|
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
from zwave_js_server.model.value import (
|
from zwave_js_server.model.value import (
|
||||||
@ -27,8 +26,6 @@ from .discovery import ZwaveDiscoveryInfo
|
|||||||
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
|
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
|
||||||
|
|
||||||
EVENT_VALUE_REMOVED = "value removed"
|
EVENT_VALUE_REMOVED = "value removed"
|
||||||
EVENT_DEAD = "dead"
|
|
||||||
EVENT_ALIVE = "alive"
|
|
||||||
|
|
||||||
|
|
||||||
class ZWaveBaseEntity(Entity):
|
class ZWaveBaseEntity(Entity):
|
||||||
@ -141,11 +138,6 @@ class ZWaveBaseEntity(Entity):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for status_event in (EVENT_ALIVE, EVENT_DEAD):
|
|
||||||
self.async_on_remove(
|
|
||||||
self.info.node.on(status_event, self._node_status_alive_or_dead)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -211,19 +203,7 @@ class ZWaveBaseEntity(Entity):
|
|||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return entity availability."""
|
"""Return entity availability."""
|
||||||
return (
|
return self.driver.client.connected and bool(self.info.node.ready)
|
||||||
self.driver.client.connected
|
|
||||||
and bool(self.info.node.ready)
|
|
||||||
and self.info.node.status != NodeStatus.DEAD
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _node_status_alive_or_dead(self, event_data: dict) -> None:
|
|
||||||
"""Call when node status changes to alive or dead.
|
|
||||||
|
|
||||||
Should not be overridden by subclasses.
|
|
||||||
"""
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _value_changed(self, event_data: dict) -> None:
|
def _value_changed(self, event_data: dict) -> None:
|
||||||
|
@ -108,6 +108,10 @@
|
|||||||
"start_addon": {
|
"start_addon": {
|
||||||
"title": "Configuring add-on"
|
"title": "Configuring add-on"
|
||||||
},
|
},
|
||||||
|
"confirm_usb_migration": {
|
||||||
|
"description": "You are about to migrate your Z-Wave network from the old adapter to the new adapter {usb_title}. This will take a backup of the network from the old adapter and restore the network to the new adapter.\n\nPress Submit to continue with the migration.",
|
||||||
|
"title": "Migrate to a new adapter"
|
||||||
|
},
|
||||||
"zeroconf_confirm": {
|
"zeroconf_confirm": {
|
||||||
"description": "Do you want to add the Z-Wave Server with home ID {home_id} found at {url} to Home Assistant?",
|
"description": "Do you want to add the Z-Wave Server with home ID {home_id} found at {url} to Home Assistant?",
|
||||||
"title": "Discovered Z-Wave Server"
|
"title": "Discovered Z-Wave Server"
|
||||||
|
@ -200,18 +200,13 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# If device is asleep/dead, wait for it to wake up/become alive before
|
# If device is asleep, wait for it to wake up before attempting an update
|
||||||
# attempting an update
|
if self.node.status == NodeStatus.ASLEEP:
|
||||||
for status, event_name in (
|
if not self._status_unsub:
|
||||||
(NodeStatus.ASLEEP, "wake up"),
|
self._status_unsub = self.node.once(
|
||||||
(NodeStatus.DEAD, "alive"),
|
"wake up", self._update_on_status_change
|
||||||
):
|
)
|
||||||
if self.node.status == status:
|
return
|
||||||
if not self._status_unsub:
|
|
||||||
self._status_unsub = self.node.once(
|
|
||||||
event_name, self._update_on_status_change
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Retrieve all firmware updates including non-stable ones but filter
|
# Retrieve all firmware updates including non-stable ones but filter
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2025
|
MAJOR_VERSION: Final = 2025
|
||||||
MINOR_VERSION: Final = 7
|
MINOR_VERSION: Final = 7
|
||||||
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, 13, 2)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.7.3"
|
version = "2025.7.4"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
|
12
requirements_all.txt
generated
12
requirements_all.txt
generated
@ -185,7 +185,7 @@ aioairzone-cloud==0.6.12
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==3.5.0
|
aioamazondevices==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -1121,7 +1121,7 @@ ha-philipsjs==3.2.2
|
|||||||
ha-silabs-firmware-client==0.2.0
|
ha-silabs-firmware-client==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.habitica
|
# homeassistant.components.habitica
|
||||||
habiticalib==0.4.0
|
habiticalib==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
habluetooth==3.49.0
|
habluetooth==3.49.0
|
||||||
@ -2309,7 +2309,7 @@ pysabnzbd==1.1.1
|
|||||||
pysaj==0.0.16
|
pysaj==0.0.16
|
||||||
|
|
||||||
# homeassistant.components.schlage
|
# homeassistant.components.schlage
|
||||||
pyschlage==2025.4.0
|
pyschlage==2025.7.2
|
||||||
|
|
||||||
# homeassistant.components.sensibo
|
# homeassistant.components.sensibo
|
||||||
pysensibo==1.2.1
|
pysensibo==1.2.1
|
||||||
@ -2384,7 +2384,7 @@ pysqueezebox==0.12.1
|
|||||||
pystiebeleltron==0.1.0
|
pystiebeleltron==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.suez_water
|
# homeassistant.components.suez_water
|
||||||
pysuezV2==2.0.5
|
pysuezV2==2.0.7
|
||||||
|
|
||||||
# homeassistant.components.switchbee
|
# homeassistant.components.switchbee
|
||||||
pyswitchbee==1.8.3
|
pyswitchbee==1.8.3
|
||||||
@ -2596,7 +2596,7 @@ pywilight==0.0.74
|
|||||||
pywizlight==0.6.3
|
pywizlight==0.6.3
|
||||||
|
|
||||||
# homeassistant.components.wmspro
|
# homeassistant.components.wmspro
|
||||||
pywmspro==0.3.0
|
pywmspro==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.ws66i
|
# homeassistant.components.ws66i
|
||||||
pyws66i==1.1
|
pyws66i==1.1
|
||||||
@ -2786,7 +2786,7 @@ skyboxremote==0.0.6
|
|||||||
slack_sdk==3.33.4
|
slack_sdk==3.33.4
|
||||||
|
|
||||||
# homeassistant.components.xmpp
|
# homeassistant.components.xmpp
|
||||||
slixmpp==1.8.5
|
slixmpp==1.10.0
|
||||||
|
|
||||||
# homeassistant.components.smart_meter_texas
|
# homeassistant.components.smart_meter_texas
|
||||||
smart-meter-texas==0.5.5
|
smart-meter-texas==0.5.5
|
||||||
|
10
requirements_test_all.txt
generated
10
requirements_test_all.txt
generated
@ -173,7 +173,7 @@ aioairzone-cloud==0.6.12
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==3.5.0
|
aioamazondevices==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -982,7 +982,7 @@ ha-philipsjs==3.2.2
|
|||||||
ha-silabs-firmware-client==0.2.0
|
ha-silabs-firmware-client==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.habitica
|
# homeassistant.components.habitica
|
||||||
habiticalib==0.4.0
|
habiticalib==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
habluetooth==3.49.0
|
habluetooth==3.49.0
|
||||||
@ -1921,7 +1921,7 @@ pyrympro==0.0.9
|
|||||||
pysabnzbd==1.1.1
|
pysabnzbd==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.schlage
|
# homeassistant.components.schlage
|
||||||
pyschlage==2025.4.0
|
pyschlage==2025.7.2
|
||||||
|
|
||||||
# homeassistant.components.sensibo
|
# homeassistant.components.sensibo
|
||||||
pysensibo==1.2.1
|
pysensibo==1.2.1
|
||||||
@ -1987,7 +1987,7 @@ pysqueezebox==0.12.1
|
|||||||
pystiebeleltron==0.1.0
|
pystiebeleltron==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.suez_water
|
# homeassistant.components.suez_water
|
||||||
pysuezV2==2.0.5
|
pysuezV2==2.0.7
|
||||||
|
|
||||||
# homeassistant.components.switchbee
|
# homeassistant.components.switchbee
|
||||||
pyswitchbee==1.8.3
|
pyswitchbee==1.8.3
|
||||||
@ -2154,7 +2154,7 @@ pywilight==0.0.74
|
|||||||
pywizlight==0.6.3
|
pywizlight==0.6.3
|
||||||
|
|
||||||
# homeassistant.components.wmspro
|
# homeassistant.components.wmspro
|
||||||
pywmspro==0.3.0
|
pywmspro==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.ws66i
|
# homeassistant.components.ws66i
|
||||||
pyws66i==1.1
|
pyws66i==1.1
|
||||||
|
@ -30,6 +30,8 @@ from homeassistant.components.lifx.manager import (
|
|||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_BRIGHTNESS_PCT,
|
ATTR_BRIGHTNESS_PCT,
|
||||||
|
ATTR_BRIGHTNESS_STEP,
|
||||||
|
ATTR_BRIGHTNESS_STEP_PCT,
|
||||||
ATTR_COLOR_MODE,
|
ATTR_COLOR_MODE,
|
||||||
ATTR_COLOR_NAME,
|
ATTR_COLOR_NAME,
|
||||||
ATTR_COLOR_TEMP_KELVIN,
|
ATTR_COLOR_TEMP_KELVIN,
|
||||||
@ -1735,6 +1737,48 @@ async def test_transitions_color_bulb(hass: HomeAssistant) -> None:
|
|||||||
bulb.set_color.reset_mock()
|
bulb.set_color.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_lifx_set_state_brightness(hass: HomeAssistant) -> None:
|
||||||
|
"""Test lifx.set_state works with brightness, brightness_pct and brightness_step."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
bulb = _mocked_bulb_new_firmware()
|
||||||
|
bulb.power_level = 65535
|
||||||
|
bulb.color = [0, 0, 32768, 3500]
|
||||||
|
with (
|
||||||
|
_patch_discovery(device=bulb),
|
||||||
|
_patch_config_flow_try_connect(device=bulb),
|
||||||
|
_patch_device(device=bulb),
|
||||||
|
):
|
||||||
|
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_id = "light.my_bulb"
|
||||||
|
|
||||||
|
# brightness_step should convert from 8 bit to 16 bit
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"set_state",
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_STEP: 128},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert bulb.set_color.calls[0][0][0] == [0, 0, 65535, 3500]
|
||||||
|
bulb.set_color.reset_mock()
|
||||||
|
|
||||||
|
# brightness_step_pct should convert from percentage to 16 bit
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"set_state",
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_STEP_PCT: 50},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert bulb.set_color.calls[0][0][0] == [0, 0, 65535, 3500]
|
||||||
|
bulb.set_color.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
async def test_lifx_set_state_color(hass: HomeAssistant) -> None:
|
async def test_lifx_set_state_color(hass: HomeAssistant) -> None:
|
||||||
"""Test lifx.set_state works with color names and RGB."""
|
"""Test lifx.set_state works with color names and RGB."""
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
|
@ -131,6 +131,15 @@ async def test_dimmable_light(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test a dimmable light."""
|
"""Test a dimmable light."""
|
||||||
|
|
||||||
|
# Test for currentLevel is None
|
||||||
|
set_node_attribute(matter_node, 1, 8, 0, None)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "on"
|
||||||
|
assert state.attributes["brightness"] is None
|
||||||
|
|
||||||
# Test that the light brightness is 50 (out of 254)
|
# Test that the light brightness is 50 (out of 254)
|
||||||
set_node_attribute(matter_node, 1, 8, 0, 50)
|
set_node_attribute(matter_node, 1, 8, 0, 50)
|
||||||
await trigger_subscription_callback(hass, matter_client)
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
@ -449,3 +449,75 @@ async def test_fix_duplicate_device_ids(
|
|||||||
assert device_entry.identifiers == {(DOMAIN, MAC_ADDRESS_UNIQUE_ID)}
|
assert device_entry.identifiers == {(DOMAIN, MAC_ADDRESS_UNIQUE_ID)}
|
||||||
assert device_entry.name_by_user == expected_device_name
|
assert device_entry.name_by_user == expected_device_name
|
||||||
assert device_entry.disabled_by == expected_disabled_by
|
assert device_entry.disabled_by == expected_disabled_by
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reload_migration_with_leading_zero_mac(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test migration and reload of a device with a mac address with a leading zero."""
|
||||||
|
mac_address = "01:02:03:04:05:06"
|
||||||
|
mac_address_unique_id = dr.format_mac(mac_address)
|
||||||
|
serial_number = "0"
|
||||||
|
|
||||||
|
# Setup the config entry to be in a pre-migrated state
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=serial_number,
|
||||||
|
data={
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"password": "password",
|
||||||
|
CONF_MAC: mac_address,
|
||||||
|
"serial_number": serial_number,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
# Create a device and entity with the old unique id format
|
||||||
|
device_entry = device_registry.async_get_or_create(
|
||||||
|
config_entry_id=config_entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, f"{serial_number}-1")},
|
||||||
|
)
|
||||||
|
entity_entry = entity_registry.async_get_or_create(
|
||||||
|
"switch",
|
||||||
|
DOMAIN,
|
||||||
|
f"{serial_number}-1-zone1",
|
||||||
|
suggested_object_id="zone1",
|
||||||
|
config_entry=config_entry,
|
||||||
|
device_id=device_entry.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup the integration, which will migrate the unique ids
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Verify the device and entity were migrated to the new format
|
||||||
|
migrated_device_entry = device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, f"{mac_address_unique_id}-1")}
|
||||||
|
)
|
||||||
|
assert migrated_device_entry is not None
|
||||||
|
migrated_entity_entry = entity_registry.async_get(entity_entry.entity_id)
|
||||||
|
assert migrated_entity_entry is not None
|
||||||
|
assert migrated_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1"
|
||||||
|
|
||||||
|
# Reload the integration
|
||||||
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Verify the device and entity still have the correct identifiers and were not duplicated
|
||||||
|
reloaded_device_entry = device_registry.async_get(migrated_device_entry.id)
|
||||||
|
assert reloaded_device_entry is not None
|
||||||
|
assert reloaded_device_entry.identifiers == {(DOMAIN, f"{mac_address_unique_id}-1")}
|
||||||
|
reloaded_entity_entry = entity_registry.async_get(entity_entry.entity_id)
|
||||||
|
assert reloaded_entity_entry is not None
|
||||||
|
assert reloaded_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id))
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id))
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
@ -87,5 +87,7 @@ def mock_suez_client(recorder_mock: Recorder) -> Generator[AsyncMock]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
suez_client.fetch_aggregated_data.return_value = result
|
suez_client.fetch_aggregated_data.return_value = result
|
||||||
suez_client.get_price.return_value = PriceResult("4.74")
|
suez_client.get_price.return_value = PriceResult(
|
||||||
|
"OK", {"price": 4.74}, "Price is 4.74"
|
||||||
|
)
|
||||||
yield suez_client
|
yield suez_client
|
||||||
|
@ -364,7 +364,7 @@ async def test_webhook_endpoint_generates_telegram_text_event(
|
|||||||
events = async_capture_events(hass, "telegram_text")
|
events = async_capture_events(hass, "telegram_text")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=update_message_text,
|
json=update_message_text,
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
@ -391,7 +391,7 @@ async def test_webhook_endpoint_generates_telegram_command_event(
|
|||||||
events = async_capture_events(hass, "telegram_command")
|
events = async_capture_events(hass, "telegram_command")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=update_message_command,
|
json=update_message_command,
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
@ -418,7 +418,7 @@ async def test_webhook_endpoint_generates_telegram_callback_event(
|
|||||||
events = async_capture_events(hass, "telegram_callback")
|
events = async_capture_events(hass, "telegram_callback")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=update_callback_query,
|
json=update_callback_query,
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
@ -594,7 +594,7 @@ async def test_webhook_endpoint_unauthorized_update_doesnt_generate_telegram_tex
|
|||||||
events = async_capture_events(hass, "telegram_text")
|
events = async_capture_events(hass, "telegram_text")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=unauthorized_update_message_text,
|
json=unauthorized_update_message_text,
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
@ -618,7 +618,7 @@ async def test_webhook_endpoint_without_secret_token_is_denied(
|
|||||||
async_capture_events(hass, "telegram_text")
|
async_capture_events(hass, "telegram_text")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=update_message_text,
|
json=update_message_text,
|
||||||
)
|
)
|
||||||
assert response.status == 401
|
assert response.status == 401
|
||||||
@ -636,7 +636,7 @@ async def test_webhook_endpoint_invalid_secret_token_is_denied(
|
|||||||
async_capture_events(hass, "telegram_text")
|
async_capture_events(hass, "telegram_text")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
TELEGRAM_WEBHOOK_URL,
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json=update_message_text,
|
json=update_message_text,
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": incorrect_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": incorrect_secret_token},
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, patch
|
|||||||
from telegram import WebhookInfo
|
from telegram import WebhookInfo
|
||||||
from telegram.error import TimedOut
|
from telegram.error import TimedOut
|
||||||
|
|
||||||
|
from homeassistant.components.telegram_bot.webhooks import TELEGRAM_WEBHOOK_URL
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ async def test_webhooks_update_invalid_json(
|
|||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"/api/telegram_webhooks",
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
assert response.status == 400
|
assert response.status == 400
|
||||||
@ -139,7 +140,7 @@ async def test_webhooks_unauthorized_network(
|
|||||||
return_value=IPv4Network("1.2.3.4"),
|
return_value=IPv4Network("1.2.3.4"),
|
||||||
) as mock_remote:
|
) as mock_remote:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"/api/telegram_webhooks",
|
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||||
json="mock json",
|
json="mock json",
|
||||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.application_credentials import (
|
|||||||
ClientCredential,
|
ClientCredential,
|
||||||
async_import_client_credential,
|
async_import_client_credential,
|
||||||
)
|
)
|
||||||
from homeassistant.components.tesla_fleet.const import CLIENT_ID, DOMAIN
|
from homeassistant.components.tesla_fleet.const import DOMAIN
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@ -28,7 +28,7 @@ async def setup_platform(
|
|||||||
await async_import_client_credential(
|
await async_import_client_credential(
|
||||||
hass,
|
hass,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ClientCredential(CLIENT_ID, "", "Home Assistant"),
|
ClientCredential("CLIENT_ID", "CLIENT_SECRET", "Home Assistant"),
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -932,6 +932,11 @@ async def test_usb_discovery_migration(
|
|||||||
|
|
||||||
assert mock_usb_serial_by_id.call_count == 2
|
assert mock_usb_serial_by_id.call_count == 2
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "confirm_usb_migration"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
assert result["step_id"] == "backup_nvm"
|
assert result["step_id"] == "backup_nvm"
|
||||||
|
|
||||||
@ -1049,6 +1054,11 @@ async def test_usb_discovery_migration_restore_driver_ready_timeout(
|
|||||||
|
|
||||||
assert mock_usb_serial_by_id.call_count == 2
|
assert mock_usb_serial_by_id.call_count == 2
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "confirm_usb_migration"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
assert result["step_id"] == "backup_nvm"
|
assert result["step_id"] == "backup_nvm"
|
||||||
|
|
||||||
|
@ -37,7 +37,11 @@ from homeassistant.helpers import (
|
|||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .common import AIR_TEMPERATURE_SENSOR, EATON_RF9640_ENTITY
|
from .common import (
|
||||||
|
AIR_TEMPERATURE_SENSOR,
|
||||||
|
BULB_6_MULTI_COLOR_LIGHT_ENTITY,
|
||||||
|
EATON_RF9640_ENTITY,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
@ -2168,3 +2172,39 @@ async def test_factory_reset_node(
|
|||||||
assert len(notifications) == 1
|
assert len(notifications) == 1
|
||||||
assert list(notifications)[0] == msg_id
|
assert list(notifications)[0] == msg_id
|
||||||
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
|
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_available_when_node_dead(
|
||||||
|
hass: HomeAssistant, client, bulb_6_multi_color, integration
|
||||||
|
) -> None:
|
||||||
|
"""Test that entities remain available even when the node is dead."""
|
||||||
|
|
||||||
|
node = bulb_6_multi_color
|
||||||
|
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Send dead event to the node
|
||||||
|
event = Event(
|
||||||
|
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Entity should remain available even though the node is dead
|
||||||
|
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Send alive event to bring the node back
|
||||||
|
event = Event(
|
||||||
|
"alive", data={"source": "node", "event": "alive", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Entity should still be available
|
||||||
|
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
@ -28,7 +28,7 @@ from homeassistant.components.zwave_js.lock import (
|
|||||||
SERVICE_SET_LOCK_CONFIGURATION,
|
SERVICE_SET_LOCK_CONFIGURATION,
|
||||||
SERVICE_SET_LOCK_USERCODE,
|
SERVICE_SET_LOCK_USERCODE,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
@ -295,7 +295,8 @@ async def test_door_lock(
|
|||||||
assert node.status == NodeStatus.DEAD
|
assert node.status == NodeStatus.DEAD
|
||||||
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
|
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNAVAILABLE
|
# The state should still be locked, even if the node is dead
|
||||||
|
assert state.state == LockState.LOCKED
|
||||||
|
|
||||||
|
|
||||||
async def test_only_one_lock(
|
async def test_only_one_lock(
|
||||||
|
@ -277,7 +277,7 @@ async def test_update_entity_dead(
|
|||||||
zen_31,
|
zen_31,
|
||||||
integration,
|
integration,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test update occurs when device is dead after it becomes alive."""
|
"""Test update occurs even when device is dead."""
|
||||||
event = Event(
|
event = Event(
|
||||||
"dead",
|
"dead",
|
||||||
data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
|
data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
|
||||||
@ -290,17 +290,7 @@ async def test_update_entity_dead(
|
|||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Because node is asleep we shouldn't attempt to check for firmware updates
|
# Checking for firmware updates should proceed even for dead nodes
|
||||||
assert len(client.async_send_command.call_args_list) == 0
|
|
||||||
|
|
||||||
event = Event(
|
|
||||||
"alive",
|
|
||||||
data={"source": "node", "event": "alive", "nodeId": zen_31.node_id},
|
|
||||||
)
|
|
||||||
zen_31.receive_event(event)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
# Now that the node is up we can check for updates
|
|
||||||
assert len(client.async_send_command.call_args_list) > 0
|
assert len(client.async_send_command.call_args_list) > 0
|
||||||
|
|
||||||
args = client.async_send_command.call_args_list[0][0][0]
|
args = client.async_send_command.call_args_list[0][0][0]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user