mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
2024.2.2 (#110720)
This commit is contained in:
commit
7aa14e20d1
@ -249,10 +249,11 @@ class AugustData(AugustSubscriberMixin):
|
|||||||
device = self.get_device_detail(device_id)
|
device = self.get_device_detail(device_id)
|
||||||
activities = activities_from_pubnub_message(device, date_time, message)
|
activities = activities_from_pubnub_message(device, date_time, message)
|
||||||
activity_stream = self.activity_stream
|
activity_stream = self.activity_stream
|
||||||
if activities:
|
if activities and activity_stream.async_process_newer_device_activities(
|
||||||
activity_stream.async_process_newer_device_activities(activities)
|
activities
|
||||||
|
):
|
||||||
self.async_signal_device_id_update(device.device_id)
|
self.async_signal_device_id_update(device.device_id)
|
||||||
activity_stream.async_schedule_house_id_refresh(device.house_id)
|
activity_stream.async_schedule_house_id_refresh(device.house_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_stop(self) -> None:
|
def async_stop(self) -> None:
|
||||||
|
@ -28,5 +28,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pubnub", "yalexs"],
|
"loggers": ["pubnub", "yalexs"],
|
||||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.1"]
|
"requirements": ["yalexs==1.11.2", "yalexs-ble==2.4.1"]
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ from aioelectricitymaps import (
|
|||||||
ElectricityMaps,
|
ElectricityMaps,
|
||||||
ElectricityMapsError,
|
ElectricityMapsError,
|
||||||
ElectricityMapsInvalidTokenError,
|
ElectricityMapsInvalidTokenError,
|
||||||
|
ElectricityMapsNoDataError,
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -151,6 +152,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
await fetch_latest_carbon_intensity(self.hass, em, data)
|
await fetch_latest_carbon_intensity(self.hass, em, data)
|
||||||
except ElectricityMapsInvalidTokenError:
|
except ElectricityMapsInvalidTokenError:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
|
except ElectricityMapsNoDataError:
|
||||||
|
errors["base"] = "no_data"
|
||||||
except ElectricityMapsError:
|
except ElectricityMapsError:
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
else:
|
else:
|
||||||
|
@ -28,12 +28,9 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"api_ratelimit": "API Ratelimit exceeded"
|
"no_data": "No data is available for the location you have selected."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
|
||||||
"api_ratelimit": "[%key:component::co2signal::config::error::api_ratelimit%]",
|
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
|
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["async_upnp_client"],
|
"loggers": ["async_upnp_client"],
|
||||||
"requirements": ["async-upnp-client==0.38.1", "getmac==0.9.4"],
|
"requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/dlna_dms",
|
"documentation": "https://www.home-assistant.io/integrations/dlna_dms",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["async-upnp-client==0.38.1"],
|
"requirements": ["async-upnp-client==0.38.2"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||||
"requirements": ["py-sucks==0.9.8", "deebot-client==5.1.1"]
|
"requirements": ["py-sucks==0.9.9", "deebot-client==5.2.1"]
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ from types import MappingProxyType
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from elkm1_lib.elements import Element
|
from elkm1_lib.elements import Element
|
||||||
from elkm1_lib.elk import Elk
|
from elkm1_lib.elk import Elk, Panel
|
||||||
from elkm1_lib.util import parse_url
|
from elkm1_lib.util import parse_url
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -398,22 +398,30 @@ async def async_wait_for_elk_to_sync(
|
|||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_get_elk_panel(hass: HomeAssistant, service: ServiceCall) -> Panel:
|
||||||
|
"""Get the ElkM1 panel from a service call."""
|
||||||
|
prefix = service.data["prefix"]
|
||||||
|
elk = _find_elk_by_prefix(hass, prefix)
|
||||||
|
if elk is None:
|
||||||
|
raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found")
|
||||||
|
return elk.panel
|
||||||
|
|
||||||
|
|
||||||
def _create_elk_services(hass: HomeAssistant) -> None:
|
def _create_elk_services(hass: HomeAssistant) -> None:
|
||||||
def _getelk(service: ServiceCall) -> Elk:
|
"""Create ElkM1 services."""
|
||||||
prefix = service.data["prefix"]
|
|
||||||
elk = _find_elk_by_prefix(hass, prefix)
|
|
||||||
if elk is None:
|
|
||||||
raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found")
|
|
||||||
return elk
|
|
||||||
|
|
||||||
|
@callback
|
||||||
def _speak_word_service(service: ServiceCall) -> None:
|
def _speak_word_service(service: ServiceCall) -> None:
|
||||||
_getelk(service).panel.speak_word(service.data["number"])
|
_async_get_elk_panel(hass, service).speak_word(service.data["number"])
|
||||||
|
|
||||||
|
@callback
|
||||||
def _speak_phrase_service(service: ServiceCall) -> None:
|
def _speak_phrase_service(service: ServiceCall) -> None:
|
||||||
_getelk(service).panel.speak_phrase(service.data["number"])
|
_async_get_elk_panel(hass, service).speak_phrase(service.data["number"])
|
||||||
|
|
||||||
|
@callback
|
||||||
def _set_time_service(service: ServiceCall) -> None:
|
def _set_time_service(service: ServiceCall) -> None:
|
||||||
_getelk(service).panel.set_time(dt_util.now())
|
_async_get_elk_panel(hass, service).set_time(dt_util.now())
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA
|
DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/evohome",
|
"documentation": "https://www.home-assistant.io/integrations/evohome",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["evohomeasync", "evohomeasync2"],
|
"loggers": ["evohomeasync", "evohomeasync2"],
|
||||||
"requirements": ["evohome-async==0.4.18"]
|
"requirements": ["evohome-async==0.4.19"]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT
|
|||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .router import get_api
|
from .router import get_api, get_hosts_list_if_supported
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
# Check permissions
|
# Check permissions
|
||||||
await fbx.system.get_config()
|
await fbx.system.get_config()
|
||||||
await fbx.lan.get_hosts_list()
|
await get_hosts_list_if_supported(fbx)
|
||||||
|
|
||||||
# Close connection
|
# Close connection
|
||||||
await fbx.close()
|
await fbx.close()
|
||||||
|
@ -64,6 +64,33 @@ async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
|
|||||||
return Freepybox(APP_DESC, token_file, API_VERSION)
|
return Freepybox(APP_DESC, token_file, API_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_hosts_list_if_supported(
|
||||||
|
fbx_api: Freepybox,
|
||||||
|
) -> tuple[bool, list[dict[str, Any]]]:
|
||||||
|
"""Hosts list is not supported when freebox is configured in bridge mode."""
|
||||||
|
supports_hosts: bool = True
|
||||||
|
fbx_devices: list[dict[str, Any]] = []
|
||||||
|
try:
|
||||||
|
fbx_devices = await fbx_api.lan.get_hosts_list() or []
|
||||||
|
except HttpRequestError as err:
|
||||||
|
if (
|
||||||
|
(matcher := re.search(r"Request failed \(APIResponse: (.+)\)", str(err)))
|
||||||
|
and is_json(json_str := matcher.group(1))
|
||||||
|
and (json_resp := json.loads(json_str)).get("error_code") == "nodev"
|
||||||
|
):
|
||||||
|
# No need to retry, Host list not available
|
||||||
|
supports_hosts = False
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Host list is not available using bridge mode (%s)",
|
||||||
|
json_resp.get("msg"),
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
return supports_hosts, fbx_devices
|
||||||
|
|
||||||
|
|
||||||
class FreeboxRouter:
|
class FreeboxRouter:
|
||||||
"""Representation of a Freebox router."""
|
"""Representation of a Freebox router."""
|
||||||
|
|
||||||
@ -111,27 +138,9 @@ class FreeboxRouter:
|
|||||||
|
|
||||||
# Access to Host list not available in bridge mode, API return error_code 'nodev'
|
# Access to Host list not available in bridge mode, API return error_code 'nodev'
|
||||||
if self.supports_hosts:
|
if self.supports_hosts:
|
||||||
try:
|
self.supports_hosts, fbx_devices = await get_hosts_list_if_supported(
|
||||||
fbx_devices = await self._api.lan.get_hosts_list()
|
self._api
|
||||||
except HttpRequestError as err:
|
)
|
||||||
if (
|
|
||||||
(
|
|
||||||
matcher := re.search(
|
|
||||||
r"Request failed \(APIResponse: (.+)\)", str(err)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
and is_json(json_str := matcher.group(1))
|
|
||||||
and (json_resp := json.loads(json_str)).get("error_code") == "nodev"
|
|
||||||
):
|
|
||||||
# No need to retry, Host list not available
|
|
||||||
self.supports_hosts = False
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Host list is not available using bridge mode (%s)",
|
|
||||||
json_resp.get("msg"),
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
# Adds the Freebox itself
|
# Adds the Freebox itself
|
||||||
fbx_devices.append(
|
fbx_devices.append(
|
||||||
|
@ -476,7 +476,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
|||||||
translation_placeholders={
|
translation_placeholders={
|
||||||
"entity_id": self.entity_id,
|
"entity_id": self.entity_id,
|
||||||
"source_entities": ", ".join(self._entity_ids),
|
"source_entities": ", ".join(self._entity_ids),
|
||||||
"state_classes:": ", ".join(state_classes),
|
"state_classes": ", ".join(state_classes),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@ -519,7 +519,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
|||||||
translation_placeholders={
|
translation_placeholders={
|
||||||
"entity_id": self.entity_id,
|
"entity_id": self.entity_id,
|
||||||
"source_entities": ", ".join(self._entity_ids),
|
"source_entities": ", ".join(self._entity_ids),
|
||||||
"device_classes:": ", ".join(device_classes),
|
"device_classes": ", ".join(device_classes),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
@ -265,7 +265,7 @@
|
|||||||
},
|
},
|
||||||
"state_classes_not_matching": {
|
"state_classes_not_matching": {
|
||||||
"title": "State classes is not correct",
|
"title": "State classes is not correct",
|
||||||
"description": "Device classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue."
|
"description": "State classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"xknx==2.12.0",
|
"xknx==2.12.0",
|
||||||
"xknxproject==3.5.0",
|
"xknxproject==3.6.0",
|
||||||
"knx-frontend==2024.1.20.105944"
|
"knx-frontend==2024.1.20.105944"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,12 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from linear_garage_door import Linear
|
from linear_garage_door import Linear
|
||||||
from linear_garage_door.errors import InvalidLoginError, ResponseError
|
from linear_garage_door.errors import InvalidLoginError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -55,6 +56,7 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
email=self._email,
|
email=self._email,
|
||||||
password=self._password,
|
password=self._password,
|
||||||
device_id=self._device_id,
|
device_id=self._device_id,
|
||||||
|
client_session=async_get_clientsession(self.hass),
|
||||||
)
|
)
|
||||||
except InvalidLoginError as err:
|
except InvalidLoginError as err:
|
||||||
if (
|
if (
|
||||||
@ -63,8 +65,6 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
):
|
):
|
||||||
raise ConfigEntryAuthFailed from err
|
raise ConfigEntryAuthFailed from err
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
except ResponseError as err:
|
|
||||||
raise ConfigEntryNotReady from err
|
|
||||||
|
|
||||||
if not self._devices:
|
if not self._devices:
|
||||||
self._devices = await linear.get_devices(self._site_id)
|
self._devices = await linear.get_devices(self._site_id)
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/linear_garage_door",
|
"documentation": "https://www.home-assistant.io/integrations/linear_garage_door",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["linear-garage-door==0.2.7"]
|
"requirements": ["linear-garage-door==0.2.9"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/lutron",
|
"documentation": "https://www.home-assistant.io/integrations/lutron",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pylutron"],
|
"loggers": ["pylutron"],
|
||||||
"requirements": ["pylutron==0.2.8"]
|
"requirements": ["pylutron==0.2.12"]
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import datapoint
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
@ -16,7 +17,7 @@ from homeassistant.const import (
|
|||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
|
||||||
@ -34,9 +35,6 @@ from .const import (
|
|||||||
from .data import MetOfficeData
|
from .data import MetOfficeData
|
||||||
from .helpers import fetch_data, fetch_site
|
from .helpers import fetch_data, fetch_site
|
||||||
|
|
||||||
if sys.version_info < (3, 12):
|
|
||||||
import datapoint
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||||
@ -44,10 +42,6 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up a Met Office entry."""
|
"""Set up a Met Office entry."""
|
||||||
if sys.version_info >= (3, 12):
|
|
||||||
raise HomeAssistantError(
|
|
||||||
"Met Office is not supported on Python 3.12. Please use Python 3.11."
|
|
||||||
)
|
|
||||||
|
|
||||||
latitude = entry.data[CONF_LATITUDE]
|
latitude = entry.data[CONF_LATITUDE]
|
||||||
longitude = entry.data[CONF_LONGITUDE]
|
longitude = entry.data[CONF_LONGITUDE]
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.version_info < (3, 12):
|
from datapoint.Forecast import Forecast
|
||||||
from datapoint.Forecast import Forecast
|
from datapoint.Site import Site
|
||||||
from datapoint.Site import Site
|
from datapoint.Timestep import Timestep
|
||||||
from datapoint.Timestep import Timestep
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
|
import datapoint
|
||||||
|
from datapoint.Site import Site
|
||||||
|
|
||||||
from homeassistant.helpers.update_coordinator import UpdateFailed
|
from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
@ -10,11 +12,6 @@ from homeassistant.util.dt import utcnow
|
|||||||
from .const import MODE_3HOURLY
|
from .const import MODE_3HOURLY
|
||||||
from .data import MetOfficeData
|
from .data import MetOfficeData
|
||||||
|
|
||||||
if sys.version_info < (3, 12):
|
|
||||||
import datapoint
|
|
||||||
from datapoint.Site import Site
|
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -34,7 +31,7 @@ def fetch_site(
|
|||||||
def fetch_data(connection: datapoint.Manager, site: Site, mode: str) -> MetOfficeData:
|
def fetch_data(connection: datapoint.Manager, site: Site, mode: str) -> MetOfficeData:
|
||||||
"""Fetch weather and forecast from Datapoint API."""
|
"""Fetch weather and forecast from Datapoint API."""
|
||||||
try:
|
try:
|
||||||
forecast = connection.get_forecast_for_site(site.id, mode)
|
forecast = connection.get_forecast_for_site(site.location_id, mode)
|
||||||
except (ValueError, datapoint.exceptions.APIException) as err:
|
except (ValueError, datapoint.exceptions.APIException) as err:
|
||||||
_LOGGER.error("Check Met Office connection: %s", err.args)
|
_LOGGER.error("Check Met Office connection: %s", err.args)
|
||||||
raise UpdateFailed from err
|
raise UpdateFailed from err
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
"name": "Met Office",
|
"name": "Met Office",
|
||||||
"codeowners": ["@MrHarcombe", "@avee87"],
|
"codeowners": ["@MrHarcombe", "@avee87"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"disabled": "Integration library not compatible with Python 3.12",
|
|
||||||
"documentation": "https://www.home-assistant.io/integrations/metoffice",
|
"documentation": "https://www.home-assistant.io/integrations/metoffice",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["datapoint"],
|
"loggers": ["datapoint"],
|
||||||
"requirements": ["datapoint==0.9.8;python_version<'3.12'"]
|
"requirements": ["datapoint==0.9.9"]
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,6 @@ class MetOfficeCurrentSensor(
|
|||||||
return {
|
return {
|
||||||
ATTR_LAST_UPDATE: self.coordinator.data.now.date,
|
ATTR_LAST_UPDATE: self.coordinator.data.now.date,
|
||||||
ATTR_SENSOR_ID: self.entity_description.key,
|
ATTR_SENSOR_ID: self.entity_description.key,
|
||||||
ATTR_SITE_ID: self.coordinator.data.site.id,
|
ATTR_SITE_ID: self.coordinator.data.site.location_id,
|
||||||
ATTR_SITE_NAME: self.coordinator.data.site.name,
|
ATTR_SITE_NAME: self.coordinator.data.site.name,
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
self._precision = config.get(CONF_PRECISION, 2)
|
self._precision = config.get(CONF_PRECISION, 2)
|
||||||
else:
|
else:
|
||||||
self._precision = config.get(CONF_PRECISION, 0)
|
self._precision = config.get(CONF_PRECISION, 0)
|
||||||
|
if self._precision > 0 or self._scale != int(self._scale):
|
||||||
|
self._value_is_int = False
|
||||||
|
|
||||||
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
|
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
|
||||||
"""Do swap as needed."""
|
"""Do swap as needed."""
|
||||||
|
@ -400,6 +400,7 @@ class MotionTDBUDevice(MotionPositionDevice):
|
|||||||
def __init__(self, coordinator, blind, device_class, motor):
|
def __init__(self, coordinator, blind, device_class, motor):
|
||||||
"""Initialize the blind."""
|
"""Initialize the blind."""
|
||||||
super().__init__(coordinator, blind, device_class)
|
super().__init__(coordinator, blind, device_class)
|
||||||
|
delattr(self, "_attr_name")
|
||||||
self._motor = motor
|
self._motor = motor
|
||||||
self._motor_key = motor[0]
|
self._motor_key = motor[0]
|
||||||
self._attr_translation_key = motor.lower()
|
self._attr_translation_key = motor.lower()
|
||||||
|
@ -212,7 +212,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
password = entry.data[CONF_PASSWORD]
|
password = entry.data[CONF_PASSWORD]
|
||||||
|
|
||||||
if DOMAIN not in hass.data:
|
if not (data := hass.data.get(DOMAIN)) or data.websession.closed:
|
||||||
websession = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
|
websession = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
|
||||||
|
|
||||||
hass.data[DOMAIN] = LTEData(websession)
|
hass.data[DOMAIN] = LTEData(websession)
|
||||||
@ -258,7 +258,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
if entry.state == ConfigEntryState.LOADED
|
if entry.state == ConfigEntryState.LOADED
|
||||||
]
|
]
|
||||||
if len(loaded_entries) == 1:
|
if len(loaded_entries) == 1:
|
||||||
hass.data.pop(DOMAIN)
|
hass.data.pop(DOMAIN, None)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aiopegelonline"],
|
"loggers": ["aiopegelonline"],
|
||||||
"requirements": ["aiopegelonline==0.0.6"]
|
"requirements": ["aiopegelonline==0.0.8"]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,10 @@ from homeassistant.components.climate import (
|
|||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
HVACAction,
|
HVACAction,
|
||||||
)
|
)
|
||||||
from homeassistant.components.cover import ATTR_POSITION, ATTR_TILT_POSITION
|
from homeassistant.components.cover import (
|
||||||
|
ATTR_CURRENT_POSITION,
|
||||||
|
ATTR_CURRENT_TILT_POSITION,
|
||||||
|
)
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.components.humidifier import ATTR_AVAILABLE_MODES, ATTR_HUMIDITY
|
from homeassistant.components.humidifier import ATTR_AVAILABLE_MODES, ATTR_HUMIDITY
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||||
@ -437,7 +440,7 @@ class PrometheusMetrics:
|
|||||||
float(cover_state == state.state)
|
float(cover_state == state.state)
|
||||||
)
|
)
|
||||||
|
|
||||||
position = state.attributes.get(ATTR_POSITION)
|
position = state.attributes.get(ATTR_CURRENT_POSITION)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
position_metric = self._metric(
|
position_metric = self._metric(
|
||||||
"cover_position",
|
"cover_position",
|
||||||
@ -446,7 +449,7 @@ class PrometheusMetrics:
|
|||||||
)
|
)
|
||||||
position_metric.labels(**self._labels(state)).set(float(position))
|
position_metric.labels(**self._labels(state)).set(float(position))
|
||||||
|
|
||||||
tilt_position = state.attributes.get(ATTR_TILT_POSITION)
|
tilt_position = state.attributes.get(ATTR_CURRENT_TILT_POSITION)
|
||||||
if tilt_position is not None:
|
if tilt_position is not None:
|
||||||
tilt_position_metric = self._metric(
|
tilt_position_metric = self._metric(
|
||||||
"cover_tilt_position",
|
"cover_tilt_position",
|
||||||
|
@ -115,6 +115,7 @@ async def setup_device(
|
|||||||
device.name,
|
device.name,
|
||||||
)
|
)
|
||||||
_LOGGER.debug(err)
|
_LOGGER.debug(err)
|
||||||
|
await mqtt_client.async_release()
|
||||||
raise err
|
raise err
|
||||||
coordinator = RoborockDataUpdateCoordinator(
|
coordinator = RoborockDataUpdateCoordinator(
|
||||||
hass, device, networking, product_info, mqtt_client
|
hass, device, networking, product_info, mqtt_client
|
||||||
@ -125,6 +126,7 @@ async def setup_device(
|
|||||||
try:
|
try:
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
except ConfigEntryNotReady:
|
except ConfigEntryNotReady:
|
||||||
|
await coordinator.release()
|
||||||
if isinstance(coordinator.api, RoborockMqttClient):
|
if isinstance(coordinator.api, RoborockMqttClient):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Not setting up %s because the we failed to get data for the first time using the online client. "
|
"Not setting up %s because the we failed to get data for the first time using the online client. "
|
||||||
@ -153,14 +155,10 @@ async def setup_device(
|
|||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Handle removal of an entry."""
|
"""Handle removal of an entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
if unload_ok:
|
release_tasks = set()
|
||||||
await asyncio.gather(
|
for coordinator in hass.data[DOMAIN][entry.entry_id].values():
|
||||||
*(
|
release_tasks.add(coordinator.release())
|
||||||
coordinator.release()
|
|
||||||
for coordinator in hass.data[DOMAIN][entry.entry_id].values()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
await asyncio.gather(*release_tasks)
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -77,7 +77,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
|
|
||||||
async def release(self) -> None:
|
async def release(self) -> None:
|
||||||
"""Disconnect from API."""
|
"""Disconnect from API."""
|
||||||
await self.api.async_disconnect()
|
await self.api.async_release()
|
||||||
|
await self.cloud_api.async_release()
|
||||||
|
|
||||||
async def _update_device_prop(self) -> None:
|
async def _update_device_prop(self) -> None:
|
||||||
"""Update device properties."""
|
"""Update device properties."""
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Support for Roborock device base class."""
|
"""Support for Roborock device base class."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from roborock.api import AttributeCache, RoborockClient
|
from roborock.api import AttributeCache, RoborockClient
|
||||||
@ -7,6 +6,7 @@ from roborock.cloud_api import RoborockMqttClient
|
|||||||
from roborock.command_cache import CacheableAttribute
|
from roborock.command_cache import CacheableAttribute
|
||||||
from roborock.containers import Consumable, Status
|
from roborock.containers import Consumable, Status
|
||||||
from roborock.exceptions import RoborockException
|
from roborock.exceptions import RoborockException
|
||||||
|
from roborock.roborock_message import RoborockDataProtocol
|
||||||
from roborock.roborock_typing import RoborockCommand
|
from roborock.roborock_typing import RoborockCommand
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
@ -24,7 +24,10 @@ class RoborockEntity(Entity):
|
|||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, unique_id: str, device_info: DeviceInfo, api: RoborockClient
|
self,
|
||||||
|
unique_id: str,
|
||||||
|
device_info: DeviceInfo,
|
||||||
|
api: RoborockClient,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the coordinated Roborock Device."""
|
"""Initialize the coordinated Roborock Device."""
|
||||||
self._attr_unique_id = unique_id
|
self._attr_unique_id = unique_id
|
||||||
@ -75,6 +78,9 @@ class RoborockCoordinatedEntity(
|
|||||||
self,
|
self,
|
||||||
unique_id: str,
|
unique_id: str,
|
||||||
coordinator: RoborockDataUpdateCoordinator,
|
coordinator: RoborockDataUpdateCoordinator,
|
||||||
|
listener_request: list[RoborockDataProtocol]
|
||||||
|
| RoborockDataProtocol
|
||||||
|
| None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the coordinated Roborock Device."""
|
"""Initialize the coordinated Roborock Device."""
|
||||||
RoborockEntity.__init__(
|
RoborockEntity.__init__(
|
||||||
@ -85,6 +91,23 @@ class RoborockCoordinatedEntity(
|
|||||||
)
|
)
|
||||||
CoordinatorEntity.__init__(self, coordinator=coordinator)
|
CoordinatorEntity.__init__(self, coordinator=coordinator)
|
||||||
self._attr_unique_id = unique_id
|
self._attr_unique_id = unique_id
|
||||||
|
if isinstance(listener_request, RoborockDataProtocol):
|
||||||
|
listener_request = [listener_request]
|
||||||
|
self.listener_requests = listener_request or []
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Add listeners when the device is added to hass."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
for listener_request in self.listener_requests:
|
||||||
|
self.api.add_listener(
|
||||||
|
listener_request, self._update_from_listener, cache=self.api.cache
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
"""Remove listeners when the device is removed from hass."""
|
||||||
|
for listener_request in self.listener_requests:
|
||||||
|
self.api.remove_listener(listener_request, self._update_from_listener)
|
||||||
|
await super().async_will_remove_from_hass()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _device_status(self) -> Status:
|
def _device_status(self) -> Status:
|
||||||
@ -107,7 +130,7 @@ class RoborockCoordinatedEntity(
|
|||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _update_from_listener(self, value: Status | Consumable):
|
def _update_from_listener(self, value: Status | Consumable) -> None:
|
||||||
"""Update the status or consumable data from a listener and then write the new entity state."""
|
"""Update the status or consumable data from a listener and then write the new entity state."""
|
||||||
if isinstance(value, Status):
|
if isinstance(value, Status):
|
||||||
self.coordinator.roborock_device_info.props.status = value
|
self.coordinator.roborock_device_info.props.status = value
|
||||||
|
@ -107,10 +107,8 @@ class RoborockSelectEntity(RoborockCoordinatedEntity, SelectEntity):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Create a select entity."""
|
"""Create a select entity."""
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
super().__init__(unique_id, coordinator)
|
super().__init__(unique_id, coordinator, entity_description.protocol_listener)
|
||||||
self._attr_options = options
|
self._attr_options = options
|
||||||
if (protocol := self.entity_description.protocol_listener) is not None:
|
|
||||||
self.api.add_listener(protocol, self._update_from_listener, self.api.cache)
|
|
||||||
|
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
"""Set the option."""
|
"""Set the option."""
|
||||||
|
@ -232,10 +232,8 @@ class RoborockSensorEntity(RoborockCoordinatedEntity, SensorEntity):
|
|||||||
description: RoborockSensorDescription,
|
description: RoborockSensorDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(unique_id, coordinator)
|
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
if (protocol := self.entity_description.protocol_listener) is not None:
|
super().__init__(unique_id, coordinator, description.protocol_listener)
|
||||||
self.api.add_listener(protocol, self._update_from_listener, self.api.cache)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime.datetime:
|
def native_value(self) -> StateType | datetime.datetime:
|
||||||
|
@ -92,14 +92,16 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a vacuum."""
|
"""Initialize a vacuum."""
|
||||||
StateVacuumEntity.__init__(self)
|
StateVacuumEntity.__init__(self)
|
||||||
RoborockCoordinatedEntity.__init__(self, unique_id, coordinator)
|
RoborockCoordinatedEntity.__init__(
|
||||||
|
self,
|
||||||
|
unique_id,
|
||||||
|
coordinator,
|
||||||
|
listener_request=[
|
||||||
|
RoborockDataProtocol.FAN_POWER,
|
||||||
|
RoborockDataProtocol.STATE,
|
||||||
|
],
|
||||||
|
)
|
||||||
self._attr_fan_speed_list = self._device_status.fan_power_options
|
self._attr_fan_speed_list = self._device_status.fan_power_options
|
||||||
self.api.add_listener(
|
|
||||||
RoborockDataProtocol.FAN_POWER, self._update_from_listener, self.api.cache
|
|
||||||
)
|
|
||||||
self.api.add_listener(
|
|
||||||
RoborockDataProtocol.STATE, self._update_from_listener, self.api.cache
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
"samsungctl[websocket]==0.7.1",
|
"samsungctl[websocket]==0.7.1",
|
||||||
"samsungtvws[async,encrypted]==2.6.0",
|
"samsungtvws[async,encrypted]==2.6.0",
|
||||||
"wakeonlan==2.1.0",
|
"wakeonlan==2.1.0",
|
||||||
"async-upnp-client==0.38.1"
|
"async-upnp-client==0.38.2"
|
||||||
],
|
],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -173,9 +173,9 @@ async def async_setup_entry(
|
|||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_ENABLE_CLIMATE_REACT,
|
SERVICE_ENABLE_CLIMATE_REACT,
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): float,
|
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): vol.Coerce(float),
|
||||||
vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict,
|
vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict,
|
||||||
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): float,
|
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): vol.Coerce(float),
|
||||||
vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict,
|
vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict,
|
||||||
vol.Required(ATTR_SMART_TYPE): vol.In(
|
vol.Required(ATTR_SMART_TYPE): vol.In(
|
||||||
["temperature", "feelsLike", "humidity"]
|
["temperature", "feelsLike", "humidity"]
|
||||||
|
@ -117,7 +117,7 @@
|
|||||||
"speed": {
|
"speed": {
|
||||||
"default": "mdi:speedometer"
|
"default": "mdi:speedometer"
|
||||||
},
|
},
|
||||||
"sulfur_dioxide": {
|
"sulphur_dioxide": {
|
||||||
"default": "mdi:molecule"
|
"default": "mdi:molecule"
|
||||||
},
|
},
|
||||||
"temperature": {
|
"temperature": {
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["async_upnp_client"],
|
"loggers": ["async_upnp_client"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["async-upnp-client==0.38.1"]
|
"requirements": ["async-upnp-client==0.38.2"]
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ class StarlineSensor(StarlineEntity, SensorEntity):
|
|||||||
if self._key == "mileage" and self._device.mileage:
|
if self._key == "mileage" and self._device.mileage:
|
||||||
return self._device.mileage.get("val")
|
return self._device.mileage.get("val")
|
||||||
if self._key == "gps_count" and self._device.position:
|
if self._key == "gps_count" and self._device.position:
|
||||||
return self._device.position["sat_qty"]
|
return self._device.position.get("sat_qty")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -39,5 +39,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["switchbot"],
|
"loggers": ["switchbot"],
|
||||||
"requirements": ["PySwitchbot==0.44.0"]
|
"requirements": ["PySwitchbot==0.45.0"]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""DataUpdateCoordinators for the System monitor integration."""
|
"""DataUpdateCoordinators for the System monitor integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
@ -43,7 +44,8 @@ dataT = TypeVar(
|
|||||||
| sswap
|
| sswap
|
||||||
| VirtualMemory
|
| VirtualMemory
|
||||||
| tuple[float, float, float]
|
| tuple[float, float, float]
|
||||||
| sdiskusage,
|
| sdiskusage
|
||||||
|
| None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -130,12 +132,15 @@ class SystemMonitorLoadCoordinator(MonitorCoordinator[tuple[float, float, float]
|
|||||||
return os.getloadavg()
|
return os.getloadavg()
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorProcessorCoordinator(MonitorCoordinator[float]):
|
class SystemMonitorProcessorCoordinator(MonitorCoordinator[float | None]):
|
||||||
"""A System monitor Processor Data Update Coordinator."""
|
"""A System monitor Processor Data Update Coordinator."""
|
||||||
|
|
||||||
def update_data(self) -> float:
|
def update_data(self) -> float | None:
|
||||||
"""Fetch data."""
|
"""Fetch data."""
|
||||||
return psutil.cpu_percent(interval=None)
|
cpu_percent = psutil.cpu_percent(interval=None)
|
||||||
|
if cpu_percent > 0.0:
|
||||||
|
return cpu_percent
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorBootTimeCoordinator(MonitorCoordinator[datetime]):
|
class SystemMonitorBootTimeCoordinator(MonitorCoordinator[datetime]):
|
||||||
|
@ -344,7 +344,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
|||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data),
|
value_fn=lambda entity: (
|
||||||
|
round(entity.coordinator.data) if entity.coordinator.data else None
|
||||||
|
),
|
||||||
),
|
),
|
||||||
"processor_temperature": SysMonitorSensorEntityDescription[
|
"processor_temperature": SysMonitorSensorEntityDescription[
|
||||||
dict[str, list[shwtemp]]
|
dict[str, list[shwtemp]]
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/technove",
|
"documentation": "https://www.home-assistant.io/integrations/technove",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["python-technove==1.2.1"],
|
"requirements": ["python-technove==1.2.2"],
|
||||||
"zeroconf": ["_technove-stations._tcp.local."]
|
"zeroconf": ["_technove-stations._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"unplugged": "Unplugged",
|
"unplugged": "Unplugged",
|
||||||
"plugged_waiting": "Plugged, waiting",
|
"plugged_waiting": "Plugged, waiting",
|
||||||
"plugged_charging": "Plugged, charging"
|
"plugged_charging": "Plugged, charging",
|
||||||
|
"out_of_activation_period": "Out of activation period",
|
||||||
|
"high_charge_period": "High charge period"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
||||||
"""Handle discovery via dhcp."""
|
"""Handle discovery via dhcp."""
|
||||||
return await self._async_handle_discovery(
|
return await self._async_handle_discovery(
|
||||||
discovery_info.ip, discovery_info.macaddress
|
discovery_info.ip, dr.format_mac(discovery_info.macaddress)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_integration_discovery(
|
async def async_step_integration_discovery(
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiounifi"],
|
"loggers": ["aiounifi"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aiounifi==70"],
|
"requirements": ["aiounifi==71"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["async_upnp_client"],
|
"loggers": ["async_upnp_client"],
|
||||||
"requirements": ["async-upnp-client==0.38.1", "getmac==0.9.4"],
|
"requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
"st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aio_geojson_usgs_earthquakes"],
|
"loggers": ["aio_geojson_usgs_earthquakes"],
|
||||||
"requirements": ["aio-geojson-usgs-earthquakes==0.2"]
|
"requirements": ["aio-geojson-usgs-earthquakes==0.3"]
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["async_upnp_client", "yeelight"],
|
"loggers": ["async_upnp_client", "yeelight"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.1"],
|
"requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.2"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_miio._udp.local.",
|
"type": "_miio._udp.local.",
|
||||||
|
@ -135,7 +135,7 @@ def async_active_zone(
|
|||||||
is None
|
is None
|
||||||
# Skip zone that are outside the radius aka the
|
# Skip zone that are outside the radius aka the
|
||||||
# lat/long is outside the zone
|
# lat/long is outside the zone
|
||||||
or not (zone_dist - (radius := zone_attrs[ATTR_RADIUS]) < radius)
|
or not (zone_dist - (zone_radius := zone_attrs[ATTR_RADIUS]) < radius)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ def async_active_zone(
|
|||||||
zone_dist < min_dist
|
zone_dist < min_dist
|
||||||
or (
|
or (
|
||||||
# If same distance, prefer smaller zone
|
# If same distance, prefer smaller zone
|
||||||
zone_dist == min_dist and radius < closest.attributes[ATTR_RADIUS]
|
zone_dist == min_dist and zone_radius < closest.attributes[ATTR_RADIUS]
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
@ -16,7 +16,7 @@ from .helpers.deprecation import (
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 2
|
MINOR_VERSION: Final = 2
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -6,7 +6,7 @@ aiohttp-zlib-ng==0.3.1
|
|||||||
aiohttp==3.9.3
|
aiohttp==3.9.3
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
astral==2.2
|
astral==2.2
|
||||||
async-upnp-client==0.38.1
|
async-upnp-client==0.38.2
|
||||||
atomicwrites-homeassistant==1.4.1
|
atomicwrites-homeassistant==1.4.1
|
||||||
attrs==23.2.0
|
attrs==23.2.0
|
||||||
awesomeversion==24.2.0
|
awesomeversion==24.2.0
|
||||||
@ -36,7 +36,7 @@ janus==1.0.0
|
|||||||
Jinja2==3.1.3
|
Jinja2==3.1.3
|
||||||
lru-dict==1.3.0
|
lru-dict==1.3.0
|
||||||
mutagen==1.47.0
|
mutagen==1.47.0
|
||||||
orjson==3.9.13
|
orjson==3.9.14
|
||||||
packaging>=23.1
|
packaging>=23.1
|
||||||
paho-mqtt==1.6.1
|
paho-mqtt==1.6.1
|
||||||
Pillow==10.2.0
|
Pillow==10.2.0
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,5 +4,4 @@ ARG \
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
raspberrypi-userland \
|
raspberrypi-utils
|
||||||
raspberrypi-userland-libs
|
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.2.1"
|
version = "2024.2.2"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -46,7 +46,7 @@ dependencies = [
|
|||||||
"cryptography==42.0.2",
|
"cryptography==42.0.2",
|
||||||
# pyOpenSSL 23.2.0 is required to work with cryptography 41+
|
# pyOpenSSL 23.2.0 is required to work with cryptography 41+
|
||||||
"pyOpenSSL==24.0.0",
|
"pyOpenSSL==24.0.0",
|
||||||
"orjson==3.9.13",
|
"orjson==3.9.14",
|
||||||
"packaging>=23.1",
|
"packaging>=23.1",
|
||||||
"pip>=21.3.1",
|
"pip>=21.3.1",
|
||||||
"python-slugify==8.0.1",
|
"python-slugify==8.0.1",
|
||||||
|
@ -22,7 +22,7 @@ lru-dict==1.3.0
|
|||||||
PyJWT==2.8.0
|
PyJWT==2.8.0
|
||||||
cryptography==42.0.2
|
cryptography==42.0.2
|
||||||
pyOpenSSL==24.0.0
|
pyOpenSSL==24.0.0
|
||||||
orjson==3.9.13
|
orjson==3.9.14
|
||||||
packaging>=23.1
|
packaging>=23.1
|
||||||
pip>=21.3.1
|
pip>=21.3.1
|
||||||
python-slugify==8.0.1
|
python-slugify==8.0.1
|
||||||
|
@ -96,7 +96,7 @@ PyRMVtransport==0.3.3
|
|||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.44.0
|
PySwitchbot==0.45.0
|
||||||
|
|
||||||
# homeassistant.components.switchmate
|
# homeassistant.components.switchmate
|
||||||
PySwitchmate==0.5.1
|
PySwitchmate==0.5.1
|
||||||
@ -179,7 +179,7 @@ aio-geojson-geonetnz-volcano==0.9
|
|||||||
aio-geojson-nsw-rfs-incidents==0.7
|
aio-geojson-nsw-rfs-incidents==0.7
|
||||||
|
|
||||||
# homeassistant.components.usgs_earthquakes_feed
|
# homeassistant.components.usgs_earthquakes_feed
|
||||||
aio-geojson-usgs-earthquakes==0.2
|
aio-geojson-usgs-earthquakes==0.3
|
||||||
|
|
||||||
# homeassistant.components.gdacs
|
# homeassistant.components.gdacs
|
||||||
aio-georss-gdacs==0.9
|
aio-georss-gdacs==0.9
|
||||||
@ -318,7 +318,7 @@ aiooncue==0.3.5
|
|||||||
aioopenexchangerates==0.4.0
|
aioopenexchangerates==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.pegel_online
|
# homeassistant.components.pegel_online
|
||||||
aiopegelonline==0.0.6
|
aiopegelonline==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.acmeda
|
# homeassistant.components.acmeda
|
||||||
aiopulse==0.4.4
|
aiopulse==0.4.4
|
||||||
@ -383,7 +383,7 @@ aiotankerkoenig==0.3.0
|
|||||||
aiotractive==0.5.6
|
aiotractive==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==70
|
aiounifi==71
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
@ -478,7 +478,7 @@ asterisk_mbox==0.5.0
|
|||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
# homeassistant.components.upnp
|
# homeassistant.components.upnp
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
async-upnp-client==0.38.1
|
async-upnp-client==0.38.2
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
# homeassistant.components.keyboard_remote
|
||||||
asyncinotify==4.0.2
|
asyncinotify==4.0.2
|
||||||
@ -671,6 +671,9 @@ crownstone-uart==2.1.0
|
|||||||
# homeassistant.components.datadog
|
# homeassistant.components.datadog
|
||||||
datadog==0.15.0
|
datadog==0.15.0
|
||||||
|
|
||||||
|
# homeassistant.components.metoffice
|
||||||
|
datapoint==0.9.9
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
dbus-fast==2.21.1
|
dbus-fast==2.21.1
|
||||||
|
|
||||||
@ -684,7 +687,7 @@ debugpy==1.8.0
|
|||||||
# decora==0.6
|
# decora==0.6
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==5.1.1
|
deebot-client==5.2.1
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -818,7 +821,7 @@ eufylife-ble-client==0.1.8
|
|||||||
# evdev==1.6.1
|
# evdev==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
evohome-async==0.4.18
|
evohome-async==0.4.19
|
||||||
|
|
||||||
# homeassistant.components.faa_delays
|
# homeassistant.components.faa_delays
|
||||||
faadelays==2023.9.1
|
faadelays==2023.9.1
|
||||||
@ -1220,7 +1223,7 @@ lightwave==0.24
|
|||||||
limitlessled==1.1.3
|
limitlessled==1.1.3
|
||||||
|
|
||||||
# homeassistant.components.linear_garage_door
|
# homeassistant.components.linear_garage_door
|
||||||
linear-garage-door==0.2.7
|
linear-garage-door==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.linode
|
# homeassistant.components.linode
|
||||||
linode-api==4.1.9b1
|
linode-api==4.1.9b1
|
||||||
@ -1609,7 +1612,7 @@ py-nightscout==1.2.2
|
|||||||
py-schluter==0.1.7
|
py-schluter==0.1.7
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
py-sucks==0.9.8
|
py-sucks==0.9.9
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==2.1.4
|
py-synologydsm-api==2.1.4
|
||||||
@ -1928,7 +1931,7 @@ pylitterbot==2023.4.9
|
|||||||
pylutron-caseta==0.19.0
|
pylutron-caseta==0.19.0
|
||||||
|
|
||||||
# homeassistant.components.lutron
|
# homeassistant.components.lutron
|
||||||
pylutron==0.2.8
|
pylutron==0.2.12
|
||||||
|
|
||||||
# homeassistant.components.mailgun
|
# homeassistant.components.mailgun
|
||||||
pymailgunner==1.4
|
pymailgunner==1.4
|
||||||
@ -2284,7 +2287,7 @@ python-songpal==0.16.1
|
|||||||
python-tado==0.17.4
|
python-tado==0.17.4
|
||||||
|
|
||||||
# homeassistant.components.technove
|
# homeassistant.components.technove
|
||||||
python-technove==1.2.1
|
python-technove==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
@ -2859,7 +2862,7 @@ xiaomi-ble==0.23.1
|
|||||||
xknx==2.12.0
|
xknx==2.12.0
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknxproject==3.5.0
|
xknxproject==3.6.0
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
# homeassistant.components.fritz
|
# homeassistant.components.fritz
|
||||||
@ -2880,7 +2883,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
yalexs-ble==2.4.1
|
yalexs-ble==2.4.1
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.10.0
|
yalexs==1.11.2
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
@ -84,7 +84,7 @@ PyRMVtransport==0.3.3
|
|||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.44.0
|
PySwitchbot==0.45.0
|
||||||
|
|
||||||
# homeassistant.components.syncthru
|
# homeassistant.components.syncthru
|
||||||
PySyncThru==0.7.10
|
PySyncThru==0.7.10
|
||||||
@ -158,7 +158,7 @@ aio-geojson-geonetnz-volcano==0.9
|
|||||||
aio-geojson-nsw-rfs-incidents==0.7
|
aio-geojson-nsw-rfs-incidents==0.7
|
||||||
|
|
||||||
# homeassistant.components.usgs_earthquakes_feed
|
# homeassistant.components.usgs_earthquakes_feed
|
||||||
aio-geojson-usgs-earthquakes==0.2
|
aio-geojson-usgs-earthquakes==0.3
|
||||||
|
|
||||||
# homeassistant.components.gdacs
|
# homeassistant.components.gdacs
|
||||||
aio-georss-gdacs==0.9
|
aio-georss-gdacs==0.9
|
||||||
@ -291,7 +291,7 @@ aiooncue==0.3.5
|
|||||||
aioopenexchangerates==0.4.0
|
aioopenexchangerates==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.pegel_online
|
# homeassistant.components.pegel_online
|
||||||
aiopegelonline==0.0.6
|
aiopegelonline==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.acmeda
|
# homeassistant.components.acmeda
|
||||||
aiopulse==0.4.4
|
aiopulse==0.4.4
|
||||||
@ -356,7 +356,7 @@ aiotankerkoenig==0.3.0
|
|||||||
aiotractive==0.5.6
|
aiotractive==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==70
|
aiounifi==71
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
@ -430,7 +430,7 @@ arcam-fmj==1.4.0
|
|||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
# homeassistant.components.upnp
|
# homeassistant.components.upnp
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
async-upnp-client==0.38.1
|
async-upnp-client==0.38.2
|
||||||
|
|
||||||
# homeassistant.components.sleepiq
|
# homeassistant.components.sleepiq
|
||||||
asyncsleepiq==1.5.2
|
asyncsleepiq==1.5.2
|
||||||
@ -552,6 +552,9 @@ crownstone-uart==2.1.0
|
|||||||
# homeassistant.components.datadog
|
# homeassistant.components.datadog
|
||||||
datadog==0.15.0
|
datadog==0.15.0
|
||||||
|
|
||||||
|
# homeassistant.components.metoffice
|
||||||
|
datapoint==0.9.9
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
dbus-fast==2.21.1
|
dbus-fast==2.21.1
|
||||||
|
|
||||||
@ -559,7 +562,7 @@ dbus-fast==2.21.1
|
|||||||
debugpy==1.8.0
|
debugpy==1.8.0
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==5.1.1
|
deebot-client==5.2.1
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -971,7 +974,7 @@ librouteros==3.2.0
|
|||||||
libsoundtouch==0.8
|
libsoundtouch==0.8
|
||||||
|
|
||||||
# homeassistant.components.linear_garage_door
|
# homeassistant.components.linear_garage_door
|
||||||
linear-garage-door==0.2.7
|
linear-garage-door==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
lmcloud==0.4.35
|
lmcloud==0.4.35
|
||||||
@ -1259,7 +1262,7 @@ py-nextbusnext==1.0.2
|
|||||||
py-nightscout==1.2.2
|
py-nightscout==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
py-sucks==0.9.8
|
py-sucks==0.9.9
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==2.1.4
|
py-synologydsm-api==2.1.4
|
||||||
@ -1485,7 +1488,7 @@ pylitterbot==2023.4.9
|
|||||||
pylutron-caseta==0.19.0
|
pylutron-caseta==0.19.0
|
||||||
|
|
||||||
# homeassistant.components.lutron
|
# homeassistant.components.lutron
|
||||||
pylutron==0.2.8
|
pylutron==0.2.12
|
||||||
|
|
||||||
# homeassistant.components.mailgun
|
# homeassistant.components.mailgun
|
||||||
pymailgunner==1.4
|
pymailgunner==1.4
|
||||||
@ -1751,7 +1754,7 @@ python-songpal==0.16.1
|
|||||||
python-tado==0.17.4
|
python-tado==0.17.4
|
||||||
|
|
||||||
# homeassistant.components.technove
|
# homeassistant.components.technove
|
||||||
python-technove==1.2.1
|
python-technove==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
@ -2188,7 +2191,7 @@ xiaomi-ble==0.23.1
|
|||||||
xknx==2.12.0
|
xknx==2.12.0
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknxproject==3.5.0
|
xknxproject==3.6.0
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
# homeassistant.components.fritz
|
# homeassistant.components.fritz
|
||||||
@ -2206,7 +2209,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
yalexs-ble==2.4.1
|
yalexs-ble==2.4.1
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.10.0
|
yalexs==1.11.2
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
@ -5,6 +5,7 @@ from aioelectricitymaps import (
|
|||||||
ElectricityMapsConnectionError,
|
ElectricityMapsConnectionError,
|
||||||
ElectricityMapsError,
|
ElectricityMapsError,
|
||||||
ElectricityMapsInvalidTokenError,
|
ElectricityMapsInvalidTokenError,
|
||||||
|
ElectricityMapsNoDataError,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -139,12 +140,9 @@ async def test_form_country(hass: HomeAssistant) -> None:
|
|||||||
),
|
),
|
||||||
(ElectricityMapsError("Something else"), "unknown"),
|
(ElectricityMapsError("Something else"), "unknown"),
|
||||||
(ElectricityMapsConnectionError("Boom"), "unknown"),
|
(ElectricityMapsConnectionError("Boom"), "unknown"),
|
||||||
|
(ElectricityMapsNoDataError("I have no data"), "no_data"),
|
||||||
],
|
],
|
||||||
ids=[
|
ids=["invalid auth", "generic error", "json decode error", "no data error"],
|
||||||
"invalid auth",
|
|
||||||
"generic error",
|
|
||||||
"json decode error",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
async def test_form_error_handling(
|
async def test_form_error_handling(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -3,7 +3,7 @@ from collections.abc import Generator
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from deebot_client.const import PATH_API_APPSVR_APP
|
from deebot_client import const
|
||||||
from deebot_client.device import Device
|
from deebot_client.device import Device
|
||||||
from deebot_client.exceptions import ApiError
|
from deebot_client.exceptions import ApiError
|
||||||
from deebot_client.models import Credentials
|
from deebot_client.models import Credentials
|
||||||
@ -75,9 +75,13 @@ def mock_authenticator(device_fixture: str) -> Generator[Mock, None, None]:
|
|||||||
query_params: dict[str, Any] | None = None,
|
query_params: dict[str, Any] | None = None,
|
||||||
headers: dict[str, Any] | None = None,
|
headers: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
if path == PATH_API_APPSVR_APP:
|
match path:
|
||||||
return {"code": 0, "devices": devices, "errno": "0"}
|
case const.PATH_API_APPSVR_APP:
|
||||||
raise ApiError("Path not mocked: {path}")
|
return {"code": 0, "devices": devices, "errno": "0"}
|
||||||
|
case const.PATH_API_USERS_USER:
|
||||||
|
return {"todo": "result", "result": "ok", "devices": devices}
|
||||||
|
case _:
|
||||||
|
raise ApiError("Path not mocked: {path}")
|
||||||
|
|
||||||
authenticator.post_authenticated.side_effect = post_authenticated
|
authenticator.post_authenticated.side_effect = post_authenticated
|
||||||
yield authenticator
|
yield authenticator
|
||||||
|
@ -112,3 +112,14 @@ def mock_router_bridge_mode(mock_device_registry_devices, router):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_router_bridge_mode_error(mock_device_registry_devices, router):
|
||||||
|
"""Mock a failed connection to Freebox Bridge mode."""
|
||||||
|
|
||||||
|
router().lan.get_hosts_list = AsyncMock(
|
||||||
|
side_effect=HttpRequestError("Request failed (APIResponse: some unknown error)")
|
||||||
|
)
|
||||||
|
|
||||||
|
return router
|
||||||
|
@ -69,8 +69,8 @@ async def test_zeroconf(hass: HomeAssistant) -> None:
|
|||||||
assert result["step_id"] == "link"
|
assert result["step_id"] == "link"
|
||||||
|
|
||||||
|
|
||||||
async def test_link(hass: HomeAssistant, router: Mock) -> None:
|
async def internal_test_link(hass: HomeAssistant) -> None:
|
||||||
"""Test linking."""
|
"""Test linking internal, common to both router modes."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.freebox.async_setup_entry",
|
"homeassistant.components.freebox.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
@ -91,6 +91,30 @@ async def test_link(hass: HomeAssistant, router: Mock) -> None:
|
|||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_link(hass: HomeAssistant, router: Mock) -> None:
|
||||||
|
"""Test link with standard router mode."""
|
||||||
|
await internal_test_link(hass)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_link_bridge_mode(hass: HomeAssistant, router_bridge_mode: Mock) -> None:
|
||||||
|
"""Test linking for a freebox in bridge mode."""
|
||||||
|
await internal_test_link(hass)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_link_bridge_mode_error(
|
||||||
|
hass: HomeAssistant, mock_router_bridge_mode_error: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test linking for a freebox in bridge mode, unknown error received from API."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
async def test_abort_if_already_setup(hass: HomeAssistant) -> None:
|
async def test_abort_if_already_setup(hass: HomeAssistant) -> None:
|
||||||
"""Test we abort if component is already setup."""
|
"""Test we abort if component is already setup."""
|
||||||
MockConfigEntry(
|
MockConfigEntry(
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
"""Tests for the Freebox utility methods."""
|
"""Tests for the Freebox utility methods."""
|
||||||
import json
|
import json
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from homeassistant.components.freebox.router import is_json
|
from freebox_api.exceptions import HttpRequestError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.freebox.router import get_hosts_list_if_supported, is_json
|
||||||
|
|
||||||
from .const import DATA_LAN_GET_HOSTS_LIST_MODE_BRIDGE, DATA_WIFI_GET_GLOBAL_CONFIG
|
from .const import DATA_LAN_GET_HOSTS_LIST_MODE_BRIDGE, DATA_WIFI_GET_GLOBAL_CONFIG
|
||||||
|
|
||||||
@ -20,3 +24,33 @@ async def test_is_json() -> None:
|
|||||||
assert not is_json("")
|
assert not is_json("")
|
||||||
assert not is_json("XXX")
|
assert not is_json("XXX")
|
||||||
assert not is_json("{XXX}")
|
assert not is_json("{XXX}")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_hosts_list_if_supported(
|
||||||
|
router: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""In router mode, get_hosts_list is supported and list is filled."""
|
||||||
|
supports_hosts, fbx_devices = await get_hosts_list_if_supported(router())
|
||||||
|
assert supports_hosts is True
|
||||||
|
# List must not be empty; but it's content depends on how many unit tests are executed...
|
||||||
|
assert fbx_devices
|
||||||
|
assert "d633d0c8-958c-43cc-e807-d881b076924b" in str(fbx_devices)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_hosts_list_if_supported_bridge(
|
||||||
|
router_bridge_mode: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""In bridge mode, get_hosts_list is NOT supported and list is empty."""
|
||||||
|
supports_hosts, fbx_devices = await get_hosts_list_if_supported(
|
||||||
|
router_bridge_mode()
|
||||||
|
)
|
||||||
|
assert supports_hosts is False
|
||||||
|
assert fbx_devices == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_hosts_list_if_supported_bridge_error(
|
||||||
|
mock_router_bridge_mode_error: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Other exceptions must be propagated."""
|
||||||
|
with pytest.raises(HttpRequestError):
|
||||||
|
await get_hosts_list_if_supported(mock_router_bridge_mode_error())
|
||||||
|
@ -65,54 +65,58 @@ async def test_form(hass: HomeAssistant) -> None:
|
|||||||
async def test_reauth(hass: HomeAssistant) -> None:
|
async def test_reauth(hass: HomeAssistant) -> None:
|
||||||
"""Test reauthentication."""
|
"""Test reauthentication."""
|
||||||
|
|
||||||
entry = await async_init_integration(hass)
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN,
|
|
||||||
context={
|
|
||||||
"source": config_entries.SOURCE_REAUTH,
|
|
||||||
"entry_id": entry.entry_id,
|
|
||||||
"title_placeholders": {"name": entry.title},
|
|
||||||
"unique_id": entry.unique_id,
|
|
||||||
},
|
|
||||||
data=entry.data,
|
|
||||||
)
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.linear_garage_door.config_flow.Linear.login",
|
"homeassistant.components.linear_garage_door.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch(
|
|
||||||
"homeassistant.components.linear_garage_door.config_flow.Linear.get_sites",
|
|
||||||
return_value=[{"id": "test-site-id", "name": "test-site-name"}],
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.linear_garage_door.config_flow.Linear.close",
|
|
||||||
return_value=None,
|
|
||||||
), patch(
|
|
||||||
"uuid.uuid4",
|
|
||||||
return_value="test-uuid",
|
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
entry = await async_init_integration(hass)
|
||||||
result["flow_id"],
|
|
||||||
{
|
result1 = await hass.config_entries.flow.async_init(
|
||||||
"email": "new-email",
|
DOMAIN,
|
||||||
"password": "new-password",
|
context={
|
||||||
|
"source": config_entries.SOURCE_REAUTH,
|
||||||
|
"entry_id": entry.entry_id,
|
||||||
|
"title_placeholders": {"name": entry.title},
|
||||||
|
"unique_id": entry.unique_id,
|
||||||
},
|
},
|
||||||
|
data=entry.data,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
assert result1["type"] == FlowResultType.FORM
|
||||||
|
assert result1["step_id"] == "user"
|
||||||
|
|
||||||
assert result2["type"] == FlowResultType.ABORT
|
with patch(
|
||||||
assert result2["reason"] == "reauth_successful"
|
"homeassistant.components.linear_garage_door.config_flow.Linear.login",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.linear_garage_door.config_flow.Linear.get_sites",
|
||||||
|
return_value=[{"id": "test-site-id", "name": "test-site-name"}],
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.linear_garage_door.config_flow.Linear.close",
|
||||||
|
return_value=None,
|
||||||
|
), patch(
|
||||||
|
"uuid.uuid4",
|
||||||
|
return_value="test-uuid",
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result1["flow_id"],
|
||||||
|
{
|
||||||
|
"email": "new-email",
|
||||||
|
"password": "new-password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
entries = hass.config_entries.async_entries()
|
assert result2["type"] == FlowResultType.ABORT
|
||||||
assert len(entries) == 1
|
assert result2["reason"] == "reauth_successful"
|
||||||
assert entries[0].data == {
|
|
||||||
"email": "new-email",
|
entries = hass.config_entries.async_entries()
|
||||||
"password": "new-password",
|
assert len(entries) == 1
|
||||||
"site_id": "test-site-id",
|
assert entries[0].data == {
|
||||||
"device_id": "test-uuid",
|
"email": "new-email",
|
||||||
}
|
"password": "new-password",
|
||||||
|
"site_id": "test-site-id",
|
||||||
|
"device_id": "test-uuid",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_form_invalid_login(hass: HomeAssistant) -> None:
|
async def test_form_invalid_login(hass: HomeAssistant) -> None:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from linear_garage_door.errors import InvalidLoginError, ResponseError
|
from linear_garage_door.errors import InvalidLoginError
|
||||||
|
|
||||||
from homeassistant.components.linear_garage_door.const import DOMAIN
|
from homeassistant.components.linear_garage_door.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@ -45,32 +45,6 @@ async def test_invalid_password(
|
|||||||
assert flows[0]["context"]["source"] == "reauth"
|
assert flows[0]["context"]["source"] == "reauth"
|
||||||
|
|
||||||
|
|
||||||
async def test_response_error(hass: HomeAssistant) -> None:
|
|
||||||
"""Test response error."""
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=DOMAIN,
|
|
||||||
data={
|
|
||||||
"email": "test-email",
|
|
||||||
"password": "test-password",
|
|
||||||
"site_id": "test-site-id",
|
|
||||||
"device_id": "test-uuid",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.linear_garage_door.coordinator.Linear.login",
|
|
||||||
side_effect=ResponseError,
|
|
||||||
):
|
|
||||||
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert entries
|
|
||||||
assert len(entries) == 1
|
|
||||||
assert entries[0].state == ConfigEntryState.SETUP_RETRY
|
|
||||||
|
|
||||||
|
|
||||||
async def test_invalid_login(
|
async def test_invalid_login(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
"""Fixtures for Met Office weather integration tests."""
|
"""Fixtures for Met Office weather integration tests."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from datapoint.exceptions import APIException
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# All tests are marked as disabled, as the integration is disabled in the
|
|
||||||
# integration manifest. `datapoint` isn't compatible with Python 3.12
|
|
||||||
#
|
|
||||||
# from datapoint.exceptions import APIException
|
|
||||||
APIException = Exception
|
|
||||||
collect_ignore_glob = ["test_*.py"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_simple_manager_fail():
|
def mock_simple_manager_fail():
|
||||||
|
@ -1352,7 +1352,7 @@ async def cover_fixture(
|
|||||||
suggested_object_id="position_shade",
|
suggested_object_id="position_shade",
|
||||||
original_name="Position Shade",
|
original_name="Position Shade",
|
||||||
)
|
)
|
||||||
cover_position_attributes = {cover.ATTR_POSITION: 50}
|
cover_position_attributes = {cover.ATTR_CURRENT_POSITION: 50}
|
||||||
set_state_with_entry(hass, cover_position, STATE_OPEN, cover_position_attributes)
|
set_state_with_entry(hass, cover_position, STATE_OPEN, cover_position_attributes)
|
||||||
data["cover_position"] = cover_position
|
data["cover_position"] = cover_position
|
||||||
|
|
||||||
@ -1363,7 +1363,7 @@ async def cover_fixture(
|
|||||||
suggested_object_id="tilt_position_shade",
|
suggested_object_id="tilt_position_shade",
|
||||||
original_name="Tilt Position Shade",
|
original_name="Tilt Position Shade",
|
||||||
)
|
)
|
||||||
cover_tilt_position_attributes = {cover.ATTR_TILT_POSITION: 50}
|
cover_tilt_position_attributes = {cover.ATTR_CURRENT_TILT_POSITION: 50}
|
||||||
set_state_with_entry(
|
set_state_with_entry(
|
||||||
hass, cover_tilt_position, STATE_OPEN, cover_tilt_position_attributes
|
hass, cover_tilt_position, STATE_OPEN, cover_tilt_position_attributes
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,7 @@ async def test_unload_entry(
|
|||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
assert setup_entry.state is ConfigEntryState.LOADED
|
assert setup_entry.state is ConfigEntryState.LOADED
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.roborock.coordinator.RoborockLocalClient.async_disconnect"
|
"homeassistant.components.roborock.coordinator.RoborockLocalClient.async_release"
|
||||||
) as mock_disconnect:
|
) as mock_disconnect:
|
||||||
assert await hass.config_entries.async_unload(setup_entry.entry_id)
|
assert await hass.config_entries.async_unload(setup_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test System Monitor sensor."""
|
"""Test System Monitor sensor."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import socket
|
import socket
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
@ -429,3 +430,37 @@ async def test_exception_handling_disk_sensor(
|
|||||||
assert disk_sensor is not None
|
assert disk_sensor is not None
|
||||||
assert disk_sensor.state == "70.0"
|
assert disk_sensor.state == "70.0"
|
||||||
assert disk_sensor.attributes["unit_of_measurement"] == "%"
|
assert disk_sensor.attributes["unit_of_measurement"] == "%"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cpu_percentage_is_zero_returns_unknown(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor."""
|
||||||
|
cpu_sensor = hass.states.get("sensor.system_monitor_processor_use")
|
||||||
|
assert cpu_sensor is not None
|
||||||
|
assert cpu_sensor.state == "10"
|
||||||
|
|
||||||
|
mock_psutil.cpu_percent.return_value = 0.0
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
cpu_sensor = hass.states.get("sensor.system_monitor_processor_use")
|
||||||
|
assert cpu_sensor is not None
|
||||||
|
assert cpu_sensor.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
mock_psutil.cpu_percent.return_value = 15.0
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
cpu_sensor = hass.states.get("sensor.system_monitor_processor_use")
|
||||||
|
assert cpu_sensor is not None
|
||||||
|
assert cpu_sensor.state == "15"
|
||||||
|
27
tests/components/technove/fixtures/station_bad_status.json
Normal file
27
tests/components/technove/fixtures/station_bad_status.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"voltageIn": 238,
|
||||||
|
"voltageOut": 238,
|
||||||
|
"maxStationCurrent": 32,
|
||||||
|
"maxCurrent": 24,
|
||||||
|
"current": 23.75,
|
||||||
|
"network_ssid": "Connecting...",
|
||||||
|
"id": "AA:AA:AA:AA:AA:BB",
|
||||||
|
"auto_charge": true,
|
||||||
|
"highChargePeriodActive": false,
|
||||||
|
"normalPeriodActive": false,
|
||||||
|
"maxChargePourcentage": 0.9,
|
||||||
|
"isBatteryProtected": false,
|
||||||
|
"inSharingMode": true,
|
||||||
|
"energySession": 12.34,
|
||||||
|
"energyTotal": 1234,
|
||||||
|
"version": "1.82",
|
||||||
|
"rssi": -82,
|
||||||
|
"name": "TechnoVE Station",
|
||||||
|
"lastCharge": "1701072080,0,17.39\n",
|
||||||
|
"time": 1701000000,
|
||||||
|
"isUpToDate": true,
|
||||||
|
"isSessionActive": true,
|
||||||
|
"conflictInSharingConfig": false,
|
||||||
|
"isStaticIp": false,
|
||||||
|
"status": 12345
|
||||||
|
}
|
@ -297,6 +297,8 @@
|
|||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged_waiting',
|
'plugged_waiting',
|
||||||
'plugged_charging',
|
'plugged_charging',
|
||||||
|
'out_of_activation_period',
|
||||||
|
'high_charge_period',
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@ -333,6 +335,8 @@
|
|||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged_waiting',
|
'plugged_waiting',
|
||||||
'plugged_charging',
|
'plugged_charging',
|
||||||
|
'out_of_activation_period',
|
||||||
|
'high_charge_period',
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
|
@ -5,15 +5,20 @@ from unittest.mock import MagicMock
|
|||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
from technove import Status, TechnoVEError
|
from technove import Station, Status, TechnoVEError
|
||||||
|
|
||||||
|
from homeassistant.components.technove.const import DOMAIN
|
||||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from . import setup_with_selected_platforms
|
from . import setup_with_selected_platforms
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import (
|
||||||
|
MockConfigEntry,
|
||||||
|
async_fire_time_changed,
|
||||||
|
load_json_object_fixture,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_technove")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_technove")
|
||||||
@ -93,3 +98,27 @@ async def test_sensor_update_failure(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
|
async def test_sensor_unknown_status(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_technove: MagicMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator update failure."""
|
||||||
|
entity_id = "sensor.technove_station_status"
|
||||||
|
|
||||||
|
assert hass.states.get(entity_id).state == Status.PLUGGED_CHARGING.value
|
||||||
|
|
||||||
|
mock_technove.update.return_value = Station(
|
||||||
|
load_json_object_fixture("station_bad_status.json", DOMAIN)
|
||||||
|
)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=5, seconds=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
||||||
|
# Other sensors should still be available
|
||||||
|
assert hass.states.get("sensor.technove_station_total_energy_usage").state == "1234"
|
||||||
|
@ -36,6 +36,7 @@ IP_ADDRESS2 = "127.0.0.2"
|
|||||||
ALIAS = "My Bulb"
|
ALIAS = "My Bulb"
|
||||||
MODEL = "HS100"
|
MODEL = "HS100"
|
||||||
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
|
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
|
||||||
|
DHCP_FORMATTED_MAC_ADDRESS = MAC_ADDRESS.replace(":", "")
|
||||||
MAC_ADDRESS2 = "11:22:33:44:55:66"
|
MAC_ADDRESS2 = "11:22:33:44:55:66"
|
||||||
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
|
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
|
||||||
CREDENTIALS_HASH_LEGACY = ""
|
CREDENTIALS_HASH_LEGACY = ""
|
||||||
|
@ -33,6 +33,7 @@ from . import (
|
|||||||
DEFAULT_ENTRY_TITLE,
|
DEFAULT_ENTRY_TITLE,
|
||||||
DEVICE_CONFIG_DICT_AUTH,
|
DEVICE_CONFIG_DICT_AUTH,
|
||||||
DEVICE_CONFIG_DICT_LEGACY,
|
DEVICE_CONFIG_DICT_LEGACY,
|
||||||
|
DHCP_FORMATTED_MAC_ADDRESS,
|
||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
MAC_ADDRESS,
|
MAC_ADDRESS,
|
||||||
MAC_ADDRESS2,
|
MAC_ADDRESS2,
|
||||||
@ -144,6 +145,7 @@ async def test_discovery_auth(
|
|||||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result2["title"] == DEFAULT_ENTRY_TITLE
|
assert result2["title"] == DEFAULT_ENTRY_TITLE
|
||||||
assert result2["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result2["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -206,6 +208,7 @@ async def test_discovery_auth_errors(
|
|||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_new_credentials(
|
async def test_discovery_new_credentials(
|
||||||
@ -254,6 +257,7 @@ async def test_discovery_new_credentials(
|
|||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_new_credentials_invalid(
|
async def test_discovery_new_credentials_invalid(
|
||||||
@ -309,6 +313,7 @@ async def test_discovery_new_credentials_invalid(
|
|||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None:
|
async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None:
|
||||||
@ -365,6 +370,7 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No
|
|||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
|
assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
|
||||||
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_setup_entry.assert_called_once()
|
mock_setup_entry.assert_called_once()
|
||||||
@ -432,6 +438,7 @@ async def test_manual(hass: HomeAssistant) -> None:
|
|||||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result4["title"] == DEFAULT_ENTRY_TITLE
|
assert result4["title"] == DEFAULT_ENTRY_TITLE
|
||||||
assert result4["data"] == CREATE_ENTRY_DATA_LEGACY
|
assert result4["data"] == CREATE_ENTRY_DATA_LEGACY
|
||||||
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
# Duplicate
|
# Duplicate
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -470,6 +477,7 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["data"] == CREATE_ENTRY_DATA_LEGACY
|
assert result["data"] == CREATE_ENTRY_DATA_LEGACY
|
||||||
|
assert result["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_manual_auth(
|
async def test_manual_auth(
|
||||||
@ -510,6 +518,7 @@ async def test_manual_auth(
|
|||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result3["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -572,6 +581,7 @@ async def test_manual_auth_errors(
|
|||||||
)
|
)
|
||||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result4["data"] == CREATE_ENTRY_DATA_AUTH
|
assert result4["data"] == CREATE_ENTRY_DATA_AUTH
|
||||||
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -599,7 +609,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_DHCP},
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
data=dhcp.DhcpServiceInfo(
|
data=dhcp.DhcpServiceInfo(
|
||||||
ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -611,7 +621,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_DHCP},
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
data=dhcp.DhcpServiceInfo(
|
data=dhcp.DhcpServiceInfo(
|
||||||
ip=IP_ADDRESS, macaddress="00:00:00:00:00:00", hostname="mock_hostname"
|
ip=IP_ADDRESS, macaddress="000000000000", hostname="mock_hostname"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -625,7 +635,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_DHCP},
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
data=dhcp.DhcpServiceInfo(
|
data=dhcp.DhcpServiceInfo(
|
||||||
ip="1.2.3.5", macaddress="00:00:00:00:00:01", hostname="mock_hostname"
|
ip="1.2.3.5", macaddress="000000000001", hostname="mock_hostname"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -638,7 +648,9 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
config_entries.SOURCE_DHCP,
|
config_entries.SOURCE_DHCP,
|
||||||
dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS),
|
dhcp.DhcpServiceInfo(
|
||||||
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
||||||
@ -675,6 +687,8 @@ async def test_discovered_by_dhcp_or_discovery(
|
|||||||
|
|
||||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result2["data"] == CREATE_ENTRY_DATA_LEGACY
|
assert result2["data"] == CREATE_ENTRY_DATA_LEGACY
|
||||||
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
assert mock_async_setup.called
|
assert mock_async_setup.called
|
||||||
assert mock_async_setup_entry.called
|
assert mock_async_setup_entry.called
|
||||||
|
|
||||||
@ -684,7 +698,9 @@ async def test_discovered_by_dhcp_or_discovery(
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
config_entries.SOURCE_DHCP,
|
config_entries.SOURCE_DHCP,
|
||||||
dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS),
|
dhcp.DhcpServiceInfo(
|
||||||
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
||||||
@ -713,7 +729,7 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device(
|
|||||||
assert result["reason"] == "cannot_connect"
|
assert result["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_with_ip_change(
|
async def test_integration_discovery_with_ip_change(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
mock_discovery: AsyncMock,
|
mock_discovery: AsyncMock,
|
||||||
@ -764,6 +780,36 @@ async def test_discovery_with_ip_change(
|
|||||||
mock_connect["connect"].assert_awaited_once_with(config=config)
|
mock_connect["connect"].assert_awaited_once_with(config=config)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dhcp_discovery_with_ip_change(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_discovery: AsyncMock,
|
||||||
|
mock_connect: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test dhcp discovery with an IP change."""
|
||||||
|
mock_connect["connect"].side_effect = SmartDeviceException()
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
flows = hass.config_entries.flow.async_progress()
|
||||||
|
assert len(flows) == 0
|
||||||
|
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
|
||||||
|
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1"
|
||||||
|
|
||||||
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
|
data=dhcp.DhcpServiceInfo(
|
||||||
|
ip="127.0.0.2", macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
|
assert discovery_result["reason"] == "already_configured"
|
||||||
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth(
|
async def test_reauth(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_added_config_entry: MockConfigEntry,
|
mock_added_config_entry: MockConfigEntry,
|
||||||
@ -1022,6 +1068,7 @@ async def test_pick_device_errors(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result4["type"] == FlowResultType.CREATE_ENTRY
|
assert result4["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_timeout_connect(
|
async def test_discovery_timeout_connect(
|
||||||
@ -1046,6 +1093,7 @@ async def test_discovery_timeout_connect(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
||||||
assert mock_connect["connect"].call_count == 1
|
assert mock_connect["connect"].call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,6 +228,46 @@ async def test_in_zone_works_for_passive_zones(hass: HomeAssistant) -> None:
|
|||||||
assert zone.in_zone(hass.states.get("zone.passive_zone"), latitude, longitude)
|
assert zone.in_zone(hass.states.get("zone.passive_zone"), latitude, longitude)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_active_zone_with_non_zero_radius(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_active_zone with a non-zero radius."""
|
||||||
|
latitude = 32.880600
|
||||||
|
longitude = -117.237561
|
||||||
|
|
||||||
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
zone.DOMAIN,
|
||||||
|
{
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"name": "Small Zone",
|
||||||
|
"latitude": 32.980600,
|
||||||
|
"longitude": -117.137561,
|
||||||
|
"radius": 50000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Big Zone",
|
||||||
|
"latitude": 32.980600,
|
||||||
|
"longitude": -117.137561,
|
||||||
|
"radius": 100000,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
home_state = hass.states.get("zone.home")
|
||||||
|
assert home_state.attributes["radius"] == 100
|
||||||
|
assert home_state.attributes["latitude"] == 32.87336
|
||||||
|
assert home_state.attributes["longitude"] == -117.22743
|
||||||
|
|
||||||
|
active = zone.async_active_zone(hass, latitude, longitude, 5000)
|
||||||
|
assert active.entity_id == "zone.home"
|
||||||
|
|
||||||
|
active = zone.async_active_zone(hass, latitude, longitude, 0)
|
||||||
|
assert active.entity_id == "zone.small_zone"
|
||||||
|
|
||||||
|
|
||||||
async def test_core_config_update(hass: HomeAssistant) -> None:
|
async def test_core_config_update(hass: HomeAssistant) -> None:
|
||||||
"""Test updating core config will update home zone."""
|
"""Test updating core config will update home zone."""
|
||||||
assert await setup.async_setup_component(hass, "zone", {})
|
assert await setup.async_setup_component(hass, "zone", {})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user