mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Remove deprecated yaml config from sma (#62472)
Co-authored-by: René Klomp <rene@klomp.ws>
This commit is contained in:
parent
72674876fc
commit
f028079815
@ -6,29 +6,22 @@ import logging
|
||||
|
||||
import pysma
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryNotReady
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PATH,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SENSORS,
|
||||
CONF_SSL,
|
||||
CONF_VERIFY_SSL,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
CONF_CUSTOM,
|
||||
CONF_FACTOR,
|
||||
CONF_GROUP,
|
||||
CONF_KEY,
|
||||
CONF_UNIT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
@ -42,94 +35,6 @@ from .const import (
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _parse_legacy_options(
|
||||
entry: ConfigEntry, sensor_def: pysma.sensor.Sensors
|
||||
) -> list[str]:
|
||||
"""Parse legacy configuration options.
|
||||
|
||||
This will parse the legacy CONF_SENSORS and CONF_CUSTOM configuration options
|
||||
to support deprecated yaml config from platform setup.
|
||||
"""
|
||||
|
||||
# Add sensors from the custom config
|
||||
sensor_def.add(
|
||||
[
|
||||
pysma.sensor.Sensor(
|
||||
o[CONF_KEY], n, o[CONF_UNIT], o[CONF_FACTOR], o.get(CONF_PATH)
|
||||
)
|
||||
for n, o in entry.data.get(CONF_CUSTOM).items()
|
||||
]
|
||||
)
|
||||
|
||||
# Parsing of sensors configuration
|
||||
if not (config_sensors := entry.data.get(CONF_SENSORS)):
|
||||
return []
|
||||
|
||||
# Support import of legacy config that should have been removed from 0.99, but was still functional
|
||||
# See also #25880 and #26306. Functional support was dropped in #48003
|
||||
if isinstance(config_sensors, dict):
|
||||
config_sensors_list = []
|
||||
|
||||
for name, attr in config_sensors.items():
|
||||
config_sensors_list.append(name)
|
||||
config_sensors_list.extend(attr)
|
||||
|
||||
config_sensors = config_sensors_list
|
||||
|
||||
# Find and replace sensors removed from pysma
|
||||
# This only alters the config, the actual sensor migration takes place in _migrate_old_unique_ids
|
||||
for sensor in config_sensors.copy():
|
||||
if sensor in pysma.const.LEGACY_MAP:
|
||||
config_sensors.remove(sensor)
|
||||
config_sensors.append(pysma.const.LEGACY_MAP[sensor]["new_sensor"])
|
||||
|
||||
# Only sensors from config should be enabled
|
||||
for sensor in sensor_def:
|
||||
sensor.enabled = sensor.name in config_sensors
|
||||
|
||||
return config_sensors
|
||||
|
||||
|
||||
def _migrate_old_unique_ids(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
sensor_def: pysma.sensor.Sensors,
|
||||
config_sensors: list[str],
|
||||
) -> None:
|
||||
"""Migrate legacy sensor entity_id format to new format."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
# Create list of all possible sensor names
|
||||
possible_sensors = set(
|
||||
config_sensors + [s.name for s in sensor_def] + list(pysma.const.LEGACY_MAP)
|
||||
)
|
||||
|
||||
for sensor in possible_sensors:
|
||||
if sensor in sensor_def:
|
||||
pysma_sensor = sensor_def[sensor]
|
||||
original_key = pysma_sensor.key
|
||||
elif sensor in pysma.const.LEGACY_MAP:
|
||||
# If sensor was removed from pysma we will remap it to the new sensor
|
||||
legacy_sensor = pysma.const.LEGACY_MAP[sensor]
|
||||
pysma_sensor = sensor_def[legacy_sensor["new_sensor"]]
|
||||
original_key = legacy_sensor["old_key"]
|
||||
else:
|
||||
_LOGGER.error("%s does not exist", sensor)
|
||||
continue
|
||||
|
||||
# Find entity_id using previous format of unique ID
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
"sensor", "sma", f"sma-{original_key}-{sensor}"
|
||||
)
|
||||
|
||||
if not entity_id:
|
||||
continue
|
||||
|
||||
# Change unique_id to new format using the device serial in entry.unique_id
|
||||
new_unique_id = f"{entry.unique_id}-{pysma_sensor.key}_{pysma_sensor.key_idx}"
|
||||
entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up sma from a config entry."""
|
||||
# Init the SMA interface
|
||||
@ -163,11 +68,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
sw_version=sma_device_info["sw_version"],
|
||||
)
|
||||
|
||||
# Parse legacy options if initial setup was done from yaml
|
||||
if entry.source == SOURCE_IMPORT:
|
||||
config_sensors = _parse_legacy_options(entry, sensor_def)
|
||||
_migrate_old_unique_ids(hass, entry, sensor_def, config_sensors)
|
||||
|
||||
# Define the coordinator
|
||||
async def async_update_data():
|
||||
"""Update the used SMA sensors."""
|
||||
|
@ -8,18 +8,12 @@ import pysma
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, core
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_SENSORS,
|
||||
CONF_SSL,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_VERIFY_SSL
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import CONF_CUSTOM, CONF_GROUP, DOMAIN, GROUPS
|
||||
from .const import CONF_GROUP, DOMAIN, GROUPS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -56,8 +50,6 @@ class SmaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
CONF_VERIFY_SSL: True,
|
||||
CONF_GROUP: GROUPS[0],
|
||||
CONF_PASSWORD: vol.UNDEFINED,
|
||||
CONF_SENSORS: [],
|
||||
CONF_CUSTOM: {},
|
||||
}
|
||||
|
||||
async def async_step_user(
|
||||
@ -108,18 +100,3 @@ class SmaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(
|
||||
self, import_config: dict[str, Any] | None
|
||||
) -> FlowResult:
|
||||
"""Import a config flow from configuration."""
|
||||
device_info = await validate_input(self.hass, import_config)
|
||||
|
||||
# If unique is configured import was already run
|
||||
# This means remap was already done, so we can abort
|
||||
await self.async_set_unique_id(device_info["serial"])
|
||||
self._abort_if_unique_id_configured(import_config)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=import_config[CONF_HOST], data=import_config
|
||||
)
|
||||
|
@ -11,11 +11,7 @@ PYSMA_DEVICE_INFO = "device_info"
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_FACTOR = "factor"
|
||||
CONF_GROUP = "group"
|
||||
CONF_KEY = "key"
|
||||
CONF_UNIT = "unit"
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = 5
|
||||
|
||||
|
@ -1,31 +1,16 @@
|
||||
"""SMA Solar Webconnect interface."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pysma
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PATH,
|
||||
CONF_SENSORS,
|
||||
CONF_SSL,
|
||||
CONF_VERIFY_SSL,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
POWER_WATT,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@ -34,91 +19,7 @@ from homeassistant.helpers.update_coordinator import (
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
CONF_CUSTOM,
|
||||
CONF_FACTOR,
|
||||
CONF_GROUP,
|
||||
CONF_KEY,
|
||||
CONF_UNIT,
|
||||
DOMAIN,
|
||||
GROUPS,
|
||||
PYSMA_COORDINATOR,
|
||||
PYSMA_DEVICE_INFO,
|
||||
PYSMA_SENSORS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_sensor_schema(conf: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Check sensors and attributes are valid."""
|
||||
try:
|
||||
valid = [s.name for s in pysma.sensor.Sensors()]
|
||||
valid += pysma.const.LEGACY_MAP.keys()
|
||||
except (ImportError, AttributeError):
|
||||
return conf
|
||||
|
||||
customs = list(conf[CONF_CUSTOM])
|
||||
|
||||
for sensor in conf[CONF_SENSORS]:
|
||||
if sensor in customs:
|
||||
_LOGGER.warning(
|
||||
"All custom sensors will be added automatically, no need to include them in sensors: %s",
|
||||
sensor,
|
||||
)
|
||||
elif sensor not in valid:
|
||||
raise vol.Invalid(f"{sensor} does not exist")
|
||||
return conf
|
||||
|
||||
|
||||
CUSTOM_SCHEMA = vol.Any(
|
||||
{
|
||||
vol.Required(CONF_KEY): vol.All(cv.string, vol.Length(min=13, max=15)),
|
||||
vol.Required(CONF_UNIT): cv.string,
|
||||
vol.Optional(CONF_FACTOR, default=1): vol.Coerce(float),
|
||||
vol.Optional(CONF_PATH): vol.All(cv.ensure_list, [cv.string]),
|
||||
}
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_SSL, default=False): cv.boolean,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_GROUP, default=GROUPS[0]): vol.In(GROUPS),
|
||||
vol.Optional(CONF_SENSORS, default=[]): vol.Any(
|
||||
cv.schema_with_slug_keys(cv.ensure_list), # will be deprecated
|
||||
vol.All(cv.ensure_list, [str]),
|
||||
),
|
||||
vol.Optional(CONF_CUSTOM, default={}): cv.schema_with_slug_keys(
|
||||
CUSTOM_SCHEMA
|
||||
),
|
||||
},
|
||||
extra=vol.PREVENT_EXTRA,
|
||||
),
|
||||
_check_sensor_schema,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info=None,
|
||||
) -> None:
|
||||
"""Import the platform into a config entry."""
|
||||
_LOGGER.warning(
|
||||
"Loading SMA via platform setup is deprecated. "
|
||||
"Please remove it from your configuration"
|
||||
)
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
|
||||
)
|
||||
)
|
||||
from .const import DOMAIN, PYSMA_COORDINATOR, PYSMA_DEVICE_INFO, PYSMA_SENSORS
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -16,85 +16,6 @@ MOCK_USER_INPUT = {
|
||||
"password": "password",
|
||||
}
|
||||
|
||||
MOCK_IMPORT = {
|
||||
"platform": "sma",
|
||||
"host": "1.1.1.1",
|
||||
"ssl": True,
|
||||
"verify_ssl": False,
|
||||
"group": "user",
|
||||
"password": "password",
|
||||
"sensors": ["pv_power", "daily_yield", "total_yield", "not_existing_sensors"],
|
||||
"custom": {
|
||||
"yesterday_consumption": {
|
||||
"factor": 1000.0,
|
||||
"key": "6400_00543A01",
|
||||
"unit": "kWh",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
MOCK_IMPORT_DICT = {
|
||||
"platform": "sma",
|
||||
"host": "1.1.1.1",
|
||||
"ssl": True,
|
||||
"verify_ssl": False,
|
||||
"group": "user",
|
||||
"password": "password",
|
||||
"sensors": {
|
||||
"pv_power": [],
|
||||
"pv_gen_meter": [],
|
||||
"solar_daily": ["daily_yield", "total_yield"],
|
||||
"status": ["grid_power", "frequency", "voltage_l1", "operating_time"],
|
||||
},
|
||||
"custom": {
|
||||
"operating_time": {"key": "6400_00462E00", "unit": "uur", "factor": 3600},
|
||||
"solar_daily": {"key": "6400_00262200", "unit": "kWh", "factor": 1000},
|
||||
},
|
||||
}
|
||||
|
||||
MOCK_CUSTOM_SENSOR = {
|
||||
"name": "yesterday_consumption",
|
||||
"key": "6400_00543A01",
|
||||
"unit": "kWh",
|
||||
"factor": 1000,
|
||||
}
|
||||
|
||||
MOCK_CUSTOM_SENSOR2 = {
|
||||
"name": "device_type_id",
|
||||
"key": "6800_08822000",
|
||||
"unit": "",
|
||||
"path": '"1"[0].val[0].tag',
|
||||
}
|
||||
|
||||
MOCK_SETUP_DATA = dict(
|
||||
{
|
||||
"custom": {},
|
||||
"sensors": [],
|
||||
},
|
||||
**MOCK_USER_INPUT,
|
||||
)
|
||||
|
||||
MOCK_CUSTOM_SETUP_DATA = dict(
|
||||
{
|
||||
"custom": {
|
||||
MOCK_CUSTOM_SENSOR["name"]: {
|
||||
"factor": MOCK_CUSTOM_SENSOR["factor"],
|
||||
"key": MOCK_CUSTOM_SENSOR["key"],
|
||||
"path": None,
|
||||
"unit": MOCK_CUSTOM_SENSOR["unit"],
|
||||
},
|
||||
MOCK_CUSTOM_SENSOR2["name"]: {
|
||||
"factor": 1.0,
|
||||
"key": MOCK_CUSTOM_SENSOR2["key"],
|
||||
"path": MOCK_CUSTOM_SENSOR2["path"],
|
||||
"unit": MOCK_CUSTOM_SENSOR2["unit"],
|
||||
},
|
||||
},
|
||||
"sensors": [],
|
||||
},
|
||||
**MOCK_USER_INPUT,
|
||||
)
|
||||
|
||||
|
||||
def _patch_async_setup_entry(return_value=True):
|
||||
return patch(
|
||||
|
@ -9,7 +9,7 @@ import pytest
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sma.const import DOMAIN
|
||||
|
||||
from . import MOCK_CUSTOM_SETUP_DATA, MOCK_DEVICE
|
||||
from . import MOCK_DEVICE, MOCK_USER_INPUT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -21,7 +21,7 @@ def mock_config_entry():
|
||||
domain=DOMAIN,
|
||||
title=MOCK_DEVICE["name"],
|
||||
unique_id=MOCK_DEVICE["serial"],
|
||||
data=MOCK_CUSTOM_SETUP_DATA,
|
||||
data=MOCK_USER_INPUT,
|
||||
source=config_entries.SOURCE_IMPORT,
|
||||
)
|
||||
|
||||
|
@ -8,21 +8,14 @@ from pysma.exceptions import (
|
||||
)
|
||||
|
||||
from homeassistant.components.sma.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
RESULT_TYPE_FORM,
|
||||
)
|
||||
|
||||
from . import (
|
||||
MOCK_DEVICE,
|
||||
MOCK_IMPORT,
|
||||
MOCK_IMPORT_DICT,
|
||||
MOCK_SETUP_DATA,
|
||||
MOCK_USER_INPUT,
|
||||
_patch_async_setup_entry,
|
||||
)
|
||||
from . import MOCK_DEVICE, MOCK_USER_INPUT, _patch_async_setup_entry
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
@ -45,7 +38,7 @@ async def test_form(hass):
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == MOCK_USER_INPUT["host"]
|
||||
assert result["data"] == MOCK_SETUP_DATA
|
||||
assert result["data"] == MOCK_USER_INPUT
|
||||
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
@ -148,43 +141,3 @@ async def test_form_already_configured(hass, mock_config_entry):
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert len(mock_setup_entry.mock_calls) == 0
|
||||
|
||||
|
||||
async def test_import(hass):
|
||||
"""Test we can import."""
|
||||
|
||||
with patch("pysma.SMA.new_session", return_value=True), patch(
|
||||
"pysma.SMA.device_info", return_value=MOCK_DEVICE
|
||||
), patch(
|
||||
"pysma.SMA.close_session", return_value=True
|
||||
), _patch_async_setup_entry() as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT,
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == MOCK_USER_INPUT["host"]
|
||||
assert result["data"] == MOCK_IMPORT
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_import_sensor_dict(hass):
|
||||
"""Test we can import."""
|
||||
|
||||
with patch("pysma.SMA.new_session", return_value=True), patch(
|
||||
"pysma.SMA.device_info", return_value=MOCK_DEVICE
|
||||
), patch(
|
||||
"pysma.SMA.close_session", return_value=True
|
||||
), _patch_async_setup_entry() as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_DICT,
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == MOCK_USER_INPUT["host"]
|
||||
assert result["data"] == MOCK_IMPORT_DICT
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
@ -1,11 +1,5 @@
|
||||
"""Test the sma sensor platform."""
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
POWER_WATT,
|
||||
)
|
||||
|
||||
from . import MOCK_CUSTOM_SENSOR
|
||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, POWER_WATT
|
||||
|
||||
|
||||
async def test_sensors(hass, init_integration):
|
||||
@ -13,7 +7,3 @@ async def test_sensors(hass, init_integration):
|
||||
state = hass.states.get("sensor.grid_power")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == POWER_WATT
|
||||
|
||||
state = hass.states.get(f"sensor.{MOCK_CUSTOM_SENSOR['name']}")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
|
||||
|
Loading…
x
Reference in New Issue
Block a user