Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
85 changes: 1 addition & 84 deletions examples/speed_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
- basic: Original tests (parallel calls, read_parameters, filtering)
- scalability: Large parameter set tests
- dual-circuit: Single vs parallel calls for dual heating circuit params
- triple-circuit: Same idea extended to 3 heating circuits
- hot-water: Hot water parameter group loading tests

Usage:
Expand Down Expand Up @@ -59,26 +58,17 @@
HC1_PARAMS = ["700", "710", "900", "8000", "8740", "8749"]

# Heating circuit 2 (1000-series) — mirrors HC1 with offset
HC2_PARAMS = ["1000", "1010", "1200", "8001", "8741", "8750"]

# Heating circuit 3 (1300-series) — mirrors HC1 with offset
HC3_PARAMS = ["1300", "1310", "1500", "8002", "8742", "8751"]
HC2_PARAMS = ["1000", "1010", "1200", "8001", "8770", "8779"]

# Static values per circuit
HC1_STATIC_PARAMS = ["714", "716"]
HC2_STATIC_PARAMS = ["1014", "1016"]
HC3_STATIC_PARAMS = ["1314", "1316"]

# Combined dual circuit parameter sets
DUAL_HEATING_PARAMS = HC1_PARAMS + HC2_PARAMS
DUAL_STATIC_PARAMS = HC1_STATIC_PARAMS + HC2_STATIC_PARAMS
DUAL_ALL_PARAMS = DUAL_HEATING_PARAMS + DUAL_STATIC_PARAMS

# Triple circuit parameter sets
TRIPLE_HEATING_PARAMS = HC1_PARAMS + HC2_PARAMS + HC3_PARAMS
TRIPLE_STATIC_PARAMS = HC1_STATIC_PARAMS + HC2_STATIC_PARAMS + HC3_STATIC_PARAMS
TRIPLE_ALL_PARAMS = TRIPLE_HEATING_PARAMS + TRIPLE_STATIC_PARAMS

# Sensor parameters
SENSOR_PARAMS = ["8700", "8740"]

Expand Down Expand Up @@ -515,78 +505,6 @@ async def _sequential_hc1_hc2() -> None:
return suite


def build_triple_circuit_suite(bsblan: BSBLAN) -> BenchmarkSuite:
"""Build the triple heating circuit benchmark suite.

Same idea as dual-circuit but for 3 circuits. Most systems have
at most 2 circuits; HC3 params will return '---' on those
devices but this still measures the network call overhead.
"""
suite = BenchmarkSuite(
name="Triple Heating Circuit",
description=(
"Compare fetching strategies for 3 heating circuits.\n"
" HC1: " + ", ".join(HC1_PARAMS) + "\n"
" HC2: " + ", ".join(HC2_PARAMS) + "\n"
" HC3: " + ", ".join(HC3_PARAMS)
),
)

suite.add(
(f"HC1+HC2+HC3 combined — 1 call ({len(TRIPLE_HEATING_PARAMS)} params)"),
f"1 call ({len(TRIPLE_HEATING_PARAMS)}p)",
lambda: bsblan.read_parameters(TRIPLE_HEATING_PARAMS),
param_count=len(TRIPLE_HEATING_PARAMS),
)

suite.add(
"HC1+HC2+HC3 parallel — 3 calls",
"3 parallel",
lambda: asyncio.gather(
bsblan.read_parameters(HC1_PARAMS),
bsblan.read_parameters(HC2_PARAMS),
bsblan.read_parameters(HC3_PARAMS),
),
param_count=len(TRIPLE_HEATING_PARAMS),
)

async def _sequential_3() -> None:
await bsblan.read_parameters(HC1_PARAMS)
await bsblan.read_parameters(HC2_PARAMS)
await bsblan.read_parameters(HC3_PARAMS)

suite.add(
"HC1+HC2+HC3 sequential — 3 calls",
"3 sequential",
_sequential_3,
param_count=len(TRIPLE_HEATING_PARAMS),
)

# Full init with static values
suite.add(
(f"All circuits + static — 1 call ({len(TRIPLE_ALL_PARAMS)} params)"),
f"1 call all ({len(TRIPLE_ALL_PARAMS)}p)",
lambda: bsblan.read_parameters(TRIPLE_ALL_PARAMS),
param_count=len(TRIPLE_ALL_PARAMS),
)

suite.add(
"All circuits + static — 6 parallel (heat+static per circ)",
"6 parallel per section",
lambda: asyncio.gather(
bsblan.read_parameters(HC1_PARAMS),
bsblan.read_parameters(HC2_PARAMS),
bsblan.read_parameters(HC3_PARAMS),
bsblan.read_parameters(HC1_STATIC_PARAMS),
bsblan.read_parameters(HC2_STATIC_PARAMS),
bsblan.read_parameters(HC3_STATIC_PARAMS),
),
param_count=len(TRIPLE_ALL_PARAMS),
)

return suite


def build_hot_water_suite(bsblan: BSBLAN) -> BenchmarkSuite:
"""Build the hot water parameter benchmark suite."""
suite = BenchmarkSuite(
Expand Down Expand Up @@ -645,7 +563,6 @@ def build_hot_water_suite(bsblan: BSBLAN) -> BenchmarkSuite:
"basic": build_basic_suite,
"scalability": build_scalability_suite,
"dual-circuit": build_dual_circuit_suite,
"triple-circuit": build_triple_circuit_suite,
"hot-water": build_hot_water_suite,
}

Expand Down
10 changes: 4 additions & 6 deletions src/bsblan/bsblan.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@
"sensor",
"hot_water",
"heating_circuit2",
"heating_circuit3",
"staticValues_circuit2",
"staticValues_circuit3",
]

# TypeVar for hot water data models
Expand Down Expand Up @@ -184,10 +182,10 @@ async def initialize(self) -> None:
async def get_available_circuits(self) -> list[int]:
"""Detect which heating circuits are available on the device.

Uses a two-step probe for each circuit (1, 2, 3):
Uses a two-step probe for each circuit (1, 2):
1. Query the operating mode parameter — the response must be
non-empty and contain actual data.
2. Query the status parameter (8000/8001/8002) — an inactive
2. Query the status parameter (8000/8001) — an inactive
circuit returns ``value="0"`` with ``desc="---"``.
Comment thread
liudger marked this conversation as resolved.

A circuit is only considered available when both checks pass.
Expand Down Expand Up @@ -680,7 +678,7 @@ async def _initialize_temperature_range(
self._max_temp = temp_range["max"]
self._temperature_range_initialized = True
else:
# HC2/HC3 use per-circuit storage
# HC2 uses per-circuit storage
self._circuit_temp_ranges[circuit] = temp_range
self._circuit_temp_initialized.add(circuit)

Expand Down Expand Up @@ -1254,7 +1252,7 @@ async def _validate_target_temperature(
min_temp = self._min_temp
max_temp = self._max_temp
else:
# HC2/HC3 use per-circuit storage
# HC2 uses per-circuit storage
if circuit not in self._circuit_temp_initialized:
await self._initialize_temperature_range(circuit)

Expand Down
54 changes: 5 additions & 49 deletions src/bsblan/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

# Supported heating circuits (1-based)
MIN_CIRCUIT: Final[int] = 1
MAX_CIRCUIT: Final[int] = 3
VALID_CIRCUITS: Final[set[int]] = {1, 2, 3}
MAX_CIRCUIT: Final[int] = 2
VALID_CIRCUITS: Final[set[int]] = {1, 2}
Comment thread
liudger marked this conversation as resolved.


# API Versions
Expand All @@ -20,11 +20,9 @@ class APIConfig(TypedDict):
device: dict[str, str]
sensor: dict[str, str]
hot_water: dict[str, str]
# Multi-circuit sections (heating circuit 2 and 3)
# Multi-circuit sections (heating circuit 2)
heating_circuit2: dict[str, str]
heating_circuit3: dict[str, str]
staticValues_circuit2: dict[str, str]
staticValues_circuit3: dict[str, str]


# Base parameters that exist in all API versions
Expand Down Expand Up @@ -111,8 +109,8 @@ class APIConfig(TypedDict):
"1200": "hvac_mode_changeover",
# -------
"8001": "hvac_action",
"8741": "current_temperature",
"8750": "room1_thermostat_mode",
"8770": "current_temperature",
"8779": "room1_thermostat_mode",
}

BASE_STATIC_VALUES_CIRCUIT2_PARAMS: Final[dict[str, str]] = {
Expand All @@ -131,68 +129,35 @@ class APIConfig(TypedDict):
"1016": "max_temp",
}

# --- Heating Circuit 3 parameters (1300-series) ---
# These mirror HC1 (700-series) with an offset of +600
BASE_HEATING_CIRCUIT3_PARAMS: Final[dict[str, str]] = {
"1300": "hvac_mode",
"1310": "target_temperature",
"1500": "hvac_mode_changeover",
# -------
"8002": "hvac_action",
"8742": "current_temperature",
"8751": "room1_thermostat_mode",
}

BASE_STATIC_VALUES_CIRCUIT3_PARAMS: Final[dict[str, str]] = {
"1314": "min_temp",
}

V1_STATIC_VALUES_CIRCUIT3_EXTENSIONS: Final[dict[str, str]] = {
"1330": "max_temp",
}

V3_HEATING_CIRCUIT3_EXTENSIONS: Final[dict[str, str]] = {
"1370": "room1_temp_setpoint_boost",
}

V3_STATIC_VALUES_CIRCUIT3_EXTENSIONS: Final[dict[str, str]] = {
"1316": "max_temp",
}

# Mapping from circuit number to section names
CIRCUIT_HEATING_SECTIONS: Final[dict[int, str]] = {
1: "heating",
2: "heating_circuit2",
3: "heating_circuit3",
}

CIRCUIT_STATIC_SECTIONS: Final[dict[int, str]] = {
1: "staticValues",
2: "staticValues_circuit2",
3: "staticValues_circuit3",
}

# Mapping from circuit number to thermostat parameter IDs
CIRCUIT_THERMOSTAT_PARAMS: Final[dict[int, dict[str, str]]] = {
1: {"target_temperature": "710", "hvac_mode": "700"},
2: {"target_temperature": "1010", "hvac_mode": "1000"},
3: {"target_temperature": "1310", "hvac_mode": "1300"},
}

# Parameter IDs used to probe whether a heating circuit exists on the device.
# We query the operating mode (hvac_mode) for each circuit.
CIRCUIT_PROBE_PARAMS: Final[dict[int, str]] = {
1: "700",
2: "1000",
3: "1300",
}

# Status parameter IDs used as a secondary check for circuit availability.
# Inactive circuits return value="0" and desc="---" for these parameters.
CIRCUIT_STATUS_PARAMS: Final[dict[int, str]] = {
1: "8000",
2: "8001",
3: "8002",
}

# Marker value returned by BSB-LAN for parameters on inactive circuits
Expand All @@ -217,30 +182,21 @@ def build_api_config(version: str) -> APIConfig:
"hot_water": BASE_HOT_WATER_PARAMS.copy(),
# Multi-circuit sections
"heating_circuit2": BASE_HEATING_CIRCUIT2_PARAMS.copy(),
"heating_circuit3": BASE_HEATING_CIRCUIT3_PARAMS.copy(),
"staticValues_circuit2": BASE_STATIC_VALUES_CIRCUIT2_PARAMS.copy(),
"staticValues_circuit3": BASE_STATIC_VALUES_CIRCUIT3_PARAMS.copy(),
}

if version == "v1":
config["staticValues"].update(V1_STATIC_VALUES_EXTENSIONS)
config["staticValues_circuit2"].update(
V1_STATIC_VALUES_CIRCUIT2_EXTENSIONS,
)
config["staticValues_circuit3"].update(
V1_STATIC_VALUES_CIRCUIT3_EXTENSIONS,
)
elif version == "v3":
config["heating"].update(V3_HEATING_EXTENSIONS)
config["staticValues"].update(V3_STATIC_VALUES_EXTENSIONS)
config["heating_circuit2"].update(V3_HEATING_CIRCUIT2_EXTENSIONS)
config["staticValues_circuit2"].update(
V3_STATIC_VALUES_CIRCUIT2_EXTENSIONS,
)
config["heating_circuit3"].update(V3_HEATING_CIRCUIT3_EXTENSIONS)
config["staticValues_circuit3"].update(
V3_STATIC_VALUES_CIRCUIT3_EXTENSIONS,
)

return config

Expand Down
6 changes: 3 additions & 3 deletions tests/fixtures/state_circuit2.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"readwrite": 1,
"unit": ""
},
"8741": {
"name": "Room temperature setpoint 1",
"8770": {
"name": "Room temperature actual value 2",
"dataType_name": "TEMP",
"dataType_family": "VALS",
"error": 0,
Expand All @@ -61,7 +61,7 @@
"readwrite": 1,
"unit": "°C"
},
"8750": {
"8779": {
"name": "Room thermostat heating circuit 2",
"dataType_name": "ENUM",
"dataType_family": "ENUM",
Expand Down
2 changes: 0 additions & 2 deletions tests/test_api_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,7 @@ async def test_validate_api_section_hot_water_cache() -> None:
"device": {},
"hot_water": {"1600": "operating_mode", "1610": "nominal_setpoint"},
"heating_circuit2": {},
"heating_circuit3": {},
"staticValues_circuit2": {},
"staticValues_circuit3": {},
}
bsblan._api_validator = APIValidator(bsblan._api_data)

Expand Down
Loading
Loading