|
41 | 41 | DC_CHARGER_RUNNING_INFO_REGISTERS, |
42 | 42 | DC_CHARGER_PARAMETER_REGISTERS, |
43 | 43 | ) |
44 | | -from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry |
45 | | -from .common import generate_device_id |
46 | | -from .const import DOMAIN |
47 | | -from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry |
48 | | -from .common import generate_device_id |
49 | | -from .const import DOMAIN |
50 | 44 |
|
51 | 45 | _LOGGER = logging.getLogger(__name__) |
52 | 46 |
|
| 47 | +# Pymodbus version compatibility handling |
| 48 | +def _get_pymodbus_version_info(): |
| 49 | + """Get pymodbus version information for compatibility handling.""" |
| 50 | + try: |
| 51 | + # Parse version string like "3.6.8" or "3.10.0" |
| 52 | + version_parts = pymodbus_version.split('.') |
| 53 | + major = int(version_parts[0]) |
| 54 | + minor = int(version_parts[1]) |
| 55 | + return major, minor |
| 56 | + except (ValueError, IndexError): |
| 57 | + # Fallback to assuming older version if parsing fails |
| 58 | + _LOGGER.warning("Could not parse pymodbus version '%s', assuming < 3.10", pymodbus_version) |
| 59 | + return 3, 0 |
| 60 | + |
| 61 | +# Check if we need to use 'device_id' instead of 'slave' |
| 62 | +_PYMODBUS_MAJOR, _PYMODBUS_MINOR = _get_pymodbus_version_info() |
| 63 | +_USE_DEVICE_ID = (_PYMODBUS_MAJOR > 3) or (_PYMODBUS_MAJOR == 3 and _PYMODBUS_MINOR >= 10) |
| 64 | + |
| 65 | +def _call_modbus_method_safe(client_method, *args, **kwargs): |
| 66 | + """ |
| 67 | + Call a pymodbus client method with version-compatible parameter handling. |
| 68 | +
|
| 69 | + This function handles the parameter name change from 'slave' to 'device_id' |
| 70 | + that occurred in pymodbus 3.10+. |
| 71 | +
|
| 72 | + Args: |
| 73 | + client_method: The pymodbus client method to call |
| 74 | + *args: Positional arguments |
| 75 | + **kwargs: Keyword arguments, including 'slave' or 'device_id' |
| 76 | +
|
| 77 | + Returns: |
| 78 | + The result of the client method call |
| 79 | +
|
| 80 | + Raises: |
| 81 | + TypeError: If the method call fails due to parameter incompatibility |
| 82 | + Other exceptions from the underlying method |
| 83 | + """ |
| 84 | + if _USE_DEVICE_ID: |
| 85 | + # For pymodbus >= 3.10, use 'device_id' parameter |
| 86 | + if 'slave' in kwargs: |
| 87 | + kwargs['device_id'] = kwargs.pop('slave') |
| 88 | + |
| 89 | + try: |
| 90 | + return client_method(*args, **kwargs) |
| 91 | + except TypeError as e: |
| 92 | + if 'device_id' in str(e): |
| 93 | + # If device_id also fails, try falling back to positional args |
| 94 | + _LOGGER.debug("device_id parameter failed, trying positional arguments") |
| 95 | + # Remove device_id and try with positional args if possible |
| 96 | + kwargs_copy = kwargs.copy() |
| 97 | + if 'device_id' in kwargs_copy: |
| 98 | + device_id = kwargs_copy.pop('device_id') |
| 99 | + # Try calling with device_id as positional argument |
| 100 | + try: |
| 101 | + return client_method(*args, device_id, **kwargs_copy) |
| 102 | + except TypeError as inner_e: |
| 103 | + # If positional also fails, re-raise original error |
| 104 | + raise e from inner_e |
| 105 | + else: |
| 106 | + raise e |
| 107 | + else: |
| 108 | + raise e |
| 109 | + else: |
| 110 | + # For pymodbus < 3.10, use 'slave' parameter |
| 111 | + try: |
| 112 | + return client_method(*args, **kwargs) |
| 113 | + except TypeError as e: |
| 114 | + if 'slave' in str(e): |
| 115 | + # If slave fails, try falling back to positional args |
| 116 | + _LOGGER.debug("slave parameter failed, trying positional arguments") |
| 117 | + kwargs_copy = kwargs.copy() |
| 118 | + if 'slave' in kwargs_copy: |
| 119 | + slave = kwargs_copy.pop('slave') |
| 120 | + try: |
| 121 | + return client_method(*args, slave, **kwargs_copy) |
| 122 | + except TypeError as inner_e: |
| 123 | + raise e from inner_e |
| 124 | + else: |
| 125 | + raise e |
| 126 | + else: |
| 127 | + raise e |
| 128 | + |
53 | 129 | @dataclass |
54 | 130 | class ModbusConnectionConfig: |
55 | 131 | """Configuration for a Modbus connection.""" |
@@ -293,13 +369,15 @@ async def _probe_single_register( |
293 | 369 |
|
294 | 370 | with _suppress_pymodbus_logging(really_suppress= False if _LOGGER.isEnabledFor(logging.DEBUG) else True): |
295 | 371 | if register.register_type == RegisterType.READ_ONLY: |
296 | | - result = await client.read_input_registers( |
| 372 | + result = await _call_modbus_method_safe( |
| 373 | + client.read_input_registers, |
297 | 374 | address=register.address, |
298 | 375 | count=register.count, |
299 | 376 | slave=slave_id |
300 | 377 | ) |
301 | 378 | elif register.register_type == RegisterType.HOLDING: |
302 | | - result = await client.read_holding_registers( |
| 379 | + result = await _call_modbus_method_safe( |
| 380 | + client.read_holding_registers, |
303 | 381 | address=register.address, |
304 | 382 | count=register.count, |
305 | 383 | slave=slave_id |
@@ -363,7 +441,6 @@ async def async_probe_registers( |
363 | 441 | _LOGGER.debug("No registers need probing for %s.", device_info_log) |
364 | 442 | # If no probing is needed, still generate intervals from already known registers |
365 | 443 | # This handles the case where probing was already done in a previous run. |
366 | | - pass |
367 | 444 | else: |
368 | 445 | _LOGGER.debug("Probing %d registers concurrently for %s...", len(tasks), device_info_log) |
369 | 446 |
|
@@ -501,10 +578,8 @@ async def async_read_registers( |
501 | 578 |
|
502 | 579 | async with self._locks[key]: |
503 | 580 | with _suppress_pymodbus_logging(really_suppress= False if _LOGGER.isEnabledFor(logging.DEBUG) else True): |
504 | | - result = await client.read_input_registers( |
505 | | - address=address, count=count, slave=slave_id |
506 | | - ) if register_type == RegisterType.READ_ONLY \ |
507 | | - else await client.read_holding_registers( |
| 581 | + result = await _call_modbus_method_safe( |
| 582 | + client.read_input_registers if register_type == RegisterType.READ_ONLY else client.read_holding_registers, |
508 | 583 | address=address, count=count, slave=slave_id |
509 | 584 | ) |
510 | 585 |
|
@@ -590,13 +665,15 @@ async def async_write_register( |
590 | 665 | ) |
591 | 666 |
|
592 | 667 | if approach["function"] == "write_registers": |
593 | | - result = await client.write_registers( |
| 668 | + result = await _call_modbus_method_safe( |
| 669 | + client.write_registers, |
594 | 670 | address=approach["address"], |
595 | 671 | values=approach["values"], |
596 | 672 | slave=slave_id |
597 | 673 | ) |
598 | 674 | else: # write_register |
599 | | - result = await client.write_register( |
| 675 | + result = await _call_modbus_method_safe( |
| 676 | + client.write_register, |
600 | 677 | address=approach["address"], |
601 | 678 | value=approach["value"], |
602 | 679 | slave=slave_id |
|
0 commit comments