Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion custom_components/sigen/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
"loggers": ["custom_components.sigen"],
"quality_scale": "custom",
"requirements": ["pymodbus>=3.0.0"],
"version": "1.0.3"
"version": "1.0.4"
}
248 changes: 126 additions & 122 deletions custom_components/sigen/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,38 +567,41 @@ async def async_write_registers(

async with self._locks[key]:
if register_type in [RegisterType.HOLDING, RegisterType.WRITE_ONLY]:
# For Modbus addresses starting with 4xxxx,
# some devices expect the offset (address - 40001)
if address >= 40001:
# Try with the offset addressing first
offset_address = address - 40001
_LOGGER.debug(
"Writing to registers starting at %s (offset address %s) \
with values %s for slave %s",
address, offset_address, values, slave_id
)
result = await client.write_registers(
address=offset_address, values=values, slave=slave_id
)
else:
_LOGGER.debug(
"Writing to registers starting at %s with values %s for slave %s",
address, values, slave_id
)
result = await client.write_registers(
address=address, values=values, slave=slave_id
)

tried_offset = False
last_error = None
# Try offset addressing first if address >= 40001
# if address >= 40001:
# offset_address = address - 40001
# _LOGGER.debug(
# "Trying write_registers at offset address %s (orig %s) with values %s for slave %s",
# offset_address, address, values, slave_id
# )
# result = await client.write_registers(
# address=offset_address, values=values, slave=slave_id
# )
# if not result.isError():
# _LOGGER.debug("Successfully wrote to registers at offset address %s", offset_address)
# return
# _LOGGER.debug("Offset write failed, will try original address. Error: %s", result)
# last_error = result
# tried_offset = True
# Try original address if not tried yet or offset failed
_LOGGER.debug(
"Trying write_registers at original address %s with values %s for slave %s",
address, values, slave_id
)
result = await client.write_registers(
address=address, values=values, slave=slave_id
)
if result.isError():
_LOGGER.warning("Modbus write_registers error for %s:%s@%s (address %s): %s. Marking connection as closed.", key[0], key[1], slave_id, address, result)
self._connected[key] = False
_LOGGER.debug("Error response from write_registers: %s", result)
raise SigenergyModbusError(
f"Error writing registers at address {address}: {result}"
f"Error writing registers at address {address}: {result if not tried_offset else last_error}, {result}"
)
else:
_LOGGER.debug("Successfully wrote to registers starting at address %s",
address)
_LOGGER.debug("Successfully wrote to registers at address %s", address)
else:
raise SigenergyModbusError(
f"Register type {register_type} is not writable"
Expand Down Expand Up @@ -922,7 +925,8 @@ async def async_write_parameter(
ValueError: If device_type is invalid or device_identifier is missing when required.
"""
if self.read_only:
raise SigenergyModbusError("Cannot write parameter while in read-only mode")
_LOGGER.error("Cannot write parameter while in read-only mode")
return

slave_id: Optional[int] = None
parameter_registers: Dict[str, ModbusRegisterDefinition] = {}
Expand Down Expand Up @@ -1005,104 +1009,104 @@ async def async_write_parameter(
# plant_info is guaranteed to be a dict here

# Special handling for plant_remote_ems_enable register
if register_name == "plant_remote_ems_enable":
_LOGGER.debug("Special handling for plant_remote_ems_enable register")
approaches = [
{"function": "write_registers", "address": register_def.address,
"values": [int(value)]},
{"function": "write_register", "address": register_def.address,
"value": int(value)},
{"function": "write_registers", "address": register_def.address - 40001,
"values": [int(value)]},
{"function": "write_register", "address": register_def.address - 40001,
"value": int(value)},
]
last_error = None
success = False
try:
async with self._locks[key]:
client = await self._get_client(device_info)
for approach in approaches:
try:
_LOGGER.debug(
"Plant write attempt: %s", approach['description'] \
if 'description' in approach \
else f"{approach['function']} @ {approach['address']}")
if approach["function"] == "write_registers":
result = await client.write_registers(
address=approach["address"],
values=approach["values"],
slave=slave_id
)
else:
result = await client.write_register(
address=approach["address"],
value=approach["value"],
slave=slave_id
)
if not result.isError():
success = True
break
last_error = result
except Exception as ex_inner:
last_error = ex_inner
if not success:
raise SigenergyModbusError("Failed plant_remote_ems_enable write after "
f"all attempts. Last error: {last_error}")
return # Success
except (ConnectionException, ModbusException, SigenergyModbusError) as ex_outer:
self._connected[key] = False
raise SigenergyModbusError(f"Error during special plant write: {ex_outer}") \
from ex_outer
except Exception as ex_outer:
raise SigenergyModbusError("Unexpected error during special plant write:"
f" {ex_outer}") from ex_outer
# if register_name == "plant_remote_ems_enable":
# _LOGGER.debug("Special handling for plant_remote_ems_enable register")
# approaches = [
# {"function": "write_registers", "address": register_def.address,
# "values": [int(value)]},
# {"function": "write_register", "address": register_def.address,
# "value": int(value)},
# {"function": "write_registers", "address": register_def.address - 40001,
# "values": [int(value)]},
# {"function": "write_register", "address": register_def.address - 40001,
# "value": int(value)},
# ]
# last_error = None
# success = False
# try:
# async with self._locks[key]:
# client = await self._get_client(device_info)
# for approach in approaches:
# try:
# _LOGGER.debug(
# "Plant write attempt: %s", approach['description'] \
# if 'description' in approach \
# else f"{approach['function']} @ {approach['address']}")
# if approach["function"] == "write_registers":
# result = await client.write_registers(
# address=approach["address"],
# values=approach["values"],
# slave=slave_id
# )
# else:
# result = await client.write_register(
# address=approach["address"],
# value=approach["value"],
# slave=slave_id
# )
# if not result.isError():
# success = True
# break
# last_error = result
# except Exception as ex_inner:
# last_error = ex_inner
# if not success:
# raise SigenergyModbusError("Failed plant_remote_ems_enable write after "
# f"all attempts. Last error: {last_error}")
# return # Success
# except (ConnectionException, ModbusException, SigenergyModbusError) as ex_outer:
# self._connected[key] = False
# raise SigenergyModbusError(f"Error during special plant write: {ex_outer}") \
# from ex_outer
# except Exception as ex_outer:
# raise SigenergyModbusError("Unexpected error during special plant write:"
# f" {ex_outer}") from ex_outer

# Special handling for U32/S32 registers
elif register_def.data_type in [DataType.U32, DataType.S32]:
_LOGGER.debug("Special handling for U32/S32 register %s", register_name)
encoded_values = self._encode_value(value=value, data_type=register_def.data_type,
gain=register_def.gain)
_LOGGER.debug("Encoded values for %s: %s", register_name, encoded_values)
approaches = [
{"address": register_def.address},
{"address": register_def.address - 40001},
{"address": register_def.address - 40000},
{"address": register_def.address % 10000},
]
last_error = None
success = False
try:
async with self._locks[key]:
client = await self._get_client(device_info)
for approach in approaches:
try:
_LOGGER.debug("Plant U32/S32 write attempt: write_registers @ %s",
approach['address'])
result = await client.write_registers(
address=approach["address"],
values=encoded_values,
slave=slave_id
)
if not result.isError():
success = True
break
last_error = result
except Exception as ex_inner:
last_error = ex_inner
if not success:
raise SigenergyModbusError(
f"Failed U32/S32 write for {register_name} after all attempts. "
f"Last error: {last_error}"
)
return # Success
except (ConnectionException, ModbusException, SigenergyModbusError) as ex_outer:
self._connected[key] = False
raise SigenergyModbusError("Error during special plant U32/S32 write:"\
f" {ex_outer}") from ex_outer
except Exception as ex_outer:
raise SigenergyModbusError("Unexpected error during special plant " \
f"U32/S32 write: {ex_outer}") from ex_outer
# if register_def.data_type in [DataType.U32, DataType.S32]:
# _LOGGER.debug("Special handling for U32/S32 register %s", register_name)
# encoded_values = self._encode_value(value=value, data_type=register_def.data_type,
# gain=register_def.gain)
# _LOGGER.debug("Encoded values for %s: %s", register_name, encoded_values)
# approaches = [
# {"address": register_def.address},
# {"address": register_def.address - 40001},
# {"address": register_def.address - 40000},
# {"address": register_def.address % 10000},
# ]
# last_error = None
# success = False
# try:
# async with self._locks[key]:
# client = await self._get_client(device_info)
# for approach in approaches:
# try:
# _LOGGER.debug("Plant U32/S32 write attempt: write_registers @ %s",
# approach['address'])
# result = await client.write_registers(
# address=approach["address"],
# values=encoded_values,
# slave=slave_id
# )
# if not result.isError():
# success = True
# break
# last_error = result
# except Exception as ex_inner:
# last_error = ex_inner
# if not success:
# raise SigenergyModbusError(
# f"Failed U32/S32 write for {register_name} after all attempts. "
# f"Last error: {last_error}"
# )
# return # Success
# except (ConnectionException, ModbusException, SigenergyModbusError) as ex_outer:
# self._connected[key] = False
# raise SigenergyModbusError("Error during special plant U32/S32 write:"\
# f" {ex_outer}") from ex_outer
# except Exception as ex_outer:
# raise SigenergyModbusError("Unexpected error during special plant " \
# f"U32/S32 write: {ex_outer}") from ex_outer

# === General Write Logic ===
# (Executes if device_type is not 'plant' or if it's a plant register
Expand Down