mirror of https://github.com/home-assistant/core
Implement TechnoVE integration (#106029)
* Implement TechnoVE integration Only the basic sensors for now. * Add technoVE to strict typing * Implement TechnoVE PR suggestions * Remove Diagnostic from TechnoVE initial PR * Switch status sensor to Enum device class * Revert zeroconf for adding it back in subsequent PR * Implement changes from feedback in TechnoVE PR * Update homeassistant/components/technove/models.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/technove/sensor.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/technove/models.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Remove unnecessary translation keys * Fix existing technoVE tests * Use snapshot testing for TechnoVE sensors * Improve unit tests for TechnoVE * Add missing coverage for technoVE config flow * Add TechnoVE coordinator tests * Modify device_fixture for TechnoVE from PR Feedback * Change CONF_IP_ADDRESS to CONF_HOST for TechnoVE * Update homeassistant/components/technove/config_flow.py Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Update homeassistant/components/technove/models.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/technove/models.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Implement feedback from TechnoVE PR * Add test_sensor_update_failure to TechnoVE sensor tests * Add test for error recovery during config flow of TechnoVE * Remove test_coordinator.py from TechnoVE --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com>
This commit is contained in:
parent
a8b67d5a0a
commit
44f2b8e6a3
|
@ -404,6 +404,7 @@ homeassistant.components.tailwind.*
|
|||
homeassistant.components.tami4.*
|
||||
homeassistant.components.tautulli.*
|
||||
homeassistant.components.tcp.*
|
||||
homeassistant.components.technove.*
|
||||
homeassistant.components.tedee.*
|
||||
homeassistant.components.text.*
|
||||
homeassistant.components.threshold.*
|
||||
|
|
|
@ -1328,6 +1328,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/tasmota/ @emontnemery
|
||||
/homeassistant/components/tautulli/ @ludeeus @tkdrob
|
||||
/tests/components/tautulli/ @ludeeus @tkdrob
|
||||
/homeassistant/components/technove/ @Moustachauve
|
||||
/tests/components/technove/ @Moustachauve
|
||||
/homeassistant/components/tedee/ @patrickhilker @zweckj
|
||||
/tests/components/tedee/ @patrickhilker @zweckj
|
||||
/homeassistant/components/tellduslive/ @fredrike
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
"""The TechnoVE integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TechnoVEDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up TechnoVE from a config entry."""
|
||||
coordinator = TechnoVEDataUpdateCoordinator(hass)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
|
@ -0,0 +1,50 @@
|
|||
"""Config flow for TechnoVE."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from technove import Station as TechnoVEStation, TechnoVE, TechnoVEConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class TechnoVEConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for TechnoVE."""
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initiated by the user."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
station = await self._async_get_station(user_input[CONF_HOST])
|
||||
except TechnoVEConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(station.info.mac_address)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title=station.info.name,
|
||||
data={
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def _async_get_station(self, host: str) -> TechnoVEStation:
|
||||
"""Get information from a TechnoVE station."""
|
||||
api = TechnoVE(host, session=async_get_clientsession(self.hass))
|
||||
return await api.update()
|
|
@ -0,0 +1,8 @@
|
|||
"""Constants for the TechnoVE integration."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
DOMAIN = "technove"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
|
@ -0,0 +1,40 @@
|
|||
"""DataUpdateCoordinator for TechnoVE."""
|
||||
from __future__ import annotations
|
||||
|
||||
from technove import Station as TechnoVEStation, TechnoVE, TechnoVEError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
|
||||
class TechnoVEDataUpdateCoordinator(DataUpdateCoordinator[TechnoVEStation]):
|
||||
"""Class to manage fetching TechnoVE data from single endpoint."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize global TechnoVE data updater."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
self.technove = TechnoVE(
|
||||
self.config_entry.data[CONF_HOST],
|
||||
session=async_get_clientsession(hass),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> TechnoVEStation:
|
||||
"""Fetch data from TechnoVE."""
|
||||
try:
|
||||
station = await self.technove.update()
|
||||
except TechnoVEError as error:
|
||||
raise UpdateFailed(f"Invalid response from API: {error}") from error
|
||||
|
||||
return station
|
|
@ -0,0 +1,26 @@
|
|||
"""Entity for TechnoVE."""
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TechnoVEDataUpdateCoordinator
|
||||
|
||||
|
||||
class TechnoVEEntity(CoordinatorEntity[TechnoVEDataUpdateCoordinator]):
|
||||
"""Defines a base TechnoVE entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: TechnoVEDataUpdateCoordinator, key: str) -> None:
|
||||
"""Initialize a base TechnoVE entity."""
|
||||
super().__init__(coordinator)
|
||||
info = self.coordinator.data.info
|
||||
self._attr_unique_id = f"{info.mac_address}_{key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_NETWORK_MAC, info.mac_address)},
|
||||
identifiers={(DOMAIN, info.mac_address)},
|
||||
name=info.name,
|
||||
manufacturer="TechnoVE",
|
||||
model=f"TechnoVE i{info.max_station_current}",
|
||||
sw_version=info.version,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"domain": "technove",
|
||||
"name": "TechnoVE",
|
||||
"codeowners": ["@Moustachauve"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/technove",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["python-technove==1.1.1"]
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
"""Platform for sensor integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from technove import Station as TechnoVEStation, Status
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TechnoVEDataUpdateCoordinator
|
||||
from .entity import TechnoVEEntity
|
||||
|
||||
STATUS_TYPE = [s.value for s in Status]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TechnoVESensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes TechnoVE sensor entity."""
|
||||
|
||||
value_fn: Callable[[TechnoVEStation], StateType]
|
||||
|
||||
|
||||
SENSORS: tuple[TechnoVESensorEntityDescription, ...] = (
|
||||
TechnoVESensorEntityDescription(
|
||||
key="voltage_in",
|
||||
translation_key="voltage_in",
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.voltage_in,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="voltage_out",
|
||||
translation_key="voltage_out",
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.voltage_out,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="max_current",
|
||||
translation_key="max_current",
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.max_current,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="max_station_current",
|
||||
translation_key="max_station_current",
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.max_station_current,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="current",
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.current,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="energy_total",
|
||||
translation_key="energy_total",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.energy_total,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="energy_session",
|
||||
translation_key="energy_session",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.energy_session,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="rssi",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda station: station.info.rssi,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="ssid",
|
||||
translation_key="ssid",
|
||||
icon="mdi:wifi",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda station: station.info.network_ssid,
|
||||
),
|
||||
TechnoVESensorEntityDescription(
|
||||
key="status",
|
||||
translation_key="status",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=STATUS_TYPE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda station: station.info.status.value,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensor platform."""
|
||||
coordinator: TechnoVEDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities(
|
||||
TechnoVESensorEntity(coordinator, description) for description in SENSORS
|
||||
)
|
||||
|
||||
|
||||
class TechnoVESensorEntity(TechnoVEEntity, SensorEntity):
|
||||
"""Defines a TechnoVE sensor entity."""
|
||||
|
||||
entity_description: TechnoVESensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TechnoVEDataUpdateCoordinator,
|
||||
description: TechnoVESensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a TechnoVE sensor entity."""
|
||||
super().__init__(coordinator, description.key)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"config": {
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Set up your TechnoVE station to integrate with Home Assistant.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Hostname or IP address of your TechnoVE station."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"voltage_in": {
|
||||
"name": "Input voltage"
|
||||
},
|
||||
"voltage_out": {
|
||||
"name": "Output voltage"
|
||||
},
|
||||
"max_current": {
|
||||
"name": "Max current"
|
||||
},
|
||||
"max_station_current": {
|
||||
"name": "Max station current"
|
||||
},
|
||||
"energy_total": {
|
||||
"name": "Total energy usage"
|
||||
},
|
||||
"energy_session": {
|
||||
"name": "Last session energy usage"
|
||||
},
|
||||
"ssid": {
|
||||
"name": "Wi-Fi network name"
|
||||
},
|
||||
"status": {
|
||||
"name": "Status",
|
||||
"state": {
|
||||
"unplugged": "Unplugged",
|
||||
"plugged_waiting": "Plugged, waiting",
|
||||
"plugged_charging": "Plugged, charging"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -508,6 +508,7 @@ FLOWS = {
|
|||
"tankerkoenig",
|
||||
"tasmota",
|
||||
"tautulli",
|
||||
"technove",
|
||||
"tedee",
|
||||
"tellduslive",
|
||||
"tesla_wall_connector",
|
||||
|
|
|
@ -5837,6 +5837,12 @@
|
|||
"config_flow": false,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"technove": {
|
||||
"name": "TechnoVE",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"ted5000": {
|
||||
"name": "The Energy Detective TED5000",
|
||||
"integration_type": "hub",
|
||||
|
|
10
mypy.ini
10
mypy.ini
|
@ -3802,6 +3802,16 @@ disallow_untyped_defs = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.technove.*]
|
||||
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.tedee.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -2264,6 +2264,9 @@ python-songpal==0.16
|
|||
# homeassistant.components.tado
|
||||
python-tado==0.17.3
|
||||
|
||||
# homeassistant.components.technove
|
||||
python-technove==1.1.1
|
||||
|
||||
# homeassistant.components.telegram_bot
|
||||
python-telegram-bot==13.1
|
||||
|
||||
|
|
|
@ -1725,6 +1725,9 @@ python-songpal==0.16
|
|||
# homeassistant.components.tado
|
||||
python-tado==0.17.3
|
||||
|
||||
# homeassistant.components.technove
|
||||
python-technove==1.1.1
|
||||
|
||||
# homeassistant.components.telegram_bot
|
||||
python-telegram-bot==13.1
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the TechnoVE integration."""
|
|
@ -0,0 +1,66 @@
|
|||
"""Fixtures for TechnoVE integration tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from technove import Station as TechnoVEStation
|
||||
|
||||
from homeassistant.components.technove.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return the default mocked config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "192.168.1.123"},
|
||||
unique_id="AA:AA:AA:AA:AA:BB",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Mock setting up a config entry."""
|
||||
with patch(
|
||||
"homeassistant.components.technove.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
yield mock_setup
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_fixture() -> TechnoVEStation:
|
||||
"""Return the device fixture for a specific device."""
|
||||
return TechnoVEStation(load_json_object_fixture("station_charging.json", DOMAIN))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_technove(device_fixture: TechnoVEStation) -> Generator[MagicMock, None, None]:
|
||||
"""Return a mocked TechnoVE client."""
|
||||
with patch(
|
||||
"homeassistant.components.technove.coordinator.TechnoVE", autospec=True
|
||||
) as technove_mock, patch(
|
||||
"homeassistant.components.technove.config_flow.TechnoVE", new=technove_mock
|
||||
):
|
||||
technove = technove_mock.return_value
|
||||
technove.update.return_value = device_fixture
|
||||
technove.ip_address = "127.0.0.1"
|
||||
yield technove
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_technove: MagicMock,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the TechnoVE integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"voltageIn": 238,
|
||||
"voltageOut": 238,
|
||||
"maxStationCurrent": 32,
|
||||
"maxCurrent": 24,
|
||||
"current": 23.75,
|
||||
"network_ssid": "Connecting...",
|
||||
"id": "AA:AA:AA:AA:AA:BB",
|
||||
"auto_charge": true,
|
||||
"highChargePeriodActive": false,
|
||||
"normalPeriodActive": false,
|
||||
"maxChargePourcentage": 0.9,
|
||||
"isBatteryProtected": false,
|
||||
"inSharingMode": true,
|
||||
"energySession": 12.34,
|
||||
"energyTotal": 1234,
|
||||
"version": "1.82",
|
||||
"rssi": -82,
|
||||
"name": "TechnoVE Station",
|
||||
"lastCharge": "1701072080,0,17.39\n",
|
||||
"time": 1701000000,
|
||||
"isUpToDate": true,
|
||||
"isSessionActive": true,
|
||||
"conflictInSharingConfig": false,
|
||||
"isStaticIp": false,
|
||||
"status": 67
|
||||
}
|
|
@ -0,0 +1,635 @@
|
|||
# serializer version: 1
|
||||
# name: test_sensors[sensor.technove_station_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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Current',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.75',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_current]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.75',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_input_voltage-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_input_voltage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Input voltage',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'voltage_in',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_voltage_in',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_input_voltage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'TechnoVE Station Input voltage',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_input_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '238',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_input_voltage]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'TechnoVE Station Input voltage',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_input_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '238',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_last_session_energy_usage-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_last_session_energy_usage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last session energy usage',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_session',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_energy_session',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_last_session_energy_usage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'TechnoVE Station Last session energy usage',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_last_session_energy_usage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '12.34',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_last_session_energy_usage]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'TechnoVE Station Last session energy usage',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_last_session_energy_usage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '12.34',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_max_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Max current',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_current',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_max_current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Max current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_max_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '24',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_current]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Max current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_max_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '24',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_station_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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_max_station_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Max station current',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_station_current',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_max_station_current',
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_station_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Max station current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_max_station_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_max_station_current]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'TechnoVE Station Max station current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_max_station_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_output_voltage-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_output_voltage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Output voltage',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'voltage_out',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_voltage_out',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_output_voltage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'TechnoVE Station Output voltage',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_output_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '238',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_output_voltage]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'TechnoVE Station Output voltage',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_output_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '238',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_signal_strength-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_signal_strength',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_rssi',
|
||||
'unit_of_measurement': 'dBm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_signal_strength-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'TechnoVE Station Signal strength',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'dBm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_signal_strength',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-82',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_signal_strength]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'TechnoVE Station Signal strength',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'dBm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_signal_strength',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-82',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_status-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'unplugged',
|
||||
'plugged_waiting',
|
||||
'plugged_charging',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_status',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Status',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'status',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_status-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'TechnoVE Station Status',
|
||||
'options': list([
|
||||
'unplugged',
|
||||
'plugged_waiting',
|
||||
'plugged_charging',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_status',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'plugged_charging',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_status]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'TechnoVE Station Status',
|
||||
'options': list([
|
||||
'unplugged',
|
||||
'plugged_waiting',
|
||||
'plugged_charging',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_status',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'plugged_charging',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_total_energy_usage-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_total_energy_usage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy usage',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_total',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_energy_total',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_total_energy_usage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'TechnoVE Station Total energy usage',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_total_energy_usage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1234',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_total_energy_usage]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'TechnoVE Station Total energy usage',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_total_energy_usage',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1234',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_wi_fi_network_name-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.technove_station_wi_fi_network_name',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi network name',
|
||||
'platform': 'technove',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ssid',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:BB_ssid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_wi_fi_network_name-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'TechnoVE Station Wi-Fi network name',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_wi_fi_network_name',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Connecting...',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.technove_station_wi_fi_network_name]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'TechnoVE Station Wi-Fi network name',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.technove_station_wi_fi_network_name',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Connecting...',
|
||||
})
|
||||
# ---
|
|
@ -0,0 +1,104 @@
|
|||
"""Tests for the TechnoVE config flow."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from technove import TechnoVEConnectionError
|
||||
|
||||
from homeassistant.components.technove.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry", "mock_technove")
|
||||
async def test_full_user_flow_implementation(hass: HomeAssistant) -> None:
|
||||
"""Test the full manual user flow from start to finish."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result.get("step_id") == "user"
|
||||
assert result.get("type") == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_HOST: "192.168.1.123"}
|
||||
)
|
||||
|
||||
assert result.get("title") == "TechnoVE Station"
|
||||
assert result.get("type") == FlowResultType.CREATE_ENTRY
|
||||
assert "data" in result
|
||||
assert result["data"][CONF_HOST] == "192.168.1.123"
|
||||
assert "result" in result
|
||||
assert result["result"].unique_id == "AA:AA:AA:AA:AA:BB"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_technove")
|
||||
async def test_user_device_exists_abort(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_technove: MagicMock,
|
||||
) -> None:
|
||||
"""Test we abort the config flow if TechnoVE station is already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "192.168.1.123"},
|
||||
)
|
||||
|
||||
assert result.get("type") == FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
async def test_connection_error(hass: HomeAssistant, mock_technove: MagicMock) -> None:
|
||||
"""Test we show user form on TechnoVE connection error."""
|
||||
mock_technove.update.side_effect = TechnoVEConnectionError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "example.com"},
|
||||
)
|
||||
|
||||
assert result.get("type") == FlowResultType.FORM
|
||||
assert result.get("step_id") == "user"
|
||||
assert result.get("errors") == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry", "mock_technove")
|
||||
async def test_full_user_flow_with_error(
|
||||
hass: HomeAssistant, mock_technove: MagicMock
|
||||
) -> None:
|
||||
"""Test the full manual user flow from start to finish with some errors in the middle."""
|
||||
mock_technove.update.side_effect = TechnoVEConnectionError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result.get("step_id") == "user"
|
||||
assert result.get("type") == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_HOST: "192.168.1.123"}
|
||||
)
|
||||
|
||||
assert result.get("type") == FlowResultType.FORM
|
||||
assert result.get("step_id") == "user"
|
||||
assert result.get("errors") == {"base": "cannot_connect"}
|
||||
|
||||
mock_technove.update.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_HOST: "192.168.1.123"}
|
||||
)
|
||||
|
||||
assert result.get("title") == "TechnoVE Station"
|
||||
assert result.get("type") == FlowResultType.CREATE_ENTRY
|
||||
assert "data" in result
|
||||
assert result["data"][CONF_HOST] == "192.168.1.123"
|
||||
assert "result" in result
|
||||
assert result["result"].unique_id == "AA:AA:AA:AA:AA:BB"
|
|
@ -0,0 +1,36 @@
|
|||
"""Tests for the TechnoVE integration."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from technove import TechnoVEConnectionError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_async_setup_entry(
|
||||
hass: HomeAssistant, init_integration: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test a successful setup entry and unload."""
|
||||
|
||||
init_integration.add_to_hass(hass)
|
||||
assert init_integration.state is ConfigEntryState.LOADED
|
||||
|
||||
assert await hass.config_entries.async_unload(init_integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert init_integration.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_async_setup_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_technove: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test a connection error after setup."""
|
||||
mock_technove.update.side_effect = TechnoVEConnectionError
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
@ -0,0 +1,97 @@
|
|||
"""Tests for the TechnoVE sensor platform."""
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
from technove import Status, TechnoVEError
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_technove")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test the creation and values of the TechnoVE sensors."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entity_registry = er.async_get(hass)
|
||||
entity_entries = er.async_entries_for_config_entry(
|
||||
entity_registry, mock_config_entry.entry_id
|
||||
)
|
||||
|
||||
assert entity_entries
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
||||
assert hass.states.get(entity_entry.entity_id) == snapshot(
|
||||
name=f"{entity_entry.entity_id}-state"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entity_id",
|
||||
(
|
||||
"sensor.technove_station_signal_strength",
|
||||
"sensor.technove_station_wi_fi_network_name",
|
||||
),
|
||||
)
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_disabled_by_default_sensors(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry, entity_id: str
|
||||
) -> None:
|
||||
"""Test the disabled by default TechnoVE sensors."""
|
||||
assert hass.states.get(entity_id) is None
|
||||
|
||||
assert (entry := entity_registry.async_get(entity_id))
|
||||
assert entry.disabled
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_no_wifi_support(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_technove: MagicMock,
|
||||
) -> None:
|
||||
"""Test missing Wi-Fi information from TechnoVE device."""
|
||||
# Remove Wi-Fi info
|
||||
device = mock_technove.update.return_value
|
||||
device.info.network_ssid = None
|
||||
|
||||
# Setup
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get("sensor.technove_station_wi_fi_network_name"))
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_sensor_update_failure(
|
||||
hass: HomeAssistant,
|
||||
mock_technove: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test coordinator update failure."""
|
||||
entity_id = "sensor.technove_station_status"
|
||||
|
||||
assert hass.states.get(entity_id).state == Status.PLUGGED_CHARGING.value
|
||||
|
||||
freezer.tick(timedelta(minutes=5, seconds=1))
|
||||
async_fire_time_changed(hass)
|
||||
mock_technove.update.side_effect = TechnoVEError("Test error")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
Loading…
Reference in New Issue