232 lines
7.2 KiB
Python
232 lines
7.2 KiB
Python
"""The sma integration."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
import logging
|
|
|
|
import pysma
|
|
|
|
from homeassistant.config_entries import SOURCE_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,
|
|
PYSMA_COORDINATOR,
|
|
PYSMA_DEVICE_INFO,
|
|
PYSMA_OBJECT,
|
|
PYSMA_REMOVE_LISTENER,
|
|
PYSMA_SENSORS,
|
|
)
|
|
|
|
_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
|
|
protocol = "https" if entry.data[CONF_SSL] else "http"
|
|
url = f"{protocol}://{entry.data[CONF_HOST]}"
|
|
verify_ssl = entry.data[CONF_VERIFY_SSL]
|
|
group = entry.data[CONF_GROUP]
|
|
password = entry.data[CONF_PASSWORD]
|
|
|
|
session = async_get_clientsession(hass, verify_ssl=verify_ssl)
|
|
sma = pysma.SMA(session, url, password, group)
|
|
|
|
try:
|
|
# Get updated device info
|
|
sma_device_info = await sma.device_info()
|
|
# Get all device sensors
|
|
sensor_def = await sma.get_sensors()
|
|
except (
|
|
pysma.exceptions.SmaReadException,
|
|
pysma.exceptions.SmaConnectionException,
|
|
) as exc:
|
|
raise ConfigEntryNotReady from exc
|
|
|
|
# Create DeviceInfo object from sma_device_info
|
|
device_info = DeviceInfo(
|
|
configuration_url=url,
|
|
identifiers={(DOMAIN, entry.unique_id)},
|
|
manufacturer=sma_device_info["manufacturer"],
|
|
model=sma_device_info["type"],
|
|
name=sma_device_info["name"],
|
|
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."""
|
|
try:
|
|
await sma.read(sensor_def)
|
|
except (
|
|
pysma.exceptions.SmaReadException,
|
|
pysma.exceptions.SmaConnectionException,
|
|
) as exc:
|
|
raise UpdateFailed(exc) from exc
|
|
|
|
interval = timedelta(
|
|
seconds=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
|
)
|
|
|
|
coordinator = DataUpdateCoordinator(
|
|
hass,
|
|
_LOGGER,
|
|
name="sma",
|
|
update_method=async_update_data,
|
|
update_interval=interval,
|
|
)
|
|
|
|
try:
|
|
await coordinator.async_config_entry_first_refresh()
|
|
except ConfigEntryNotReady:
|
|
await sma.close_session()
|
|
raise
|
|
|
|
# Ensure we logout on shutdown
|
|
async def async_close_session(event):
|
|
"""Close the session."""
|
|
await sma.close_session()
|
|
|
|
remove_stop_listener = hass.bus.async_listen_once(
|
|
EVENT_HOMEASSISTANT_STOP, async_close_session
|
|
)
|
|
|
|
hass.data.setdefault(DOMAIN, {})
|
|
hass.data[DOMAIN][entry.entry_id] = {
|
|
PYSMA_OBJECT: sma,
|
|
PYSMA_COORDINATOR: coordinator,
|
|
PYSMA_SENSORS: sensor_def,
|
|
PYSMA_REMOVE_LISTENER: remove_stop_listener,
|
|
PYSMA_DEVICE_INFO: device_info,
|
|
}
|
|
|
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Unload a config entry."""
|
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
if unload_ok:
|
|
data = hass.data[DOMAIN].pop(entry.entry_id)
|
|
await data[PYSMA_OBJECT].close_session()
|
|
data[PYSMA_REMOVE_LISTENER]()
|
|
|
|
return unload_ok
|