|
26 | 26 |
|
27 | 27 | MODBUS_DEVICE_BUSY_EXCEPTION = 6 |
28 | 28 | MODBUS_GATEWAY_TARGET_FAILED_TO_RESPOND = 11 |
| 29 | +WEB_API_MAX_REGISTERS_PER_REQUEST = 30 |
29 | 30 |
|
30 | 31 | READ_BLOCKS = [ |
31 | 32 | (1001, 62), |
@@ -530,11 +531,13 @@ def __init__( |
530 | 531 | self, |
531 | 532 | address: str, |
532 | 533 | session: aiohttp.ClientSession, |
| 534 | + max_registers_per_request: int = WEB_API_MAX_REGISTERS_PER_REQUEST, |
533 | 535 | ) -> None: |
534 | 536 | """Systemair API Client.""" |
535 | 537 | self._address = address |
536 | 538 | self._session = session |
537 | 539 | self._lock = asyncio.Lock() |
| 540 | + self._max_registers_per_request = max_registers_per_request |
538 | 541 |
|
539 | 542 | @property |
540 | 543 | def address(self) -> str: |
@@ -620,21 +623,32 @@ async def get_all_data(self) -> dict[str, Any]: |
620 | 623 | return all_registers |
621 | 624 |
|
622 | 625 | async def _read_block(self, start: int, count: int) -> dict[str, Any]: |
623 | | - """Read a single block of registers.""" |
624 | | - # Build list of registers for this block |
625 | | - regs = [start - 1 + offset for offset in range(count)] |
| 626 | + """Read a single block of registers, splitting into smaller chunks if needed to avoid URL length limit.""" |
| 627 | + all_data = {} |
626 | 628 |
|
627 | | - # Build query params |
628 | | - query_params = ",".join(f"%22{reg}%22:1" for reg in regs) |
629 | | - url = f"http://{self._address}/mread?{{{query_params}}}" |
| 629 | + # Split large blocks into smaller chunks to avoid 414 URI Too Long error |
| 630 | + chunks_needed = (count + self._max_registers_per_request - 1) // self._max_registers_per_request |
630 | 631 |
|
631 | | - try: |
632 | | - result = await self._api_wrapper(method="get", url=url) |
633 | | - # Convert result to match Modbus TCP format (string keys) |
634 | | - return {str(k): v for k, v in result.items()} |
635 | | - except SystemairApiClientError as e: |
636 | | - msg = f"Failed to read block starting at {start}: {e}" |
637 | | - raise ModbusConnectionError(msg) from e |
| 632 | + for chunk_idx in range(chunks_needed): |
| 633 | + chunk_start = start + (chunk_idx * self._max_registers_per_request) |
| 634 | + chunk_count = min(self._max_registers_per_request, count - (chunk_idx * self._max_registers_per_request)) |
| 635 | + |
| 636 | + # Build list of registers for this chunk |
| 637 | + regs = [chunk_start - 1 + offset for offset in range(chunk_count)] |
| 638 | + |
| 639 | + # Build query params |
| 640 | + query_params = ",".join(f"%22{reg}%22:1" for reg in regs) |
| 641 | + url = f"http://{self._address}/mread?{{{query_params}}}" |
| 642 | + |
| 643 | + try: |
| 644 | + result = await self._api_wrapper(method="get", url=url) |
| 645 | + # Convert result to match Modbus TCP format (string keys) |
| 646 | + all_data.update({str(k): v for k, v in result.items()}) |
| 647 | + except SystemairApiClientError as e: |
| 648 | + msg = f"Failed to read block chunk starting at {chunk_start}: {e}" |
| 649 | + raise ModbusConnectionError(msg) from e |
| 650 | + |
| 651 | + return all_data |
638 | 652 |
|
639 | 653 | async def _parse_response(self, response: aiohttp.ClientResponse, *, retry: bool) -> Any: |
640 | 654 | """Parse the response.""" |
|
0 commit comments