Skip to content

Commit 740f59c

Browse files
committed
- Added max registers per request option for WebAPI client (related to #14 tests)
1 parent c69ac51 commit 740f59c

File tree

7 files changed

+69
-26
lines changed

7 files changed

+69
-26
lines changed

custom_components/systemair/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
CONF_SERIAL_PORT,
2222
CONF_SLAVE_ID,
2323
CONF_STOPBITS,
24+
CONF_WEB_API_MAX_REGISTERS,
25+
DEFAULT_WEB_API_MAX_REGISTERS,
2426
)
2527
from .coordinator import SystemairDataUpdateCoordinator
2628
from .data import SystemairConfigEntry, SystemairData
@@ -46,9 +48,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: SystemairConfigEntry) ->
4648
api_type = entry.data.get(CONF_API_TYPE, API_TYPE_MODBUS_TCP)
4749

4850
if api_type == API_TYPE_MODBUS_WEBAPI:
51+
max_registers = entry.options.get(CONF_WEB_API_MAX_REGISTERS, DEFAULT_WEB_API_MAX_REGISTERS)
4952
client = SystemairWebApiClient(
5053
address=entry.data[CONF_IP_ADDRESS],
5154
session=async_get_clientsession(hass),
55+
max_registers_per_request=max_registers,
5256
)
5357
elif api_type == API_TYPE_MODBUS_SERIAL:
5458
client = SystemairSerialClient(

custom_components/systemair/api.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
MODBUS_DEVICE_BUSY_EXCEPTION = 6
2828
MODBUS_GATEWAY_TARGET_FAILED_TO_RESPOND = 11
29+
WEB_API_MAX_REGISTERS_PER_REQUEST = 30
2930

3031
READ_BLOCKS = [
3132
(1001, 62),
@@ -530,11 +531,13 @@ def __init__(
530531
self,
531532
address: str,
532533
session: aiohttp.ClientSession,
534+
max_registers_per_request: int = WEB_API_MAX_REGISTERS_PER_REQUEST,
533535
) -> None:
534536
"""Systemair API Client."""
535537
self._address = address
536538
self._session = session
537539
self._lock = asyncio.Lock()
540+
self._max_registers_per_request = max_registers_per_request
538541

539542
@property
540543
def address(self) -> str:
@@ -620,21 +623,32 @@ async def get_all_data(self) -> dict[str, Any]:
620623
return all_registers
621624

622625
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 = {}
626628

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
630631

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
638652

639653
async def _parse_response(self, response: aiohttp.ClientResponse, *, retry: bool) -> Any:
640654
"""Parse the response."""

custom_components/systemair/config_flow.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
CONF_SERIAL_PORT,
3030
CONF_SLAVE_ID,
3131
CONF_STOPBITS,
32+
CONF_WEB_API_MAX_REGISTERS,
3233
DEFAULT_BAUDRATE,
3334
DEFAULT_BYTESIZE,
3435
DEFAULT_PARITY,
3536
DEFAULT_PORT,
3637
DEFAULT_SERIAL_PORT,
3738
DEFAULT_SLAVE_ID,
3839
DEFAULT_STOPBITS,
40+
DEFAULT_WEB_API_MAX_REGISTERS,
3941
DOMAIN,
4042
LOGGER,
4143
MODEL_SPECS,
@@ -337,17 +339,30 @@ async def async_step_init(self, user_input: dict | None = None) -> config_entrie
337339
return self.async_create_entry(title="", data=user_input)
338340

339341
default_model = self.config_entry.options.get(CONF_MODEL, self.config_entry.data.get(CONF_MODEL, "VSR 300"))
342+
api_type = self.config_entry.data.get(CONF_API_TYPE)
343+
344+
# Base schema with model selection
345+
schema_dict = {
346+
vol.Required(CONF_MODEL, default=default_model): selector.SelectSelector(
347+
selector.SelectSelectorConfig(
348+
options=list(MODEL_SPECS.keys()),
349+
mode=selector.SelectSelectorMode.DROPDOWN,
350+
)
351+
),
352+
}
353+
354+
# Web API specific options
355+
if api_type == API_TYPE_MODBUS_WEBAPI:
356+
default_max_registers = self.config_entry.options.get(CONF_WEB_API_MAX_REGISTERS, DEFAULT_WEB_API_MAX_REGISTERS)
357+
schema_dict[vol.Optional(CONF_WEB_API_MAX_REGISTERS, default=default_max_registers)] = selector.NumberSelector(
358+
selector.NumberSelectorConfig(
359+
min=30,
360+
max=125,
361+
mode=selector.NumberSelectorMode.BOX,
362+
)
363+
)
340364

341365
return self.async_show_form(
342366
step_id="init",
343-
data_schema=vol.Schema(
344-
{
345-
vol.Required(CONF_MODEL, default=default_model): selector.SelectSelector(
346-
selector.SelectSelectorConfig(
347-
options=list(MODEL_SPECS.keys()),
348-
mode=selector.SelectSelectorMode.DROPDOWN,
349-
)
350-
),
351-
}
352-
),
367+
data_schema=vol.Schema(schema_dict),
353368
)

custom_components/systemair/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
CONF_MODEL = "model"
1212
CONF_SLAVE_ID = "slave_id"
1313
CONF_API_TYPE = "api_type"
14+
CONF_WEB_API_MAX_REGISTERS = "web_api_max_registers"
1415
DEFAULT_PORT = 502
1516
DEFAULT_SLAVE_ID = 1
17+
DEFAULT_WEB_API_MAX_REGISTERS = 30
1618

1719
# --- API Types ---
1820
API_TYPE_MODBUS_TCP = "modbus_tcp"

custom_components/systemair/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
"aiohttp",
1616
"async-timeout"
1717
],
18-
"version": "1.0.14"
18+
"version": "1.0.15"
1919
}

custom_components/systemair/translations/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@
5151
"init": {
5252
"title": "Systemair Settings",
5353
"data": {
54-
"model": "Ventilation Unit Model"
54+
"model": "Ventilation Unit Model",
55+
"web_api_max_registers": "Max registers per Web API request"
56+
},
57+
"data_description": {
58+
"web_api_max_registers": "Maximum number of registers to read in a single Web API request. Range: 30-125."
5559
}
5660
}
5761
}

custom_components/systemair/translations/ru.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@
5151
"init": {
5252
"title": "Настройки Systemair",
5353
"data": {
54-
"model": "Модель устройства"
54+
"model": "Модель устройства",
55+
"web_api_max_registers": "Макс. регистров на запрос Web API"
56+
},
57+
"data_description": {
58+
"web_api_max_registers": "Максимальное количество регистров, читаемых за один запрос Web API. Диапазон: 30-125."
5559
}
5660
}
5761
}

0 commit comments

Comments
 (0)