Skip to content

Commit

Permalink
feat: Add Read Input Registers (0x04) service
Browse files Browse the repository at this point in the history
  • Loading branch information
davidrapan committed Aug 11, 2024
1 parent 18e5005 commit 55615e5
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 6 deletions.
23 changes: 19 additions & 4 deletions custom_components/solarman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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}")

Expand Down
1 change: 1 addition & 0 deletions custom_components/solarman/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
39 changes: 37 additions & 2 deletions custom_components/solarman/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -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}")

Expand Down Expand Up @@ -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(
Expand All @@ -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)

Expand Down
44 changes: 44 additions & 0 deletions custom_components/solarman/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down

0 comments on commit 55615e5

Please sign in to comment.