From 55615e585a59c5a6a07fe64f97a57e60355ffa2f Mon Sep 17 00:00:00 2001 From: David Rapan Date: Sun, 11 Aug 2024 12:52:39 +0200 Subject: [PATCH] feat: Add Read Input Registers (0x04) service --- custom_components/solarman/api.py | 23 ++++++++++--- custom_components/solarman/const.py | 1 + custom_components/solarman/services.py | 39 +++++++++++++++++++-- custom_components/solarman/services.yaml | 44 ++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/custom_components/solarman/api.py b/custom_components/solarman/api.py index 918528b..67b6b3f 100644 --- a/custom_components/solarman/api.py +++ b/custom_components/solarman/api.py @@ -129,17 +129,17 @@ async def async_shutdown(self, loud = True) -> None: await self.async_disconnect(loud) async def async_read(self, params, code, start, end) -> None: - length = end - start + 1 + quantity = end - start + 1 await self.async_connect() match code: case 3: - response = await self.read_holding_registers(register_addr = start, quantity = length) + response = await self.read_holding_registers(start, quantity) case 4: - response = await self.read_input_registers(register_addr = start, quantity = length) + response = await self.read_input_registers(start, quantity) - params.parse(response, start, length) + params.parse(response, start, quantity) def get_sensors(self): if self.parameter_definition: @@ -256,6 +256,21 @@ async def service_read_holding_registers(self, register, quantity, wait_for_atte if not self.auto_reconnect: await self.async_disconnect() + async def service_read_input_registers(self, register, quantity, wait_for_attempts = ACTION_ATTEMPTS): + _LOGGER.debug(f"service_read_input_registers: [{register}], quantity: [{quantity}]") + + if await self.wait_for_reading_done(wait_for_attempts): + _LOGGER.debug(f"service_read_input_registers: Timeout.") + raise TimeoutError("Coordinator is currently reading data from the device!") + + try: + await self.async_connect() + return await self.read_input_registers(register, quantity) + except Exception as e: + _LOGGER.warning(f"service_read_input_registers: [{register}], quantity: [{quantity}] failed. [{format_exception(e)}]") + if not self.auto_reconnect: + await self.async_disconnect() + async def service_write_holding_register(self, register, value, wait_for_attempts = ACTION_ATTEMPTS) -> bool: _LOGGER.debug(f"service_write_holding_register: {register}, value: {value}") diff --git a/custom_components/solarman/const.py b/custom_components/solarman/const.py index fdd4d28..4f28d8c 100644 --- a/custom_components/solarman/const.py +++ b/custom_components/solarman/const.py @@ -71,5 +71,6 @@ SERVICES_PARAM_WAIT_FOR_ATTEMPTS = "wait_for_attempts" SERVICE_READ_HOLDING_REGISTERS = "read_holding_registers" +SERVICE_READ_INPUT_REGISTERS = "read_input_registers" SERVICE_WRITE_HOLDING_REGISTER = "write_holding_register" SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS = "write_multiple_holding_registers" diff --git a/custom_components/solarman/services.py b/custom_components/solarman/services.py index e721812..56ada3d 100644 --- a/custom_components/solarman/services.py +++ b/custom_components/solarman/services.py @@ -19,7 +19,7 @@ # Apart from this, it also need to be defined in the file # services.yaml for the Home Assistant UI in "Developer Tools" -SERVICE_READ_HOLDING_REGISTERS_SCHEMA = vol.Schema( +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)), @@ -89,6 +89,36 @@ async def read_holding_registers(call: ServiceCall) -> int: return result + 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: + raise ServiceValidationError( + "No communication interface for device found", + translation_domain = DOMAIN, + translation_key = "no_interface_found" + ) + + register = call.data.get(SERVICES_PARAM_REGISTER) + quantity = call.data.get(SERVICES_PARAM_QUANTITY) + + try: + response = await inverter.service_read_input_registers(register, quantity, + wait_for_attempts = call.data.get(SERVICES_PARAM_WAIT_FOR_ATTEMPTS)) + except Exception as e: + raise ServiceValidationError( + e, + translation_domain = DOMAIN, + translation_key = "call_failed" + ) + + result = {} + + for i in range(0, quantity): + result[register + i] = response[i] + + return result + async def write_holding_register(call: ServiceCall) -> None: _LOGGER.debug(f"write_holding_register: {call}") @@ -138,7 +168,11 @@ 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_HOLDING_REGISTERS_SCHEMA, supports_response = SupportsResponse.OPTIONAL + DOMAIN, SERVICE_READ_HOLDING_REGISTERS, read_holding_registers, schema = SERVICE_READ_REGISTERS_SCHEMA, supports_response = SupportsResponse.OPTIONAL + ) + + hass.services.async_register( + DOMAIN, SERVICE_READ_INPUT_REGISTERS, read_holding_registers, schema = SERVICE_READ_REGISTERS_SCHEMA, supports_response = SupportsResponse.OPTIONAL ) hass.services.async_register( @@ -155,6 +189,7 @@ 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) diff --git a/custom_components/solarman/services.yaml b/custom_components/solarman/services.yaml index 5e09b58..74c188c 100644 --- a/custom_components/solarman/services.yaml +++ b/custom_components/solarman/services.yaml @@ -42,6 +42,50 @@ read_holding_registers: max: 30 mode: box +read_input_registers: + name: Read Input Registers (Modbus Function Code 4) + description: Read values from consecutive registers at once. (Defaults to reading a single register) + fields: + device: + name: Device + description: The Device + example: "Inverter" + required: true + selector: + device: + filter: + - integration: solarman + register: + name: Register + description: Modbus register address + example: 16384 + required: true + selector: + number: + min: 0 + max: 65535 + mode: box + quantity: + name: Quantity + description: Quantity of registers to read + default: 1 + required: true + selector: + number: + min: 1 + max: 65535 + mode: box + wait_for_attempts: + name: Wait for attempts + description: Wait for coordinator attempts + default: 5 + required: true + selector: + number: + min: 0 + max: 30 + mode: box + write_holding_register: name: Write Holding Register (Modbus Function Code 6) description: USE WITH CARE! (Some devices might not accept Code 6 in this case try to use 'Write Multiple Holding Registers')