mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Add lektrico integration (#102371)
* Add Lektrico Integration * Make the changes proposed by Lash-L: new coordinator.py, new entity.py; use: translation_key, last_update_sucess, PlatformNotReady; remove: global variables * Replace FlowResult with ConfigFlowResult and add tests. * Remove unused lines. * Remove Options from condif_flow * Fix ruff and mypy. * Fix CODEOWNERS. * Run python3 -m script.hassfest. * Correct rebase mistake. * Make modifications suggested by emontnemery. * Add pytest fixtures. * Remove meaningless patches. * Update .coveragerc * Replace CONF_FRIENDLY_NAME with CONF_NAME. * Remove underscores. * Update tests. * Update test file with is and no config_entries. . * Set serial_number in DeviceInfo and add return type of the async_update_data to DataUpdateCoordinator. * Use suggested_unit_of_measurement for KILO_WATT and replace Any in value_fn (sensor file). * Add device class duration to charging_time sensor. * Change raising PlatformNotReady to raising IntegrationError. * Test the unique id of the entry. * Rename PF Lx with Power factor Lx and remove PF from strings.json. * Remove comment. * Make state and limit reason sensors to be enum sensors. * Use result variable to check unique_id in test. * Remove CONF_NAME from entry and __init__ from LektricoFlowHandler. * Remove session parameter from LektricoDeviceDataUpdateCoordinator. * Use config_entry: ConfigEntry in coordinator. * Replace Connected,NeedAuth with Waiting for Authentication. * Use lektricowifi 0.0.29. * Use lektricowifi 0.0.39 * Use lektricowifi 0.0.40 * Use lektricowifi 0.0.41 * Replace hass.data with entry.runtime_data * Delete .coveragerc * Restructure the user step * Fix tests * Add returned value of _async_update_data to class DataUpdateCoordinator * Use hw_version at DeviceInfo * Remove a variable * Use StateType * Replace friendly_name with device_name * Use sentence case in translation strings * Uncomment and fix test_discovered_zeroconf * Add type LektricoConfigEntry * Remove commented code * Remove the type of coordinator in sensor async_setup_entry * Make zeroconf test end in ABORT, not FORM * Remove all async_block_till_done from tests * End test_user_setup_device_offline with CREATE_ENTRY * Patch the full Device * Add snapshot tests * Overwrite the type LektricoSensorEntityDescription outside of the constructor * Test separate already_configured for zeroconf --------- Co-authored-by: mihaela.tarjoianu <mihaela.tarjoianu@scada.ro> Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
397198c6d0
commit
5bd736029f
@ -279,6 +279,7 @@ homeassistant.components.lawn_mower.*
|
||||
homeassistant.components.lcn.*
|
||||
homeassistant.components.ld2410_ble.*
|
||||
homeassistant.components.led_ble.*
|
||||
homeassistant.components.lektrico.*
|
||||
homeassistant.components.lidarr.*
|
||||
homeassistant.components.lifx.*
|
||||
homeassistant.components.light.*
|
||||
|
@ -799,6 +799,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/leaone/ @bdraco
|
||||
/homeassistant/components/led_ble/ @bdraco
|
||||
/tests/components/led_ble/ @bdraco
|
||||
/homeassistant/components/lektrico/ @lektrico
|
||||
/tests/components/lektrico/ @lektrico
|
||||
/homeassistant/components/lg_netcast/ @Drafteed @splinter98
|
||||
/tests/components/lg_netcast/ @Drafteed @splinter98
|
||||
/homeassistant/components/lidarr/ @tkdrob
|
||||
|
51
homeassistant/components/lektrico/__init__.py
Normal file
51
homeassistant/components/lektrico/__init__.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""The Lektrico Charging Station integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from lektricowifi import Device
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_SERIAL_NUMBER, CONF_TYPE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import LektricoDeviceDataUpdateCoordinator
|
||||
|
||||
# List the platforms that charger supports.
|
||||
CHARGERS_PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
# List the platforms that load balancer device supports.
|
||||
LB_DEVICES_PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
type LektricoConfigEntry = ConfigEntry[LektricoDeviceDataUpdateCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LektricoConfigEntry) -> bool:
|
||||
"""Set up Lektrico Charging Station from a config entry."""
|
||||
coordinator = LektricoDeviceDataUpdateCoordinator(
|
||||
hass,
|
||||
f"{entry.data[CONF_TYPE]}_{entry.data[ATTR_SERIAL_NUMBER]}",
|
||||
)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, _get_platforms(entry))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
|
||||
return await hass.config_entries.async_unload_platforms(
|
||||
entry, _get_platforms(entry)
|
||||
)
|
||||
|
||||
|
||||
def _get_platforms(entry: ConfigEntry) -> list[Platform]:
|
||||
"""Return the platforms for this type of device."""
|
||||
_device_type: str = entry.data[CONF_TYPE]
|
||||
if _device_type in (Device.TYPE_1P7K, Device.TYPE_3P22K):
|
||||
return CHARGERS_PLATFORMS
|
||||
return LB_DEVICES_PLATFORMS
|
138
homeassistant/components/lektrico/config_flow.py
Normal file
138
homeassistant/components/lektrico/config_flow.py
Normal file
@ -0,0 +1,138 @@
|
||||
"""Config flow for Lektrico Charging Station."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from lektricowifi import Device, DeviceConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.zeroconf import ZeroconfServiceInfo
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import (
|
||||
ATTR_HW_VERSION,
|
||||
ATTR_SERIAL_NUMBER,
|
||||
CONF_HOST,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class LektricoFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a Lektrico config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
_host: str
|
||||
_name: str
|
||||
_serial_number: str
|
||||
_board_revision: str
|
||||
_device_type: str
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initiated by the user."""
|
||||
errors = None
|
||||
|
||||
if user_input is not None:
|
||||
self._host = user_input[CONF_HOST]
|
||||
|
||||
# obtain serial number
|
||||
try:
|
||||
await self._get_lektrico_device_settings_and_treat_unique_id()
|
||||
return self._async_create_entry()
|
||||
except DeviceConnectionError:
|
||||
errors = {CONF_HOST: "cannot_connect"}
|
||||
|
||||
return self._async_show_setup_form(user_input=user_input, errors=errors)
|
||||
|
||||
@callback
|
||||
def _async_show_setup_form(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
errors: dict[str, str] | None = None,
|
||||
) -> ConfigFlowResult:
|
||||
"""Show the setup form to the user."""
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
|
||||
schema = self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_create_entry(self) -> ConfigFlowResult:
|
||||
return self.async_create_entry(
|
||||
title=self._name,
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
ATTR_SERIAL_NUMBER: self._serial_number,
|
||||
CONF_TYPE: self._device_type,
|
||||
ATTR_HW_VERSION: self._board_revision,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: ZeroconfServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
self._host = discovery_info.host # 192.168.100.11
|
||||
|
||||
# read settings from the device
|
||||
try:
|
||||
await self._get_lektrico_device_settings_and_treat_unique_id()
|
||||
except DeviceConnectionError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
self.context["title_placeholders"] = {
|
||||
"serial_number": self._serial_number,
|
||||
"name": self._name,
|
||||
}
|
||||
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def _get_lektrico_device_settings_and_treat_unique_id(self) -> None:
|
||||
"""Get device's serial number from a Lektrico device."""
|
||||
device = Device(
|
||||
_host=self._host,
|
||||
asyncClient=get_async_client(self.hass),
|
||||
)
|
||||
|
||||
settings = await device.device_config()
|
||||
self._serial_number = str(settings["serial_number"])
|
||||
self._device_type = settings["type"]
|
||||
self._board_revision = settings["board_revision"]
|
||||
self._name = f"{settings["type"]}_{self._serial_number}"
|
||||
|
||||
# Check if already configured
|
||||
# Set unique id
|
||||
await self.async_set_unique_id(self._serial_number, raise_on_progress=True)
|
||||
# Abort if already configured, but update the last-known host
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: self._host}, reload_on_update=True
|
||||
)
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Allow the user to confirm adding the device."""
|
||||
|
||||
if user_input is not None:
|
||||
return self._async_create_entry()
|
||||
|
||||
self._set_confirm_only()
|
||||
return self.async_show_form(step_id="confirm")
|
9
homeassistant/components/lektrico/const.py
Normal file
9
homeassistant/components/lektrico/const.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""Constants for the Lektrico Charging Station integration."""
|
||||
|
||||
from logging import Logger, getLogger
|
||||
|
||||
# Integration domain
|
||||
DOMAIN = "lektrico"
|
||||
|
||||
# Logger
|
||||
LOGGER: Logger = getLogger(__package__)
|
52
homeassistant/components/lektrico/coordinator.py
Normal file
52
homeassistant/components/lektrico/coordinator.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Coordinator for the Lektrico Charging Station integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from lektricowifi import Device, DeviceConnectionError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_HW_VERSION,
|
||||
ATTR_SERIAL_NUMBER,
|
||||
CONF_HOST,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
|
||||
class LektricoDeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Data update coordinator for Lektrico device."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, device_name: str) -> None:
|
||||
"""Initialize a Lektrico Device."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=device_name,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
self.device = Device(
|
||||
self.config_entry.data[CONF_HOST],
|
||||
asyncClient=get_async_client(hass),
|
||||
)
|
||||
self.serial_number: str = self.config_entry.data[ATTR_SERIAL_NUMBER]
|
||||
self.board_revision: str = self.config_entry.data[ATTR_HW_VERSION]
|
||||
self.device_type: str = self.config_entry.data[CONF_TYPE]
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Async Update device state."""
|
||||
try:
|
||||
return await self.device.device_info(self.device_type)
|
||||
except DeviceConnectionError as lek_ex:
|
||||
raise UpdateFailed(lek_ex) from lek_ex
|
33
homeassistant/components/lektrico/entity.py
Normal file
33
homeassistant/components/lektrico/entity.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""Entity classes for the Lektrico integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import LektricoDeviceDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class LektricoEntity(CoordinatorEntity[LektricoDeviceDataUpdateCoordinator]):
|
||||
"""Define an Lektrico entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LektricoDeviceDataUpdateCoordinator,
|
||||
device_name: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.serial_number)},
|
||||
model=coordinator.device_type.upper(),
|
||||
name=device_name,
|
||||
manufacturer="Lektrico",
|
||||
sw_version=coordinator.data["fw_version"],
|
||||
hw_version=coordinator.board_revision,
|
||||
serial_number=coordinator.serial_number,
|
||||
)
|
16
homeassistant/components/lektrico/manifest.json
Normal file
16
homeassistant/components/lektrico/manifest.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"domain": "lektrico",
|
||||
"name": "Lektrico Charging Station",
|
||||
"codeowners": ["@lektrico"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/lektrico",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["lektricowifi==0.0.41"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_http._tcp.local.",
|
||||
"name": "lektrico*"
|
||||
}
|
||||
]
|
||||
}
|
324
homeassistant/components/lektrico/sensor.py
Normal file
324
homeassistant/components/lektrico/sensor.py
Normal file
@ -0,0 +1,324 @@
|
||||
"""Support for Lektrico charging station sensors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from lektricowifi import Device
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_SERIAL_NUMBER,
|
||||
CONF_TYPE,
|
||||
PERCENTAGE,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import IntegrationError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from . import LektricoConfigEntry, LektricoDeviceDataUpdateCoordinator
|
||||
from .entity import LektricoEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class LektricoSensorEntityDescription(SensorEntityDescription):
|
||||
"""A class that describes the Lektrico sensor entities."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
|
||||
|
||||
SENSORS_FOR_CHARGERS: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="state",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[
|
||||
"available",
|
||||
"connected",
|
||||
"need_auth",
|
||||
"paused",
|
||||
"charging",
|
||||
"error",
|
||||
"updating_firmware",
|
||||
],
|
||||
translation_key="state",
|
||||
value_fn=lambda data: str(data["charger_state"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="charging_time",
|
||||
translation_key="charging_time",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
value_fn=lambda data: int(data["charging_time"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="power",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
value_fn=lambda data: float(data["instant_power"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="energy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda data: float(data["session_energy"]) / 1000,
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: float(data["temperature"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="lifetime_energy",
|
||||
translation_key="lifetime_energy",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda data: int(data["total_charged_energy"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="installation_current",
|
||||
translation_key="installation_current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: int(data["install_current"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="limit_reason",
|
||||
translation_key="limit_reason",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[
|
||||
"no_limit",
|
||||
"installation_current",
|
||||
"user_limit",
|
||||
"dynamic_limit",
|
||||
"schedule",
|
||||
"em_offline",
|
||||
"em",
|
||||
"ocpp",
|
||||
],
|
||||
value_fn=lambda data: str(data["current_limit_reason"]),
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_FOR_LB_DEVICES: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="breaker_current",
|
||||
translation_key="breaker_current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: int(data["breaker_curent"]),
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_FOR_1_PHASE: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="voltage",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
value_fn=lambda data: float(data["voltage_l1"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: float(data["current_l1"]),
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_FOR_3_PHASE: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="voltage_l1",
|
||||
translation_key="voltage_l1",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
value_fn=lambda data: float(data["voltage_l1"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="voltage_l2",
|
||||
translation_key="voltage_l2",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
value_fn=lambda data: float(data["voltage_l2"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="voltage_l3",
|
||||
translation_key="voltage_l3",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
value_fn=lambda data: float(data["voltage_l3"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="current_l1",
|
||||
translation_key="current_l1",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: float(data["current_l1"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="current_l2",
|
||||
translation_key="current_l2",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: float(data["current_l2"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="current_l3",
|
||||
translation_key="current_l3",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
value_fn=lambda data: float(data["current_l3"]),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
SENSORS_FOR_LB_1_PHASE: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="power",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
value_fn=lambda data: float(data["power_l1"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="pf",
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: float(data["power_factor_l1"]) * 100,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
SENSORS_FOR_LB_3_PHASE: tuple[LektricoSensorEntityDescription, ...] = (
|
||||
LektricoSensorEntityDescription(
|
||||
key="power_l1",
|
||||
translation_key="power_l1",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
value_fn=lambda data: float(data["power_l1"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="power_l2",
|
||||
translation_key="power_l2",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
value_fn=lambda data: float(data["power_l2"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="power_l3",
|
||||
translation_key="power_l3",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
value_fn=lambda data: float(data["power_l3"]),
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="pf_l1",
|
||||
translation_key="pf_l1",
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: float(data["power_factor_l1"]) * 100,
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="pf_l2",
|
||||
translation_key="pf_l2",
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: float(data["power_factor_l2"]) * 100,
|
||||
),
|
||||
LektricoSensorEntityDescription(
|
||||
key="pf_l3",
|
||||
translation_key="pf_l3",
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: float(data["power_factor_l3"]) * 100,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: LektricoConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Lektrico charger based on a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
sensors_to_be_used: tuple[LektricoSensorEntityDescription, ...]
|
||||
if coordinator.device_type == Device.TYPE_1P7K:
|
||||
sensors_to_be_used = SENSORS_FOR_CHARGERS + SENSORS_FOR_1_PHASE
|
||||
elif coordinator.device_type == Device.TYPE_3P22K:
|
||||
sensors_to_be_used = SENSORS_FOR_CHARGERS + SENSORS_FOR_3_PHASE
|
||||
elif coordinator.device_type == Device.TYPE_EM:
|
||||
sensors_to_be_used = (
|
||||
SENSORS_FOR_LB_DEVICES + SENSORS_FOR_1_PHASE + SENSORS_FOR_LB_1_PHASE
|
||||
)
|
||||
elif coordinator.device_type == Device.TYPE_3EM:
|
||||
sensors_to_be_used = (
|
||||
SENSORS_FOR_LB_DEVICES + SENSORS_FOR_3_PHASE + SENSORS_FOR_LB_3_PHASE
|
||||
)
|
||||
else:
|
||||
raise IntegrationError
|
||||
|
||||
async_add_entities(
|
||||
LektricoSensor(
|
||||
description,
|
||||
coordinator,
|
||||
f"{entry.data[CONF_TYPE]}_{entry.data[ATTR_SERIAL_NUMBER]}",
|
||||
)
|
||||
for description in sensors_to_be_used
|
||||
)
|
||||
|
||||
|
||||
class LektricoSensor(LektricoEntity, SensorEntity):
|
||||
"""The entity class for Lektrico charging stations sensors."""
|
||||
|
||||
entity_description: LektricoSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: LektricoSensorEntityDescription,
|
||||
coordinator: LektricoDeviceDataUpdateCoordinator,
|
||||
device_name: str,
|
||||
) -> None:
|
||||
"""Initialize Lektrico charger."""
|
||||
super().__init__(coordinator, device_name)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.serial_number}_{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
101
homeassistant/components/lektrico/strings.json
Normal file
101
homeassistant/components/lektrico/strings.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Set required parameters to connect to your device",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"device_name": "[%key:common::config_flow::data::name%]"
|
||||
}
|
||||
},
|
||||
"zeroconf_confirm": {
|
||||
"description": "Do you want to add the Lektrico Charger with serial number `{serial_number}` to Home Assistant?",
|
||||
"title": "Discovered Lektrico Charger device"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"state": {
|
||||
"name": "State",
|
||||
"state": {
|
||||
"available": "Available",
|
||||
"connected": "Connected",
|
||||
"need_auth": "Waiting for authentication",
|
||||
"paused": "Paused",
|
||||
"charging": "Charging",
|
||||
"error": "Error",
|
||||
"updating_firmware": "Updating firmware"
|
||||
}
|
||||
},
|
||||
"charging_time": {
|
||||
"name": "Charging time"
|
||||
},
|
||||
"lifetime_energy": {
|
||||
"name": "Lifetime energy"
|
||||
},
|
||||
"installation_current": {
|
||||
"name": "Installation current"
|
||||
},
|
||||
"limit_reason": {
|
||||
"name": "Limit reason",
|
||||
"state": {
|
||||
"no_limit": "No limit",
|
||||
"installation_current": "Installation current",
|
||||
"user_limit": "User limit",
|
||||
"dynamic_limit": "Dynamic limit",
|
||||
"schedule": "Schedule",
|
||||
"em_offline": "EM offline",
|
||||
"em": "EM",
|
||||
"ocpp": "OCPP"
|
||||
}
|
||||
},
|
||||
"breaker_current": {
|
||||
"name": "Breaker current"
|
||||
},
|
||||
"voltage_l1": {
|
||||
"name": "Voltage L1"
|
||||
},
|
||||
"voltage_l2": {
|
||||
"name": "Voltage L2"
|
||||
},
|
||||
"voltage_l3": {
|
||||
"name": "Voltage L3"
|
||||
},
|
||||
"current_l1": {
|
||||
"name": "Current L1"
|
||||
},
|
||||
"current_l2": {
|
||||
"name": "Current L2"
|
||||
},
|
||||
"current_l3": {
|
||||
"name": "Current L3"
|
||||
},
|
||||
"power_l1": {
|
||||
"name": "Power L1"
|
||||
},
|
||||
"power_l2": {
|
||||
"name": "Power L2"
|
||||
},
|
||||
"power_l3": {
|
||||
"name": "Power L3"
|
||||
},
|
||||
"pf_l1": {
|
||||
"name": "Power factor L1"
|
||||
},
|
||||
"pf_l2": {
|
||||
"name": "Power factor L2"
|
||||
},
|
||||
"pf_l3": {
|
||||
"name": "Power factor L3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -315,6 +315,7 @@ FLOWS = {
|
||||
"ld2410_ble",
|
||||
"leaone",
|
||||
"led_ble",
|
||||
"lektrico",
|
||||
"lg_netcast",
|
||||
"lg_soundbar",
|
||||
"lidarr",
|
||||
|
@ -3211,6 +3211,12 @@
|
||||
"integration_type": "virtual",
|
||||
"supported_by": "netatmo"
|
||||
},
|
||||
"lektrico": {
|
||||
"name": "Lektrico Charging Station",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"leviton": {
|
||||
"name": "Leviton",
|
||||
"iot_standards": [
|
||||
|
@ -527,6 +527,10 @@ ZEROCONF = {
|
||||
"domain": "bosch_shc",
|
||||
"name": "bosch shc*",
|
||||
},
|
||||
{
|
||||
"domain": "lektrico",
|
||||
"name": "lektrico*",
|
||||
},
|
||||
{
|
||||
"domain": "loqed",
|
||||
"name": "loqed*",
|
||||
|
10
mypy.ini
10
mypy.ini
@ -2546,6 +2546,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.lektrico.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.lidarr.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -1254,6 +1254,9 @@ leaone-ble==0.1.0
|
||||
# homeassistant.components.led_ble
|
||||
led-ble==1.0.2
|
||||
|
||||
# homeassistant.components.lektrico
|
||||
lektricowifi==0.0.41
|
||||
|
||||
# homeassistant.components.foscam
|
||||
libpyfoscam==1.2.2
|
||||
|
||||
|
@ -1047,6 +1047,9 @@ leaone-ble==0.1.0
|
||||
# homeassistant.components.led_ble
|
||||
led-ble==1.0.2
|
||||
|
||||
# homeassistant.components.lektrico
|
||||
lektricowifi==0.0.41
|
||||
|
||||
# homeassistant.components.foscam
|
||||
libpyfoscam==1.2.2
|
||||
|
||||
|
13
tests/components/lektrico/__init__.py
Normal file
13
tests/components/lektrico/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""Tests for Lektrico integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
92
tests/components/lektrico/conftest.py
Normal file
92
tests/components/lektrico/conftest.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""Fixtures for Lektrico Charging Station integration tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from ipaddress import ip_address
|
||||
import json
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lektrico.const import DOMAIN
|
||||
from homeassistant.components.zeroconf import ZeroconfServiceInfo
|
||||
from homeassistant.const import (
|
||||
ATTR_HW_VERSION,
|
||||
ATTR_SERIAL_NUMBER,
|
||||
CONF_HOST,
|
||||
CONF_TYPE,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
MOCKED_DEVICE_IP_ADDRESS = "192.168.100.10"
|
||||
MOCKED_DEVICE_SERIAL_NUMBER = "500006"
|
||||
MOCKED_DEVICE_TYPE = "1p7k"
|
||||
MOCKED_DEVICE_BOARD_REV = "B"
|
||||
|
||||
MOCKED_DEVICE_ZC_NAME = "Lektrico-1p7k-500006._http._tcp"
|
||||
MOCKED_DEVICE_ZC_TYPE = "_http._tcp.local."
|
||||
MOCKED_DEVICE_ZEROCONF_DATA = ZeroconfServiceInfo(
|
||||
ip_address=ip_address(MOCKED_DEVICE_IP_ADDRESS),
|
||||
ip_addresses=[ip_address(MOCKED_DEVICE_IP_ADDRESS)],
|
||||
hostname=f"{MOCKED_DEVICE_ZC_NAME.lower()}.local.",
|
||||
port=80,
|
||||
type=MOCKED_DEVICE_ZC_TYPE,
|
||||
name=MOCKED_DEVICE_ZC_NAME,
|
||||
properties={
|
||||
"id": "1p7k_500006",
|
||||
"fw_id": "20230109-124642/v1.22-36-g56a3edd-develop-dirty",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_device() -> Generator[AsyncMock]:
|
||||
"""Mock a Lektrico device."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.lektrico.Device",
|
||||
autospec=True,
|
||||
) as mock_device,
|
||||
patch(
|
||||
"homeassistant.components.lektrico.config_flow.Device",
|
||||
new=mock_device,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.lektrico.coordinator.Device",
|
||||
new=mock_device,
|
||||
),
|
||||
):
|
||||
device = mock_device.return_value
|
||||
|
||||
device.device_config.return_value = json.loads(
|
||||
load_fixture("get_config.json", DOMAIN)
|
||||
)
|
||||
device.device_info.return_value = json.loads(
|
||||
load_fixture("get_info.json", DOMAIN)
|
||||
)
|
||||
|
||||
yield device
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Mock setup entry."""
|
||||
with patch(
|
||||
"homeassistant.components.lektrico.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock a config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
CONF_TYPE: MOCKED_DEVICE_TYPE,
|
||||
ATTR_SERIAL_NUMBER: MOCKED_DEVICE_SERIAL_NUMBER,
|
||||
ATTR_HW_VERSION: "B",
|
||||
},
|
||||
unique_id=MOCKED_DEVICE_SERIAL_NUMBER,
|
||||
)
|
16
tests/components/lektrico/fixtures/current_measures.json
Normal file
16
tests/components/lektrico/fixtures/current_measures.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"charger_state": "Available",
|
||||
"charging_time": 0,
|
||||
"instant_power": 0,
|
||||
"session_energy": 0.0,
|
||||
"temperature": 34.5,
|
||||
"total_charged_energy": 0,
|
||||
"install_current": 6,
|
||||
"current_limit_reason": "Installation current",
|
||||
"voltage_l1": 220.0,
|
||||
"current_l1": 0.0,
|
||||
"type": "1p7k",
|
||||
"serial_number": "500006",
|
||||
"board_revision": "B",
|
||||
"fw_version": "1.44"
|
||||
}
|
5
tests/components/lektrico/fixtures/get_config.json
Normal file
5
tests/components/lektrico/fixtures/get_config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "1p7k",
|
||||
"serial_number": "500006",
|
||||
"board_revision": "B"
|
||||
}
|
13
tests/components/lektrico/fixtures/get_info.json
Normal file
13
tests/components/lektrico/fixtures/get_info.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"charger_state": "available",
|
||||
"charging_time": 0,
|
||||
"instant_power": 0,
|
||||
"session_energy": 0.0,
|
||||
"temperature": 34.5,
|
||||
"total_charged_energy": 0,
|
||||
"install_current": 6,
|
||||
"current_limit_reason": "installation_current",
|
||||
"voltage_l1": 220.0,
|
||||
"current_l1": 0.0,
|
||||
"fw_version": "1.44"
|
||||
}
|
33
tests/components/lektrico/snapshots/test_init.ambr
Normal file
33
tests/components/lektrico/snapshots/test_init.ambr
Normal file
@ -0,0 +1,33 @@
|
||||
# serializer version: 1
|
||||
# name: test_device_info
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'configuration_url': None,
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': 'B',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'lektrico',
|
||||
'500006',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Lektrico',
|
||||
'model': '1P7K',
|
||||
'model_id': None,
|
||||
'name': '1p7k_500006',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': '500006',
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.44',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
534
tests/components/lektrico/snapshots/test_sensor.ambr
Normal file
534
tests/components/lektrico/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,534 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[sensor.1p7k_500006_charging_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_charging_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Charging time',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'charging_time',
|
||||
'unique_id': '500006_charging_time',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_charging_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
'friendly_name': '1p7k_500006 Charging time',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_charging_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_current-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Current',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '500006_current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': '1p7k_500006 Current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_energy',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '500006_energy',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': '1p7k_500006 Energy',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_energy',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_installation_current-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_installation_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Installation current',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'installation_current',
|
||||
'unique_id': '500006_installation_current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_installation_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': '1p7k_500006 Installation current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_installation_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '6',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_lifetime_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_lifetime_energy',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Lifetime energy',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'lifetime_energy',
|
||||
'unique_id': '500006_lifetime_energy',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_lifetime_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': '1p7k_500006 Lifetime energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_lifetime_energy',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_limit_reason-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'no_limit',
|
||||
'installation_current',
|
||||
'user_limit',
|
||||
'dynamic_limit',
|
||||
'schedule',
|
||||
'em_offline',
|
||||
'em',
|
||||
'ocpp',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_limit_reason',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Limit reason',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'limit_reason',
|
||||
'unique_id': '500006_limit_reason',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_limit_reason-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': '1p7k_500006 Limit reason',
|
||||
'options': list([
|
||||
'no_limit',
|
||||
'installation_current',
|
||||
'user_limit',
|
||||
'dynamic_limit',
|
||||
'schedule',
|
||||
'em_offline',
|
||||
'em',
|
||||
'ocpp',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_limit_reason',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'installation_current',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_power-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_power',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '500006_power',
|
||||
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_power-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': '1p7k_500006 Power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_power',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0000',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'available',
|
||||
'connected',
|
||||
'need_auth',
|
||||
'paused',
|
||||
'charging',
|
||||
'error',
|
||||
'updating_firmware',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'State',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'state',
|
||||
'unique_id': '500006_state',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': '1p7k_500006 State',
|
||||
'options': list([
|
||||
'available',
|
||||
'connected',
|
||||
'need_auth',
|
||||
'paused',
|
||||
'charging',
|
||||
'error',
|
||||
'updating_firmware',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'available',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_temperature',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '500006_temperature',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_temperature-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': '1p7k_500006 Temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '34.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_voltage-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.1p7k_500006_voltage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Voltage',
|
||||
'platform': 'lektrico',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '500006_voltage',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.1p7k_500006_voltage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': '1p7k_500006 Voltage',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.1p7k_500006_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '220.0',
|
||||
})
|
||||
# ---
|
173
tests/components/lektrico/test_config_flow.py
Normal file
173
tests/components/lektrico/test_config_flow.py
Normal file
@ -0,0 +1,173 @@
|
||||
"""Tests for the Lektrico Charging Station config flow."""
|
||||
|
||||
import dataclasses
|
||||
from ipaddress import ip_address
|
||||
|
||||
from lektricowifi import DeviceConnectionError
|
||||
|
||||
from homeassistant.components.lektrico.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
|
||||
from homeassistant.const import (
|
||||
ATTR_HW_VERSION,
|
||||
ATTR_SERIAL_NUMBER,
|
||||
CONF_HOST,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from .conftest import (
|
||||
MOCKED_DEVICE_BOARD_REV,
|
||||
MOCKED_DEVICE_IP_ADDRESS,
|
||||
MOCKED_DEVICE_SERIAL_NUMBER,
|
||||
MOCKED_DEVICE_TYPE,
|
||||
MOCKED_DEVICE_ZEROCONF_DATA,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_user_setup(hass: HomeAssistant, mock_device, mock_setup_entry) -> None:
|
||||
"""Test manually setting up."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == SOURCE_USER
|
||||
assert "flow_id" in result
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
},
|
||||
)
|
||||
|
||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||
assert result.get("title") == f"{MOCKED_DEVICE_TYPE}_{MOCKED_DEVICE_SERIAL_NUMBER}"
|
||||
assert result.get("data") == {
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
ATTR_SERIAL_NUMBER: MOCKED_DEVICE_SERIAL_NUMBER,
|
||||
CONF_TYPE: MOCKED_DEVICE_TYPE,
|
||||
ATTR_HW_VERSION: MOCKED_DEVICE_BOARD_REV,
|
||||
}
|
||||
assert "result" in result
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert result.get("result").unique_id == MOCKED_DEVICE_SERIAL_NUMBER
|
||||
|
||||
|
||||
async def test_user_setup_already_exists(
|
||||
hass: HomeAssistant, mock_device, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test manually setting up when the device already exists."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert not result["errors"]
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_user_setup_device_offline(hass: HomeAssistant, mock_device) -> None:
|
||||
"""Test manually setting up when device is offline."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert not result["errors"]
|
||||
|
||||
mock_device.device_config.side_effect = DeviceConnectionError
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {CONF_HOST: "cannot_connect"}
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
mock_device.device_config.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_discovered_zeroconf(
|
||||
hass: HomeAssistant, mock_device, mock_setup_entry
|
||||
) -> None:
|
||||
"""Test we can setup when discovered from zeroconf."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=MOCKED_DEVICE_ZEROCONF_DATA,
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] is None
|
||||
assert result.get("step_id") == "confirm"
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
CONF_HOST: MOCKED_DEVICE_IP_ADDRESS,
|
||||
ATTR_SERIAL_NUMBER: MOCKED_DEVICE_SERIAL_NUMBER,
|
||||
CONF_TYPE: MOCKED_DEVICE_TYPE,
|
||||
ATTR_HW_VERSION: MOCKED_DEVICE_BOARD_REV,
|
||||
}
|
||||
assert result2["title"] == f"{MOCKED_DEVICE_TYPE}_{MOCKED_DEVICE_SERIAL_NUMBER}"
|
||||
|
||||
|
||||
async def test_zeroconf_setup_already_exists(
|
||||
hass: HomeAssistant, mock_device, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test we abort zeroconf flow if device already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
zc_data_new_ip = dataclasses.replace(MOCKED_DEVICE_ZEROCONF_DATA)
|
||||
zc_data_new_ip.ip_address = ip_address(MOCKED_DEVICE_IP_ADDRESS)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=zc_data_new_ip,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_discovered_zeroconf_device_connection_error(
|
||||
hass: HomeAssistant, mock_device
|
||||
) -> None:
|
||||
"""Test we can setup when discovered from zeroconf but device went offline."""
|
||||
|
||||
mock_device.device_config.side_effect = DeviceConnectionError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=MOCKED_DEVICE_ZEROCONF_DATA,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
29
tests/components/lektrico/test_init.py
Normal file
29
tests/components/lektrico/test_init.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Tests for the Lektrico integration."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.lektrico.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_device_info(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_device: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test device registry integration."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, mock_config_entry.unique_id)}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert device_entry == snapshot
|
31
tests/components/lektrico/test_sensor.py
Normal file
31
tests/components/lektrico/test_sensor.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""Tests for the Lektrico sensor platform."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_device: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
with patch(
|
||||
"homeassistant.components.lektrico.CHARGERS_PLATFORMS", [Platform.SENSOR]
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
Loading…
x
Reference in New Issue
Block a user