Compare commits

..

32 Commits

Author SHA1 Message Date
Franck Nijhof ea4bdbb3a0 Bump version to 2024.2.0b10 2024-02-07 08:48:17 +01:00
puddly 40cfc31dcb Bump ZHA dependency zigpy to 0.62.3 (#109848) 2024-02-07 08:48:07 +01:00
starkillerOG 031aadff00 Bump motionblinds to 0.6.20 (#109837) 2024-02-07 08:48:04 +01:00
Vilppu Vuorinen 27691b7d48 Disable energy report based operations with API lib upgrade (#109832)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-02-07 08:48:01 +01:00
Joost Lekkerkerker fe94107af7 Make integration fields in Analytics Insights optional (#109789) 2024-02-07 08:47:58 +01:00
Teemu R d784a76d32 Add tapo virtual integration (#109765) 2024-02-07 08:47:55 +01:00
Joost Lekkerkerker ebb1912617 Show domain in oauth2 error log (#109708)
* Show token url in oauth2 error log

* Fix tests

* Use domain
2024-02-07 08:47:50 +01:00
Franck Nijhof 8c605c29c3 Bump version to 2024.2.0b9 2024-02-06 22:49:53 +01:00
Joakim Sørensen 74a75e709f Bump awesomeversion from 23.11.0 to 24.2.0 (#109830) 2024-02-06 22:49:41 +01:00
J. Nick Koston 2103875ff7 Bump aioesphomeapi to 21.0.2 (#109824) 2024-02-06 22:49:38 +01:00
Erik Montnemery 5c83b774bb Bump python-otbr-api to 2.6.0 (#109823) 2024-02-06 22:49:34 +01:00
Joost Lekkerkerker 2c870f9da9 Bump aioecowitt to 2024.2.0 (#109817) 2024-02-06 22:49:31 +01:00
Maciej Bieniek 40adb3809f Ignore trackable without details in Tractive integration (#109814)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-02-06 22:49:28 +01:00
wittypluck 8aa1242221 Mark Unifi bandwidth sensors as unavailable when client disconnects (#109812)
* Set sensor as unavailable instead of resetting value to 0 on disconnect

* Update unit test on unavailable bandwidth sensor
2024-02-06 22:49:25 +01:00
J. Nick Koston 8569ddc5f9 Fix entity services targeting entities outside the platform when using areas/devices (#109810) 2024-02-06 22:49:22 +01:00
Franck Nijhof 7032415528 Don't block Supervisor entry setup with refreshing updates (#109809) 2024-02-06 22:49:19 +01:00
puddly d099fb2a26 Pin chacha20poly1305-reuseable>=0.12.1 (#109807)
* Pin `chacha20poly1305-reuseable`
Prevents a runtime `assert isinstance(cipher, AESGCM)` error

* Update `gen_requirements_all.py` as well
2024-02-06 22:49:16 +01:00
Jan-Philipp Benecke 35fad52913 Bump aioelectricitymaps to 0.3.1 (#109797) 2024-02-06 22:49:13 +01:00
Vilppu Vuorinen c170132827 Update MELCloud codeowners (#109793)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-02-06 22:49:10 +01:00
Matthias Alphart 439f82a4ec Update xknx to 2.12.0 and xknxproject to 3.5.0 (#109787) 2024-02-06 22:49:07 +01:00
Steven B 2481d14632 Bump ring_doorbell to 0.8.7 (#109783) 2024-02-06 22:49:04 +01:00
Steven B 3cf826dc93 Bump ring_doorbell to 0.8.6 (#109199) 2024-02-06 22:48:59 +01:00
Jan-Philipp Benecke e25ddf9650 Change state class of Tesla wall connector session energy entity (#109778) 2024-02-06 22:46:34 +01:00
puddly 8d79ac67f5 Bump ZHA dependencies (#109770)
* Bump ZHA dependencies

* Bump universal-silabs-flasher to 0.0.18

* Flip `Server_to_Client` enum in ZHA unit test

* Bump zigpy to 0.62.2
2024-02-06 22:46:31 +01:00
David F. Mulcahey 5025c15165 Buffer JsonDecodeError in Flo (#109767) 2024-02-06 22:46:28 +01:00
Joost Lekkerkerker ffd5e04a29 Fix Radarr health check singularity (#109762)
* Fix Radarr health check singularity

* Fix comment
2024-02-06 22:46:25 +01:00
G Johansson 9fcdfd1b16 Bump holidays to 0.42 (#109760) 2024-02-06 22:46:21 +01:00
Vilppu Vuorinen c1e5b2e6cc Fix compatibility issues with older pymelcloud version (#109757) 2024-02-06 22:42:58 +01:00
suaveolent 31c0d21204 Improve lupusec code quality (#109727)
* renamed async_add_devices

* fixed typo

* patch class instead of __init__

* ensure non blocking get_alarm

* exception handling

* added test case for json decode error

* avoid blockign calls

---------

Co-authored-by: suaveolent <suaveolent@users.noreply.github.com>
2024-02-06 22:42:54 +01:00
spycle 3ba63fc78f Fix keymitt_ble config-flow (#109644)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-02-06 22:42:51 +01:00
spycle 0395315267 Bump pyMicrobot to 0.0.10 (#109628) 2024-02-06 22:42:48 +01:00
TheJulianJES 6b354457c2 Fix ZHA creating unnecessary "summ received" entity after upgrade (#109268)
* Do not create `current_summ_received` entity until initialized once

* Update zha_devices_list.py to not expect summation received entities

The attribute isn't initialized for these devices in the test (which our check now expects it to be), hence we need to remove them from this list.

* Update sensor tests to have initial state for current_summ_received entity

The attribute needs to be initialized for it to be created which we do by plugging the attribute read.
The test expects the initial state to be "unknown", but hence we plugged the attribute (to create the entity), the state is whatever we plug the attribute read as.

* Update sensor tests to expect not updating current_summ_received entity if it doesn't exist
2024-02-06 22:42:43 +01:00
59 changed files with 550 additions and 239 deletions
-2
View File
@@ -786,8 +786,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/media_source/ @hunterjm
/tests/components/media_source/ @hunterjm
/homeassistant/components/mediaroom/ @dgomes
/homeassistant/components/melcloud/ @vilppuvuorinen
/tests/components/melcloud/ @vilppuvuorinen
/homeassistant/components/melissa/ @kennedyshead
/tests/components/melissa/ @kennedyshead
/homeassistant/components/melnor/ @vanstinator
+1 -1
View File
@@ -1,6 +1,6 @@
{
"domain": "tplink",
"name": "TP-Link",
"integrations": ["tplink", "tplink_omada", "tplink_lte"],
"integrations": ["tplink", "tplink_omada", "tplink_lte", "tplink_tapo"],
"iot_standards": ["matter"]
}
@@ -53,10 +53,25 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
) -> FlowResult:
"""Handle the initial step."""
self._async_abort_entries_match()
if user_input:
return self.async_create_entry(
title="Home Assistant Analytics Insights", data={}, options=user_input
)
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
):
errors["base"] = "no_integrations_selected"
else:
return self.async_create_entry(
title="Home Assistant Analytics Insights",
data={},
options={
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
CONF_TRACKED_CUSTOM_INTEGRATIONS: user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS, []
),
},
)
client = HomeassistantAnalyticsClient(
session=async_get_clientsession(self.hass)
@@ -78,16 +93,17 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
]
return self.async_show_form(
step_id="user",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,
multiple=True,
sort=True,
)
),
vol.Required(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=list(custom_integrations),
multiple=True,
@@ -106,8 +122,24 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input:
return self.async_create_entry(title="", data=user_input)
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
):
errors["base"] = "no_integrations_selected"
else:
return self.async_create_entry(
title="",
data={
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
CONF_TRACKED_CUSTOM_INTEGRATIONS: user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS, []
),
},
)
client = HomeassistantAnalyticsClient(
session=async_get_clientsession(self.hass)
@@ -129,17 +161,18 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
]
return self.async_show_form(
step_id="init",
errors=errors,
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,
multiple=True,
sort=True,
)
),
vol.Required(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=list(custom_integrations),
multiple=True,
@@ -15,6 +15,9 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
},
"error": {
"no_integration_selected": "You must select at least one integration to track"
}
},
"options": {
@@ -32,6 +35,9 @@
},
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]"
}
},
"entity": {
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aioelectricitymaps"],
"requirements": ["aioelectricitymaps==0.3.0"]
"requirements": ["aioelectricitymaps==0.3.1"]
}
+2 -2
View File
@@ -38,8 +38,8 @@ class EcowittEntity(Entity):
"""Update the state on callback."""
self.async_write_ha_state()
self.ecowitt.update_cb.append(_update_state) # type: ignore[arg-type] # upstream bug
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state)) # type: ignore[arg-type] # upstream bug
self.ecowitt.update_cb.append(_update_state)
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state))
@property
def available(self) -> bool:
@@ -6,5 +6,5 @@
"dependencies": ["webhook"],
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
"iot_class": "local_push",
"requirements": ["aioecowitt==2023.5.0"]
"requirements": ["aioecowitt==2024.2.0"]
}
@@ -15,7 +15,7 @@
"iot_class": "local_push",
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"requirements": [
"aioesphomeapi==21.0.1",
"aioesphomeapi==21.0.2",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==0.4.1"
],
+2 -1
View File
@@ -7,6 +7,7 @@ from typing import Any
from aioflo.api import API
from aioflo.errors import RequestError
from orjson import JSONDecodeError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -46,7 +47,7 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=
await self._update_device()
await self._update_consumption_data()
self._failure_count = 0
except (RequestError, TimeoutError) as error:
except (RequestError, TimeoutError, JSONDecodeError) as error:
self._failure_count += 1
if self._failure_count > 3:
raise UpdateFailed(error) from error
+7 -1
View File
@@ -1001,12 +1001,18 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=has
raise_on_entry_error: bool = False,
) -> None:
"""Refresh data."""
if not scheduled:
if not scheduled and not raise_on_auth_failed:
# Force refreshing updates for non-scheduled updates
# If `raise_on_auth_failed` is set, it means this is
# the first refresh and we do not want to delay
# startup or cause a timeout so we only refresh the
# updates if this is not a scheduled refresh and
# we are not doing the first refresh.
try:
await self.hassio.refresh_updates()
except HassioAPIError as err:
_LOGGER.warning("Error on Supervisor API: %s", err)
await super()._async_refresh(
log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error
)
+1 -1
View File
@@ -459,7 +459,7 @@ class HassIO:
This method returns a coroutine.
"""
return self.send_command("/refresh_updates", timeout=None)
return self.send_command("/refresh_updates", timeout=300)
@api_data
def retrieve_discovery_messages(self) -> Coroutine:
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.41", "babel==2.13.1"]
"requirements": ["holidays==0.42", "babel==2.13.1"]
}
@@ -138,6 +138,8 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN):
await self._client.connect(init=True)
return self.async_show_form(step_id="link")
if not await self._client.is_connected():
await self._client.connect(init=False)
if not await self._client.is_connected():
errors["base"] = "linking"
else:
@@ -13,7 +13,8 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/keymitt_ble",
"integration_type": "hub",
"iot_class": "assumed_state",
"loggers": ["keymitt_ble"],
"requirements": ["PyMicroBot==0.0.9"]
"requirements": ["PyMicroBot==0.0.10"]
}
+2 -2
View File
@@ -11,8 +11,8 @@
"loggers": ["xknx", "xknxproject"],
"quality_scale": "platinum",
"requirements": [
"xknx==2.11.2",
"xknxproject==3.4.0",
"xknx==2.12.0",
"xknxproject==3.5.0",
"knx-frontend==2024.1.20.105944"
]
}
+3 -7
View File
@@ -1,4 +1,5 @@
"""Support for Lupusec Home Security system."""
from json import JSONDecodeError
import logging
import lupupy
@@ -111,16 +112,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
lupusec_system = await hass.async_add_executor_job(
lupupy.Lupusec, username, password, host
)
except LupusecException:
_LOGGER.error("Failed to connect to Lupusec device at %s", host)
return False
except Exception as ex: # pylint: disable=broad-except
_LOGGER.error(
"Unknown error while trying to connect to Lupusec device at %s: %s",
host,
ex,
)
except JSONDecodeError:
_LOGGER.error("Failed to connect to Lupusec device at %s", host)
return False
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = lupusec_system
@@ -29,14 +29,14 @@ SCAN_INTERVAL = timedelta(seconds=2)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up an alarm control panel for a Lupusec device."""
data = hass.data[DOMAIN][config_entry.entry_id]
alarm_devices = [LupusecAlarm(data, data.get_alarm(), config_entry.entry_id)]
alarm = await hass.async_add_executor_job(data.get_alarm)
async_add_devices(alarm_devices)
async_add_entities([LupusecAlarm(data, alarm, config_entry.entry_id)])
class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity):
@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from functools import partial
import logging
import lupupy.constants as CONST
@@ -25,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a binary sensors for a Lupusec device."""
@@ -34,10 +35,12 @@ async def async_setup_entry(
device_types = CONST.TYPE_OPENING + CONST.TYPE_SENSOR
sensors = []
for device in data.get_devices(generic_type=device_types):
partial_func = partial(data.get_devices, generic_type=device_types)
devices = await hass.async_add_executor_job(partial_func)
for device in devices:
sensors.append(LupusecBinarySensor(device, config_entry.entry_id))
async_add_devices(sensors)
async_add_entities(sensors)
class LupusecBinarySensor(LupusecBaseSensor, BinarySensorEntity):
@@ -1,5 +1,6 @@
""""Config flow for Lupusec integration."""
from json import JSONDecodeError
import logging
from typing import Any
@@ -50,6 +51,8 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await test_host_connection(self.hass, host, username, password)
except CannotConnect:
errors["base"] = "cannot_connect"
except JSONDecodeError:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@@ -80,6 +83,8 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await test_host_connection(self.hass, host, username, password)
except CannotConnect:
return self.async_abort(reason="cannot_connect")
except JSONDecodeError:
return self.async_abort(reason="cannot_connect")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
return self.async_abort(reason="unknown")
@@ -21,7 +21,7 @@
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"title": "The Lupus Electronics LUPUSEC YAML configuration import failed",
"description": "Configuring Lupus Electronics LUPUSEC using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Lupus Electronics LUPUSEC works and restart Home Assistant to try again or remove the Lupus Electronics LUPUSEC YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
"description": "Configuring Lupus Electronics LUPUSEC using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to Lupus Electronics LUPUSEC works and restart Home Assistant to try again or remove the Lupus Electronics LUPUSEC YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
},
"deprecated_yaml_import_issue_unknown": {
"title": "The Lupus Electronics LUPUSEC YAML configuration import failed",
+6 -3
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from functools import partial
from typing import Any
import lupupy.constants as CONST
@@ -20,7 +21,7 @@ SCAN_INTERVAL = timedelta(seconds=2)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Lupusec switch devices."""
@@ -29,10 +30,12 @@ async def async_setup_entry(
device_types = CONST.TYPE_SWITCH
switches = []
for device in data.get_devices(generic_type=device_types):
partial_func = partial(data.get_devices, generic_type=device_types)
devices = await hass.async_add_executor_job(partial_func)
for device in devices:
switches.append(LupusecSwitch(device, config_entry.entry_id))
async_add_devices(switches)
async_add_entities(switches)
class LupusecSwitch(LupusecBaseSensor, SwitchEntity):
@@ -22,7 +22,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
@@ -123,11 +123,6 @@ class MelCloudDevice:
via_device=(DOMAIN, f"{dev.mac}-{dev.serial}"),
)
@property
def daily_energy_consumed(self) -> float | None:
"""Return energy consumed during the current day in kWh."""
return self.device.daily_energy_consumed
async def mel_devices_setup(
hass: HomeAssistant, token: str
@@ -138,8 +133,7 @@ async def mel_devices_setup(
all_devices = await get_devices(
token,
session,
user_update_interval=timedelta(minutes=30),
conf_update_interval=timedelta(minutes=15),
conf_update_interval=timedelta(minutes=30),
device_set_debounce=timedelta(seconds=2),
)
wrapped_devices: dict[str, list[MelCloudDevice]] = {}
@@ -1,10 +1,10 @@
{
"domain": "melcloud",
"name": "MELCloud",
"codeowners": ["@vilppuvuorinen"],
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/melcloud",
"iot_class": "cloud_polling",
"loggers": ["pymelcloud"],
"requirements": ["pymelcloud==2.5.8"]
"requirements": ["pymelcloud==2.5.9"]
}
@@ -58,16 +58,6 @@ ATA_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
value_fn=lambda x: x.device.total_energy_consumed,
enabled=lambda x: x.device.has_energy_consumed_meter,
),
MelcloudSensorEntityDescription(
key="daily_energy",
translation_key="daily_energy",
icon="mdi:factory",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda x: x.device.daily_energy_consumed,
enabled=lambda x: True,
),
)
ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
MelcloudSensorEntityDescription(
@@ -90,16 +80,6 @@ ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
value_fn=lambda x: x.device.tank_temperature,
enabled=lambda x: True,
),
MelcloudSensorEntityDescription(
key="daily_energy",
translation_key="daily_energy",
icon="mdi:factory",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda x: x.device.daily_energy_consumed,
enabled=lambda x: True,
),
)
ATW_ZONE_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
MelcloudSensorEntityDescription(
@@ -65,9 +65,6 @@
"room_temperature": {
"name": "Room temperature"
},
"daily_energy": {
"name": "Daily energy consumed"
},
"outside_temperature": {
"name": "Outside temperature"
},
@@ -51,6 +51,7 @@ POSITION_DEVICE_MAP = {
BlindType.CurtainLeft: CoverDeviceClass.CURTAIN,
BlindType.CurtainRight: CoverDeviceClass.CURTAIN,
BlindType.SkylightBlind: CoverDeviceClass.SHADE,
BlindType.InsectScreen: CoverDeviceClass.SHADE,
}
TILT_DEVICE_MAP = {
@@ -69,6 +70,7 @@ TILT_ONLY_DEVICE_MAP = {
TDBU_DEVICE_MAP = {
BlindType.TopDownBottomUp: CoverDeviceClass.SHADE,
BlindType.TriangleBlind: CoverDeviceClass.BLIND,
}
@@ -21,5 +21,5 @@
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
"iot_class": "local_push",
"loggers": ["motionblinds"],
"requirements": ["motionblinds==0.6.19"]
"requirements": ["motionblinds==0.6.20"]
}
@@ -1,5 +1,6 @@
"""Support for Motion Blinds sensors."""
from motionblinds import DEVICE_TYPES_WIFI, BlindType
from motionblinds import DEVICE_TYPES_WIFI
from motionblinds.motion_blinds import DEVICE_TYPE_TDBU
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
@@ -29,7 +30,7 @@ async def async_setup_entry(
for blind in motion_gateway.device_list.values():
entities.append(MotionSignalStrengthSensor(coordinator, blind))
if blind.type == BlindType.TopDownBottomUp:
if blind.device_type == DEVICE_TYPE_TDBU:
entities.append(MotionTDBUBatterySensor(coordinator, blind, "Bottom"))
entities.append(MotionTDBUBatterySensor(coordinator, blind, "Top"))
elif blind.battery_voltage is not None and blind.battery_voltage > 0:
+1 -1
View File
@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/otbr",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["python-otbr-api==2.5.0"]
"requirements": ["python-otbr-api==2.6.0"]
}
@@ -96,7 +96,7 @@ class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[RootFolder
"""Fetch the data."""
root_folders = await self.api_client.async_get_root_folders()
if isinstance(root_folders, RootFolder):
root_folders = [root_folders]
return [root_folders]
return root_folders
@@ -105,7 +105,10 @@ class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[Health]]):
async def _fetch_data(self) -> list[Health]:
"""Fetch the health data."""
return await self.api_client.async_get_failed_health_checks()
health = await self.api_client.async_get_failed_health_checks()
if isinstance(health, Health):
return [health]
return health
class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]):
+1 -1
View File
@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/ring",
"iot_class": "cloud_polling",
"loggers": ["ring_doorbell"],
"requirements": ["ring-doorbell[listen]==0.8.5"]
"requirements": ["ring-doorbell[listen]==0.8.7"]
}
@@ -156,7 +156,7 @@ WALL_CONNECTOR_SENSORS = [
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value_fn=lambda data: data[WALLCONNECTOR_DATA_VITALS].session_energy_wh,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.MEASUREMENT,
state_class=SensorStateClass.TOTAL_INCREASING,
),
WallConnectorSensorDescription(
key="energy_kWh",
@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/thread",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["python-otbr-api==2.5.0", "pyroute2==0.7.5"],
"requirements": ["python-otbr-api==2.6.0", "pyroute2==0.7.5"],
"zeroconf": ["_meshcop._udp.local."]
}
@@ -0,0 +1 @@
"""Virtual integration: TP-Link Tapo."""
@@ -0,0 +1,6 @@
{
"domain": "tplink_tapo",
"name": "Tapo",
"integration_type": "virtual",
"supported_by": "tplink"
}
@@ -129,6 +129,13 @@ async def _generate_trackables(
if not trackable["device_id"]:
return None
if "details" not in trackable:
_LOGGER.info(
"Tracker %s has no details and will be skipped. This happens for shared trackers",
trackable["device_id"],
)
return None
tracker = client.tracker(trackable["device_id"])
tracker_details, hw_info, pos_report = await asyncio.gather(
+3 -3
View File
@@ -430,10 +430,10 @@ class UnifiSensorEntity(UnifiEntity[HandlerT, ApiItemT], SensorEntity):
def _make_disconnected(self, *_: core_Event) -> None:
"""No heart beat by device.
Reset sensor value to 0 when client device is disconnected
Set sensor as unavailable when client device is disconnected
"""
if self._attr_native_value != 0:
self._attr_native_value = 0
if self._attr_available:
self._attr_available = False
self.async_write_ha_state()
@callback
@@ -7,5 +7,5 @@
"iot_class": "local_polling",
"loggers": ["holidays"],
"quality_scale": "internal",
"requirements": ["holidays==0.41"]
"requirements": ["holidays==0.42"]
}
+5 -5
View File
@@ -21,16 +21,16 @@
"universal_silabs_flasher"
],
"requirements": [
"bellows==0.37.6",
"bellows==0.38.0",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.110",
"zigpy-deconz==0.22.4",
"zigpy==0.61.0",
"zha-quirks==0.0.111",
"zigpy-deconz==0.23.0",
"zigpy==0.62.3",
"zigpy-xbee==0.20.1",
"zigpy-zigate==0.12.0",
"zigpy-znp==0.12.1",
"universal-silabs-flasher==0.0.15",
"universal-silabs-flasher==0.0.18",
"pyserial-asyncio-fast==0.11"
],
"usb": [
+20
View File
@@ -838,6 +838,26 @@ class SmartEnergySummationReceived(PolledSmartEnergySummation):
_unique_id_suffix = "summation_received"
_attr_translation_key: str = "summation_received"
@classmethod
def create_entity(
cls,
unique_id: str,
zha_device: ZHADevice,
cluster_handlers: list[ClusterHandler],
**kwargs: Any,
) -> Self | None:
"""Entity Factory.
This attribute only started to be initialized in HA 2024.2.0,
so the entity would still be created on the first HA start after the upgrade for existing devices,
as the initialization to see if an attribute is unsupported happens later in the background.
To avoid creating a lot of unnecessary entities for existing devices,
wait until the attribute was properly initialized once for now.
"""
if cluster_handlers[0].cluster.get(cls._attribute_name) is None:
return None
return super().create_entity(unique_id, zha_device, cluster_handlers, **kwargs)
@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_PRESSURE)
# pylint: disable-next=hass-invalid-inheritance # needs fixing
+1 -1
View File
@@ -16,7 +16,7 @@ from .helpers.deprecation import (
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 2
PATCH_VERSION: Final = "0b8"
PATCH_VERSION: Final = "0b10"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
@@ -6156,6 +6156,12 @@
"config_flow": false,
"iot_class": "local_polling",
"name": "TP-Link LTE"
},
"tplink_tapo": {
"integration_type": "virtual",
"config_flow": false,
"supported_by": "tplink",
"name": "Tapo"
}
},
"iot_standards": [
@@ -209,7 +209,10 @@ class LocalOAuth2Implementation(AbstractOAuth2Implementation):
error_code = error_response.get("error", "unknown")
error_description = error_response.get("error_description", "unknown error")
_LOGGER.error(
"Token request failed (%s): %s", error_code, error_description
"Token request for %s failed (%s): %s",
self.domain,
error_code,
error_description,
)
resp.raise_for_status()
return cast(dict, await resp.json())
+23 -3
View File
@@ -57,6 +57,7 @@ SLOW_ADD_MIN_TIMEOUT = 500
PLATFORM_NOT_READY_RETRIES = 10
DATA_ENTITY_PLATFORM = "entity_platform"
DATA_DOMAIN_ENTITIES = "domain_entities"
DATA_DOMAIN_PLATFORM_ENTITIES = "domain_platform_entities"
PLATFORM_NOT_READY_BASE_WAIT_TIME = 30 # seconds
_LOGGER = getLogger(__name__)
@@ -124,6 +125,8 @@ class EntityPlatform:
self.scan_interval = scan_interval
self.entity_namespace = entity_namespace
self.config_entry: config_entries.ConfigEntry | None = None
# Storage for entities for this specific platform only
# which are indexed by entity_id
self.entities: dict[str, Entity] = {}
self.component_translations: dict[str, Any] = {}
self.platform_translations: dict[str, Any] = {}
@@ -145,9 +148,24 @@ class EntityPlatform:
# which powers entity_component.add_entities
self.parallel_updates_created = platform is None
self.domain_entities: dict[str, Entity] = hass.data.setdefault(
# Storage for entities indexed by domain
# with the child dict indexed by entity_id
#
# This is usually media_player, light, switch, etc.
domain_entities: dict[str, dict[str, Entity]] = hass.data.setdefault(
DATA_DOMAIN_ENTITIES, {}
).setdefault(domain, {})
)
self.domain_entities = domain_entities.setdefault(domain, {})
# Storage for entities indexed by domain and platform
# with the child dict indexed by entity_id
#
# This is usually media_player.yamaha, light.hue, switch.tplink, etc.
domain_platform_entities: dict[
tuple[str, str], dict[str, Entity]
] = hass.data.setdefault(DATA_DOMAIN_PLATFORM_ENTITIES, {})
key = (domain, platform_name)
self.domain_platform_entities = domain_platform_entities.setdefault(key, {})
def __repr__(self) -> str:
"""Represent an EntityPlatform."""
@@ -743,6 +761,7 @@ class EntityPlatform:
entity_id = entity.entity_id
self.entities[entity_id] = entity
self.domain_entities[entity_id] = entity
self.domain_platform_entities[entity_id] = entity
if not restored:
# Reserve the state in the state machine
@@ -756,6 +775,7 @@ class EntityPlatform:
"""Remove entity from entities dict."""
self.entities.pop(entity_id)
self.domain_entities.pop(entity_id)
self.domain_platform_entities.pop(entity_id)
entity.async_on_remove(remove_entity_cb)
@@ -852,7 +872,7 @@ class EntityPlatform:
partial(
service.entity_service_call,
self.hass,
self.domain_entities,
self.domain_platform_entities,
service_func,
required_features=required_features,
),
+4 -1
View File
@@ -9,7 +9,7 @@ astral==2.2
async-upnp-client==0.38.1
atomicwrites-homeassistant==1.4.1
attrs==23.2.0
awesomeversion==23.11.0
awesomeversion==24.2.0
bcrypt==4.0.1
bleak-retry-connector==3.4.0
bleak==0.21.1
@@ -188,3 +188,6 @@ dacite>=1.7.0
# Musle wheels for pandas 2.2.0 cannot be build for any architecture.
pandas==2.1.4
# chacha20poly1305-reuseable==0.12.0 is incompatible with cryptography==42.0.x
chacha20poly1305-reuseable>=0.12.1
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2024.2.0b8"
version = "2024.2.0b10"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
@@ -30,7 +30,7 @@ dependencies = [
"astral==2.2",
"attrs==23.2.0",
"atomicwrites-homeassistant==1.4.1",
"awesomeversion==23.11.0",
"awesomeversion==24.2.0",
"bcrypt==4.0.1",
"certifi>=2021.5.30",
"ciso8601==2.3.0",
+1 -1
View File
@@ -10,7 +10,7 @@ aiohttp-zlib-ng==0.3.1
astral==2.2
attrs==23.2.0
atomicwrites-homeassistant==1.4.1
awesomeversion==23.11.0
awesomeversion==24.2.0
bcrypt==4.0.1
certifi>=2021.5.30
ciso8601==2.3.0
+16 -16
View File
@@ -76,7 +76,7 @@ PyMetEireann==2021.8.0
PyMetno==0.11.0
# homeassistant.components.keymitt_ble
PyMicroBot==0.0.9
PyMicroBot==0.0.10
# homeassistant.components.nina
PyNINA==0.3.3
@@ -230,16 +230,16 @@ aioeafm==0.1.2
aioeagle==1.1.0
# homeassistant.components.ecowitt
aioecowitt==2023.5.0
aioecowitt==2024.2.0
# homeassistant.components.co2signal
aioelectricitymaps==0.3.0
aioelectricitymaps==0.3.1
# homeassistant.components.emonitor
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==21.0.1
aioesphomeapi==21.0.2
# homeassistant.components.flo
aioflo==2021.11.0
@@ -535,7 +535,7 @@ beautifulsoup4==4.12.2
# beewi-smartclim==0.0.10
# homeassistant.components.zha
bellows==0.37.6
bellows==0.38.0
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.14.6
@@ -1056,7 +1056,7 @@ hole==0.8.0
# homeassistant.components.holiday
# homeassistant.components.workday
holidays==0.41
holidays==0.42
# homeassistant.components.frontend
home-assistant-frontend==20240205.0
@@ -1310,7 +1310,7 @@ moehlenhoff-alpha2==1.3.0
mopeka-iot-ble==0.5.0
# homeassistant.components.motion_blinds
motionblinds==0.6.19
motionblinds==0.6.20
# homeassistant.components.motioneye
motioneye-client==0.3.14
@@ -1940,7 +1940,7 @@ pymata-express==1.19
pymediaroom==0.6.5.4
# homeassistant.components.melcloud
pymelcloud==2.5.8
pymelcloud==2.5.9
# homeassistant.components.meteoclimatic
pymeteoclimatic==0.1.0
@@ -2257,7 +2257,7 @@ python-opensky==1.0.0
# homeassistant.components.otbr
# homeassistant.components.thread
python-otbr-api==2.5.0
python-otbr-api==2.6.0
# homeassistant.components.picnic
python-picnic-api==1.1.0
@@ -2429,7 +2429,7 @@ rfk101py==0.0.1
rflink==0.0.65
# homeassistant.components.ring
ring-doorbell[listen]==0.8.5
ring-doorbell[listen]==0.8.7
# homeassistant.components.fleetgo
ritassist==0.9.2
@@ -2757,7 +2757,7 @@ unifi_ap==0.0.1
unifiled==0.11
# homeassistant.components.zha
universal-silabs-flasher==0.0.15
universal-silabs-flasher==0.0.18
# homeassistant.components.upb
upb-lib==0.5.4
@@ -2856,10 +2856,10 @@ xbox-webapi==2.0.11
xiaomi-ble==0.23.1
# homeassistant.components.knx
xknx==2.11.2
xknx==2.12.0
# homeassistant.components.knx
xknxproject==3.4.0
xknxproject==3.5.0
# homeassistant.components.bluesound
# homeassistant.components.fritz
@@ -2913,7 +2913,7 @@ zeroconf==0.131.0
zeversolar==0.3.1
# homeassistant.components.zha
zha-quirks==0.0.110
zha-quirks==0.0.111
# homeassistant.components.zhong_hong
zhong-hong-hvac==1.0.9
@@ -2922,7 +2922,7 @@ zhong-hong-hvac==1.0.9
ziggo-mediabox-xl==1.1.0
# homeassistant.components.zha
zigpy-deconz==0.22.4
zigpy-deconz==0.23.0
# homeassistant.components.zha
zigpy-xbee==0.20.1
@@ -2934,7 +2934,7 @@ zigpy-zigate==0.12.0
zigpy-znp==0.12.1
# homeassistant.components.zha
zigpy==0.61.0
zigpy==0.62.3
# homeassistant.components.zoneminder
zm-py==0.5.4
+16 -16
View File
@@ -64,7 +64,7 @@ PyMetEireann==2021.8.0
PyMetno==0.11.0
# homeassistant.components.keymitt_ble
PyMicroBot==0.0.9
PyMicroBot==0.0.10
# homeassistant.components.nina
PyNINA==0.3.3
@@ -209,16 +209,16 @@ aioeafm==0.1.2
aioeagle==1.1.0
# homeassistant.components.ecowitt
aioecowitt==2023.5.0
aioecowitt==2024.2.0
# homeassistant.components.co2signal
aioelectricitymaps==0.3.0
aioelectricitymaps==0.3.1
# homeassistant.components.emonitor
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==21.0.1
aioesphomeapi==21.0.2
# homeassistant.components.flo
aioflo==2021.11.0
@@ -457,7 +457,7 @@ base36==0.1.1
beautifulsoup4==4.12.2
# homeassistant.components.zha
bellows==0.37.6
bellows==0.38.0
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.14.6
@@ -852,7 +852,7 @@ hole==0.8.0
# homeassistant.components.holiday
# homeassistant.components.workday
holidays==0.41
holidays==0.42
# homeassistant.components.frontend
home-assistant-frontend==20240205.0
@@ -1046,7 +1046,7 @@ moehlenhoff-alpha2==1.3.0
mopeka-iot-ble==0.5.0
# homeassistant.components.motion_blinds
motionblinds==0.6.19
motionblinds==0.6.20
# homeassistant.components.motioneye
motioneye-client==0.3.14
@@ -1494,7 +1494,7 @@ pymailgunner==1.4
pymata-express==1.19
# homeassistant.components.melcloud
pymelcloud==2.5.8
pymelcloud==2.5.9
# homeassistant.components.meteoclimatic
pymeteoclimatic==0.1.0
@@ -1727,7 +1727,7 @@ python-opensky==1.0.0
# homeassistant.components.otbr
# homeassistant.components.thread
python-otbr-api==2.5.0
python-otbr-api==2.6.0
# homeassistant.components.picnic
python-picnic-api==1.1.0
@@ -1860,7 +1860,7 @@ reolink-aio==0.8.7
rflink==0.0.65
# homeassistant.components.ring
ring-doorbell[listen]==0.8.5
ring-doorbell[listen]==0.8.7
# homeassistant.components.roku
rokuecp==0.19.0
@@ -2098,7 +2098,7 @@ ultraheat-api==0.5.7
unifi-discovery==1.1.7
# homeassistant.components.zha
universal-silabs-flasher==0.0.15
universal-silabs-flasher==0.0.18
# homeassistant.components.upb
upb-lib==0.5.4
@@ -2185,10 +2185,10 @@ xbox-webapi==2.0.11
xiaomi-ble==0.23.1
# homeassistant.components.knx
xknx==2.11.2
xknx==2.12.0
# homeassistant.components.knx
xknxproject==3.4.0
xknxproject==3.5.0
# homeassistant.components.bluesound
# homeassistant.components.fritz
@@ -2233,10 +2233,10 @@ zeroconf==0.131.0
zeversolar==0.3.1
# homeassistant.components.zha
zha-quirks==0.0.110
zha-quirks==0.0.111
# homeassistant.components.zha
zigpy-deconz==0.22.4
zigpy-deconz==0.23.0
# homeassistant.components.zha
zigpy-xbee==0.20.1
@@ -2248,7 +2248,7 @@ zigpy-zigate==0.12.0
zigpy-znp==0.12.1
# homeassistant.components.zha
zigpy==0.61.0
zigpy==0.62.3
# homeassistant.components.zwave_js
zwave-js-server-python==0.55.3
+3
View File
@@ -181,6 +181,9 @@ dacite>=1.7.0
# Musle wheels for pandas 2.2.0 cannot be build for any architecture.
pandas==2.1.4
# chacha20poly1305-reuseable==0.12.0 is incompatible with cryptography==42.0.x
chacha20poly1305-reuseable>=0.12.1
"""
GENERATED_MESSAGE = (
@@ -1,6 +1,8 @@
"""Test the Homeassistant Analytics config flow."""
from typing import Any
from unittest.mock import AsyncMock
import pytest
from python_homeassistant_analytics import HomeassistantAnalyticsConnectionError
from homeassistant import config_entries
@@ -16,8 +18,45 @@ from tests.common import MockConfigEntry
from tests.components.analytics_insights import setup_integration
@pytest.mark.parametrize(
("user_input", "expected_options"),
[
(
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
),
(
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
},
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
),
(
{
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
),
],
)
async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_analytics_client: AsyncMock
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_analytics_client: AsyncMock,
user_input: dict[str, Any],
expected_options: dict[str, Any],
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
@@ -25,6 +64,50 @@ async def test_form(
)
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Home Assistant Analytics Insights"
assert result["data"] == {}
assert result["options"] == expected_options
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
"user_input",
[
{
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
{},
],
)
async def test_submitting_empty_form(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_analytics_client: AsyncMock,
user_input: dict[str, Any],
) -> None:
"""Test we can't submit an empty form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "no_integrations_selected"}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
@@ -81,10 +164,45 @@ async def test_form_already_configured(
assert result["reason"] == "already_configured"
@pytest.mark.parametrize(
("user_input", "expected_options"),
[
(
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
),
(
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
},
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
),
(
{
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
),
],
)
async def test_options_flow(
hass: HomeAssistant,
mock_analytics_client: AsyncMock,
mock_config_entry: MockConfigEntry,
user_input: dict[str, Any],
expected_options: dict[str, Any],
) -> None:
"""Test options flow."""
await setup_integration(hass, mock_config_entry)
@@ -95,7 +213,50 @@ async def test_options_flow(
mock_analytics_client.get_integrations.reset_mock()
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
user_input,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == expected_options
await hass.async_block_till_done()
mock_analytics_client.get_integrations.assert_called_once()
@pytest.mark.parametrize(
"user_input",
[
{
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
{},
],
)
async def test_submitting_empty_options_flow(
hass: HomeAssistant,
mock_analytics_client: AsyncMock,
mock_config_entry: MockConfigEntry,
user_input: dict[str, Any],
) -> None:
"""Test options flow."""
await setup_integration(hass, mock_config_entry)
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "no_integrations_selected"}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{
CONF_TRACKED_INTEGRATIONS: ["youtube", "hue"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@@ -108,7 +269,6 @@ async def test_options_flow(
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
}
await hass.async_block_till_done()
mock_analytics_client.get_integrations.assert_called_once()
async def test_options_flow_cannot_connect(
+32 -28
View File
@@ -245,7 +245,7 @@ async def test_setup_api_ping(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0"
assert hass.components.hassio.is_hassio()
@@ -290,7 +290,7 @@ async def test_setup_api_push_api_data(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert aioclient_mock.mock_calls[1][2]["watchdog"]
@@ -309,7 +309,7 @@ async def test_setup_api_push_api_data_server_host(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert not aioclient_mock.mock_calls[1][2]["watchdog"]
@@ -326,7 +326,7 @@ async def test_setup_api_push_api_data_default(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"]
@@ -406,7 +406,7 @@ async def test_setup_api_existing_hassio_user(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
assert aioclient_mock.mock_calls[1][2]["refresh_token"] == token.token
@@ -423,7 +423,7 @@ async def test_setup_core_push_timezone(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone"
with patch("homeassistant.util.dt.set_default_time_zone"):
@@ -443,7 +443,7 @@ async def test_setup_hassio_no_additional_data(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert aioclient_mock.mock_calls[-1][3]["Authorization"] == "Bearer 123456"
@@ -525,14 +525,14 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 24
assert aioclient_mock.call_count == 23
assert aioclient_mock.mock_calls[-1][2] == "test"
await hass.services.async_call("hassio", "host_shutdown", {})
await hass.services.async_call("hassio", "host_reboot", {})
await hass.async_block_till_done()
assert aioclient_mock.call_count == 26
assert aioclient_mock.call_count == 25
await hass.services.async_call("hassio", "backup_full", {})
await hass.services.async_call(
@@ -547,7 +547,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 28
assert aioclient_mock.call_count == 27
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 03:48:00",
"homeassistant": True,
@@ -572,7 +572,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 30
assert aioclient_mock.call_count == 29
assert aioclient_mock.mock_calls[-1][2] == {
"addons": ["test"],
"folders": ["ssl"],
@@ -591,7 +591,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 31
assert aioclient_mock.call_count == 30
assert aioclient_mock.mock_calls[-1][2] == {
"name": "backup_name",
"location": "backup_share",
@@ -607,7 +607,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 32
assert aioclient_mock.call_count == 31
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 03:48:00",
"location": None,
@@ -625,7 +625,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 34
assert aioclient_mock.call_count == 33
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 11:48:00",
"location": None,
@@ -702,12 +702,12 @@ async def test_service_calls_core(
await hass.services.async_call("homeassistant", "stop")
await hass.async_block_till_done()
assert aioclient_mock.call_count == 6
assert aioclient_mock.call_count == 5
await hass.services.async_call("homeassistant", "check_config")
await hass.async_block_till_done()
assert aioclient_mock.call_count == 6
assert aioclient_mock.call_count == 5
with patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
@@ -716,7 +716,7 @@ async def test_service_calls_core(
await hass.async_block_till_done()
assert mock_check_config.called
assert aioclient_mock.call_count == 7
assert aioclient_mock.call_count == 6
async def test_entry_load_and_unload(hass: HomeAssistant) -> None:
@@ -897,14 +897,17 @@ async def test_coordinator_updates(
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial refresh without stats
assert refresh_updates_mock.call_count == 1
# Initial refresh, no update refresh call
assert refresh_updates_mock.call_count == 0
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
) as refresh_updates_mock:
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
# Scheduled refresh, no update refresh call
assert refresh_updates_mock.call_count == 0
with patch(
@@ -921,13 +924,14 @@ async def test_coordinator_updates(
},
blocking=True,
)
assert refresh_updates_mock.call_count == 0
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
assert refresh_updates_mock.call_count == 0
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 1
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
@@ -968,14 +972,14 @@ async def test_coordinator_updates_stats_entities_enabled(
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial refresh without stats
assert refresh_updates_mock.call_count == 1
assert refresh_updates_mock.call_count == 0
# Refresh with stats once we know which ones are needed
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 2
assert refresh_updates_mock.call_count == 1
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
@@ -1059,7 +1063,7 @@ async def test_setup_hardware_integration(
await hass.async_block_till_done()
assert result
assert aioclient_mock.call_count == 20
assert aioclient_mock.call_count == 19
assert len(mock_setup_entry.mock_calls) == 1
+8 -8
View File
@@ -1,5 +1,6 @@
""""Unit tests for the Lupusec config flow."""
from json import JSONDecodeError
from unittest.mock import patch
from lupupy import LupusecException
@@ -51,8 +52,7 @@ async def test_form_valid_input(hass: HomeAssistant) -> None:
"homeassistant.components.lupusec.async_setup_entry",
return_value=True,
) as mock_setup_entry, patch(
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec.__init__",
return_value=None,
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec",
) as mock_initialize_lupusec:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -71,6 +71,7 @@ async def test_form_valid_input(hass: HomeAssistant) -> None:
("raise_error", "text_error"),
[
(LupusecException("Test lupusec exception"), "cannot_connect"),
(JSONDecodeError("Test JSONDecodeError", "test", 1), "cannot_connect"),
(Exception("Test unknown exception"), "unknown"),
],
)
@@ -85,7 +86,7 @@ async def test_flow_user_init_data_error_and_recover(
assert result["errors"] == {}
with patch(
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec.__init__",
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec",
side_effect=raise_error,
) as mock_initialize_lupusec:
result2 = await hass.config_entries.flow.async_configure(
@@ -104,8 +105,7 @@ async def test_flow_user_init_data_error_and_recover(
"homeassistant.components.lupusec.async_setup_entry",
return_value=True,
) as mock_setup_entry, patch(
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec.__init__",
return_value=None,
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec",
) as mock_initialize_lupusec:
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -164,8 +164,7 @@ async def test_flow_source_import(
"homeassistant.components.lupusec.async_setup_entry",
return_value=True,
) as mock_setup_entry, patch(
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec.__init__",
return_value=None,
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec",
) as mock_initialize_lupusec:
result = await hass.config_entries.flow.async_init(
DOMAIN,
@@ -186,6 +185,7 @@ async def test_flow_source_import(
("raise_error", "text_error"),
[
(LupusecException("Test lupusec exception"), "cannot_connect"),
(JSONDecodeError("Test JSONDecodeError", "test", 1), "cannot_connect"),
(Exception("Test unknown exception"), "unknown"),
],
)
@@ -195,7 +195,7 @@ async def test_flow_source_import_error_and_recover(
"""Test exceptions and recovery."""
with patch(
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec.__init__",
"homeassistant.components.lupusec.config_flow.lupupy.Lupusec",
side_effect=raise_error,
) as mock_initialize_lupusec:
result = await hass.config_entries.flow.async_init(
+2 -2
View File
@@ -416,8 +416,8 @@ async def test_bandwidth_sensors(
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
assert hass.states.get("sensor.wireless_client_rx").state == "0"
assert hass.states.get("sensor.wireless_client_tx").state == "0"
assert hass.states.get("sensor.wireless_client_rx").state == STATE_UNAVAILABLE
assert hass.states.get("sensor.wireless_client_tx").state == STATE_UNAVAILABLE
# Disable option
+23 -6
View File
@@ -368,6 +368,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"report_count",
"read_plug",
"unsupported_attrs",
"initial_sensor_state",
),
(
(
@@ -377,6 +378,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
1,
None,
None,
STATE_UNKNOWN,
),
(
measurement.TemperatureMeasurement.cluster_id,
@@ -385,6 +387,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
1,
None,
None,
STATE_UNKNOWN,
),
(
measurement.PressureMeasurement.cluster_id,
@@ -393,6 +396,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
1,
None,
None,
STATE_UNKNOWN,
),
(
measurement.IlluminanceMeasurement.cluster_id,
@@ -401,6 +405,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
1,
None,
None,
STATE_UNKNOWN,
),
(
smartenergy.Metering.cluster_id,
@@ -415,6 +420,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"status": 0x00,
},
{"current_summ_delivered", "current_summ_received"},
STATE_UNKNOWN,
),
(
smartenergy.Metering.cluster_id,
@@ -431,6 +437,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"unit_of_measure": 0x00,
},
{"instaneneous_demand", "current_summ_received"},
STATE_UNKNOWN,
),
(
smartenergy.Metering.cluster_id,
@@ -445,8 +452,10 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"status": 0x00,
"summation_formatting": 0b1_0111_010,
"unit_of_measure": 0x00,
"current_summ_received": 0,
},
{"instaneneous_demand", "current_summ_delivered"},
"0.0",
),
(
homeautomation.ElectricalMeasurement.cluster_id,
@@ -455,6 +464,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
7,
{"ac_power_divisor": 1000, "ac_power_multiplier": 1},
{"apparent_power", "rms_current", "rms_voltage"},
STATE_UNKNOWN,
),
(
homeautomation.ElectricalMeasurement.cluster_id,
@@ -463,6 +473,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
7,
{"ac_power_divisor": 1000, "ac_power_multiplier": 1},
{"active_power", "rms_current", "rms_voltage"},
STATE_UNKNOWN,
),
(
homeautomation.ElectricalMeasurement.cluster_id,
@@ -471,6 +482,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
7,
{"ac_power_divisor": 1000, "ac_power_multiplier": 1},
{"active_power", "apparent_power", "rms_current", "rms_voltage"},
STATE_UNKNOWN,
),
(
homeautomation.ElectricalMeasurement.cluster_id,
@@ -479,6 +491,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
7,
{"ac_current_divisor": 1000, "ac_current_multiplier": 1},
{"active_power", "apparent_power", "rms_voltage"},
STATE_UNKNOWN,
),
(
homeautomation.ElectricalMeasurement.cluster_id,
@@ -487,6 +500,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
7,
{"ac_voltage_divisor": 10, "ac_voltage_multiplier": 1},
{"active_power", "apparent_power", "rms_current"},
STATE_UNKNOWN,
),
(
general.PowerConfiguration.cluster_id,
@@ -499,6 +513,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"battery_quantity": 3,
},
None,
STATE_UNKNOWN,
),
(
general.PowerConfiguration.cluster_id,
@@ -511,6 +526,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
"battery_quantity": 3,
},
None,
STATE_UNKNOWN,
),
(
general.DeviceTemperature.cluster_id,
@@ -519,6 +535,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
1,
None,
None,
STATE_UNKNOWN,
),
(
hvac.Thermostat.cluster_id,
@@ -527,6 +544,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
10,
None,
None,
STATE_UNKNOWN,
),
(
hvac.Thermostat.cluster_id,
@@ -535,6 +553,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id):
10,
None,
None,
STATE_UNKNOWN,
),
),
)
@@ -548,6 +567,7 @@ async def test_sensor(
report_count,
read_plug,
unsupported_attrs,
initial_sensor_state,
) -> None:
"""Test ZHA sensor platform."""
@@ -582,8 +602,8 @@ async def test_sensor(
# allow traffic to flow through the gateway and devices
await async_enable_traffic(hass, [zha_device])
# test that the sensor now have a state of unknown
assert hass.states.get(entity_id).state == STATE_UNKNOWN
# test that the sensor now have their correct initial state (mostly unknown)
assert hass.states.get(entity_id).state == initial_sensor_state
# test sensor associated logic
await test_func(hass, cluster, entity_id)
@@ -826,7 +846,6 @@ async def test_electrical_measurement_init(
},
{
"summation_delivered",
"summation_received",
},
{
"instantaneous_demand",
@@ -834,12 +853,11 @@ async def test_electrical_measurement_init(
),
(
smartenergy.Metering.cluster_id,
{"instantaneous_demand", "current_summ_delivered", "current_summ_received"},
{"instantaneous_demand", "current_summ_delivered"},
{},
{
"instantaneous_demand",
"summation_delivered",
"summation_received",
},
),
(
@@ -848,7 +866,6 @@ async def test_electrical_measurement_init(
{
"instantaneous_demand",
"summation_delivered",
"summation_received",
},
{},
),
+1 -1
View File
@@ -205,7 +205,7 @@ def make_packet(zigpy_device, cluster, cmd_name: str, **kwargs):
command_id=cluster.commands_by_name[cmd_name].id,
schema=cluster.commands_by_name[cmd_name].schema,
disable_default_response=False,
direction=foundation.Direction.Server_to_Client,
direction=foundation.Direction.Client_to_Server,
args=(),
kwargs=kwargs,
)
-52
View File
@@ -243,11 +243,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.centralite_3210_l_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.centralite_3210_l_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -616,13 +611,6 @@ DEVICES = [
"sensor.climaxtechnology_psmp5_00_00_02_02tc_summation_delivered"
),
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: (
"sensor.climaxtechnology_psmp5_00_00_02_02tc_summation_received"
),
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -1617,11 +1605,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45852_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45852_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -1682,11 +1665,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45856_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45856_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -1747,11 +1725,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45857_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.jasco_products_45857_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -2370,11 +2343,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_relay_c2acn01_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_relay_c2acn01_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergyMetering",
@@ -4511,11 +4479,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sercomm_corp_sz_esw01_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sercomm_corp_sz_esw01_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -5362,11 +5325,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_e11_g13_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_e11_g13_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -5420,11 +5378,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_e12_n14_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_e12_n14_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -5478,11 +5431,6 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_z01_a19nae26_summation_delivered",
},
("sensor", "00:11:22:33:44:55:66:77-1-1794-summation_received"): {
DEV_SIG_CLUSTER_HANDLERS: ["smartenergy_metering"],
DEV_SIG_ENT_MAP_CLASS: "SmartEnergySummation",
DEV_SIG_ENT_MAP_ID: "sensor.sengled_z01_a19nae26_summation_received",
},
("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): {
DEV_SIG_CLUSTER_HANDLERS: ["basic"],
DEV_SIG_ENT_MAP_CLASS: "RSSISensor",
@@ -396,19 +396,19 @@ async def test_abort_discovered_multiple(
HTTPStatus.UNAUTHORIZED,
{},
"oauth_unauthorized",
"Token request failed (unknown): unknown",
"Token request for oauth2_test failed (unknown): unknown",
),
(
HTTPStatus.NOT_FOUND,
{},
"oauth_failed",
"Token request failed (unknown): unknown",
"Token request for oauth2_test failed (unknown): unknown",
),
(
HTTPStatus.INTERNAL_SERVER_ERROR,
{},
"oauth_failed",
"Token request failed (unknown): unknown",
"Token request for oauth2_test failed (unknown): unknown",
),
(
HTTPStatus.BAD_REQUEST,
@@ -418,7 +418,7 @@ async def test_abort_discovered_multiple(
"error_uri": "See the full API docs at https://authorization-server.com/docs/access_token",
},
"oauth_failed",
"Token request failed (invalid_request): Request was missing the",
"Token request for oauth2_test failed (invalid_request): Request was missing the",
),
],
)
@@ -541,7 +541,7 @@ async def test_abort_if_oauth_token_closing_error(
with caplog.at_level(logging.DEBUG):
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert "Token request failed (unknown): unknown" in caplog.text
assert "Token request for oauth2_test failed (unknown): unknown" in caplog.text
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "oauth_unauthorized"
+82
View File
@@ -19,6 +19,7 @@ from homeassistant.core import (
)
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_platform,
entity_registry as er,
@@ -1628,6 +1629,87 @@ async def test_register_entity_service_response_data_multiple_matches_raises(
)
async def test_register_entity_service_limited_to_matching_platforms(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
area_registry: ar.AreaRegistry,
) -> None:
"""Test an entity services only targets entities for the platform and domain."""
mock_area = area_registry.async_get_or_create("mock_area")
entity1_entry = entity_registry.async_get_or_create(
"base_platform", "mock_platform", "1234", suggested_object_id="entity1"
)
entity_registry.async_update_entity(entity1_entry.entity_id, area_id=mock_area.id)
entity2_entry = entity_registry.async_get_or_create(
"base_platform", "mock_platform", "5678", suggested_object_id="entity2"
)
entity_registry.async_update_entity(entity2_entry.entity_id, area_id=mock_area.id)
entity3_entry = entity_registry.async_get_or_create(
"base_platform", "other_mock_platform", "7891", suggested_object_id="entity3"
)
entity_registry.async_update_entity(entity3_entry.entity_id, area_id=mock_area.id)
entity4_entry = entity_registry.async_get_or_create(
"base_platform", "other_mock_platform", "1433", suggested_object_id="entity4"
)
entity_registry.async_update_entity(entity4_entry.entity_id, area_id=mock_area.id)
async def generate_response(
target: MockEntity, call: ServiceCall
) -> ServiceResponse:
assert call.return_response
return {"response-key": f"response-value-{target.entity_id}"}
entity_platform = MockEntityPlatform(
hass, domain="base_platform", platform_name="mock_platform", platform=None
)
entity1 = MockEntity(
entity_id=entity1_entry.entity_id, unique_id=entity1_entry.unique_id
)
entity2 = MockEntity(
entity_id=entity2_entry.entity_id, unique_id=entity2_entry.unique_id
)
await entity_platform.async_add_entities([entity1, entity2])
other_entity_platform = MockEntityPlatform(
hass, domain="base_platform", platform_name="other_mock_platform", platform=None
)
entity3 = MockEntity(
entity_id=entity3_entry.entity_id, unique_id=entity3_entry.unique_id
)
entity4 = MockEntity(
entity_id=entity4_entry.entity_id, unique_id=entity4_entry.unique_id
)
await other_entity_platform.async_add_entities([entity3, entity4])
entity_platform.async_register_entity_service(
"hello",
{"some": str},
generate_response,
supports_response=SupportsResponse.ONLY,
)
response_data = await hass.services.async_call(
"mock_platform",
"hello",
service_data={"some": "data"},
target={"area_id": [mock_area.id]},
blocking=True,
return_response=True,
)
# We should not target entity3 and entity4 even though they are in the area
# because they are only part of the domain and not the platform
assert response_data == {
"base_platform.entity1": {
"response-key": "response-value-base_platform.entity1"
},
"base_platform.entity2": {
"response-key": "response-value-base_platform.entity2"
},
}
async def test_invalid_entity_id(hass: HomeAssistant) -> None:
"""Test specifying an invalid entity id."""
platform = MockEntityPlatform(hass)