mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 16:57:19 +00:00
2025.7.4 (#149526)
This commit is contained in:
commit
777b3128bb
@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"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_LOW,
|
||||
FAN_MEDIUM,
|
||||
FAN_OFF,
|
||||
SWING_BOTH,
|
||||
SWING_HORIZONTAL,
|
||||
SWING_OFF,
|
||||
@ -31,6 +32,7 @@ from .coordinator import FGLairConfigEntry, FGLairCoordinator
|
||||
from .entity import FGLairEntity
|
||||
|
||||
HA_TO_FUJI_FAN = {
|
||||
FAN_OFF: FanSpeed.QUIET,
|
||||
FAN_LOW: FanSpeed.LOW,
|
||||
FAN_MEDIUM: FanSpeed.MEDIUM,
|
||||
FAN_HIGH: FanSpeed.HIGH,
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["habiticalib"],
|
||||
"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
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_STEP,
|
||||
ATTR_BRIGHTNESS_STEP_PCT,
|
||||
ATTR_EFFECT,
|
||||
ATTR_TRANSITION,
|
||||
LIGHT_TURN_ON_SCHEMA,
|
||||
@ -234,6 +237,20 @@ class LIFXLight(LIFXEntity, LightEntity):
|
||||
else:
|
||||
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
|
||||
power_on = kwargs.get(ATTR_POWER, False)
|
||||
power_off = not kwargs.get(ATTR_POWER, True)
|
||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Objects import NullValue
|
||||
from matter_server.client.models import device_types
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@ -241,7 +242,7 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
|
||||
return int(color_temp)
|
||||
|
||||
def _get_brightness(self) -> int:
|
||||
def _get_brightness(self) -> int | None:
|
||||
"""Get brightness from matter."""
|
||||
|
||||
level_control = self._endpoint.get_cluster(clusters.LevelControl)
|
||||
@ -255,6 +256,10 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
self.entity_id,
|
||||
)
|
||||
|
||||
if level_control.currentLevel is NullValue:
|
||||
# currentLevel is a nullable value.
|
||||
return None
|
||||
|
||||
return round(
|
||||
renormalize(
|
||||
level_control.currentLevel,
|
||||
|
@ -218,6 +218,9 @@ def _async_fix_device_id(
|
||||
for device_entry in device_entries:
|
||||
unique_id = str(next(iter(device_entry.identifiers))[1])
|
||||
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:
|
||||
migrations[unique_id] = f"{mac_address}{suffix}"
|
||||
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/schlage",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["pyschlage==2025.4.0"]
|
||||
"requirements": ["pyschlage==2025.7.2"]
|
||||
}
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysuez", "regex"],
|
||||
"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(
|
||||
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:
|
||||
"""Shutdown the app."""
|
||||
@ -98,9 +98,11 @@ class PushBot(BaseTelegramBot):
|
||||
api_kwargs={"secret_token": self.secret_token},
|
||||
connect_timeout=5,
|
||||
)
|
||||
except TelegramError:
|
||||
except TelegramError as err:
|
||||
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
|
||||
|
||||
@ -143,7 +145,6 @@ class PushBotView(HomeAssistantView):
|
||||
"""View for handling webhook calls from Telegram."""
|
||||
|
||||
requires_auth = False
|
||||
url = TELEGRAM_WEBHOOK_URL
|
||||
name = "telegram_webhooks"
|
||||
|
||||
def __init__(
|
||||
@ -160,6 +161,7 @@ class PushBotView(HomeAssistantView):
|
||||
self.application = application
|
||||
self.trusted_networks = trusted_networks
|
||||
self.secret_token = secret_token
|
||||
self.url = _get_webhook_url(bot)
|
||||
|
||||
async def post(self, request: HomeAssistantRequest) -> Response | None:
|
||||
"""Accept the POST from telegram."""
|
||||
@ -183,3 +185,7 @@ class PushBotView(HomeAssistantView):
|
||||
await self.application.process_update(update)
|
||||
|
||||
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__)
|
||||
|
||||
CLIENT_ID = "71b813eb-4a2e-483a-b831-4dec5cb9bf0d"
|
||||
AUTHORIZE_URL = "https://auth.tesla.com/oauth2/v3/authorize"
|
||||
TOKEN_URL = "https://auth.tesla.com/oauth2/v3/token"
|
||||
AUTHORIZE_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/authorize"
|
||||
TOKEN_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token"
|
||||
|
||||
SCOPES = [
|
||||
Scope.OPENID,
|
||||
|
@ -23,7 +23,7 @@ async def async_setup_entry(
|
||||
entities: list[WebControlProGenericEntity] = [
|
||||
WebControlProIdentifyButton(config_entry.entry_id, dest)
|
||||
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)
|
||||
|
@ -32,9 +32,9 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
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))
|
||||
elif dest.action(
|
||||
elif dest.hasAction(
|
||||
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
|
||||
):
|
||||
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))
|
||||
|
@ -33,9 +33,9 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
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))
|
||||
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))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
@ -14,5 +14,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/wmspro",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pywmspro==0.3.0"]
|
||||
"requirements": ["pywmspro==0.3.2"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pyasn1", "slixmpp"],
|
||||
"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.force_starttls = use_tls
|
||||
self.enable_starttls = use_tls
|
||||
self.enable_direct_tls = use_tls
|
||||
self.use_ipv6 = False
|
||||
self.add_event_handler("failed_all_auth", self.disconnect_on_login_fail)
|
||||
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_0363") # HTTP upload
|
||||
|
||||
self.connect(force_starttls=self.force_starttls, use_ssl=False)
|
||||
self.connect()
|
||||
|
||||
async def start(self, event):
|
||||
"""Start the communication and sends the message."""
|
||||
|
@ -495,10 +495,23 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._usb_discovery = True
|
||||
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()
|
||||
|
||||
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(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from zwave_js_server.const import NodeStatus
|
||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
||||
from zwave_js_server.model.driver import Driver
|
||||
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
|
||||
|
||||
EVENT_VALUE_REMOVED = "value removed"
|
||||
EVENT_DEAD = "dead"
|
||||
EVENT_ALIVE = "alive"
|
||||
|
||||
|
||||
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(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
@ -211,19 +203,7 @@ class ZWaveBaseEntity(Entity):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return entity availability."""
|
||||
return (
|
||||
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()
|
||||
return self.driver.client.connected and bool(self.info.node.ready)
|
||||
|
||||
@callback
|
||||
def _value_changed(self, event_data: dict) -> None:
|
||||
|
@ -108,6 +108,10 @@
|
||||
"start_addon": {
|
||||
"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": {
|
||||
"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"
|
||||
|
@ -200,18 +200,13 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
)
|
||||
return
|
||||
|
||||
# If device is asleep/dead, wait for it to wake up/become alive before
|
||||
# attempting an update
|
||||
for status, event_name in (
|
||||
(NodeStatus.ASLEEP, "wake up"),
|
||||
(NodeStatus.DEAD, "alive"),
|
||||
):
|
||||
if self.node.status == status:
|
||||
if not self._status_unsub:
|
||||
self._status_unsub = self.node.once(
|
||||
event_name, self._update_on_status_change
|
||||
)
|
||||
return
|
||||
# If device is asleep, wait for it to wake up before attempting an update
|
||||
if self.node.status == NodeStatus.ASLEEP:
|
||||
if not self._status_unsub:
|
||||
self._status_unsub = self.node.once(
|
||||
"wake up", self._update_on_status_change
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
# Retrieve all firmware updates including non-stable ones but filter
|
||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 7
|
||||
PATCH_VERSION: Final = "3"
|
||||
PATCH_VERSION: Final = "4"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.7.3"
|
||||
version = "2025.7.4"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
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
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==3.5.0
|
||||
aioamazondevices==3.5.1
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@ -1121,7 +1121,7 @@ ha-philipsjs==3.2.2
|
||||
ha-silabs-firmware-client==0.2.0
|
||||
|
||||
# homeassistant.components.habitica
|
||||
habiticalib==0.4.0
|
||||
habiticalib==0.4.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==3.49.0
|
||||
@ -2309,7 +2309,7 @@ pysabnzbd==1.1.1
|
||||
pysaj==0.0.16
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.4.0
|
||||
pyschlage==2025.7.2
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.2.1
|
||||
@ -2384,7 +2384,7 @@ pysqueezebox==0.12.1
|
||||
pystiebeleltron==0.1.0
|
||||
|
||||
# homeassistant.components.suez_water
|
||||
pysuezV2==2.0.5
|
||||
pysuezV2==2.0.7
|
||||
|
||||
# homeassistant.components.switchbee
|
||||
pyswitchbee==1.8.3
|
||||
@ -2596,7 +2596,7 @@ pywilight==0.0.74
|
||||
pywizlight==0.6.3
|
||||
|
||||
# homeassistant.components.wmspro
|
||||
pywmspro==0.3.0
|
||||
pywmspro==0.3.2
|
||||
|
||||
# homeassistant.components.ws66i
|
||||
pyws66i==1.1
|
||||
@ -2786,7 +2786,7 @@ skyboxremote==0.0.6
|
||||
slack_sdk==3.33.4
|
||||
|
||||
# homeassistant.components.xmpp
|
||||
slixmpp==1.8.5
|
||||
slixmpp==1.10.0
|
||||
|
||||
# homeassistant.components.smart_meter_texas
|
||||
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
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==3.5.0
|
||||
aioamazondevices==3.5.1
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@ -982,7 +982,7 @@ ha-philipsjs==3.2.2
|
||||
ha-silabs-firmware-client==0.2.0
|
||||
|
||||
# homeassistant.components.habitica
|
||||
habiticalib==0.4.0
|
||||
habiticalib==0.4.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==3.49.0
|
||||
@ -1921,7 +1921,7 @@ pyrympro==0.0.9
|
||||
pysabnzbd==1.1.1
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.4.0
|
||||
pyschlage==2025.7.2
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.2.1
|
||||
@ -1987,7 +1987,7 @@ pysqueezebox==0.12.1
|
||||
pystiebeleltron==0.1.0
|
||||
|
||||
# homeassistant.components.suez_water
|
||||
pysuezV2==2.0.5
|
||||
pysuezV2==2.0.7
|
||||
|
||||
# homeassistant.components.switchbee
|
||||
pyswitchbee==1.8.3
|
||||
@ -2154,7 +2154,7 @@ pywilight==0.0.74
|
||||
pywizlight==0.6.3
|
||||
|
||||
# homeassistant.components.wmspro
|
||||
pywmspro==0.3.0
|
||||
pywmspro==0.3.2
|
||||
|
||||
# homeassistant.components.ws66i
|
||||
pyws66i==1.1
|
||||
|
@ -30,6 +30,8 @@ from homeassistant.components.lifx.manager import (
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_BRIGHTNESS_STEP,
|
||||
ATTR_BRIGHTNESS_STEP_PCT,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_NAME,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
@ -1735,6 +1737,48 @@ async def test_transitions_color_bulb(hass: HomeAssistant) -> None:
|
||||
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:
|
||||
"""Test lifx.set_state works with color names and RGB."""
|
||||
config_entry = MockConfigEntry(
|
||||
|
@ -131,6 +131,15 @@ async def test_dimmable_light(
|
||||
) -> None:
|
||||
"""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)
|
||||
set_node_attribute(matter_node, 1, 8, 0, 50)
|
||||
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.name_by_user == expected_device_name
|
||||
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.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
|
||||
|
@ -364,7 +364,7 @@ async def test_webhook_endpoint_generates_telegram_text_event(
|
||||
events = async_capture_events(hass, "telegram_text")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=update_message_text,
|
||||
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")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=update_message_command,
|
||||
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")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=update_callback_query,
|
||||
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")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=unauthorized_update_message_text,
|
||||
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")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=update_message_text,
|
||||
)
|
||||
assert response.status == 401
|
||||
@ -636,7 +636,7 @@ async def test_webhook_endpoint_invalid_secret_token_is_denied(
|
||||
async_capture_events(hass, "telegram_text")
|
||||
|
||||
response = await client.post(
|
||||
TELEGRAM_WEBHOOK_URL,
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json=update_message_text,
|
||||
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.error import TimedOut
|
||||
|
||||
from homeassistant.components.telegram_bot.webhooks import TELEGRAM_WEBHOOK_URL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@ -115,7 +116,7 @@ async def test_webhooks_update_invalid_json(
|
||||
client = await hass_client()
|
||||
|
||||
response = await client.post(
|
||||
"/api/telegram_webhooks",
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||
)
|
||||
assert response.status == 400
|
||||
@ -139,7 +140,7 @@ async def test_webhooks_unauthorized_network(
|
||||
return_value=IPv4Network("1.2.3.4"),
|
||||
) as mock_remote:
|
||||
response = await client.post(
|
||||
"/api/telegram_webhooks",
|
||||
f"{TELEGRAM_WEBHOOK_URL}_123456",
|
||||
json="mock json",
|
||||
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
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.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -28,7 +28,7 @@ async def setup_platform(
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
ClientCredential(CLIENT_ID, "", "Home Assistant"),
|
||||
ClientCredential("CLIENT_ID", "CLIENT_SECRET", "Home Assistant"),
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
|
@ -932,6 +932,11 @@ async def test_usb_discovery_migration(
|
||||
|
||||
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["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 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["step_id"] == "backup_nvm"
|
||||
|
||||
|
@ -37,7 +37,11 @@ from homeassistant.helpers import (
|
||||
)
|
||||
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 (
|
||||
MockConfigEntry,
|
||||
@ -2168,3 +2172,39 @@ async def test_factory_reset_node(
|
||||
assert len(notifications) == 1
|
||||
assert list(notifications)[0] == msg_id
|
||||
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_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.exceptions import HomeAssistantError
|
||||
|
||||
@ -295,7 +295,8 @@ async def test_door_lock(
|
||||
assert node.status == NodeStatus.DEAD
|
||||
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
|
||||
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(
|
||||
|
@ -277,7 +277,7 @@ async def test_update_entity_dead(
|
||||
zen_31,
|
||||
integration,
|
||||
) -> None:
|
||||
"""Test update occurs when device is dead after it becomes alive."""
|
||||
"""Test update occurs even when device is dead."""
|
||||
event = Event(
|
||||
"dead",
|
||||
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))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Because node is asleep we shouldn't attempt to check for firmware updates
|
||||
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
|
||||
# Checking for firmware updates should proceed even for dead nodes
|
||||
assert len(client.async_send_command.call_args_list) > 0
|
||||
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
|
Loading…
x
Reference in New Issue
Block a user