mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Remove sunweg integration (#124230)
* chore(sunweg): remove sunweg integration * Update homeassistant/components/sunweg/strings.json Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com> * Update homeassistant/components/sunweg/manifest.json Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com> * feat: added async remove entry * Clean setup_entry; add tests --------- Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com> Co-authored-by: abmantis <amfcalt@gmail.com>
This commit is contained in:
parent
68d1a3c0a2
commit
3d49000c75
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@ -1480,8 +1480,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/suez_water/ @ooii @jb101010-2
|
||||
/homeassistant/components/sun/ @Swamp-Ig
|
||||
/tests/components/sun/ @Swamp-Ig
|
||||
/homeassistant/components/sunweg/ @rokam
|
||||
/tests/components/sunweg/ @rokam
|
||||
/homeassistant/components/supla/ @mwegrzynek
|
||||
/homeassistant/components/surepetcare/ @benleb @danielhiversen
|
||||
/tests/components/surepetcare/ @benleb @danielhiversen
|
||||
|
@ -1,197 +1,39 @@
|
||||
"""The Sun WEG inverter sensor integration."""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from sunweg.api import APIHelper
|
||||
from sunweg.plant import Plant
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.typing import StateType, UndefinedType
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import CONF_PLANT_ID, DOMAIN, PLATFORMS, DeviceType
|
||||
|
||||
SCAN_INTERVAL = datetime.timedelta(minutes=5)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = "sunweg"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||
) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
||||
"""Load the saved entities."""
|
||||
api = APIHelper(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD])
|
||||
if not await hass.async_add_executor_job(api.authenticate):
|
||||
raise ConfigEntryAuthFailed("Username or Password may be incorrect!")
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = SunWEGData(
|
||||
api, entry.data[CONF_PLANT_ID]
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"issue": "https://github.com/rokam/sunweg/issues/13",
|
||||
"entries": "/config/integrations/integration/sunweg",
|
||||
},
|
||||
)
|
||||
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."""
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if len(hass.data[DOMAIN]) == 0:
|
||||
hass.data.pop(DOMAIN)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
class SunWEGData:
|
||||
"""The class for handling data retrieval."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: APIHelper,
|
||||
plant_id: int,
|
||||
) -> None:
|
||||
"""Initialize the probe."""
|
||||
|
||||
self.api = api
|
||||
self.plant_id = plant_id
|
||||
self.data: Plant = None
|
||||
self.previous_values: dict = {}
|
||||
|
||||
@Throttle(SCAN_INTERVAL)
|
||||
def update(self) -> None:
|
||||
"""Update probe data."""
|
||||
_LOGGER.debug("Updating data for plant %s", self.plant_id)
|
||||
try:
|
||||
self.data = self.api.plant(self.plant_id)
|
||||
for inverter in self.data.inverters:
|
||||
self.api.complete_inverter(inverter)
|
||||
except json.decoder.JSONDecodeError:
|
||||
_LOGGER.error("Unable to fetch data from SunWEG server")
|
||||
_LOGGER.debug("Finished updating data for plant %s", self.plant_id)
|
||||
|
||||
def get_api_value(
|
||||
self,
|
||||
variable: str,
|
||||
device_type: DeviceType,
|
||||
inverter_id: int = 0,
|
||||
deep_name: str | None = None,
|
||||
):
|
||||
"""Retrieve from a Plant the desired variable value."""
|
||||
if device_type == DeviceType.TOTAL:
|
||||
return self.data.__dict__.get(variable)
|
||||
|
||||
inverter_list = [i for i in self.data.inverters if i.id == inverter_id]
|
||||
if len(inverter_list) == 0:
|
||||
return None
|
||||
inverter = inverter_list[0]
|
||||
|
||||
if device_type == DeviceType.INVERTER:
|
||||
return inverter.__dict__.get(variable)
|
||||
if device_type == DeviceType.PHASE:
|
||||
for phase in inverter.phases:
|
||||
if phase.name == deep_name:
|
||||
return phase.__dict__.get(variable)
|
||||
elif device_type == DeviceType.STRING:
|
||||
for mppt in inverter.mppts:
|
||||
for string in mppt.strings:
|
||||
if string.name == deep_name:
|
||||
return string.__dict__.get(variable)
|
||||
return None
|
||||
|
||||
def get_data(
|
||||
self,
|
||||
*,
|
||||
api_variable_key: str,
|
||||
api_variable_unit: str | None,
|
||||
deep_name: str | None,
|
||||
device_type: DeviceType,
|
||||
inverter_id: int,
|
||||
name: str | UndefinedType | None,
|
||||
native_unit_of_measurement: str | None,
|
||||
never_resets: bool,
|
||||
previous_value_drop_threshold: float | None,
|
||||
) -> tuple[StateType | datetime.datetime, str | None]:
|
||||
"""Get the data."""
|
||||
_LOGGER.debug(
|
||||
"Data request for: %s",
|
||||
name,
|
||||
)
|
||||
variable = api_variable_key
|
||||
previous_unit = native_unit_of_measurement
|
||||
api_value = self.get_api_value(variable, device_type, inverter_id, deep_name)
|
||||
previous_value = self.previous_values.get(variable)
|
||||
return_value = api_value
|
||||
if api_variable_unit is not None:
|
||||
native_unit_of_measurement = self.get_api_value(
|
||||
api_variable_unit,
|
||||
device_type,
|
||||
inverter_id,
|
||||
deep_name,
|
||||
)
|
||||
|
||||
# If we have a 'drop threshold' specified, then check it and correct if needed
|
||||
if (
|
||||
previous_value_drop_threshold is not None
|
||||
and previous_value is not None
|
||||
and api_value is not None
|
||||
and previous_unit == native_unit_of_measurement
|
||||
):
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"%s - Drop threshold specified (%s), checking for drop... API"
|
||||
" Value: %s, Previous Value: %s"
|
||||
),
|
||||
name,
|
||||
previous_value_drop_threshold,
|
||||
api_value,
|
||||
previous_value,
|
||||
)
|
||||
diff = float(api_value) - float(previous_value)
|
||||
|
||||
# Check if the value has dropped (negative value i.e. < 0) and it has only
|
||||
# dropped by a small amount, if so, use the previous value.
|
||||
# Note - The energy dashboard takes care of drops within 10%
|
||||
# of the current value, however if the value is low e.g. 0.2
|
||||
# and drops by 0.1 it classes as a reset.
|
||||
if -(previous_value_drop_threshold) <= diff < 0:
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"Diff is negative, but only by a small amount therefore not a"
|
||||
" nightly reset, using previous value (%s) instead of api value"
|
||||
" (%s)"
|
||||
),
|
||||
previous_value,
|
||||
api_value,
|
||||
)
|
||||
return_value = previous_value
|
||||
else:
|
||||
_LOGGER.debug("%s - No drop detected, using API value", name)
|
||||
|
||||
# Lifetime total values should always be increasing, they will never reset,
|
||||
# however the API sometimes returns 0 values when the clock turns to 00:00
|
||||
# local time in that scenario we should just return the previous value
|
||||
# Scenarios:
|
||||
# 1 - System has a genuine 0 value when it it first commissioned:
|
||||
# - will return 0 until a non-zero value is registered
|
||||
# 2 - System has been running fine but temporarily resets to 0 briefly
|
||||
# at midnight:
|
||||
# - will return the previous value
|
||||
# 3 - HA is restarted during the midnight 'outage' - Not handled:
|
||||
# - Previous value will not exist meaning 0 will be returned
|
||||
# - This is an edge case that would be better handled by looking
|
||||
# up the previous value of the entity from the recorder
|
||||
if never_resets and api_value == 0 and previous_value:
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"API value is 0, but this value should never reset, returning"
|
||||
" previous value (%s) instead"
|
||||
),
|
||||
previous_value,
|
||||
)
|
||||
return_value = previous_value
|
||||
|
||||
self.previous_values[variable] = return_value
|
||||
|
||||
return (return_value, native_unit_of_measurement)
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Remove a config entry."""
|
||||
if not hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||
# Remove any remaining disabled or ignored entries
|
||||
for _entry in hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))
|
||||
|
@ -1,129 +1,11 @@
|
||||
"""Config flow for Sun WEG integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
|
||||
from sunweg.api import APIHelper, SunWegApiError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import CONF_PLANT_ID, DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class SunWEGConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow class."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialise sun weg server flow."""
|
||||
self.api: APIHelper = None
|
||||
self.data: dict[str, Any] = {}
|
||||
|
||||
@callback
|
||||
def _async_show_user_form(self, step_id: str, errors=None) -> ConfigFlowResult:
|
||||
"""Show the form to the user."""
|
||||
default_username = ""
|
||||
if CONF_USERNAME in self.data:
|
||||
default_username = self.data[CONF_USERNAME]
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME, default=default_username): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id=step_id, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
def _set_auth_data(
|
||||
self, step: str, username: str, password: str
|
||||
) -> ConfigFlowResult | None:
|
||||
"""Set username and password."""
|
||||
if self.api:
|
||||
# Set username and password
|
||||
self.api.username = username
|
||||
self.api.password = password
|
||||
else:
|
||||
# Initialise the library with the username & password
|
||||
self.api = APIHelper(username, password)
|
||||
|
||||
try:
|
||||
if not self.api.authenticate():
|
||||
return self._async_show_user_form(step, {"base": "invalid_auth"})
|
||||
except SunWegApiError:
|
||||
return self._async_show_user_form(step, {"base": "timeout_connect"})
|
||||
|
||||
return None
|
||||
|
||||
async def async_step_user(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Handle the start of the config flow."""
|
||||
if not user_input:
|
||||
return self._async_show_user_form("user")
|
||||
|
||||
# Store authentication info
|
||||
self.data = user_input
|
||||
|
||||
conf_result = await self.hass.async_add_executor_job(
|
||||
self._set_auth_data,
|
||||
"user",
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_PASSWORD],
|
||||
)
|
||||
|
||||
return await self.async_step_plant() if conf_result is None else conf_result
|
||||
|
||||
async def async_step_plant(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Handle adding a "plant" to Home Assistant."""
|
||||
plant_list = await self.hass.async_add_executor_job(self.api.listPlants)
|
||||
|
||||
if len(plant_list) == 0:
|
||||
return self.async_abort(reason="no_plants")
|
||||
|
||||
plants = {plant.id: plant.name for plant in plant_list}
|
||||
|
||||
if user_input is None and len(plant_list) > 1:
|
||||
data_schema = vol.Schema({vol.Required(CONF_PLANT_ID): vol.In(plants)})
|
||||
|
||||
return self.async_show_form(step_id="plant", data_schema=data_schema)
|
||||
|
||||
if user_input is None and len(plant_list) == 1:
|
||||
user_input = {CONF_PLANT_ID: plant_list[0].id}
|
||||
|
||||
user_input[CONF_NAME] = plants[user_input[CONF_PLANT_ID]]
|
||||
await self.async_set_unique_id(user_input[CONF_PLANT_ID])
|
||||
self._abort_if_unique_id_configured()
|
||||
self.data.update(user_input)
|
||||
return self.async_create_entry(title=self.data[CONF_NAME], data=self.data)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthorization request from SunWEG."""
|
||||
self.data.update(entry_data)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthorization flow."""
|
||||
if user_input is None:
|
||||
return self._async_show_user_form("reauth_confirm")
|
||||
|
||||
self.data.update(user_input)
|
||||
conf_result = await self.hass.async_add_executor_job(
|
||||
self._set_auth_data,
|
||||
"reauth_confirm",
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_PASSWORD],
|
||||
)
|
||||
if conf_result is not None:
|
||||
return conf_result
|
||||
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(), data=self.data
|
||||
)
|
||||
|
@ -1,25 +0,0 @@
|
||||
"""Define constants for the Sun WEG component."""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
|
||||
class DeviceType(Enum):
|
||||
"""Device Type Enum."""
|
||||
|
||||
TOTAL = 1
|
||||
INVERTER = 2
|
||||
PHASE = 3
|
||||
STRING = 4
|
||||
|
||||
|
||||
CONF_PLANT_ID = "plant_id"
|
||||
|
||||
DEFAULT_PLANT_ID = 0
|
||||
|
||||
DEFAULT_NAME = "Sun WEG"
|
||||
|
||||
DOMAIN = "sunweg"
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"domain": "sunweg",
|
||||
"name": "Sun WEG",
|
||||
"codeowners": ["@rokam"],
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sunweg",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["sunweg"],
|
||||
"requirements": ["sunweg==3.0.2"]
|
||||
"loggers": [],
|
||||
"requirements": []
|
||||
}
|
||||
|
@ -1,178 +0,0 @@
|
||||
"""Read status of SunWEG inverters."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
from sunweg.api import APIHelper
|
||||
from sunweg.device import Inverter
|
||||
from sunweg.plant import Plant
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .. import SunWEGData
|
||||
from ..const import CONF_PLANT_ID, DEFAULT_PLANT_ID, DOMAIN, DeviceType
|
||||
from .inverter import INVERTER_SENSOR_TYPES
|
||||
from .phase import PHASE_SENSOR_TYPES
|
||||
from .sensor_entity_description import SunWEGSensorEntityDescription
|
||||
from .string import STRING_SENSOR_TYPES
|
||||
from .total import TOTAL_SENSOR_TYPES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_device_list(
|
||||
api: APIHelper, config: MappingProxyType[str, Any]
|
||||
) -> tuple[list[Inverter], int]:
|
||||
"""Retrieve the device list for the selected plant."""
|
||||
plant_id = int(config[CONF_PLANT_ID])
|
||||
|
||||
if plant_id == DEFAULT_PLANT_ID:
|
||||
plant_info: list[Plant] = api.listPlants()
|
||||
plant_id = plant_info[0].id
|
||||
|
||||
devices: list[Inverter] = []
|
||||
# Get a list of devices for specified plant to add sensors for.
|
||||
for inverter in api.plant(plant_id).inverters:
|
||||
api.complete_inverter(inverter)
|
||||
devices.append(inverter)
|
||||
return (devices, plant_id)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the SunWEG sensor."""
|
||||
name = config_entry.data[CONF_NAME]
|
||||
|
||||
probe: SunWEGData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
devices, plant_id = await hass.async_add_executor_job(
|
||||
get_device_list, probe.api, config_entry.data
|
||||
)
|
||||
|
||||
entities = [
|
||||
SunWEGInverter(
|
||||
probe,
|
||||
name=f"{name} Total",
|
||||
unique_id=f"{plant_id}-{description.key}",
|
||||
description=description,
|
||||
device_type=DeviceType.TOTAL,
|
||||
)
|
||||
for description in TOTAL_SENSOR_TYPES
|
||||
]
|
||||
|
||||
# Add sensors for each device in the specified plant.
|
||||
entities.extend(
|
||||
[
|
||||
SunWEGInverter(
|
||||
probe,
|
||||
name=f"{device.name}",
|
||||
unique_id=f"{device.sn}-{description.key}",
|
||||
description=description,
|
||||
device_type=DeviceType.INVERTER,
|
||||
inverter_id=device.id,
|
||||
)
|
||||
for device in devices
|
||||
for description in INVERTER_SENSOR_TYPES
|
||||
]
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
[
|
||||
SunWEGInverter(
|
||||
probe,
|
||||
name=f"{device.name} {phase.name}",
|
||||
unique_id=f"{device.sn}-{phase.name}-{description.key}",
|
||||
description=description,
|
||||
inverter_id=device.id,
|
||||
device_type=DeviceType.PHASE,
|
||||
deep_name=phase.name,
|
||||
)
|
||||
for device in devices
|
||||
for phase in device.phases
|
||||
for description in PHASE_SENSOR_TYPES
|
||||
]
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
[
|
||||
SunWEGInverter(
|
||||
probe,
|
||||
name=f"{device.name} {string.name}",
|
||||
unique_id=f"{device.sn}-{string.name}-{description.key}",
|
||||
description=description,
|
||||
inverter_id=device.id,
|
||||
device_type=DeviceType.STRING,
|
||||
deep_name=string.name,
|
||||
)
|
||||
for device in devices
|
||||
for mppt in device.mppts
|
||||
for string in mppt.strings
|
||||
for description in STRING_SENSOR_TYPES
|
||||
]
|
||||
)
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class SunWEGInverter(SensorEntity):
|
||||
"""Representation of a SunWEG Sensor."""
|
||||
|
||||
entity_description: SunWEGSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
probe: SunWEGData,
|
||||
name: str,
|
||||
unique_id: str,
|
||||
description: SunWEGSensorEntityDescription,
|
||||
device_type: DeviceType,
|
||||
inverter_id: int = 0,
|
||||
deep_name: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize a sensor."""
|
||||
self.probe = probe
|
||||
self.entity_description = description
|
||||
self.device_type = device_type
|
||||
self.inverter_id = inverter_id
|
||||
self.deep_name = deep_name
|
||||
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_icon = (
|
||||
description.icon if description.icon is not None else "mdi:solar-power"
|
||||
)
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, str(probe.plant_id))},
|
||||
manufacturer="SunWEG",
|
||||
name=name,
|
||||
)
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the latest data from the Sun WEG API and updates the state."""
|
||||
self.probe.update()
|
||||
(
|
||||
self._attr_native_value,
|
||||
self._attr_native_unit_of_measurement,
|
||||
) = self.probe.get_data(
|
||||
api_variable_key=self.entity_description.api_variable_key,
|
||||
api_variable_unit=self.entity_description.api_variable_unit,
|
||||
deep_name=self.deep_name,
|
||||
device_type=self.device_type,
|
||||
inverter_id=self.inverter_id,
|
||||
name=self.entity_description.name,
|
||||
native_unit_of_measurement=self.native_unit_of_measurement,
|
||||
never_resets=self.entity_description.never_resets,
|
||||
previous_value_drop_threshold=self.entity_description.previous_value_drop_threshold,
|
||||
)
|
@ -1,70 +0,0 @@
|
||||
"""SunWEG Sensor definitions for the Inverter type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.const import (
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
|
||||
from .sensor_entity_description import SunWEGSensorEntityDescription
|
||||
|
||||
INVERTER_SENSOR_TYPES: tuple[SunWEGSensorEntityDescription, ...] = (
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_energy_today",
|
||||
name="Energy today",
|
||||
api_variable_key="_today_energy",
|
||||
api_variable_unit="_today_energy_metric",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_energy_total",
|
||||
name="Lifetime energy output",
|
||||
api_variable_key="_total_energy",
|
||||
api_variable_unit="_total_energy_metric",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
suggested_display_precision=1,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
never_resets=True,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_frequency",
|
||||
name="AC frequency",
|
||||
api_variable_key="_frequency",
|
||||
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
||||
device_class=SensorDeviceClass.FREQUENCY,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_current_wattage",
|
||||
name="Output power",
|
||||
api_variable_key="_power",
|
||||
api_variable_unit="_power_metric",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_temperature",
|
||||
name="Temperature",
|
||||
api_variable_key="_temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
icon="mdi:temperature-celsius",
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="inverter_power_factor",
|
||||
name="Power Factor",
|
||||
api_variable_key="_power_factor",
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
)
|
@ -1,27 +0,0 @@
|
||||
"""SunWEG Sensor definitions for the Phase type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import UnitOfElectricCurrent, UnitOfElectricPotential
|
||||
|
||||
from .sensor_entity_description import SunWEGSensorEntityDescription
|
||||
|
||||
PHASE_SENSOR_TYPES: tuple[SunWEGSensorEntityDescription, ...] = (
|
||||
SunWEGSensorEntityDescription(
|
||||
key="voltage",
|
||||
name="Voltage",
|
||||
api_variable_key="_voltage",
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="amperage",
|
||||
name="Amperage",
|
||||
api_variable_key="_amperage",
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
"""Sensor Entity Description for the SunWEG integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import SensorEntityDescription
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SunWEGRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
api_variable_key: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SunWEGSensorEntityDescription(SensorEntityDescription, SunWEGRequiredKeysMixin):
|
||||
"""Describes SunWEG sensor entity."""
|
||||
|
||||
api_variable_unit: str | None = None
|
||||
previous_value_drop_threshold: float | None = None
|
||||
never_resets: bool = False
|
||||
icon: str | None = None
|
@ -1,27 +0,0 @@
|
||||
"""SunWEG Sensor definitions for the String type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import UnitOfElectricCurrent, UnitOfElectricPotential
|
||||
|
||||
from .sensor_entity_description import SunWEGSensorEntityDescription
|
||||
|
||||
STRING_SENSOR_TYPES: tuple[SunWEGSensorEntityDescription, ...] = (
|
||||
SunWEGSensorEntityDescription(
|
||||
key="voltage",
|
||||
name="Voltage",
|
||||
api_variable_key="_voltage",
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="amperage",
|
||||
name="Amperage",
|
||||
api_variable_key="_amperage",
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
)
|
@ -1,50 +0,0 @@
|
||||
"""SunWEG Sensor definitions for Totals."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.const import UnitOfEnergy, UnitOfPower
|
||||
|
||||
from .sensor_entity_description import SunWEGSensorEntityDescription
|
||||
|
||||
TOTAL_SENSOR_TYPES: tuple[SunWEGSensorEntityDescription, ...] = (
|
||||
SunWEGSensorEntityDescription(
|
||||
key="total_money_total",
|
||||
name="Money lifetime",
|
||||
api_variable_key="_saving",
|
||||
icon="mdi:cash",
|
||||
native_unit_of_measurement="R$",
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="total_energy_today",
|
||||
name="Energy Today",
|
||||
api_variable_key="_today_energy",
|
||||
api_variable_unit="_today_energy_metric",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="total_output_power",
|
||||
name="Output Power",
|
||||
api_variable_key="_total_power",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="total_energy_output",
|
||||
name="Lifetime energy output",
|
||||
api_variable_key="_total_energy",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
never_resets=True,
|
||||
),
|
||||
SunWEGSensorEntityDescription(
|
||||
key="last_update",
|
||||
name="Last Update",
|
||||
api_variable_key="_last_update",
|
||||
device_class=SensorDeviceClass.DATE,
|
||||
),
|
||||
)
|
@ -1,35 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"no_plants": "No plants have been found on this account",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]"
|
||||
},
|
||||
"step": {
|
||||
"plant": {
|
||||
"data": {
|
||||
"plant_id": "Plant"
|
||||
},
|
||||
"title": "Select your plant"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"title": "Enter your Sun WEG information"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
}
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"title": "The SunWEG integration has been removed",
|
||||
"description": "The SunWEG integration has been removed from Home Assistant.\n\nThe library that Home Assistant uses to connect with SunWEG services, [doesn't work as expected anymore, demanding daily token renew]({issue}).\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing SunWEG integration entries]({entries})."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,12 +461,10 @@ filterwarnings = [
|
||||
# Modify app state for testing
|
||||
"ignore:Changing state of started or joined application is deprecated:DeprecationWarning:tests.components.http.test_ban",
|
||||
|
||||
# -- Tests
|
||||
# Ignore custom pytest marks
|
||||
"ignore:Unknown pytest.mark.disable_autouse_fixture:pytest.PytestUnknownMarkWarning:tests.components.met",
|
||||
"ignore:Unknown pytest.mark.dataset:pytest.PytestUnknownMarkWarning:tests.components.screenlogic",
|
||||
# https://github.com/rokam/sunweg/blob/3.1.0/sunweg/plant.py#L96 - v3.1.0 - 2024-10-02
|
||||
"ignore:The '(kwh_per_kwp|performance_rate)' property is deprecated and will return 0:DeprecationWarning:tests.components.sunweg.test_init",
|
||||
# -- Tests
|
||||
# Ignore custom pytest marks
|
||||
"ignore:Unknown pytest.mark.disable_autouse_fixture:pytest.PytestUnknownMarkWarning:tests.components.met",
|
||||
"ignore:Unknown pytest.mark.dataset:pytest.PytestUnknownMarkWarning:tests.components.screenlogic",
|
||||
|
||||
# -- design choice 3rd party
|
||||
# https://github.com/gwww/elkm1/blob/2.2.10/elkm1_lib/util.py#L8-L19
|
||||
|
3
requirements_all.txt
generated
3
requirements_all.txt
generated
@ -2830,9 +2830,6 @@ stringcase==1.2.0
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.7.13
|
||||
|
||||
# homeassistant.components.sunweg
|
||||
sunweg==3.0.2
|
||||
|
||||
# homeassistant.components.surepetcare
|
||||
surepy==0.9.0
|
||||
|
||||
|
3
requirements_test_all.txt
generated
3
requirements_test_all.txt
generated
@ -2289,9 +2289,6 @@ stringcase==1.2.0
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.7.13
|
||||
|
||||
# homeassistant.components.sunweg
|
||||
sunweg==3.0.2
|
||||
|
||||
# homeassistant.components.surepetcare
|
||||
surepy==0.9.0
|
||||
|
||||
|
@ -1050,7 +1050,6 @@
|
||||
"melnor": 42,
|
||||
"plaato": 45,
|
||||
"freedompro": 26,
|
||||
"sunweg": 3,
|
||||
"logi_circle": 18,
|
||||
"proxy": 16,
|
||||
"statsd": 4,
|
||||
|
@ -1 +1 @@
|
||||
"""Tests for the sunweg component."""
|
||||
"""Tests for the Sun WEG integration."""
|
||||
|
@ -1,22 +0,0 @@
|
||||
"""Common functions needed to setup tests for Sun WEG."""
|
||||
|
||||
from homeassistant.components.sunweg.const import CONF_PLANT_ID, DOMAIN
|
||||
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
SUNWEG_USER_INPUT = {
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "password",
|
||||
}
|
||||
|
||||
SUNWEG_MOCK_ENTRY = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=0,
|
||||
data={
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_PLANT_ID: 0,
|
||||
CONF_NAME: "Name",
|
||||
},
|
||||
)
|
@ -1,90 +0,0 @@
|
||||
"""Conftest for SunWEG tests."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from sunweg.device import MPPT, Inverter, Phase, String
|
||||
from sunweg.plant import Plant
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def string_fixture() -> String:
|
||||
"""Define String fixture."""
|
||||
return String("STR1", 450.3, 23.4, 0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mppt_fixture(string_fixture) -> MPPT:
|
||||
"""Define MPPT fixture."""
|
||||
mppt = MPPT("mppt")
|
||||
mppt.strings.append(string_fixture)
|
||||
return mppt
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def phase_fixture() -> Phase:
|
||||
"""Define Phase fixture."""
|
||||
return Phase("PhaseA", 120.0, 3.2, 0, 0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inverter_fixture(phase_fixture, mppt_fixture) -> Inverter:
|
||||
"""Define inverter fixture."""
|
||||
inverter = Inverter(
|
||||
21255,
|
||||
"INVERSOR01",
|
||||
"J63T233018RE074",
|
||||
23.2,
|
||||
0.0,
|
||||
0.0,
|
||||
"MWh",
|
||||
0,
|
||||
"kWh",
|
||||
0.0,
|
||||
1,
|
||||
0,
|
||||
"kW",
|
||||
)
|
||||
inverter.phases.append(phase_fixture)
|
||||
inverter.mppts.append(mppt_fixture)
|
||||
return inverter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def plant_fixture(inverter_fixture) -> Plant:
|
||||
"""Define Plant fixture."""
|
||||
plant = Plant(
|
||||
123456,
|
||||
"Plant #123",
|
||||
29.5,
|
||||
0.5,
|
||||
0,
|
||||
12.786912,
|
||||
24.0,
|
||||
"kWh",
|
||||
332.2,
|
||||
0.012296,
|
||||
datetime(2023, 2, 16, 14, 22, 37),
|
||||
)
|
||||
plant.inverters.append(inverter_fixture)
|
||||
return plant
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def plant_fixture_alternative(inverter_fixture) -> Plant:
|
||||
"""Define Plant fixture."""
|
||||
plant = Plant(
|
||||
123456,
|
||||
"Plant #123",
|
||||
29.5,
|
||||
0.5,
|
||||
0,
|
||||
12.786912,
|
||||
24.0,
|
||||
"kWh",
|
||||
332.2,
|
||||
0.012296,
|
||||
None,
|
||||
)
|
||||
plant.inverters.append(inverter_fixture)
|
||||
return plant
|
@ -1,223 +0,0 @@
|
||||
"""Tests for the Sun WEG server config flow."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from sunweg.api import APIHelper, SunWegApiError
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sunweg.const import CONF_PLANT_ID, DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from .common import SUNWEG_MOCK_ENTRY, SUNWEG_USER_INPUT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_show_authenticate_form(hass: HomeAssistant) -> None:
|
||||
"""Test that the setup form is served."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_incorrect_login(hass: HomeAssistant) -> None:
|
||||
"""Test that it shows the appropriate error when an incorrect username/password/server is entered."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch.object(APIHelper, "authenticate", return_value=False):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_server_unavailable(hass: HomeAssistant) -> None:
|
||||
"""Test when the SunWEG server don't respond."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
APIHelper, "authenticate", side_effect=SunWegApiError("Internal Server Error")
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "timeout_connect"}
|
||||
|
||||
|
||||
async def test_reauth(hass: HomeAssistant, plant_fixture, inverter_fixture) -> None:
|
||||
"""Test reauth flow."""
|
||||
mock_entry = SUNWEG_MOCK_ENTRY
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
entries = hass.config_entries.async_entries()
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data[CONF_USERNAME] == SUNWEG_MOCK_ENTRY.data[CONF_USERNAME]
|
||||
assert entries[0].data[CONF_PASSWORD] == SUNWEG_MOCK_ENTRY.data[CONF_PASSWORD]
|
||||
|
||||
result = await mock_entry.start_reauth_flow(hass)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
|
||||
with patch.object(APIHelper, "authenticate", return_value=False):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=SUNWEG_USER_INPUT,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
with patch.object(
|
||||
APIHelper, "authenticate", side_effect=SunWegApiError("Internal Server Error")
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=SUNWEG_USER_INPUT,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["errors"] == {"base": "timeout_connect"}
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(APIHelper, "listPlants", return_value=[plant_fixture]),
|
||||
patch.object(APIHelper, "plant", return_value=plant_fixture),
|
||||
patch.object(APIHelper, "inverter", return_value=inverter_fixture),
|
||||
patch.object(APIHelper, "complete_inverter"),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=SUNWEG_USER_INPUT,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
entries = hass.config_entries.async_entries()
|
||||
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data[CONF_USERNAME] == SUNWEG_USER_INPUT[CONF_USERNAME]
|
||||
assert entries[0].data[CONF_PASSWORD] == SUNWEG_USER_INPUT[CONF_PASSWORD]
|
||||
|
||||
|
||||
async def test_no_plants_on_account(hass: HomeAssistant) -> None:
|
||||
"""Test registering an integration with wrong auth then with no plants available."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch.object(APIHelper, "authenticate", return_value=False):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(APIHelper, "listPlants", return_value=[]),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "no_plants"
|
||||
|
||||
|
||||
async def test_multiple_plant_ids(hass: HomeAssistant, plant_fixture) -> None:
|
||||
"""Test registering an integration and finishing flow with an selected plant_id."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(
|
||||
APIHelper, "listPlants", return_value=[plant_fixture, plant_fixture]
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "plant"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_PLANT_ID: 123456}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_USERNAME] == SUNWEG_USER_INPUT[CONF_USERNAME]
|
||||
assert result["data"][CONF_PASSWORD] == SUNWEG_USER_INPUT[CONF_PASSWORD]
|
||||
assert result["data"][CONF_PLANT_ID] == 123456
|
||||
|
||||
|
||||
async def test_one_plant_on_account(hass: HomeAssistant, plant_fixture) -> None:
|
||||
"""Test registering an integration and finishing flow with current plant_id."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(
|
||||
APIHelper,
|
||||
"listPlants",
|
||||
return_value=[plant_fixture],
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_USERNAME] == SUNWEG_USER_INPUT[CONF_USERNAME]
|
||||
assert result["data"][CONF_PASSWORD] == SUNWEG_USER_INPUT[CONF_PASSWORD]
|
||||
assert result["data"][CONF_PLANT_ID] == 123456
|
||||
|
||||
|
||||
async def test_existing_plant_configured(hass: HomeAssistant, plant_fixture) -> None:
|
||||
"""Test entering an existing plant_id."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, unique_id=123456)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(
|
||||
APIHelper,
|
||||
"listPlants",
|
||||
return_value=[plant_fixture],
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], SUNWEG_USER_INPUT
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
@ -1,209 +1,79 @@
|
||||
"""Tests for the Sun WEG init."""
|
||||
"""Tests for the Sun WEG integration."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from sunweg.api import APIHelper, SunWegApiError
|
||||
|
||||
from homeassistant.components.sunweg import SunWEGData
|
||||
from homeassistant.components.sunweg.const import DOMAIN, DeviceType
|
||||
from homeassistant.components.sunweg.sensor.sensor_entity_description import (
|
||||
SunWEGSensorEntityDescription,
|
||||
from homeassistant.components.sunweg import DOMAIN
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
ConfigEntryDisabler,
|
||||
ConfigEntryState,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .common import SUNWEG_MOCK_ENTRY
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_methods(hass: HomeAssistant, plant_fixture, inverter_fixture) -> None:
|
||||
"""Test methods."""
|
||||
mock_entry = SUNWEG_MOCK_ENTRY
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
patch.object(APIHelper, "authenticate", return_value=True),
|
||||
patch.object(APIHelper, "listPlants", return_value=[plant_fixture]),
|
||||
patch.object(APIHelper, "plant", return_value=plant_fixture),
|
||||
patch.object(APIHelper, "inverter", return_value=inverter_fixture),
|
||||
patch.object(APIHelper, "complete_inverter"),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, mock_entry.data)
|
||||
await hass.async_block_till_done()
|
||||
assert await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
|
||||
async def test_setup_wrongpass(hass: HomeAssistant) -> None:
|
||||
"""Test setup with wrong pass."""
|
||||
mock_entry = SUNWEG_MOCK_ENTRY
|
||||
mock_entry.add_to_hass(hass)
|
||||
with patch.object(APIHelper, "authenticate", return_value=False):
|
||||
assert await async_setup_component(hass, DOMAIN, mock_entry.data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_setup_error_500(hass: HomeAssistant) -> None:
|
||||
"""Test setup with wrong pass."""
|
||||
mock_entry = SUNWEG_MOCK_ENTRY
|
||||
mock_entry.add_to_hass(hass)
|
||||
with patch.object(
|
||||
APIHelper, "authenticate", side_effect=SunWegApiError("Error 500")
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, mock_entry.data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_sunwegdata_update_exception() -> None:
|
||||
"""Test SunWEGData exception on update."""
|
||||
api = MagicMock()
|
||||
api.plant = MagicMock(side_effect=json.decoder.JSONDecodeError("Message", "Doc", 1))
|
||||
data = SunWEGData(api, 0)
|
||||
data.update()
|
||||
assert data.data is None
|
||||
|
||||
|
||||
async def test_sunwegdata_update_success(plant_fixture) -> None:
|
||||
"""Test SunWEGData success on update."""
|
||||
api = MagicMock()
|
||||
api.plant = MagicMock(return_value=plant_fixture)
|
||||
api.complete_inverter = MagicMock()
|
||||
data = SunWEGData(api, 0)
|
||||
data.update()
|
||||
assert data.data.id == plant_fixture.id
|
||||
assert data.data.name == plant_fixture.name
|
||||
assert data.data.kwh_per_kwp == plant_fixture.kwh_per_kwp
|
||||
assert data.data.last_update == plant_fixture.last_update
|
||||
assert data.data.performance_rate == plant_fixture.performance_rate
|
||||
assert data.data.saving == plant_fixture.saving
|
||||
assert len(data.data.inverters) == 1
|
||||
|
||||
|
||||
async def test_sunwegdata_update_success_alternative(plant_fixture_alternative) -> None:
|
||||
"""Test SunWEGData success on update."""
|
||||
api = MagicMock()
|
||||
api.plant = MagicMock(return_value=plant_fixture_alternative)
|
||||
api.complete_inverter = MagicMock()
|
||||
data = SunWEGData(api, 0)
|
||||
data.update()
|
||||
assert data.data.id == plant_fixture_alternative.id
|
||||
assert data.data.name == plant_fixture_alternative.name
|
||||
assert data.data.kwh_per_kwp == plant_fixture_alternative.kwh_per_kwp
|
||||
assert data.data.last_update == plant_fixture_alternative.last_update
|
||||
assert data.data.performance_rate == plant_fixture_alternative.performance_rate
|
||||
assert data.data.saving == plant_fixture_alternative.saving
|
||||
assert len(data.data.inverters) == 1
|
||||
|
||||
|
||||
async def test_sunwegdata_get_api_value_none(plant_fixture) -> None:
|
||||
"""Test SunWEGData none return on get_api_value."""
|
||||
api = MagicMock()
|
||||
data = SunWEGData(api, 123456)
|
||||
data.data = plant_fixture
|
||||
assert data.get_api_value("variable", DeviceType.INVERTER, 0, "deep_name") is None
|
||||
assert data.get_api_value("variable", DeviceType.STRING, 21255, "deep_name") is None
|
||||
|
||||
|
||||
async def test_sunwegdata_get_data_drop_threshold() -> None:
|
||||
"""Test SunWEGData get_data with drop threshold."""
|
||||
api = MagicMock()
|
||||
data = SunWEGData(api, 123456)
|
||||
data.get_api_value = MagicMock()
|
||||
entity_description = SunWEGSensorEntityDescription(
|
||||
api_variable_key="variable", key="key", previous_value_drop_threshold=0.1
|
||||
async def test_sunweg_repair_issue(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test the Sun WEG configuration entry loading/unloading handles the repair."""
|
||||
config_entry_1 = MockConfigEntry(
|
||||
title="Example 1",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
data.get_api_value.return_value = 3.0
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (3.0, None)
|
||||
data.get_api_value.return_value = 2.91
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (3.0, None)
|
||||
data.get_api_value.return_value = 2.8
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (2.8, None)
|
||||
config_entry_1.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry_1.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_sunwegdata_get_data_never_reset() -> None:
|
||||
"""Test SunWEGData get_data with never reset."""
|
||||
api = MagicMock()
|
||||
data = SunWEGData(api, 123456)
|
||||
data.get_api_value = MagicMock()
|
||||
entity_description = SunWEGSensorEntityDescription(
|
||||
api_variable_key="variable", key="key", never_resets=True
|
||||
# Add a second one
|
||||
config_entry_2 = MockConfigEntry(
|
||||
title="Example 2",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
data.get_api_value.return_value = 3.0
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (3.0, None)
|
||||
data.get_api_value.return_value = 0
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (3.0, None)
|
||||
data.get_api_value.return_value = 2.8
|
||||
assert data.get_data(
|
||||
api_variable_key=entity_description.api_variable_key,
|
||||
api_variable_unit=entity_description.api_variable_unit,
|
||||
deep_name=None,
|
||||
device_type=DeviceType.TOTAL,
|
||||
inverter_id=0,
|
||||
name=entity_description.name,
|
||||
native_unit_of_measurement=entity_description.native_unit_of_measurement,
|
||||
never_resets=entity_description.never_resets,
|
||||
previous_value_drop_threshold=entity_description.previous_value_drop_threshold,
|
||||
) == (2.8, None)
|
||||
config_entry_2.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
async def test_reauth_started(hass: HomeAssistant) -> None:
|
||||
"""Test reauth flow started."""
|
||||
mock_entry = SUNWEG_MOCK_ENTRY
|
||||
mock_entry.add_to_hass(hass)
|
||||
with patch.object(APIHelper, "authenticate", return_value=False):
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert mock_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "reauth_confirm"
|
||||
# Add an ignored entry
|
||||
config_entry_3 = MockConfigEntry(
|
||||
source=SOURCE_IGNORE,
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_3.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_3.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_3.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
# Add a disabled entry
|
||||
config_entry_4 = MockConfigEntry(
|
||||
disabled_by=ConfigEntryDisabler.USER,
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_4.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_4.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_4.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
# Remove the first one
|
||||
await hass.config_entries.async_remove(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the second one
|
||||
await hass.config_entries.async_remove(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.NOT_LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None
|
||||
|
||||
# Check the ignored and disabled entries are removed
|
||||
assert not hass.config_entries.async_entries(DOMAIN)
|
||||
|
Loading…
x
Reference in New Issue
Block a user