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