mirror of
https://github.com/home-assistant/core.git
synced 2026-05-31 21:19:56 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deff0e15cc | |||
| ddf5613ac3 | |||
| ec0bd39b7c | |||
| c6844a8223 |
@@ -8,7 +8,6 @@ import avea
|
||||
from bleak.exc import BleakError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothServiceInfoBleak,
|
||||
async_discovered_service_info,
|
||||
@@ -67,15 +66,6 @@ def _is_avea_discovery(discovery_info: BluetoothServiceInfoBleak) -> bool:
|
||||
return AVEA_SERVICE_UUID in discovery_info.service_uuids
|
||||
|
||||
|
||||
def _discovery_label(discovery_info: BluetoothServiceInfoBleak) -> str:
|
||||
"""Return a label for a discovered Avea bulb."""
|
||||
if (
|
||||
name := _normalize_name(discovery_info.name)
|
||||
) and name != discovery_info.address:
|
||||
return f"{name} ({discovery_info.address})"
|
||||
return discovery_info.address
|
||||
|
||||
|
||||
class AveaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Avea."""
|
||||
|
||||
@@ -160,7 +150,6 @@ class AveaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if discovery := self._discovery_info:
|
||||
self._discovered_devices[discovery.address] = discovery
|
||||
else:
|
||||
await bluetooth.async_request_active_scan(self.hass)
|
||||
current_addresses = self._async_current_ids(include_ignore=False)
|
||||
for discovery in async_discovered_service_info(self.hass):
|
||||
if (
|
||||
@@ -176,10 +165,11 @@ class AveaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if self._discovery_info:
|
||||
disc = self._discovery_info
|
||||
label = f"{disc.name or disc.address} ({disc.address})"
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ADDRESS, default=disc.address): vol.In(
|
||||
{disc.address: _discovery_label(disc)}
|
||||
{disc.address: label}
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -188,7 +178,10 @@ class AveaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
{
|
||||
vol.Required(CONF_ADDRESS): vol.In(
|
||||
{
|
||||
service_info.address: _discovery_label(service_info)
|
||||
service_info.address: (
|
||||
f"{service_info.name or service_info.address}"
|
||||
f" ({service_info.address})"
|
||||
)
|
||||
for service_info in self._discovered_devices.values()
|
||||
}
|
||||
),
|
||||
|
||||
@@ -294,9 +294,6 @@
|
||||
"vacuum_raw_get_positions_not_supported": {
|
||||
"message": "Retrieving the positions of the chargers and the device itself is not supported"
|
||||
},
|
||||
"vacuum_send_command_not_supported": {
|
||||
"message": "The {command} command is not supported by {name}"
|
||||
},
|
||||
"vacuum_send_command_params_dict": {
|
||||
"message": "Params must be a dictionary and not a list"
|
||||
},
|
||||
|
||||
@@ -353,10 +353,11 @@ class EcovacsVacuum(
|
||||
if self._capability.clean.action.area is None:
|
||||
info = self._device.device_info
|
||||
name = info.get("nick", info["name"])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="vacuum_send_command_not_supported",
|
||||
translation_placeholders={"command": command, "name": name},
|
||||
translation_key="vacuum_send_command_area_not_supported",
|
||||
translation_placeholders={"name": name},
|
||||
)
|
||||
|
||||
if command == "spot_area":
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
|
||||
"requirements": [
|
||||
"aiolifx==1.2.2",
|
||||
"aiolifx==1.2.1",
|
||||
"aiolifx-effects==0.3.2",
|
||||
"aiolifx-themes==1.0.4"
|
||||
"aiolifx-themes==1.0.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import asyncio
|
||||
import logging
|
||||
from typing import TypedDict
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.web import Request
|
||||
from loqedAPI import loqed
|
||||
|
||||
@@ -161,20 +160,14 @@ class LoqedDataCoordinator(DataUpdateCoordinator[StatusMessage]):
|
||||
|
||||
_LOGGER.debug("Webhook URL: %s", webhook_url)
|
||||
|
||||
try:
|
||||
webhooks = await self.lock.getWebhooks()
|
||||
webhooks = await self.lock.getWebhooks()
|
||||
|
||||
webhook_index = next(
|
||||
(x["id"] for x in webhooks if x["url"] == webhook_url), None
|
||||
)
|
||||
webhook_index = next(
|
||||
(x["id"] for x in webhooks if x["url"] == webhook_url), None
|
||||
)
|
||||
|
||||
if webhook_index:
|
||||
await self.lock.deleteWebhook(webhook_index)
|
||||
except (TimeoutError, aiohttp.ClientError) as err:
|
||||
_LOGGER.warning(
|
||||
"Could not remove webhook from LOQED bridge; the bridge may be offline. Continuing to unload the entry anyway: %s",
|
||||
err,
|
||||
)
|
||||
if webhook_index:
|
||||
await self.lock.deleteWebhook(webhook_index)
|
||||
|
||||
|
||||
async def async_cloudhook_generate_url(
|
||||
|
||||
@@ -530,7 +530,7 @@ class MqttAttributesMixin(Entity):
|
||||
self._attributes_message_received,
|
||||
{
|
||||
"_attr_extra_state_attributes",
|
||||
"_attr_location_accuracy",
|
||||
"_attr_gps_accuracy",
|
||||
"_attr_latitude",
|
||||
"_attr_location_name",
|
||||
"_attr_longitude",
|
||||
|
||||
@@ -96,7 +96,7 @@ BINARY_SENSOR_DESCRIPTIONS: list[OverkizBinarySensorDescription] = [
|
||||
# DomesticHotWaterProduction/WaterHeatingSystem
|
||||
OverkizBinarySensorDescription(
|
||||
key=OverkizState.IO_OPERATING_MODE_CAPABILITIES,
|
||||
name="Energy demand status",
|
||||
name="Energy Demand Status",
|
||||
device_class=BinarySensorDeviceClass.HEAT,
|
||||
value_fn=lambda state: (
|
||||
cast(dict, state).get(OverkizCommandParam.ENERGY_DEMAND_STATUS) == 1
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
import switchbot
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.components.bluetooth import BluetoothReachabilityIntent
|
||||
from homeassistant.components.sensor import ConfigType
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -310,6 +311,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: SwitchbotConfigEntry) ->
|
||||
translation_placeholders={
|
||||
"sensor_type": entry.data[CONF_SENSOR_TYPE],
|
||||
"address": entry.data[CONF_ADDRESS],
|
||||
"reason": bluetooth.async_address_reachability_diagnostics(
|
||||
hass,
|
||||
entry.data[CONF_ADDRESS].upper(),
|
||||
BluetoothReachabilityIntent.CONNECTION,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -331,7 +337,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: SwitchbotConfigEntry) ->
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_not_found_error",
|
||||
translation_placeholders={"sensor_type": sensor_type, "address": address},
|
||||
translation_placeholders={
|
||||
"sensor_type": sensor_type,
|
||||
"address": address,
|
||||
"reason": bluetooth.async_address_reachability_diagnostics(
|
||||
hass,
|
||||
address.upper(),
|
||||
BluetoothReachabilityIntent.CONNECTION
|
||||
if connectable
|
||||
else BluetoothReachabilityIntent.PASSIVE_ADVERTISEMENT,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
cls = CLASS_BY_DEVICE.get(sensor_type, switchbot.SwitchbotDevice)
|
||||
|
||||
@@ -384,7 +384,7 @@
|
||||
"message": "The device ID {device_id} does not belong to SwitchBot integration."
|
||||
},
|
||||
"device_not_found_error": {
|
||||
"message": "Could not find Switchbot {sensor_type} with address {address}"
|
||||
"message": "Could not find Switchbot {sensor_type} with address {address}: {reason}"
|
||||
},
|
||||
"device_without_config_entry": {
|
||||
"message": "The device ID {device_id} is not associated with a config entry."
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from tuya_device_handlers import TUYA_QUIRKS_REGISTRY
|
||||
from tuya_device_handlers.devices import register_tuya_quirks
|
||||
from tuya_device_handlers.devices import TUYA_QUIRKS_REGISTRY, register_tuya_quirks
|
||||
from tuya_sharing import (
|
||||
CustomerDevice,
|
||||
Manager,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["tuya_sharing"],
|
||||
"requirements": [
|
||||
"tuya-device-handlers==0.0.22",
|
||||
"tuya-device-handlers==0.0.21",
|
||||
"tuya-device-sharing-sdk==0.2.8"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -112,8 +112,3 @@ class WLEDUpdateEntity(WLEDEntity, UpdateEntity):
|
||||
version = cast(str, self.latest_version)
|
||||
await self.coordinator.wled.upgrade(version=version)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity."""
|
||||
await super().async_update()
|
||||
await self.releases_coordinator.async_request_refresh()
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"universal_silabs_flasher",
|
||||
"serialx"
|
||||
],
|
||||
"requirements": ["zha==1.4.1"],
|
||||
"requirements": ["zha==1.4.0"],
|
||||
"usb": [
|
||||
{
|
||||
"description": "*2652*",
|
||||
|
||||
Generated
+4
-4
@@ -318,10 +318,10 @@ aiolichess==1.3.0
|
||||
aiolifx-effects==0.3.2
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx-themes==1.0.4
|
||||
aiolifx-themes==1.0.2
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx==1.2.2
|
||||
aiolifx==1.2.1
|
||||
|
||||
# homeassistant.components.lookin
|
||||
aiolookin==1.0.0
|
||||
@@ -3207,7 +3207,7 @@ ttls==1.8.3
|
||||
ttn_client==1.3.0
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-handlers==0.0.22
|
||||
tuya-device-handlers==0.0.21
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.2.8
|
||||
@@ -3442,7 +3442,7 @@ zeroconf==0.149.16
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==1.4.1
|
||||
zha==1.4.0
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
||||
@@ -27,7 +27,7 @@ pytest-aiohttp==1.1.0
|
||||
pytest-cov==7.1.0
|
||||
pytest-freezer==0.4.9
|
||||
pytest-github-actions-annotate-failures==0.4.0
|
||||
pytest-socket==0.8.0
|
||||
pytest-socket==0.7.0
|
||||
pytest-sugar==1.1.1
|
||||
pytest-timeout==2.4.0
|
||||
pytest-unordered==0.7.0
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.avea.const import AVEA_SERVICE_UUID, DOMAIN
|
||||
from homeassistant.components.avea.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_IMPORT, SOURCE_USER
|
||||
from homeassistant.const import CONF_ADDRESS, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -12,11 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import AVEA_DISCOVERY_INFO, NOT_AVEA_DISCOVERY_INFO
|
||||
|
||||
from tests.components.bluetooth import (
|
||||
generate_advertisement_data,
|
||||
generate_ble_device,
|
||||
inject_bluetooth_service_info,
|
||||
)
|
||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("enable_bluetooth")
|
||||
|
||||
@@ -39,22 +35,13 @@ async def test_user_step_success(hass: HomeAssistant) -> None:
|
||||
inject_bluetooth_service_info(hass, AVEA_DISCOVERY_INFO)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.avea.config_flow.bluetooth.async_request_active_scan"
|
||||
) as mock_request_active_scan:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
assert result["data_schema"].schema[CONF_ADDRESS].container == {
|
||||
AVEA_DISCOVERY_INFO.address: (
|
||||
f"{AVEA_DISCOVERY_INFO.name} ({AVEA_DISCOVERY_INFO.address})"
|
||||
)
|
||||
}
|
||||
mock_request_active_scan.assert_awaited_once_with(hass)
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -80,62 +67,14 @@ async def test_user_step_no_devices_found(hass: HomeAssistant) -> None:
|
||||
inject_bluetooth_service_info(hass, NOT_AVEA_DISCOVERY_INFO)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.avea.config_flow.bluetooth.async_request_active_scan"
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
async def test_user_step_unnamed_device_label(hass: HomeAssistant) -> None:
|
||||
"""Test unnamed discovered devices are shown without duplicating the address."""
|
||||
discovery_info = type(AVEA_DISCOVERY_INFO)(
|
||||
name=AVEA_DISCOVERY_INFO.address,
|
||||
address=AVEA_DISCOVERY_INFO.address,
|
||||
rssi=-60,
|
||||
manufacturer_data={},
|
||||
service_uuids=[AVEA_SERVICE_UUID],
|
||||
service_data={},
|
||||
source="local",
|
||||
device=generate_ble_device(
|
||||
address=AVEA_DISCOVERY_INFO.address, name=AVEA_DISCOVERY_INFO.address
|
||||
),
|
||||
advertisement=generate_advertisement_data(
|
||||
local_name=AVEA_DISCOVERY_INFO.address,
|
||||
manufacturer_data={},
|
||||
service_data={},
|
||||
service_uuids=[AVEA_SERVICE_UUID],
|
||||
),
|
||||
time=0,
|
||||
connectable=True,
|
||||
tx_power=-127,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.avea.config_flow.async_discovered_service_info",
|
||||
return_value=[discovery_info],
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.avea.config_flow.bluetooth.async_request_active_scan"
|
||||
) as mock_request_active_scan,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["data_schema"].schema[CONF_ADDRESS].container == {
|
||||
AVEA_DISCOVERY_INFO.address: AVEA_DISCOVERY_INFO.address
|
||||
}
|
||||
mock_request_active_scan.assert_awaited_once_with(hass)
|
||||
|
||||
|
||||
async def test_user_step_cannot_connect_recovers(hass: HomeAssistant) -> None:
|
||||
"""Test the user step recovers after a cannot connect error."""
|
||||
inject_bluetooth_service_info(hass, AVEA_DISCOVERY_INFO)
|
||||
|
||||
@@ -1488,6 +1488,11 @@
|
||||
'infrared': False,
|
||||
'matrix': False,
|
||||
'max_kelvin': 9000,
|
||||
'min_ext_mz_firmware': 1532997580,
|
||||
'min_ext_mz_firmware_components': list([
|
||||
2,
|
||||
77,
|
||||
]),
|
||||
'min_kelvin': 1500,
|
||||
'multizone': True,
|
||||
'relays': False,
|
||||
|
||||
@@ -8,7 +8,6 @@ from unittest.mock import AsyncMock, patch
|
||||
import aiohttp
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from loqedAPI import loqed
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.loqed.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -215,22 +214,3 @@ async def test_unload_entry_fails(
|
||||
lock.deleteWebhook = AsyncMock(side_effect=Exception)
|
||||
|
||||
assert not await hass.config_entries.async_unload(integration.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("error", [aiohttp.ClientError, TimeoutError])
|
||||
async def test_unload_entry_with_unreachable_bridge(
|
||||
hass: HomeAssistant,
|
||||
integration: MockConfigEntry,
|
||||
lock: loqed.Lock,
|
||||
error: type[Exception],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test entry still unloads when the bridge is unreachable."""
|
||||
lock.getWebhooks = AsyncMock(side_effect=error)
|
||||
|
||||
assert await hass.config_entries.async_unload(integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert integration.state is ConfigEntryState.NOT_LOADED
|
||||
assert not hass.data.get(DOMAIN)
|
||||
assert "Could not remove webhook from LOQED bridge" in caplog.text
|
||||
|
||||
@@ -8,10 +8,6 @@ import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from wled import Releases, WLEDError
|
||||
|
||||
from homeassistant.components.homeassistant import (
|
||||
DOMAIN as HOME_ASSISTANT_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
)
|
||||
from homeassistant.components.update import (
|
||||
ATTR_INSTALLED_VERSION,
|
||||
ATTR_LATEST_VERSION,
|
||||
@@ -29,8 +25,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.update_coordinator import REQUEST_REFRESH_DEFAULT_COOLDOWN
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
@@ -192,44 +186,3 @@ async def test_update_stay_beta(
|
||||
)
|
||||
assert mock_wled.upgrade.call_count == 1
|
||||
mock_wled.upgrade.assert_called_with(version="1.0.0b5")
|
||||
|
||||
|
||||
async def test_update_entities(
|
||||
hass: HomeAssistant,
|
||||
mock_wled: MagicMock,
|
||||
mock_wled_releases: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test update entity async_update method."""
|
||||
await async_setup_component(hass, HOME_ASSISTANT_DOMAIN, {})
|
||||
|
||||
assert (state := hass.states.get("update.wled_rgb_light_firmware"))
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_LATEST_VERSION] == "0.99.0"
|
||||
mock_wled_releases.releases.assert_called_once()
|
||||
|
||||
mock_wled_releases.releases.return_value = Releases(
|
||||
beta="1.0.0b5",
|
||||
nightly=None,
|
||||
repo="wled/WLED",
|
||||
stable="16.0.0",
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
HOME_ASSISTANT_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
{ATTR_ENTITY_ID: state.entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# Ensure we pass the debouncer interval to allow async_request_refresh to execute
|
||||
freezer.tick(REQUEST_REFRESH_DEFAULT_COOLDOWN)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# releases() should be called twice: once on setup, once for the manual update
|
||||
assert mock_wled_releases.releases.call_count == 2
|
||||
|
||||
assert (state := hass.states.get("update.wled_rgb_light_firmware"))
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_LATEST_VERSION] == "16.0.0"
|
||||
|
||||
Reference in New Issue
Block a user