From dc12385cce0ebd4e7fa6aef6f31be3d125d81ea8 Mon Sep 17 00:00:00 2001 From: David Rapan Date: Mon, 9 Dec 2024 18:14:26 +0100 Subject: [PATCH] refactor: Chained DATA_SCHEMA for services --- custom_components/solarman/__init__.py | 4 +- custom_components/solarman/api.py | 6 +- custom_components/solarman/services.py | 96 +++++++++--------------- custom_components/solarman/services.yaml | 4 +- 4 files changed, 44 insertions(+), 66 deletions(-) diff --git a/custom_components/solarman/__init__.py b/custom_components/solarman/__init__.py index dd28d89f..989e496f 100644 --- a/custom_components/solarman/__init__.py +++ b/custom_components/solarman/__init__.py @@ -16,7 +16,7 @@ from .coordinator import Inverter, InverterCoordinator from .config_flow import ConfigFlowHandler from .entity import migrate_unique_ids -from .services import register_services +from .services import async_register _LOGGER = logging.getLogger(__name__) @@ -25,7 +25,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.debug(f"async_setup") - register_services(hass) + async_register(hass) return True diff --git a/custom_components/solarman/api.py b/custom_components/solarman/api.py index 3fa5dd02..4fa94b83 100644 --- a/custom_components/solarman/api.py +++ b/custom_components/solarman/api.py @@ -13,7 +13,7 @@ from .const import * from .common import * from .provider import * -from .include.pysolarmanv5 import PySolarmanV5Async, V5FrameError +from .include.pysolarmanv5 import PySolarmanV5Async _LOGGER = logging.getLogger(__name__) @@ -198,6 +198,7 @@ async def try_read_write(self, code, start, arg, message, incremental_wait): _LOGGER.debug(f"[{self.config.serial}] {message} ...") response = None + attempts_left = ACTION_ATTEMPTS while attempts_left > 0 and not response: attempts_left -= 1 @@ -206,7 +207,6 @@ async def try_read_write(self, code, start, arg, message, incremental_wait): raise Exception(f"[{self.config.serial}] Unexpected response: Invalid length! (Length: {length}, Expected: {expected})") _LOGGER.debug(f"[{self.config.serial}] {message} succeeded, response: {response}") - return response except Exception as e: _LOGGER.debug(f"[{self.config.serial}] {message} failed, attempts left: {attempts_left}{'' if attempts_left > 0 else ', aborting.'} [{format_exception(e)}]") @@ -215,6 +215,8 @@ async def try_read_write(self, code, start, arg, message, incremental_wait): if not attempts_left > 0: raise await asyncio.sleep(((ACTION_ATTEMPTS - attempts_left) * TIMINGS_WAIT_SLEEP) if incremental_wait else TIMINGS_WAIT_SLEEP) + + return response async def get_failed(self): _LOGGER.debug(f"[{self.config.serial}] Fetching failed. [Previous State: {self.get_connection_state} ({self.state})]") diff --git a/custom_components/solarman/services.py b/custom_components/solarman/services.py index aa6a48bd..cefc5025 100644 --- a/custom_components/solarman/services.py +++ b/custom_components/solarman/services.py @@ -4,52 +4,40 @@ import voluptuous as vol -from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, SupportsResponse -from homeassistant.helpers import config_validation as cv, device_registry as dr, entity -from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.exceptions import ServiceValidationError from .const import * -from .api import Inverter -from .coordinator import InverterCoordinator +from .coordinator import Inverter, InverterCoordinator _LOGGER = logging.getLogger(__name__) -# Register the services one can invoke on the inverter. -# Apart from this, it also need to be defined in the file -# services.yaml for the Home Assistant UI in "Developer Tools" - -SERVICE_READ_REGISTERS_SCHEMA = vol.Schema( - { - vol.Required(SERVICES_PARAM_DEVICE): vol.All(vol.Coerce(str)), - vol.Required(SERVICES_PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)), - vol.Required(SERVICES_PARAM_QUANTITY): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)), - vol.Required(SERVICES_PARAM_WAIT_FOR_ATTEMPTS): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 30)) - } -) - -SERVICE_WRITE_HOLDING_REGISTER_SCHEMA = vol.Schema( - { - vol.Required(SERVICES_PARAM_DEVICE): vol.All(vol.Coerce(str)), - vol.Required(SERVICES_PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)), - vol.Required(SERVICES_PARAM_VALUE): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)), - vol.Required(SERVICES_PARAM_WAIT_FOR_ATTEMPTS): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 30)) - } -) - -SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS_SCHEMA = vol.Schema( - { - vol.Required(SERVICES_PARAM_DEVICE): vol.All(vol.Coerce(str)), - vol.Required(SERVICES_PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)), - vol.Required(SERVICES_PARAM_VALUES): vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535))]), - vol.Required(SERVICES_PARAM_WAIT_FOR_ATTEMPTS): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 30)) - } -) - -def register_services(hass: HomeAssistant) -> None: - _LOGGER.debug(f"register_services") - - def getDevice(device_id) -> Inverter: +HEADER_SCHEMA = { + vol.Required(SERVICES_PARAM_DEVICE): vol.All(vol.Coerce(str)), + vol.Required(SERVICES_PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)) +} + +QUANTITY_SCHEMA = { + vol.Required(SERVICES_PARAM_QUANTITY): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 125)) +} + +VALUE_SCHEMA = { + vol.Required(SERVICES_PARAM_VALUE): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535)) +} + +VALUES_SCHEMA = { + vol.Required(SERVICES_PARAM_VALUES): vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min = 0, max = 65535))]) +} + +WAIT_SCHEMA = { + vol.Required(SERVICES_PARAM_WAIT_FOR_ATTEMPTS): vol.All(vol.Coerce(int), vol.Range(min = 0, max = 30)) +} + +def async_register(hass: HomeAssistant) -> None: + _LOGGER.debug(f"register") + + def get_device(device_id) -> Inverter: device_registry = dr.async_get(hass) device = device_registry.async_get(device_id) @@ -62,7 +50,7 @@ def getDevice(device_id) -> Inverter: async def read_holding_registers(call: ServiceCall) -> int: _LOGGER.debug(f"read_holding_registers: {call}") - if (inverter := getDevice(call.data.get(SERVICES_PARAM_DEVICE))) is None: + if (inverter := get_device(call.data.get(SERVICES_PARAM_DEVICE))) is None: raise ServiceValidationError( "No communication interface for device found", translation_domain = DOMAIN, @@ -94,7 +82,7 @@ async def read_holding_registers(call: ServiceCall) -> int: async def read_input_registers(call: ServiceCall) -> int: _LOGGER.debug(f"read_input_registers: {call}") - if (inverter := getDevice(call.data.get(SERVICES_PARAM_DEVICE))) is None: + if (inverter := get_device(call.data.get(SERVICES_PARAM_DEVICE))) is None: raise ServiceValidationError( "No communication interface for device found", translation_domain = DOMAIN, @@ -126,7 +114,7 @@ async def read_input_registers(call: ServiceCall) -> int: async def write_holding_register(call: ServiceCall) -> None: _LOGGER.debug(f"write_holding_register: {call}") - if (inverter := getDevice(call.data.get(SERVICES_PARAM_DEVICE))) is None: + if (inverter := get_device(call.data.get(SERVICES_PARAM_DEVICE))) is None: raise ServiceValidationError( "No communication interface for device found", translation_domain = DOMAIN, @@ -151,7 +139,7 @@ async def write_holding_register(call: ServiceCall) -> None: async def write_multiple_holding_registers(call: ServiceCall) -> None: _LOGGER.debug(f"write_multiple_holding_registers: {call}") - if (inverter := getDevice(call.data.get(SERVICES_PARAM_DEVICE))) is None: + if (inverter := get_device(call.data.get(SERVICES_PARAM_DEVICE))) is None: raise ServiceValidationError( "No communication interface for device found", translation_domain = DOMAIN, @@ -174,29 +162,17 @@ async def write_multiple_holding_registers(call: ServiceCall) -> None: return hass.services.async_register( - DOMAIN, SERVICE_READ_HOLDING_REGISTERS, read_holding_registers, schema = SERVICE_READ_REGISTERS_SCHEMA, supports_response = SupportsResponse.OPTIONAL + DOMAIN, SERVICE_READ_HOLDING_REGISTERS, read_holding_registers, schema = vol.Schema(HEADER_SCHEMA | QUANTITY_SCHEMA | WAIT_SCHEMA), supports_response = SupportsResponse.OPTIONAL ) hass.services.async_register( - DOMAIN, SERVICE_READ_INPUT_REGISTERS, read_input_registers, schema = SERVICE_READ_REGISTERS_SCHEMA, supports_response = SupportsResponse.OPTIONAL + DOMAIN, SERVICE_READ_INPUT_REGISTERS, read_input_registers, schema = vol.Schema(HEADER_SCHEMA | QUANTITY_SCHEMA | WAIT_SCHEMA), supports_response = SupportsResponse.OPTIONAL ) hass.services.async_register( - DOMAIN, SERVICE_WRITE_HOLDING_REGISTER, write_holding_register, schema = SERVICE_WRITE_HOLDING_REGISTER_SCHEMA + DOMAIN, SERVICE_WRITE_HOLDING_REGISTER, write_holding_register, schema = vol.Schema(HEADER_SCHEMA | VALUE_SCHEMA | WAIT_SCHEMA) ) hass.services.async_register( - DOMAIN, SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS, write_multiple_holding_registers, schema = SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS_SCHEMA + DOMAIN, SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS, write_multiple_holding_registers, schema = vol.Schema(HEADER_SCHEMA | VALUES_SCHEMA | WAIT_SCHEMA) ) - - return - -def remove_services(hass: HomeAssistant) -> None: - _LOGGER.debug(f"remove_services") - - hass.services.async_remove(DOMAIN, SERVICE_READ_HOLDING_REGISTERS) - hass.services.async_remove(DOMAIN, SERVICE_READ_INPUT_REGISTERS) - hass.services.async_remove(DOMAIN, SERVICE_WRITE_HOLDING_REGISTER) - hass.services.async_remove(DOMAIN, SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS) - - return diff --git a/custom_components/solarman/services.yaml b/custom_components/solarman/services.yaml index 74c188c4..37c7ef64 100644 --- a/custom_components/solarman/services.yaml +++ b/custom_components/solarman/services.yaml @@ -29,7 +29,7 @@ read_holding_registers: selector: number: min: 1 - max: 65535 + max: 125 mode: box wait_for_attempts: name: Wait for attempts @@ -73,7 +73,7 @@ read_input_registers: selector: number: min: 1 - max: 65535 + max: 125 mode: box wait_for_attempts: name: Wait for attempts