mirror of
https://github.com/home-assistant/core.git
synced 2025-08-14 18:11:41 +02:00
Merge branch 'aioesphomeapi_3111' into sub_devices_esphome
This commit is contained in:
@@ -18,7 +18,7 @@ from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import DOMAIN, GATEWAY_SERIAL_PATTERN, PLATFORMS
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
|
||||
type DevoloHomeControlConfigEntry = ConfigEntry[list[HomeControl]]
|
||||
|
||||
@@ -33,10 +33,6 @@ async def async_setup_entry(
|
||||
check_mydevolo_and_get_gateway_ids, mydevolo
|
||||
)
|
||||
|
||||
if entry.unique_id and GATEWAY_SERIAL_PATTERN.match(entry.unique_id):
|
||||
uuid = await hass.async_add_executor_job(mydevolo.uuid)
|
||||
hass.config_entries.async_update_entry(entry, unique_id=uuid)
|
||||
|
||||
def shutdown(event: Event) -> None:
|
||||
for gateway in entry.runtime_data:
|
||||
gateway.websocket_disconnect(
|
||||
|
@@ -1,7 +1,5 @@
|
||||
"""Constants for the devolo_home_control integration."""
|
||||
|
||||
import re
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "devolo_home_control"
|
||||
@@ -14,5 +12,4 @@ PLATFORMS = [
|
||||
Platform.SIREN,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
GATEWAY_SERIAL_PATTERN = re.compile(r"\d{16}")
|
||||
SUPPORTED_MODEL_TYPES = ["2600", "2601"]
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==33.0.0",
|
||||
"aioesphomeapi==33.1.1",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==2.16.0"
|
||||
],
|
||||
|
@@ -45,7 +45,9 @@ class ImgwPibFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
imgwpib = await ImgwPib.create(
|
||||
client_session, hydrological_station_id=station_id
|
||||
client_session,
|
||||
hydrological_station_id=station_id,
|
||||
hydrological_details=False,
|
||||
)
|
||||
hydrological_data = await imgwpib.get_hydrological_data()
|
||||
except (ClientError, TimeoutError, ApiError):
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["imgw_pib==1.0.10"]
|
||||
"requirements": ["imgw_pib==1.1.0"]
|
||||
}
|
||||
|
@@ -1,14 +1,13 @@
|
||||
"""Support for LaMetric time."""
|
||||
|
||||
from homeassistant.components import notify as hass_notify
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, discovery
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
@@ -17,16 +16,16 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the LaMetric integration."""
|
||||
async_setup_services(hass)
|
||||
hass.data[DOMAIN] = {"hass_config": config}
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LaMetricConfigEntry) -> bool:
|
||||
"""Set up LaMetric from a config entry."""
|
||||
coordinator = LaMetricDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Set up notify platform, no entry support for notify component yet,
|
||||
@@ -37,15 +36,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
Platform.NOTIFY,
|
||||
DOMAIN,
|
||||
{CONF_NAME: coordinator.data.name, "entry_id": entry.entry_id},
|
||||
hass.data[DOMAIN]["hass_config"],
|
||||
{},
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: LaMetricConfigEntry) -> bool:
|
||||
"""Unload LaMetric config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
await hass_notify.async_reload(hass, DOMAIN)
|
||||
return unload_ok
|
||||
|
@@ -9,13 +9,11 @@ from typing import Any
|
||||
from demetriek import LaMetricDevice
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
from .helpers import lametric_exception_handler
|
||||
|
||||
@@ -57,11 +55,11 @@ BUTTONS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: LaMetricConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LaMetric button based on a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LaMetricButtonEntity(
|
||||
coordinator=coordinator,
|
||||
|
@@ -13,13 +13,15 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
type LaMetricConfigEntry = ConfigEntry[LaMetricDataUpdateCoordinator]
|
||||
|
||||
|
||||
class LaMetricDataUpdateCoordinator(DataUpdateCoordinator[Device]):
|
||||
"""The LaMetric Data Update Coordinator."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: LaMetricConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
def __init__(self, hass: HomeAssistant, entry: LaMetricConfigEntry) -> None:
|
||||
"""Initialize the LaMatric coordinator."""
|
||||
self.lametric = LaMetricDevice(
|
||||
host=entry.data[CONF_HOST],
|
||||
|
@@ -6,11 +6,9 @@ import json
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry
|
||||
|
||||
TO_REDACT = {
|
||||
"device_id",
|
||||
@@ -21,10 +19,10 @@ TO_REDACT = {
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: LaMetricConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
# Round-trip via JSON to trigger serialization
|
||||
data = json.loads(coordinator.data.to_json())
|
||||
return async_redact_data(data, TO_REDACT)
|
||||
|
@@ -31,4 +31,5 @@ class LaMetricEntity(CoordinatorEntity[LaMetricDataUpdateCoordinator]):
|
||||
name=coordinator.data.name,
|
||||
sw_version=coordinator.data.os_version,
|
||||
serial_number=coordinator.data.serial_number,
|
||||
configuration_url=f"https://{coordinator.data.wifi.ip}/",
|
||||
)
|
||||
|
@@ -12,7 +12,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
|
||||
|
||||
@@ -57,15 +57,9 @@ def async_get_coordinator_by_device_id(
|
||||
if (device_entry := device_registry.async_get(device_id)) is None:
|
||||
raise ValueError(f"Unknown LaMetric device ID: {device_id}")
|
||||
|
||||
for entry_id in device_entry.config_entries:
|
||||
if (
|
||||
(entry := hass.config_entries.async_get_entry(entry_id))
|
||||
and entry.domain == DOMAIN
|
||||
and entry.entry_id in hass.data[DOMAIN]
|
||||
):
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
entry.entry_id
|
||||
]
|
||||
return coordinator
|
||||
entry: LaMetricConfigEntry
|
||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
if entry.entry_id in device_entry.config_entries:
|
||||
return entry.runtime_data
|
||||
|
||||
raise ValueError(f"No coordinator for device ID: {device_id}")
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from demetriek import (
|
||||
AlarmSound,
|
||||
@@ -24,8 +24,8 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from .const import CONF_CYCLES, CONF_ICON_TYPE, CONF_PRIORITY, CONF_SOUND, DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .const import CONF_CYCLES, CONF_ICON_TYPE, CONF_PRIORITY, CONF_SOUND
|
||||
from .coordinator import LaMetricConfigEntry
|
||||
|
||||
|
||||
async def async_get_service(
|
||||
@@ -36,10 +36,12 @@ async def async_get_service(
|
||||
"""Get the LaMetric notification service."""
|
||||
if discovery_info is None:
|
||||
return None
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
entry: LaMetricConfigEntry | None = hass.config_entries.async_get_entry(
|
||||
discovery_info["entry_id"]
|
||||
]
|
||||
return LaMetricNotificationService(coordinator.lametric)
|
||||
)
|
||||
if TYPE_CHECKING:
|
||||
assert entry is not None
|
||||
return LaMetricNotificationService(entry.runtime_data.lametric)
|
||||
|
||||
|
||||
class LaMetricNotificationService(BaseNotificationService):
|
||||
|
@@ -9,13 +9,11 @@ from typing import Any
|
||||
from demetriek import Device, LaMetricDevice, Range
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
from .helpers import lametric_exception_handler
|
||||
|
||||
@@ -57,11 +55,11 @@ NUMBERS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: LaMetricConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LaMetric number based on a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LaMetricNumberEntity(
|
||||
coordinator=coordinator,
|
||||
|
@@ -17,7 +17,7 @@ rules:
|
||||
Entities of this integration does not explicitly subscribe to events.
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: todo
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
@@ -33,6 +33,7 @@ rules:
|
||||
parallel-updates: todo
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
|
@@ -9,13 +9,11 @@ from typing import Any
|
||||
from demetriek import BrightnessMode, Device, LaMetricDevice
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
from .helpers import lametric_exception_handler
|
||||
|
||||
@@ -42,11 +40,11 @@ SELECTS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: LaMetricConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LaMetric select based on a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LaMetricSelectEntity(
|
||||
coordinator=coordinator,
|
||||
|
@@ -12,13 +12,11 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
|
||||
|
||||
@@ -44,11 +42,11 @@ SENSORS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: LaMetricConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LaMetric sensor based on a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LaMetricSensorEntity(
|
||||
coordinator=coordinator,
|
||||
|
@@ -9,13 +9,11 @@ from typing import Any
|
||||
from demetriek import Device, LaMetricDevice
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
from .coordinator import LaMetricConfigEntry, LaMetricDataUpdateCoordinator
|
||||
from .entity import LaMetricEntity
|
||||
from .helpers import lametric_exception_handler
|
||||
|
||||
@@ -47,11 +45,11 @@ SWITCHES = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: LaMetricConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LaMetric switch based on a config entry."""
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LaMetricSwitchEntity(
|
||||
coordinator=coordinator,
|
||||
|
@@ -7,20 +7,19 @@ from typing import Any
|
||||
|
||||
import ultraheat_api
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import UltraheatCoordinator
|
||||
from .coordinator import UltraheatConfigEntry, UltraheatCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: UltraheatConfigEntry) -> bool:
|
||||
"""Set up heat meter from a config entry."""
|
||||
|
||||
_LOGGER.debug("Initializing %s integration on %s", DOMAIN, entry.data[CONF_DEVICE])
|
||||
@@ -30,22 +29,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = UltraheatCoordinator(hass, entry, api)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: UltraheatConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: UltraheatConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug("Migrating from version %s", config_entry.version)
|
||||
|
||||
|
@@ -15,14 +15,19 @@ from .const import POLLING_INTERVAL, ULTRAHEAT_TIMEOUT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type UltraheatConfigEntry = ConfigEntry[UltraheatCoordinator]
|
||||
|
||||
|
||||
class UltraheatCoordinator(DataUpdateCoordinator[HeatMeterResponse]):
|
||||
"""Coordinator for getting data from the ultraheat api."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: UltraheatConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: ConfigEntry, api: HeatMeterService
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: UltraheatConfigEntry,
|
||||
api: HeatMeterService,
|
||||
) -> None:
|
||||
"""Initialize my coordinator."""
|
||||
super().__init__(
|
||||
|
@@ -15,7 +15,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
EntityCategory,
|
||||
UnitOfEnergy,
|
||||
@@ -29,13 +28,11 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .coordinator import UltraheatConfigEntry, UltraheatCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -270,14 +267,12 @@ HEAT_METER_SENSOR_TYPES = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: UltraheatConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensor platform."""
|
||||
unique_id = entry.entry_id
|
||||
coordinator: DataUpdateCoordinator[HeatMeterResponse] = hass.data[DOMAIN][
|
||||
entry.entry_id
|
||||
]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
model = entry.data["model"]
|
||||
|
||||
@@ -295,7 +290,7 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class HeatMeterSensor(
|
||||
CoordinatorEntity[DataUpdateCoordinator[HeatMeterResponse]],
|
||||
CoordinatorEntity[UltraheatCoordinator],
|
||||
SensorEntity,
|
||||
):
|
||||
"""Representation of a Sensor."""
|
||||
@@ -304,7 +299,7 @@ class HeatMeterSensor(
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator[HeatMeterResponse],
|
||||
coordinator: UltraheatCoordinator,
|
||||
description: HeatMeterSensorEntityDescription,
|
||||
device: DeviceInfo,
|
||||
) -> None:
|
||||
@@ -312,7 +307,7 @@ class HeatMeterSensor(
|
||||
super().__init__(coordinator)
|
||||
self.key = description.key
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.data['device_number']}_{description.key}" # type: ignore[union-attr]
|
||||
f"{coordinator.config_entry.data['device_number']}_{description.key}"
|
||||
)
|
||||
self._attr_name = f"Heat Meter {description.name}"
|
||||
self.entity_description = description
|
||||
|
@@ -7,21 +7,19 @@ import logging
|
||||
from laundrify_aio import LaundrifyAPI
|
||||
from laundrify_aio.exceptions import ApiConnectionException, UnauthorizedException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaundrifyUpdateCoordinator
|
||||
from .coordinator import LaundrifyConfigEntry, LaundrifyUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LaundrifyConfigEntry) -> bool:
|
||||
"""Set up laundrify from a config entry."""
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
@@ -38,26 +36,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||
"api": api_client,
|
||||
"coordinator": coordinator,
|
||||
}
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: LaundrifyConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: LaundrifyConfigEntry) -> bool:
|
||||
"""Migrate entry."""
|
||||
|
||||
_LOGGER.debug("Migrating from version %s", entry.version)
|
||||
|
@@ -10,28 +10,25 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, MODELS
|
||||
from .coordinator import LaundrifyUpdateCoordinator
|
||||
from .coordinator import LaundrifyConfigEntry, LaundrifyUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
entry: LaundrifyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors from a config entry created in the integrations UI."""
|
||||
|
||||
coordinator: LaundrifyUpdateCoordinator = hass.data[DOMAIN][config.entry_id][
|
||||
"coordinator"
|
||||
]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
LaundrifyPowerPlug(coordinator, device) for device in coordinator.data.values()
|
||||
|
@@ -16,6 +16,8 @@ from .const import DEFAULT_POLL_INTERVAL, DOMAIN, REQUEST_TIMEOUT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type LaundrifyConfigEntry = ConfigEntry[LaundrifyUpdateCoordinator]
|
||||
|
||||
|
||||
class LaundrifyUpdateCoordinator(DataUpdateCoordinator[dict[str, LaundrifyDevice]]):
|
||||
"""Class to manage fetching laundrify API data."""
|
||||
|
@@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfEnergy, UnitOfPower
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -18,21 +17,19 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaundrifyUpdateCoordinator
|
||||
from .coordinator import LaundrifyConfigEntry, LaundrifyUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
entry: LaundrifyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add power sensor for passed config_entry in HA."""
|
||||
|
||||
coordinator: LaundrifyUpdateCoordinator = hass.data[DOMAIN][config.entry_id][
|
||||
"coordinator"
|
||||
]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
sensor_entities: list[LaundrifyPowerSensor | LaundrifyEnergySensor] = []
|
||||
for device in coordinator.data.values():
|
||||
|
@@ -16,7 +16,6 @@ from pypck.connection import (
|
||||
)
|
||||
from pypck.lcn_defs import LcnEvent
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
@@ -38,21 +37,20 @@ from homeassistant.helpers import (
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_ACKNOWLEDGE,
|
||||
CONF_DIM_MODE,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_SK_NUM_TRIES,
|
||||
CONF_TARGET_VALUE_LOCKED,
|
||||
CONF_TRANSITION,
|
||||
CONNECTION,
|
||||
DEVICE_CONNECTIONS,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .helpers import (
|
||||
AddressType,
|
||||
InputType,
|
||||
LcnConfigEntry,
|
||||
LcnRuntimeData,
|
||||
async_update_config_entry,
|
||||
generate_unique_id,
|
||||
purge_device_registry,
|
||||
@@ -69,18 +67,14 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the LCN component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
async_setup_services(hass)
|
||||
await register_panel_and_ws_api(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: LcnConfigEntry) -> bool:
|
||||
"""Set up a connection to PCHK host from a config entry."""
|
||||
if config_entry.entry_id in hass.data[DOMAIN]:
|
||||
return False
|
||||
|
||||
settings = {
|
||||
"SK_NUM_TRIES": config_entry.data[CONF_SK_NUM_TRIES],
|
||||
@@ -114,11 +108,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
) from ex
|
||||
|
||||
_LOGGER.debug('LCN connected to "%s"', config_entry.title)
|
||||
hass.data[DOMAIN][config_entry.entry_id] = {
|
||||
CONNECTION: lcn_connection,
|
||||
DEVICE_CONNECTIONS: {},
|
||||
ADD_ENTITIES_CALLBACKS: {},
|
||||
}
|
||||
config_entry.runtime_data = LcnRuntimeData(
|
||||
connection=lcn_connection,
|
||||
device_connections={},
|
||||
add_entities_callbacks={},
|
||||
)
|
||||
|
||||
# Update config_entry with LCN device serials
|
||||
await async_update_config_entry(hass, config_entry)
|
||||
@@ -146,7 +140,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: LcnConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug(
|
||||
"Migrating configuration from version %s.%s",
|
||||
@@ -195,7 +191,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
|
||||
|
||||
async def async_migrate_entities(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: LcnConfigEntry
|
||||
) -> None:
|
||||
"""Migrate entity registry."""
|
||||
|
||||
@@ -217,25 +213,24 @@ async def async_migrate_entities(
|
||||
await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: LcnConfigEntry) -> bool:
|
||||
"""Close connection to PCHK host represented by config_entry."""
|
||||
# forward unloading to platforms
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
|
||||
if unload_ok and config_entry.entry_id in hass.data[DOMAIN]:
|
||||
host = hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
await host[CONNECTION].async_close()
|
||||
if unload_ok:
|
||||
await config_entry.runtime_data.connection.async_close()
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
def async_host_event_received(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, event: pypck.lcn_defs.LcnEvent
|
||||
hass: HomeAssistant, config_entry: LcnConfigEntry, event: pypck.lcn_defs.LcnEvent
|
||||
) -> None:
|
||||
"""Process received event from LCN."""
|
||||
lcn_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
|
||||
lcn_connection = config_entry.runtime_data.connection
|
||||
|
||||
async def reload_config_entry() -> None:
|
||||
"""Close connection and schedule config entry for reload."""
|
||||
@@ -258,7 +253,7 @@ def async_host_event_received(
|
||||
|
||||
def async_host_input_received(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
inp: pypck.inputs.Input,
|
||||
) -> None:
|
||||
@@ -266,7 +261,7 @@ def async_host_input_received(
|
||||
if not isinstance(inp, pypck.inputs.ModInput):
|
||||
return
|
||||
|
||||
lcn_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
|
||||
lcn_connection = config_entry.runtime_data.connection
|
||||
logical_address = lcn_connection.physical_to_logical(inp.physical_source_addr)
|
||||
address = (
|
||||
logical_address.seg_id,
|
||||
|
@@ -11,7 +11,6 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES, CONF_SOURCE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -22,19 +21,13 @@ from homeassistant.helpers.issue_registry import (
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
BINSENSOR_PORTS,
|
||||
CONF_DOMAIN_DATA,
|
||||
DOMAIN,
|
||||
SETPOINTS,
|
||||
)
|
||||
from .const import BINSENSOR_PORTS, CONF_DOMAIN_DATA, DOMAIN, SETPOINTS
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -53,7 +46,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN switch entities from a config entry."""
|
||||
@@ -63,7 +56,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_BINARY_SENSOR: add_entities}
|
||||
)
|
||||
|
||||
@@ -79,7 +72,7 @@ async def async_setup_entry(
|
||||
class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity):
|
||||
"""Representation of a LCN binary sensor for regulator locks."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN binary sensor."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -138,7 +131,7 @@ class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity):
|
||||
class LcnBinarySensor(LcnEntity, BinarySensorEntity):
|
||||
"""Representation of a LCN binary sensor for binary sensor ports."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN binary sensor."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -174,7 +167,7 @@ class LcnBinarySensor(LcnEntity, BinarySensorEntity):
|
||||
class LcnLockKeysSensor(LcnEntity, BinarySensorEntity):
|
||||
"""Representation of a LCN sensor for key locks."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN sensor."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -12,7 +12,6 @@ from homeassistant.components.climate import (
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_DOMAIN,
|
||||
@@ -26,23 +25,21 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_LOCKABLE,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_SETPOINT,
|
||||
CONF_TARGET_VALUE_LOCKED,
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -56,7 +53,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN switch entities from a config entry."""
|
||||
@@ -66,7 +63,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_CLIMATE: add_entities}
|
||||
)
|
||||
|
||||
@@ -82,7 +79,7 @@ async def async_setup_entry(
|
||||
class LcnClimate(LcnEntity, ClimateEntity):
|
||||
"""Representation of a LCN climate device."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize of a LCN climate device."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -15,12 +15,8 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
DOMAIN = "lcn"
|
||||
DATA_LCN = "lcn"
|
||||
DEFAULT_NAME = "pchk"
|
||||
|
||||
ADD_ENTITIES_CALLBACKS = "add_entities_callbacks"
|
||||
CONNECTION = "connection"
|
||||
DEVICE_CONNECTIONS = "device_connections"
|
||||
CONF_HARDWARE_SERIAL = "hardware_serial"
|
||||
CONF_SOFTWARE_SERIAL = "software_serial"
|
||||
CONF_HARDWARE_TYPE = "hardware_type"
|
||||
|
@@ -12,28 +12,25 @@ from homeassistant.components.cover import (
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_MOTOR,
|
||||
CONF_POSITIONING_MODE,
|
||||
CONF_REVERSE_TIME,
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -50,7 +47,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN cover entities from a config entry."""
|
||||
@@ -60,7 +57,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_COVER: add_entities}
|
||||
)
|
||||
|
||||
@@ -81,7 +78,7 @@ class LcnOutputsCover(LcnEntity, CoverEntity):
|
||||
_attr_is_opening = False
|
||||
_attr_assumed_state = True
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN cover."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -188,7 +185,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
|
||||
|
||||
positioning_mode: pypck.lcn_defs.MotorPositioningMode
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN cover."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_NAME
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -13,6 +12,7 @@ from .helpers import (
|
||||
AddressType,
|
||||
DeviceConnectionType,
|
||||
InputType,
|
||||
LcnConfigEntry,
|
||||
generate_unique_id,
|
||||
get_device_connection,
|
||||
get_resource,
|
||||
@@ -29,7 +29,7 @@ class LcnEntity(Entity):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigType,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the LCN device."""
|
||||
self.config = config
|
||||
|
@@ -3,11 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Iterable
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
import pypck
|
||||
from pypck.connection import PchkConnectionManager
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -33,12 +36,27 @@ from .const import (
|
||||
CONF_HARDWARE_TYPE,
|
||||
CONF_SCENES,
|
||||
CONF_SOFTWARE_SERIAL,
|
||||
CONNECTION,
|
||||
DEVICE_CONNECTIONS,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LcnRuntimeData:
|
||||
"""Data for LCN config entry."""
|
||||
|
||||
connection: PchkConnectionManager
|
||||
"""Connection to PCHK host."""
|
||||
|
||||
device_connections: dict[str, DeviceConnectionType]
|
||||
"""Logical addresses of devices connected to the host."""
|
||||
|
||||
add_entities_callbacks: dict[str, Callable[[Iterable[ConfigType]], None]]
|
||||
"""Callbacks to add entities for platforms."""
|
||||
|
||||
|
||||
# typing
|
||||
type LcnConfigEntry = ConfigEntry[LcnRuntimeData]
|
||||
|
||||
type AddressType = tuple[int, int, bool]
|
||||
type DeviceConnectionType = pypck.module.ModuleConnection | pypck.module.GroupConnection
|
||||
|
||||
@@ -62,10 +80,10 @@ DOMAIN_LOOKUP = {
|
||||
|
||||
|
||||
def get_device_connection(
|
||||
hass: HomeAssistant, address: AddressType, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, address: AddressType, config_entry: LcnConfigEntry
|
||||
) -> DeviceConnectionType:
|
||||
"""Return a lcn device_connection."""
|
||||
host_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
|
||||
host_connection = config_entry.runtime_data.connection
|
||||
addr = pypck.lcn_addr.LcnAddr(*address)
|
||||
return host_connection.get_address_conn(addr)
|
||||
|
||||
@@ -165,7 +183,7 @@ def purge_device_registry(
|
||||
device_registry.async_remove_device(device_id)
|
||||
|
||||
|
||||
def register_lcn_host_device(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
def register_lcn_host_device(hass: HomeAssistant, config_entry: LcnConfigEntry) -> None:
|
||||
"""Register LCN host for given config_entry in device registry."""
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
@@ -179,7 +197,7 @@ def register_lcn_host_device(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
|
||||
|
||||
def register_lcn_address_devices(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: LcnConfigEntry
|
||||
) -> None:
|
||||
"""Register LCN modules and groups defined in config_entry as devices in device registry.
|
||||
|
||||
@@ -217,9 +235,9 @@ def register_lcn_address_devices(
|
||||
model=device_model,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][DEVICE_CONNECTIONS][
|
||||
device_entry.id
|
||||
] = get_device_connection(hass, address, config_entry)
|
||||
config_entry.runtime_data.device_connections[device_entry.id] = (
|
||||
get_device_connection(hass, address, config_entry)
|
||||
)
|
||||
|
||||
|
||||
async def async_update_device_config(
|
||||
@@ -254,7 +272,7 @@ async def async_update_device_config(
|
||||
|
||||
|
||||
async def async_update_config_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: LcnConfigEntry
|
||||
) -> None:
|
||||
"""Fill missing values in config_entry with infos from LCN bus."""
|
||||
device_configs = deepcopy(config_entry.data[CONF_DEVICES])
|
||||
|
@@ -14,29 +14,26 @@ from homeassistant.components.light import (
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DIMMABLE,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_OUTPUT,
|
||||
CONF_TRANSITION,
|
||||
DOMAIN,
|
||||
OUTPUT_PORTS,
|
||||
)
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -53,7 +50,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN light entities from a config entry."""
|
||||
@@ -63,7 +60,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_LIGHT: add_entities}
|
||||
)
|
||||
|
||||
@@ -83,7 +80,7 @@ class LcnOutputLight(LcnEntity, LightEntity):
|
||||
_attr_is_on = False
|
||||
_attr_brightness = 255
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN light."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -175,7 +172,7 @@ class LcnRelayLight(LcnEntity, LightEntity):
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN light."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -7,28 +7,26 @@ from typing import Any
|
||||
import pypck
|
||||
|
||||
from homeassistant.components.scene import DOMAIN as DOMAIN_SCENE, Scene
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES, CONF_SCENE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_OUTPUTS,
|
||||
CONF_REGISTER,
|
||||
CONF_TRANSITION,
|
||||
DOMAIN,
|
||||
OUTPUT_PORTS,
|
||||
)
|
||||
from .entity import LcnEntity
|
||||
from .helpers import LcnConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -42,7 +40,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN switch entities from a config entry."""
|
||||
@@ -52,7 +50,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_SCENE: add_entities}
|
||||
)
|
||||
|
||||
@@ -68,7 +66,7 @@ async def async_setup_entry(
|
||||
class LcnScene(LcnEntity, Scene):
|
||||
"""Representation of a LCN scene."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN scene."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_DOMAIN,
|
||||
@@ -29,9 +28,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
DOMAIN,
|
||||
LED_PORTS,
|
||||
S0_INPUTS,
|
||||
SETPOINTS,
|
||||
@@ -39,7 +36,7 @@ from .const import (
|
||||
VARIABLES,
|
||||
)
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
DEVICE_CLASS_MAPPING = {
|
||||
pypck.lcn_defs.VarUnit.CELSIUS: SensorDeviceClass.TEMPERATURE,
|
||||
@@ -67,7 +64,7 @@ UNIT_OF_MEASUREMENT_MAPPING = {
|
||||
|
||||
|
||||
def add_lcn_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -86,7 +83,7 @@ def add_lcn_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN switch entities from a config entry."""
|
||||
@@ -96,7 +93,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_SENSOR: add_entities}
|
||||
)
|
||||
|
||||
@@ -112,7 +109,7 @@ async def async_setup_entry(
|
||||
class LcnVariableSensor(LcnEntity, SensorEntity):
|
||||
"""Representation of a LCN sensor for variables."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN sensor."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -157,7 +154,7 @@ class LcnVariableSensor(LcnEntity, SensorEntity):
|
||||
class LcnLedLogicSensor(LcnEntity, SensorEntity):
|
||||
"""Representation of a LCN sensor for leds and logicops."""
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN sensor."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -36,7 +36,6 @@ from .const import (
|
||||
CONF_TRANSITION,
|
||||
CONF_VALUE,
|
||||
CONF_VARIABLE,
|
||||
DEVICE_CONNECTIONS,
|
||||
DOMAIN,
|
||||
LED_PORTS,
|
||||
LED_STATUS,
|
||||
@@ -49,7 +48,7 @@ from .const import (
|
||||
VAR_UNITS,
|
||||
VARIABLES,
|
||||
)
|
||||
from .helpers import DeviceConnectionType, is_states_string
|
||||
from .helpers import DeviceConnectionType, LcnConfigEntry, is_states_string
|
||||
|
||||
|
||||
class LcnServiceCall:
|
||||
@@ -68,18 +67,28 @@ class LcnServiceCall:
|
||||
|
||||
def get_device_connection(self, service: ServiceCall) -> DeviceConnectionType:
|
||||
"""Get address connection object."""
|
||||
entries: list[LcnConfigEntry] = self.hass.config_entries.async_loaded_entries(
|
||||
DOMAIN
|
||||
)
|
||||
device_id = service.data[CONF_DEVICE_ID]
|
||||
device_registry = dr.async_get(self.hass)
|
||||
if not (device := device_registry.async_get(device_id)):
|
||||
if not (device := device_registry.async_get(device_id)) or not (
|
||||
entry := next(
|
||||
(
|
||||
entry
|
||||
for entry in entries
|
||||
if entry.entry_id == device.primary_config_entry
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_device_id",
|
||||
translation_placeholders={"device_id": device_id},
|
||||
)
|
||||
|
||||
return self.hass.data[DOMAIN][device.primary_config_entry][DEVICE_CONNECTIONS][
|
||||
device_id
|
||||
]
|
||||
return entry.runtime_data.device_connections[device_id]
|
||||
|
||||
async def async_call_service(self, service: ServiceCall) -> ServiceResponse:
|
||||
"""Execute service call."""
|
||||
|
@@ -7,29 +7,20 @@ from typing import Any
|
||||
import pypck
|
||||
|
||||
from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH, SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_OUTPUT,
|
||||
DOMAIN,
|
||||
OUTPUT_PORTS,
|
||||
RELAY_PORTS,
|
||||
SETPOINTS,
|
||||
)
|
||||
from .const import CONF_DOMAIN_DATA, CONF_OUTPUT, OUTPUT_PORTS, RELAY_PORTS, SETPOINTS
|
||||
from .entity import LcnEntity
|
||||
from .helpers import InputType
|
||||
from .helpers import InputType, LcnConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def add_lcn_switch_entities(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
entity_configs: Iterable[ConfigType],
|
||||
) -> None:
|
||||
@@ -52,7 +43,7 @@ def add_lcn_switch_entities(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LCN switch entities from a config entry."""
|
||||
@@ -62,7 +53,7 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][ADD_ENTITIES_CALLBACKS].update(
|
||||
config_entry.runtime_data.add_entities_callbacks.update(
|
||||
{DOMAIN_SWITCH: add_entities}
|
||||
)
|
||||
|
||||
@@ -80,7 +71,7 @@ class LcnOutputSwitch(LcnEntity, SwitchEntity):
|
||||
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN switch."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -129,7 +120,7 @@ class LcnRelaySwitch(LcnEntity, SwitchEntity):
|
||||
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN switch."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -179,7 +170,7 @@ class LcnRegulatorLockSwitch(LcnEntity, SwitchEntity):
|
||||
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN switch."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
@@ -235,7 +226,7 @@ class LcnKeyLockSwitch(LcnEntity, SwitchEntity):
|
||||
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config: ConfigType, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
|
||||
"""Initialize the LCN switch."""
|
||||
super().__init__(config, config_entry)
|
||||
|
||||
|
@@ -4,15 +4,17 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
from typing import Any, Final
|
||||
|
||||
import lcn_frontend as lcn_panel
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import panel_custom, websocket_api
|
||||
from homeassistant.components.http import StaticPathConfig
|
||||
from homeassistant.components.websocket_api import AsyncWebSocketCommandHandler
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.components.websocket_api import (
|
||||
ActiveConnection,
|
||||
AsyncWebSocketCommandHandler,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_DEVICES,
|
||||
@@ -28,16 +30,15 @@ from homeassistant.helpers import (
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ADD_ENTITIES_CALLBACKS,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_HARDWARE_SERIAL,
|
||||
CONF_HARDWARE_TYPE,
|
||||
CONF_SOFTWARE_SERIAL,
|
||||
CONNECTION,
|
||||
DOMAIN,
|
||||
)
|
||||
from .helpers import (
|
||||
DeviceConnectionType,
|
||||
LcnConfigEntry,
|
||||
async_update_device_config,
|
||||
generate_unique_id,
|
||||
get_device_config,
|
||||
@@ -58,11 +59,8 @@ from .schemas import (
|
||||
DOMAIN_DATA_SWITCH,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.websocket_api import ActiveConnection
|
||||
|
||||
type AsyncLcnWebSocketCommandHandler = Callable[
|
||||
[HomeAssistant, ActiveConnection, dict[str, Any], ConfigEntry], Awaitable[None]
|
||||
[HomeAssistant, ActiveConnection, dict[str, Any], LcnConfigEntry], Awaitable[None]
|
||||
]
|
||||
|
||||
URL_BASE: Final = "/lcn_static"
|
||||
@@ -127,7 +125,7 @@ async def websocket_get_device_configs(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Get device configs."""
|
||||
connection.send_result(msg["id"], config_entry.data[CONF_DEVICES])
|
||||
@@ -147,7 +145,7 @@ async def websocket_get_entity_configs(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Get entities configs."""
|
||||
if CONF_ADDRESS in msg:
|
||||
@@ -178,10 +176,10 @@ async def websocket_scan_devices(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Scan for new devices."""
|
||||
host_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
|
||||
host_connection = config_entry.runtime_data.connection
|
||||
await host_connection.scan_modules()
|
||||
|
||||
for device_connection in host_connection.address_conns.values():
|
||||
@@ -210,7 +208,7 @@ async def websocket_add_device(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Add a device."""
|
||||
if get_device_config(msg[CONF_ADDRESS], config_entry):
|
||||
@@ -256,7 +254,7 @@ async def websocket_delete_device(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Delete a device."""
|
||||
device_config = get_device_config(msg[CONF_ADDRESS], config_entry)
|
||||
@@ -318,7 +316,7 @@ async def websocket_add_entity(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Add an entity."""
|
||||
if not (device_config := get_device_config(msg[CONF_ADDRESS], config_entry)):
|
||||
@@ -347,9 +345,7 @@ async def websocket_add_entity(
|
||||
}
|
||||
|
||||
# Create new entity and add to corresponding component
|
||||
add_entities = hass.data[DOMAIN][msg["entry_id"]][ADD_ENTITIES_CALLBACKS][
|
||||
msg[CONF_DOMAIN]
|
||||
]
|
||||
add_entities = config_entry.runtime_data.add_entities_callbacks[msg[CONF_DOMAIN]]
|
||||
add_entities([entity_config])
|
||||
|
||||
# Add entity config to config_entry
|
||||
@@ -386,7 +382,7 @@ async def websocket_delete_entity(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Delete an entity."""
|
||||
entity_config = next(
|
||||
@@ -426,7 +422,7 @@ async def websocket_delete_entity(
|
||||
async def async_create_or_update_device_in_config_entry(
|
||||
hass: HomeAssistant,
|
||||
device_connection: DeviceConnectionType,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LcnConfigEntry,
|
||||
) -> None:
|
||||
"""Create or update device in config_entry according to given device_connection."""
|
||||
address = (
|
||||
@@ -455,7 +451,7 @@ async def async_create_or_update_device_in_config_entry(
|
||||
|
||||
|
||||
def get_entity_entry(
|
||||
hass: HomeAssistant, entity_config: dict, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, entity_config: dict, config_entry: LcnConfigEntry
|
||||
) -> er.RegistryEntry | None:
|
||||
"""Get entity RegistryEntry from entity_config."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
@@ -3,7 +3,7 @@
|
||||
from enum import StrEnum
|
||||
|
||||
DOMAIN = "wallbox"
|
||||
UPDATE_INTERVAL = 30
|
||||
UPDATE_INTERVAL = 60
|
||||
|
||||
BIDIRECTIONAL_MODEL_PREFIXES = ["QS"]
|
||||
|
||||
|
@@ -90,7 +90,9 @@ def _require_authentication[_WallboxCoordinatorT: WallboxCoordinator, **_P](
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == HTTPStatus.FORBIDDEN:
|
||||
raise ConfigEntryAuthFailed from wallbox_connection_error
|
||||
raise ConnectionError from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
return require_authentication
|
||||
|
||||
@@ -137,6 +139,7 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@_require_authentication
|
||||
def _get_data(self) -> dict[str, Any]:
|
||||
"""Get new sensor data for Wallbox component."""
|
||||
try:
|
||||
data: dict[str, Any] = self._wallbox.getChargerStatus(self._station)
|
||||
data[CHARGER_MAX_CHARGING_CURRENT_KEY] = data[CHARGER_DATA_KEY][
|
||||
CHARGER_MAX_CHARGING_CURRENT_KEY
|
||||
@@ -186,7 +189,15 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
elif eco_smart_mode == 1:
|
||||
data[CHARGER_ECO_SMART_KEY] = EcoSmartMode.FULL_SOLAR
|
||||
|
||||
return data
|
||||
return data # noqa: TRY300
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Get new sensor data for Wallbox component."""
|
||||
@@ -200,7 +211,13 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 403:
|
||||
raise InvalidAuth from wallbox_connection_error
|
||||
raise
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_set_charging_current(self, charging_current: float) -> None:
|
||||
"""Set maximum charging current for Wallbox."""
|
||||
@@ -217,7 +234,13 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 403:
|
||||
raise InvalidAuth from wallbox_connection_error
|
||||
raise
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_set_icp_current(self, icp_current: float) -> None:
|
||||
"""Set maximum icp current for Wallbox."""
|
||||
@@ -227,8 +250,16 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@_require_authentication
|
||||
def _set_energy_cost(self, energy_cost: float) -> None:
|
||||
"""Set energy cost for Wallbox."""
|
||||
|
||||
try:
|
||||
self._wallbox.setEnergyCost(self._station, energy_cost)
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_set_energy_cost(self, energy_cost: float) -> None:
|
||||
"""Set energy cost for Wallbox."""
|
||||
@@ -246,7 +277,13 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 403:
|
||||
raise InvalidAuth from wallbox_connection_error
|
||||
raise
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_set_lock_unlock(self, lock: bool) -> None:
|
||||
"""Set wallbox to locked or unlocked."""
|
||||
@@ -256,11 +293,19 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@_require_authentication
|
||||
def _pause_charger(self, pause: bool) -> None:
|
||||
"""Set wallbox to pause or resume."""
|
||||
|
||||
try:
|
||||
if pause:
|
||||
self._wallbox.pauseChargingSession(self._station)
|
||||
else:
|
||||
self._wallbox.resumeChargingSession(self._station)
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_pause_charger(self, pause: bool) -> None:
|
||||
"""Set wallbox to pause or resume."""
|
||||
@@ -270,13 +315,21 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@_require_authentication
|
||||
def _set_eco_smart(self, option: str) -> None:
|
||||
"""Set wallbox solar charging mode."""
|
||||
|
||||
try:
|
||||
if option == EcoSmartMode.ECO_MODE:
|
||||
self._wallbox.enableEcoSmart(self._station, 0)
|
||||
elif option == EcoSmartMode.FULL_SOLAR:
|
||||
self._wallbox.enableEcoSmart(self._station, 1)
|
||||
else:
|
||||
self._wallbox.disableEcoSmart(self._station)
|
||||
except requests.exceptions.HTTPError as wallbox_connection_error:
|
||||
if wallbox_connection_error.response.status_code == 429:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="too_many_requests"
|
||||
) from wallbox_connection_error
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="api_failed"
|
||||
) from wallbox_connection_error
|
||||
|
||||
async def async_set_eco_smart(self, option: str) -> None:
|
||||
"""Set wallbox solar charging mode."""
|
||||
|
@@ -7,7 +7,7 @@ from typing import Any
|
||||
from homeassistant.components.lock import LockEntity, LockEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
@@ -41,7 +41,7 @@ async def async_setup_entry(
|
||||
)
|
||||
except InvalidAuth:
|
||||
return
|
||||
except ConnectionError as exc:
|
||||
except HomeAssistantError as exc:
|
||||
raise PlatformNotReady from exc
|
||||
|
||||
async_add_entities(
|
||||
|
@@ -12,7 +12,7 @@ from typing import cast
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
@@ -93,7 +93,7 @@ async def async_setup_entry(
|
||||
)
|
||||
except InvalidAuth:
|
||||
return
|
||||
except ConnectionError as exc:
|
||||
except HomeAssistantError as exc:
|
||||
raise PlatformNotReady from exc
|
||||
|
||||
async_add_entities(
|
||||
|
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
@@ -49,11 +48,6 @@ from .const import (
|
||||
from .coordinator import WallboxCoordinator
|
||||
from .entity import WallboxEntity
|
||||
|
||||
CHARGER_STATION = "station"
|
||||
UPDATE_INTERVAL = 30
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WallboxSensorEntityDescription(SensorEntityDescription):
|
||||
|
@@ -112,6 +112,9 @@
|
||||
"exceptions": {
|
||||
"api_failed": {
|
||||
"message": "Error communicating with Wallbox API"
|
||||
},
|
||||
"too_many_requests": {
|
||||
"message": "Error communicating with Wallbox API, too many requests"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["auth", "application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
||||
"iot_class": "cloud_push",
|
||||
"requirements": ["yolink-api==0.5.2"]
|
||||
"requirements": ["yolink-api==0.5.5"]
|
||||
}
|
||||
|
@@ -155,7 +155,10 @@ class YoLinkValveEntity(YoLinkEntity, ValveEntity):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return true is device is available."""
|
||||
if self.coordinator.dev_net_type is not None:
|
||||
if (
|
||||
self.coordinator.device.is_support_mode_switching()
|
||||
and self.coordinator.dev_net_type is not None
|
||||
):
|
||||
# When the device operates in Class A mode, it cannot be controlled.
|
||||
return self.coordinator.dev_net_type != ATTR_DEVICE_MODEL_A
|
||||
return super().available
|
||||
|
@@ -46,7 +46,6 @@ ifaddr==0.2.0
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
mutagen==1.47.0
|
||||
numpy==2.3.0
|
||||
orjson==3.10.18
|
||||
packaging>=23.1
|
||||
paho-mqtt==2.1.0
|
||||
|
@@ -46,41 +46,16 @@ dependencies = [
|
||||
"ciso8601==2.3.2",
|
||||
"cronsim==2.6",
|
||||
"fnv-hash-fast==1.5.0",
|
||||
# ha-ffmpeg is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->tts->ffmpeg. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"ha-ffmpeg==3.2.2",
|
||||
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
||||
# integration
|
||||
"hass-nabucasa==0.103.0",
|
||||
# hassil is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->conversation->hassil. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"hassil==2.2.3",
|
||||
# When bumping httpx, please check the version pins of
|
||||
# httpcore, anyio, and h11 in gen_requirements_all
|
||||
"httpx==0.28.1",
|
||||
"home-assistant-bluetooth==1.13.1",
|
||||
# home_assistant_intents is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->conversation->home_assistant_intents. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"home-assistant-intents==2025.6.10",
|
||||
"ifaddr==0.2.0",
|
||||
"Jinja2==3.1.6",
|
||||
"lru-dict==1.3.0",
|
||||
# mutagen is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->tts->mutagen. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"mutagen==1.47.0",
|
||||
# numpy is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->alexa->camera->stream->numpy. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"numpy==2.3.0",
|
||||
"PyJWT==2.10.1",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
"cryptography==45.0.3",
|
||||
@@ -90,22 +65,7 @@ dependencies = [
|
||||
"orjson==3.10.18",
|
||||
"packaging>=23.1",
|
||||
"psutil-home-assistant==0.0.1",
|
||||
# pymicro_vad is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->pymicro_vad. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"pymicro-vad==1.0.1",
|
||||
# pyspeex-noise is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->assist_pipeline->pyspeex_noise. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"pyspeex-noise==1.0.2",
|
||||
"python-slugify==8.0.4",
|
||||
# PyTurboJPEG is indirectly imported from onboarding via the import chain
|
||||
# onboarding->cloud->camera->pyturbojpeg. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"PyTurboJPEG==1.8.0",
|
||||
"PyYAML==6.0.2",
|
||||
"requests==2.32.4",
|
||||
"securetar==2025.2.1",
|
||||
|
8
requirements.txt
generated
8
requirements.txt
generated
@@ -23,17 +23,12 @@ certifi>=2021.5.30
|
||||
ciso8601==2.3.2
|
||||
cronsim==2.6
|
||||
fnv-hash-fast==1.5.0
|
||||
ha-ffmpeg==3.2.2
|
||||
hass-nabucasa==0.103.0
|
||||
hassil==2.2.3
|
||||
httpx==0.28.1
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-intents==2025.6.10
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
mutagen==1.47.0
|
||||
numpy==2.3.0
|
||||
PyJWT==2.10.1
|
||||
cryptography==45.0.3
|
||||
Pillow==11.2.1
|
||||
@@ -42,10 +37,7 @@ pyOpenSSL==25.1.0
|
||||
orjson==3.10.18
|
||||
packaging>=23.1
|
||||
psutil-home-assistant==0.0.1
|
||||
pymicro-vad==1.0.1
|
||||
pyspeex-noise==1.0.2
|
||||
python-slugify==8.0.4
|
||||
PyTurboJPEG==1.8.0
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.4
|
||||
securetar==2025.2.1
|
||||
|
6
requirements_all.txt
generated
6
requirements_all.txt
generated
@@ -244,7 +244,7 @@ aioelectricitymaps==0.4.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==33.0.0
|
||||
aioesphomeapi==33.1.1
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -1231,7 +1231,7 @@ ihcsdk==2.8.5
|
||||
imeon_inverter_api==0.3.12
|
||||
|
||||
# homeassistant.components.imgw_pib
|
||||
imgw_pib==1.0.10
|
||||
imgw_pib==1.1.0
|
||||
|
||||
# homeassistant.components.incomfort
|
||||
incomfort-client==0.6.9
|
||||
@@ -3154,7 +3154,7 @@ yeelight==0.7.16
|
||||
yeelightsunflower==0.0.10
|
||||
|
||||
# homeassistant.components.yolink
|
||||
yolink-api==0.5.2
|
||||
yolink-api==0.5.5
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==2.2.0
|
||||
|
6
requirements_test_all.txt
generated
6
requirements_test_all.txt
generated
@@ -232,7 +232,7 @@ aioelectricitymaps==0.4.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==33.0.0
|
||||
aioesphomeapi==33.1.1
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -1062,7 +1062,7 @@ igloohome-api==0.1.1
|
||||
imeon_inverter_api==0.3.12
|
||||
|
||||
# homeassistant.components.imgw_pib
|
||||
imgw_pib==1.0.10
|
||||
imgw_pib==1.1.0
|
||||
|
||||
# homeassistant.components.incomfort
|
||||
incomfort-client==0.6.9
|
||||
@@ -2598,7 +2598,7 @@ yalexs==8.10.0
|
||||
yeelight==0.7.16
|
||||
|
||||
# homeassistant.components.yolink
|
||||
yolink-api==0.5.2
|
||||
yolink-api==0.5.5
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==2.2.0
|
||||
|
@@ -42,7 +42,7 @@ async def test_button_app_next(
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.configuration_url is None
|
||||
assert device_entry.configuration_url == "https://127.0.0.1/"
|
||||
assert device_entry.connections == {
|
||||
(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")
|
||||
}
|
||||
@@ -89,7 +89,7 @@ async def test_button_app_previous(
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.configuration_url is None
|
||||
assert device_entry.configuration_url == "https://127.0.0.1/"
|
||||
assert device_entry.connections == {
|
||||
(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")
|
||||
}
|
||||
@@ -137,7 +137,7 @@ async def test_button_dismiss_current_notification(
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.configuration_url is None
|
||||
assert device_entry.configuration_url == "https://127.0.0.1/"
|
||||
assert device_entry.connections == {
|
||||
(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")
|
||||
}
|
||||
@@ -185,7 +185,7 @@ async def test_button_dismiss_all_notifications(
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.configuration_url is None
|
||||
assert device_entry.configuration_url == "https://127.0.0.1/"
|
||||
assert device_entry.connections == {
|
||||
(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@ async def test_brightness(
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device.configuration_url is None
|
||||
assert device.configuration_url == "https://127.0.0.1/"
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")}
|
||||
assert device.entry_type is None
|
||||
assert device.hw_version is None
|
||||
@@ -104,7 +104,7 @@ async def test_volume(
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device.configuration_url is None
|
||||
assert device.configuration_url == "https://127.0.0.1/"
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")}
|
||||
assert device.entry_type is None
|
||||
assert device.hw_version is None
|
||||
|
@@ -48,7 +48,7 @@ async def test_brightness_mode(
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device.configuration_url is None
|
||||
assert device.configuration_url == "https://127.0.0.1/"
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")}
|
||||
assert device.entry_type is None
|
||||
assert device.hw_version is None
|
||||
|
@@ -41,7 +41,7 @@ async def test_wifi_signal(
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device.configuration_url is None
|
||||
assert device.configuration_url == "https://127.0.0.1/"
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")}
|
||||
assert device.entry_type is None
|
||||
assert device.hw_version is None
|
||||
|
@@ -50,7 +50,7 @@ async def test_bluetooth(
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device.configuration_url is None
|
||||
assert device.configuration_url == "https://127.0.0.1/"
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")}
|
||||
assert device.entry_type is None
|
||||
assert device.hw_version is None
|
||||
|
@@ -6,8 +6,7 @@ from unittest.mock import AsyncMock, patch
|
||||
from laundrify_aio import LaundrifyAPI, LaundrifyDevice
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.laundrify import DOMAIN
|
||||
from homeassistant.components.laundrify.const import MANUFACTURER
|
||||
from homeassistant.components.laundrify.const import DOMAIN, MANUFACTURER
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
@@ -162,6 +162,9 @@ test_response_no_power_boost = {
|
||||
http_404_error = requests.exceptions.HTTPError()
|
||||
http_404_error.response = requests.Response()
|
||||
http_404_error.response.status_code = HTTPStatus.NOT_FOUND
|
||||
http_429_error = requests.exceptions.HTTPError()
|
||||
http_429_error.response = requests.Response()
|
||||
http_429_error.response.status_code = HTTPStatus.TOO_MANY_REQUESTS
|
||||
|
||||
authorisation_response = {
|
||||
"data": {
|
||||
@@ -192,6 +195,24 @@ authorisation_response_unauthorised = {
|
||||
}
|
||||
}
|
||||
|
||||
invalid_reauth_response = {
|
||||
"jwt": "fakekeyhere",
|
||||
"refresh_token": "refresh_fakekeyhere",
|
||||
"user_id": 12345,
|
||||
"ttl": 145656758,
|
||||
"refresh_token_ttl": 145756758,
|
||||
"error": False,
|
||||
"status": 200,
|
||||
}
|
||||
|
||||
http_403_error = requests.exceptions.HTTPError()
|
||||
http_403_error.response = requests.Response()
|
||||
http_403_error.response.status_code = HTTPStatus.FORBIDDEN
|
||||
|
||||
http_404_error = requests.exceptions.HTTPError()
|
||||
http_404_error.response = requests.Response()
|
||||
http_404_error.response.status_code = HTTPStatus.NOT_FOUND
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None:
|
||||
"""Test wallbox sensor class setup."""
|
||||
|
@@ -1,9 +1,6 @@
|
||||
"""Test the Wallbox config flow."""
|
||||
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
|
||||
import requests_mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.wallbox import config_flow
|
||||
@@ -24,14 +21,14 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from . import (
|
||||
authorisation_response,
|
||||
authorisation_response_unauthorised,
|
||||
http_403_error,
|
||||
http_404_error,
|
||||
setup_integration,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
test_response = json.loads(
|
||||
json.dumps(
|
||||
{
|
||||
test_response = {
|
||||
CHARGER_CHARGING_POWER_KEY: 0,
|
||||
CHARGER_MAX_AVAILABLE_POWER_KEY: "xx",
|
||||
CHARGER_CHARGING_SPEED_KEY: 0,
|
||||
@@ -39,8 +36,6 @@ test_response = json.loads(
|
||||
CHARGER_ADDED_ENERGY_KEY: "44.697",
|
||||
CHARGER_DATA_KEY: {CHARGER_MAX_CHARGING_CURRENT_KEY: 24},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def test_show_set_form(hass: HomeAssistant) -> None:
|
||||
@@ -59,17 +54,16 @@ async def test_form_cannot_authenticate(hass: HomeAssistant) -> None:
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -89,17 +83,16 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response_unauthorised,
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -119,17 +112,16 @@ async def test_form_validate_input(hass: HomeAssistant) -> None:
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=HTTPStatus.OK,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=HTTPStatus.OK,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.getChargerStatus",
|
||||
new=Mock(return_value=test_response),
|
||||
),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -148,18 +140,16 @@ async def test_form_reauth(hass: HomeAssistant, entry: MockConfigEntry) -> None:
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response_unauthorised),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.getChargerStatus",
|
||||
new=Mock(return_value=test_response),
|
||||
),
|
||||
):
|
||||
result = await entry.start_reauth_flow(hass)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
@@ -183,26 +173,16 @@ async def test_form_reauth_invalid(hass: HomeAssistant, entry: MockConfigEntry)
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json={
|
||||
"jwt": "fakekeyhere",
|
||||
"refresh_token": "refresh_fakekeyhere",
|
||||
"user_id": 12345,
|
||||
"ttl": 145656758,
|
||||
"refresh_token_ttl": 145756758,
|
||||
"error": False,
|
||||
"status": 200,
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response_unauthorised),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.getChargerStatus",
|
||||
new=Mock(return_value=test_response),
|
||||
),
|
||||
):
|
||||
result = await entry.start_reauth_flow(hass)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
|
@@ -1,16 +1,15 @@
|
||||
"""Test Wallbox Init Component."""
|
||||
|
||||
import requests_mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from homeassistant.components.wallbox.const import (
|
||||
CHARGER_MAX_CHARGING_CURRENT_KEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.wallbox.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import (
|
||||
authorisation_response,
|
||||
http_403_error,
|
||||
http_429_error,
|
||||
setup_integration,
|
||||
setup_integration_connection_error,
|
||||
setup_integration_no_eco_mode,
|
||||
@@ -53,18 +52,16 @@ async def test_wallbox_refresh_failed_connection_error_auth(
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=404,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(return_value=test_response),
|
||||
),
|
||||
):
|
||||
wallbox = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
await wallbox.async_refresh()
|
||||
@@ -81,18 +78,68 @@ async def test_wallbox_refresh_failed_invalid_auth(
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=403,
|
||||
)
|
||||
mock_request.put(
|
||||
"https://api.wall-box.com/v2/charger/12345",
|
||||
json={CHARGER_MAX_CHARGING_CURRENT_KEY: 20},
|
||||
status_code=403,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
):
|
||||
wallbox = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
await wallbox.async_refresh()
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_wallbox_refresh_failed_http_error(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test Wallbox setup with authentication error."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.getChargerStatus",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
):
|
||||
wallbox = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
await wallbox.async_refresh()
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_wallbox_refresh_failed_too_many_requests(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test Wallbox setup with authentication error."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.getChargerStatus",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
):
|
||||
wallbox = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
await wallbox.async_refresh()
|
||||
@@ -109,18 +156,16 @@ async def test_wallbox_refresh_failed_connection_error(
|
||||
await setup_integration(hass, entry)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.get(
|
||||
"https://api.wall-box.com/chargers/status/12345",
|
||||
json=test_response,
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
):
|
||||
wallbox = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
await wallbox.async_refresh()
|
||||
|
@@ -1,15 +1,18 @@
|
||||
"""Test Wallbox Lock component."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from homeassistant.components.lock import SERVICE_LOCK, SERVICE_UNLOCK
|
||||
from homeassistant.components.wallbox.const import CHARGER_LOCKED_UNLOCKED_KEY
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import (
|
||||
authorisation_response,
|
||||
http_429_error,
|
||||
setup_integration,
|
||||
setup_integration_platform_not_ready,
|
||||
setup_integration_read_only,
|
||||
@@ -28,18 +31,20 @@ async def test_wallbox_lock_class(hass: HomeAssistant, entry: MockConfigEntry) -
|
||||
assert state
|
||||
assert state.state == "unlocked"
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.put(
|
||||
"https://api.wall-box.com/v2/charger/12345",
|
||||
json={CHARGER_LOCKED_UNLOCKED_KEY: False},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.lockCharger",
|
||||
new=Mock(return_value={CHARGER_LOCKED_UNLOCKED_KEY: False}),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.unlockCharger",
|
||||
new=Mock(return_value={CHARGER_LOCKED_UNLOCKED_KEY: False}),
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"lock",
|
||||
SERVICE_LOCK,
|
||||
@@ -66,19 +71,17 @@ async def test_wallbox_lock_class_connection_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.put(
|
||||
"https://api.wall-box.com/v2/charger/12345",
|
||||
json={CHARGER_LOCKED_UNLOCKED_KEY: False},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.lockCharger",
|
||||
new=Mock(side_effect=ConnectionError),
|
||||
),
|
||||
pytest.raises(ConnectionError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"lock",
|
||||
SERVICE_LOCK,
|
||||
@@ -87,7 +90,46 @@ async def test_wallbox_lock_class_connection_error(
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
with pytest.raises(ConnectionError):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.lockCharger",
|
||||
new=Mock(side_effect=ConnectionError),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.unlockCharger",
|
||||
new=Mock(side_effect=ConnectionError),
|
||||
),
|
||||
pytest.raises(ConnectionError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"lock",
|
||||
SERVICE_UNLOCK,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_LOCK_ENTITY_ID,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.lockCharger",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.unlockCharger",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"lock",
|
||||
SERVICE_UNLOCK,
|
||||
|
@@ -1,22 +1,26 @@
|
||||
"""Test Wallbox Switch component."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from homeassistant.components.input_number import ATTR_VALUE, SERVICE_SET_VALUE
|
||||
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||
from homeassistant.components.wallbox import InvalidAuth
|
||||
from homeassistant.components.wallbox.const import (
|
||||
CHARGER_ENERGY_PRICE_KEY,
|
||||
CHARGER_MAX_CHARGING_CURRENT_KEY,
|
||||
CHARGER_MAX_ICP_CURRENT_KEY,
|
||||
)
|
||||
from homeassistant.components.wallbox.coordinator import InvalidAuth
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import (
|
||||
authorisation_response,
|
||||
http_403_error,
|
||||
http_404_error,
|
||||
http_429_error,
|
||||
setup_integration,
|
||||
setup_integration_bidir,
|
||||
setup_integration_platform_not_ready,
|
||||
@@ -29,6 +33,14 @@ from .const import (
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
mock_wallbox = Mock()
|
||||
mock_wallbox.authenticate = Mock(return_value=authorisation_response)
|
||||
mock_wallbox.setEnergyCost = Mock(return_value={CHARGER_ENERGY_PRICE_KEY: 1.1})
|
||||
mock_wallbox.setMaxChargingCurrent = Mock(
|
||||
return_value={CHARGER_MAX_CHARGING_CURRENT_KEY: 20}
|
||||
)
|
||||
mock_wallbox.setIcpMaxCurrent = Mock(return_value={CHARGER_MAX_ICP_CURRENT_KEY: 10})
|
||||
|
||||
|
||||
async def test_wallbox_number_class(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
@@ -37,17 +49,16 @@ async def test_wallbox_number_class(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.put(
|
||||
"https://api.wall-box.com/v2/charger/12345",
|
||||
json={CHARGER_MAX_CHARGING_CURRENT_KEY: 20},
|
||||
status_code=200,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setMaxChargingCurrent",
|
||||
new=Mock(return_value={CHARGER_MAX_CHARGING_CURRENT_KEY: 20}),
|
||||
),
|
||||
):
|
||||
state = hass.states.get(MOCK_NUMBER_ENTITY_ID)
|
||||
assert state.attributes["min"] == 6
|
||||
assert state.attributes["max"] == 25
|
||||
@@ -82,19 +93,16 @@ async def test_wallbox_number_energy_class(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_ENERGY_PRICE_KEY: 1.1},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setEnergyCost",
|
||||
new=Mock(return_value={CHARGER_ENERGY_PRICE_KEY: 1.1}),
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -113,19 +121,17 @@ async def test_wallbox_number_class_connection_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.put(
|
||||
"https://api.wall-box.com/v2/charger/12345",
|
||||
json={CHARGER_MAX_CHARGING_CURRENT_KEY: 20},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setMaxChargingCurrent",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -137,26 +143,82 @@ async def test_wallbox_number_class_connection_error(
|
||||
)
|
||||
|
||||
|
||||
async def test_wallbox_number_class_energy_price_connection_error(
|
||||
async def test_wallbox_number_class_too_many_requests(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test wallbox sensor class."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_ENERGY_PRICE_KEY: 1.1},
|
||||
status_code=404,
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setMaxChargingCurrent",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ID,
|
||||
ATTR_VALUE: 20,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
|
||||
async def test_wallbox_number_class_energy_price_update_failed(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test wallbox sensor class."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setEnergyCost",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
|
||||
ATTR_VALUE: 1.1,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_wallbox_number_class_energy_price_update_connection_error(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test wallbox sensor class."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setEnergyCost",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -175,19 +237,17 @@ async def test_wallbox_number_class_energy_price_auth_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_ENERGY_PRICE_KEY: 1.1},
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
with pytest.raises(ConfigEntryAuthFailed):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setEnergyCost",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -218,19 +278,16 @@ async def test_wallbox_number_class_icp_energy(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setIcpMaxCurrent",
|
||||
new=Mock(return_value={CHARGER_MAX_ICP_CURRENT_KEY: 10}),
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -249,19 +306,17 @@ async def test_wallbox_number_class_icp_energy_auth_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidAuth):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setIcpMaxCurrent",
|
||||
new=Mock(side_effect=http_403_error),
|
||||
),
|
||||
pytest.raises(InvalidAuth),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
@@ -280,19 +335,46 @@ async def test_wallbox_number_class_icp_energy_connection_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/chargers/config/12345",
|
||||
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setIcpMaxCurrent",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
|
||||
ATTR_VALUE: 10,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_wallbox_number_class_icp_energy_too_many_request(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test wallbox sensor class."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.setIcpMaxCurrent",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
|
@@ -16,6 +16,7 @@ from homeassistant.core import HomeAssistant, HomeAssistantError
|
||||
from . import (
|
||||
authorisation_response,
|
||||
http_404_error,
|
||||
http_429_error,
|
||||
setup_integration_select,
|
||||
test_response,
|
||||
test_response_eco_mode,
|
||||
@@ -109,7 +110,41 @@ async def test_wallbox_select_class_error(
|
||||
"homeassistant.components.wallbox.Wallbox.enableEcoSmart",
|
||||
new=Mock(side_effect=error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError, match="Error communicating with Wallbox API"),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
|
||||
ATTR_OPTION: mode,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("mode", "response"), TEST_OPTIONS)
|
||||
async def test_wallbox_select_too_many_requests_error(
|
||||
hass: HomeAssistant,
|
||||
entry: MockConfigEntry,
|
||||
mode,
|
||||
response,
|
||||
mock_authenticate,
|
||||
) -> None:
|
||||
"""Test wallbox select class connection error."""
|
||||
|
||||
await setup_integration_select(hass, entry, response)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.disableEcoSmart",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.enableEcoSmart",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
|
@@ -1,15 +1,16 @@
|
||||
"""Test Wallbox Lock component."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from homeassistant.components.switch import SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
from homeassistant.components.wallbox.const import CHARGER_STATUS_ID_KEY
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import authorisation_response, setup_integration
|
||||
from . import authorisation_response, http_404_error, http_429_error, setup_integration
|
||||
from .const import MOCK_SWITCH_ENTITY_ID
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -26,18 +27,20 @@ async def test_wallbox_switch_class(
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/v3/chargers/12345/remote-action",
|
||||
json={CHARGER_STATUS_ID_KEY: 193},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
|
||||
new=Mock(return_value={CHARGER_STATUS_ID_KEY: 193}),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.resumeChargingSession",
|
||||
new=Mock(return_value={CHARGER_STATUS_ID_KEY: 193}),
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_ON,
|
||||
@@ -64,19 +67,18 @@ async def test_wallbox_switch_class_connection_error(
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/v3/chargers/12345/remote-action",
|
||||
json={CHARGER_STATUS_ID_KEY: 193},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.resumeChargingSession",
|
||||
new=Mock(side_effect=http_404_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
# Test behavior when a connection error occurs
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_ON,
|
||||
@@ -85,37 +87,27 @@ async def test_wallbox_switch_class_connection_error(
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
with pytest.raises(ConnectionError):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_OFF,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_SWITCH_ENTITY_ID,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_wallbox_switch_class_authentication_error(
|
||||
async def test_wallbox_switch_class_too_many_requests(
|
||||
hass: HomeAssistant, entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test wallbox switch class connection error."""
|
||||
|
||||
await setup_integration(hass, entry)
|
||||
|
||||
with requests_mock.Mocker() as mock_request:
|
||||
mock_request.get(
|
||||
"https://user-api.wall-box.com/users/signin",
|
||||
json=authorisation_response,
|
||||
status_code=200,
|
||||
)
|
||||
mock_request.post(
|
||||
"https://api.wall-box.com/v3/chargers/12345/remote-action",
|
||||
json={CHARGER_STATUS_ID_KEY: 193},
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
with pytest.raises(ConfigEntryAuthFailed):
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.authenticate",
|
||||
new=Mock(return_value=authorisation_response),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wallbox.Wallbox.resumeChargingSession",
|
||||
new=Mock(side_effect=http_429_error),
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
# Test behavior when a connection error occurs
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_ON,
|
||||
@@ -124,12 +116,3 @@ async def test_wallbox_switch_class_authentication_error(
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
with pytest.raises(ConfigEntryAuthFailed):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_OFF,
|
||||
{
|
||||
ATTR_ENTITY_ID: MOCK_SWITCH_ENTITY_ID,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
@@ -17,6 +17,9 @@ from homeassistant.components.websocket_api.auth import (
|
||||
TYPE_AUTH_OK,
|
||||
TYPE_AUTH_REQUIRED,
|
||||
)
|
||||
from homeassistant.components.websocket_api.commands import (
|
||||
ALL_SERVICE_DESCRIPTIONS_JSON_CACHE,
|
||||
)
|
||||
from homeassistant.components.websocket_api.const import FEATURE_COALESCE_MESSAGES, URL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS
|
||||
@@ -667,14 +670,41 @@ async def test_get_services(
|
||||
hass: HomeAssistant, websocket_client: MockHAClientWebSocket
|
||||
) -> None:
|
||||
"""Test get_services command."""
|
||||
for id_ in (5, 6):
|
||||
await websocket_client.send_json({"id": id_, "type": "get_services"})
|
||||
|
||||
assert ALL_SERVICE_DESCRIPTIONS_JSON_CACHE not in hass.data
|
||||
await websocket_client.send_json_auto_id({"type": "get_services"})
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == id_
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert msg["success"]
|
||||
assert msg["result"].keys() == hass.services.async_services().keys()
|
||||
assert msg == {"id": 1, "result": {}, "success": True, "type": "result"}
|
||||
|
||||
# Check cache is reused
|
||||
old_cache = hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE]
|
||||
await websocket_client.send_json_auto_id({"type": "get_services"})
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg == {"id": 2, "result": {}, "success": True, "type": "result"}
|
||||
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache
|
||||
|
||||
# Load a service and check cache is updated
|
||||
assert await async_setup_component(hass, "logger", {})
|
||||
await websocket_client.send_json_auto_id({"type": "get_services"})
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg == {
|
||||
"id": 3,
|
||||
"result": {"logger": {"set_default_level": ANY, "set_level": ANY}},
|
||||
"success": True,
|
||||
"type": "result",
|
||||
}
|
||||
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is not old_cache
|
||||
|
||||
# Check cache is reused
|
||||
old_cache = hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE]
|
||||
await websocket_client.send_json_auto_id({"type": "get_services"})
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg == {
|
||||
"id": 4,
|
||||
"result": {"logger": {"set_default_level": ANY, "set_level": ANY}},
|
||||
"success": True,
|
||||
"type": "result",
|
||||
}
|
||||
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache
|
||||
|
||||
|
||||
async def test_get_config(
|
||||
|
Reference in New Issue
Block a user