mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
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.tami4.*
|
||||||
homeassistant.components.tautulli.*
|
homeassistant.components.tautulli.*
|
||||||
homeassistant.components.tcp.*
|
homeassistant.components.tcp.*
|
||||||
|
homeassistant.components.technove.*
|
||||||
homeassistant.components.tedee.*
|
homeassistant.components.tedee.*
|
||||||
homeassistant.components.text.*
|
homeassistant.components.text.*
|
||||||
homeassistant.components.threshold.*
|
homeassistant.components.threshold.*
|
||||||
|
@ -1328,6 +1328,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/tasmota/ @emontnemery
|
/tests/components/tasmota/ @emontnemery
|
||||||
/homeassistant/components/tautulli/ @ludeeus @tkdrob
|
/homeassistant/components/tautulli/ @ludeeus @tkdrob
|
||||||
/tests/components/tautulli/ @ludeeus @tkdrob
|
/tests/components/tautulli/ @ludeeus @tkdrob
|
||||||
|
/homeassistant/components/technove/ @Moustachauve
|
||||||
|
/tests/components/technove/ @Moustachauve
|
||||||
/homeassistant/components/tedee/ @patrickhilker @zweckj
|
/homeassistant/components/tedee/ @patrickhilker @zweckj
|
||||||
/tests/components/tedee/ @patrickhilker @zweckj
|
/tests/components/tedee/ @patrickhilker @zweckj
|
||||||
/homeassistant/components/tellduslive/ @fredrike
|
/homeassistant/components/tellduslive/ @fredrike
|
||||||
|
31
homeassistant/components/technove/__init__.py
Normal file
31
homeassistant/components/technove/__init__.py
Normal file
@ -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
|
50
homeassistant/components/technove/config_flow.py
Normal file
50
homeassistant/components/technove/config_flow.py
Normal file
@ -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()
|
8
homeassistant/components/technove/const.py
Normal file
8
homeassistant/components/technove/const.py
Normal file
@ -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)
|
40
homeassistant/components/technove/coordinator.py
Normal file
40
homeassistant/components/technove/coordinator.py
Normal file
@ -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
|
26
homeassistant/components/technove/entity.py
Normal file
26
homeassistant/components/technove/entity.py
Normal file
@ -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,
|
||||||
|
)
|
10
homeassistant/components/technove/manifest.json
Normal file
10
homeassistant/components/technove/manifest.json
Normal file
@ -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"]
|
||||||
|
}
|
161
homeassistant/components/technove/sensor.py
Normal file
161
homeassistant/components/technove/sensor.py
Normal file
@ -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)
|
56
homeassistant/components/technove/strings.json
Normal file
56
homeassistant/components/technove/strings.json
Normal file
@ -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",
|
"tankerkoenig",
|
||||||
"tasmota",
|
"tasmota",
|
||||||
"tautulli",
|
"tautulli",
|
||||||
|
"technove",
|
||||||
"tedee",
|
"tedee",
|
||||||
"tellduslive",
|
"tellduslive",
|
||||||
"tesla_wall_connector",
|
"tesla_wall_connector",
|
||||||
|
@ -5837,6 +5837,12 @@
|
|||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
},
|
},
|
||||||
|
"technove": {
|
||||||
|
"name": "TechnoVE",
|
||||||
|
"integration_type": "device",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "local_polling"
|
||||||
|
},
|
||||||
"ted5000": {
|
"ted5000": {
|
||||||
"name": "The Energy Detective TED5000",
|
"name": "The Energy Detective TED5000",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
|
10
mypy.ini
10
mypy.ini
@ -3802,6 +3802,16 @@ disallow_untyped_defs = true
|
|||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = 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.*]
|
[mypy-homeassistant.components.tedee.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
@ -2264,6 +2264,9 @@ python-songpal==0.16
|
|||||||
# homeassistant.components.tado
|
# homeassistant.components.tado
|
||||||
python-tado==0.17.3
|
python-tado==0.17.3
|
||||||
|
|
||||||
|
# homeassistant.components.technove
|
||||||
|
python-technove==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
|
|
||||||
|
@ -1725,6 +1725,9 @@ python-songpal==0.16
|
|||||||
# homeassistant.components.tado
|
# homeassistant.components.tado
|
||||||
python-tado==0.17.3
|
python-tado==0.17.3
|
||||||
|
|
||||||
|
# homeassistant.components.technove
|
||||||
|
python-technove==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
|
|
||||||
|
1
tests/components/technove/__init__.py
Normal file
1
tests/components/technove/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the TechnoVE integration."""
|
66
tests/components/technove/conftest.py
Normal file
66
tests/components/technove/conftest.py
Normal file
@ -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
|
27
tests/components/technove/fixtures/station_charging.json
Normal file
27
tests/components/technove/fixtures/station_charging.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"voltageIn": 238,
|
||||||
|
"voltageOut": 238,
|
||||||
|
"maxStationCurrent": 32,
|
||||||
|
"maxCurrent": 24,
|
||||||
|
"current": 23.75,
|
||||||
|
"network_ssid": "Connecting...",
|
||||||
|
"id": "AA:AA:AA:AA:AA:BB",
|
||||||
|
"auto_charge": true,
|
||||||
|
"highChargePeriodActive": false,
|
||||||
|
"normalPeriodActive": false,
|
||||||
|
"maxChargePourcentage": 0.9,
|
||||||
|
"isBatteryProtected": false,
|
||||||
|
"inSharingMode": true,
|
||||||
|
"energySession": 12.34,
|
||||||
|
"energyTotal": 1234,
|
||||||
|
"version": "1.82",
|
||||||
|
"rssi": -82,
|
||||||
|
"name": "TechnoVE Station",
|
||||||
|
"lastCharge": "1701072080,0,17.39\n",
|
||||||
|
"time": 1701000000,
|
||||||
|
"isUpToDate": true,
|
||||||
|
"isSessionActive": true,
|
||||||
|
"conflictInSharingConfig": false,
|
||||||
|
"isStaticIp": false,
|
||||||
|
"status": 67
|
||||||
|
}
|
635
tests/components/technove/snapshots/test_sensor.ambr
Normal file
635
tests/components/technove/snapshots/test_sensor.ambr
Normal file
@ -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...',
|
||||||
|
})
|
||||||
|
# ---
|
104
tests/components/technove/test_config_flow.py
Normal file
104
tests/components/technove/test_config_flow.py
Normal file
@ -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"
|
36
tests/components/technove/test_init.py
Normal file
36
tests/components/technove/test_init.py
Normal file
@ -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
|
97
tests/components/technove/test_sensor.py
Normal file
97
tests/components/technove/test_sensor.py
Normal file
@ -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…
x
Reference in New Issue
Block a user