mirror of
https://github.com/home-assistant/core.git
synced 2026-09-13 04:01:03 +01:00
192 lines
6.3 KiB
Python
192 lines
6.3 KiB
Python
"""Services for the Sofar integration."""
|
|
|
|
from collections.abc import Awaitable
|
|
|
|
from modbus_connection import ModbusError
|
|
from sofar_modbus.modern.enums import FeedinLimitationMode, PassiveModeTimeoutAction
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, ATTR_MODE
|
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.service import (
|
|
async_get_config_entry,
|
|
async_register_admin_service,
|
|
)
|
|
|
|
from .const import DOMAIN
|
|
from .coordinator import SofarConfigEntry
|
|
|
|
SERVICE_SET_ACTIVE_POWER_LIMIT = "set_active_power_limit"
|
|
SERVICE_SET_FEED_IN_LIMIT = "set_feed_in_limit"
|
|
SERVICE_SET_PASSIVE_MODE_POWER = "set_passive_mode_power"
|
|
SERVICE_SET_PASSIVE_MODE_TIMEOUT = "set_passive_mode_timeout"
|
|
|
|
ATTR_ACTION = "action"
|
|
ATTR_BATTERY_POWER_MAX = "battery_power_max"
|
|
ATTR_BATTERY_POWER_MIN = "battery_power_min"
|
|
ATTR_ENABLED = "enabled"
|
|
ATTR_GRID_POWER = "grid_power"
|
|
ATTR_LIMIT = "limit"
|
|
ATTR_MAX_POWER = "max_power"
|
|
ATTR_TIMEOUT = "timeout"
|
|
|
|
_ENTRY_SCHEMA = vol.Schema({vol.Required(ATTR_CONFIG_ENTRY_ID): str})
|
|
|
|
# The selectors in services.yaml only bound the UI, not a scripted call.
|
|
_POWER_RANGE = vol.All(int, vol.Range(min=-100000, max=100000))
|
|
|
|
SET_FEED_IN_LIMIT_SCHEMA = _ENTRY_SCHEMA.extend(
|
|
{
|
|
vol.Required(ATTR_MODE): vol.In(
|
|
[mode.name.lower() for mode in FeedinLimitationMode]
|
|
),
|
|
# Kept fractional so the multiple-of-100 check below sees the real
|
|
# value; cv.positive_int would truncate 3000.9 into a valid 3000.
|
|
vol.Required(ATTR_MAX_POWER): vol.All(
|
|
vol.Coerce(float), vol.Range(min=0, max=100000)
|
|
),
|
|
}
|
|
)
|
|
|
|
SET_ACTIVE_POWER_LIMIT_SCHEMA = _ENTRY_SCHEMA.extend(
|
|
{
|
|
vol.Required(ATTR_ENABLED): cv.boolean,
|
|
vol.Required(ATTR_LIMIT): vol.All(vol.Coerce(float), vol.Range(min=0, max=100)),
|
|
}
|
|
)
|
|
|
|
SET_PASSIVE_MODE_TIMEOUT_SCHEMA = _ENTRY_SCHEMA.extend(
|
|
{
|
|
vol.Required(ATTR_TIMEOUT): vol.All(cv.positive_int, vol.Range(max=65535)),
|
|
vol.Required(ATTR_ACTION): vol.In(
|
|
[action.name.lower() for action in PassiveModeTimeoutAction]
|
|
),
|
|
}
|
|
)
|
|
|
|
SET_PASSIVE_MODE_POWER_SCHEMA = _ENTRY_SCHEMA.extend(
|
|
{
|
|
vol.Required(ATTR_GRID_POWER): _POWER_RANGE,
|
|
vol.Required(ATTR_BATTERY_POWER_MIN): _POWER_RANGE,
|
|
vol.Required(ATTR_BATTERY_POWER_MAX): _POWER_RANGE,
|
|
}
|
|
)
|
|
|
|
|
|
def _get_entry(
|
|
hass: HomeAssistant, call: ServiceCall, component: str
|
|
) -> SofarConfigEntry:
|
|
"""Return a loaded entry whose inverter serves the needed registers."""
|
|
entry: SofarConfigEntry = async_get_config_entry(
|
|
hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
|
|
)
|
|
if component not in entry.runtime_data.served_components:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="unsupported_action",
|
|
translation_placeholders={"title": entry.title},
|
|
)
|
|
return entry
|
|
|
|
|
|
async def _write(entry: SofarConfigEntry, write: Awaitable[None]) -> None:
|
|
"""Translate a failed write, then let the settings sensors catch up."""
|
|
try:
|
|
await write
|
|
except ValueError as err:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="invalid_action_value",
|
|
) from err
|
|
except ModbusError as err:
|
|
raise HomeAssistantError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="write_failed",
|
|
) from err
|
|
await entry.runtime_data.settings.async_request_refresh()
|
|
|
|
|
|
@callback
|
|
def async_setup_services(hass: HomeAssistant) -> None:
|
|
"""Register the Sofar services."""
|
|
|
|
async def _handle_set_feed_in_limit(call: ServiceCall) -> None:
|
|
entry = _get_entry(hass, call, "feed_in")
|
|
device = entry.runtime_data.readings.device
|
|
max_power = call.data[ATTR_MAX_POWER]
|
|
if max_power % 100:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="max_power_not_a_multiple_of_100",
|
|
)
|
|
await _write(
|
|
entry,
|
|
device.feed_in.async_write_limit(
|
|
FeedinLimitationMode[call.data[ATTR_MODE].upper()], int(max_power)
|
|
),
|
|
)
|
|
|
|
async def _handle_set_active_power_limit(call: ServiceCall) -> None:
|
|
entry = _get_entry(hass, call, "active_power_control")
|
|
device = entry.runtime_data.readings.device
|
|
await _write(
|
|
entry,
|
|
device.active_power_control.async_write_active_power_limit(
|
|
call.data[ATTR_ENABLED], call.data[ATTR_LIMIT]
|
|
),
|
|
)
|
|
|
|
async def _handle_set_passive_mode_timeout(call: ServiceCall) -> None:
|
|
entry = _get_entry(hass, call, "passive")
|
|
device = entry.runtime_data.readings.device
|
|
await _write(
|
|
entry,
|
|
device.passive.async_write_timeout(
|
|
call.data[ATTR_TIMEOUT],
|
|
PassiveModeTimeoutAction[call.data[ATTR_ACTION].upper()],
|
|
),
|
|
)
|
|
|
|
async def _handle_set_passive_mode_power(call: ServiceCall) -> None:
|
|
entry = _get_entry(hass, call, "passive")
|
|
device = entry.runtime_data.readings.device
|
|
await _write(
|
|
entry,
|
|
device.passive.async_write_power(
|
|
call.data[ATTR_GRID_POWER],
|
|
call.data[ATTR_BATTERY_POWER_MIN],
|
|
call.data[ATTR_BATTERY_POWER_MAX],
|
|
),
|
|
)
|
|
|
|
async_register_admin_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_SET_FEED_IN_LIMIT,
|
|
_handle_set_feed_in_limit,
|
|
schema=SET_FEED_IN_LIMIT_SCHEMA,
|
|
)
|
|
async_register_admin_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_SET_ACTIVE_POWER_LIMIT,
|
|
_handle_set_active_power_limit,
|
|
schema=SET_ACTIVE_POWER_LIMIT_SCHEMA,
|
|
)
|
|
async_register_admin_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_SET_PASSIVE_MODE_TIMEOUT,
|
|
_handle_set_passive_mode_timeout,
|
|
schema=SET_PASSIVE_MODE_TIMEOUT_SCHEMA,
|
|
)
|
|
async_register_admin_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_SET_PASSIVE_MODE_POWER,
|
|
_handle_set_passive_mode_power,
|
|
schema=SET_PASSIVE_MODE_POWER_SCHEMA,
|
|
)
|