Skip to content

Commit b8c33ff

Browse files
authored
Merge pull request #1212 from liudger/Optimize-hotwater-requests
2 parents 84b1466 + 5365c3b commit b8c33ff

File tree

11 files changed

+923
-229
lines changed

11 files changed

+923
-229
lines changed

examples/control.py

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# pylint: disable=W0621
2-
"""Asynchronous Python client for BSBLan."""
2+
"""Asynchronous Python client for BSBLan.
3+
4+
This example demonstrates the optimized hot water functionality:
5+
- HotWaterState: Essential parameters for frequent polling (6 fields)
6+
- HotWaterConfig: Configuration parameters checked less frequently (15 fields)
7+
- HotWaterSchedule: Time program schedules checked occasionally (8 fields)
8+
9+
This three-tier approach reduces API calls by 79% for regular monitoring.
10+
"""
311

412
from __future__ import annotations
513

@@ -13,6 +21,8 @@
1321
BSBLANConfig,
1422
Device,
1523
DeviceTime,
24+
HotWaterConfig,
25+
HotWaterSchedule,
1626
HotWaterState,
1727
Info,
1828
Sensor,
@@ -145,11 +155,11 @@ async def print_static_state(static_state: StaticState) -> None:
145155

146156

147157
async def print_hot_water_state(hot_water_state: HotWaterState) -> None:
148-
"""Print hot water state information.
158+
"""Print essential hot water state information.
149159
150160
Args:
151-
hot_water_state (HotWaterState): The hot water state information from the
152-
BSBLan device.
161+
hot_water_state (HotWaterState): The essential hot water state information
162+
from the BSBLan device (optimized for frequent polling).
153163
154164
"""
155165
attributes = {
@@ -163,41 +173,82 @@ async def print_hot_water_state(hot_water_state: HotWaterState) -> None:
163173
hot_water_state.reduced_setpoint, "value", "N/A"
164174
),
165175
"Release": await get_attribute(hot_water_state.release, "desc", "N/A"),
176+
"Current Temperature": await get_attribute(
177+
hot_water_state.dhw_actual_value_top_temperature, "value", "N/A"
178+
),
179+
"DHW Pump State": await get_attribute(
180+
hot_water_state.state_dhw_pump, "desc", "N/A"
181+
),
182+
}
183+
print_attributes("Hot Water State (Essential)", attributes)
184+
185+
186+
async def print_hot_water_config(hot_water_config: HotWaterConfig) -> None:
187+
"""Print hot water configuration information.
188+
189+
Args:
190+
hot_water_config (HotWaterConfig): The hot water configuration information
191+
from the BSBLan device (checked less frequently).
192+
193+
"""
194+
attributes = {
195+
"Nominal Setpoint Max": await get_attribute(
196+
hot_water_config.nominal_setpoint_max, "value", "N/A"
197+
),
166198
"Legionella Function": await get_attribute(
167-
hot_water_state.legionella_function, "desc", "N/A"
199+
hot_water_config.legionella_function, "desc", "N/A"
200+
),
201+
"Legionella Setpoint": await get_attribute(
202+
hot_water_config.legionella_setpoint, "value", "N/A"
168203
),
169204
"Legionella Periodicity": await get_attribute(
170-
hot_water_state.legionella_periodicity, "value", "N/A"
205+
hot_water_config.legionella_periodicity, "value", "N/A"
171206
),
172-
"Legionella Setpoint": await get_attribute(
173-
hot_water_state.legionella_setpoint, "value", "N/A"
207+
"Circulation Pump Release": await get_attribute(
208+
hot_water_config.dhw_circulation_pump_release, "desc", "N/A"
174209
),
175-
"Current Temperature": await get_attribute(
176-
hot_water_state.dhw_actual_value_top_temperature, "value", "N/A"
210+
"Circulation Setpoint": await get_attribute(
211+
hot_water_config.dhw_circulation_setpoint, "value", "N/A"
177212
),
178-
"Time Program Monday": await get_attribute(
179-
hot_water_state.dhw_time_program_monday, "value", "N/A"
213+
}
214+
print_attributes("Hot Water Configuration", attributes)
215+
216+
217+
async def print_hot_water_schedule(hot_water_schedule: HotWaterSchedule) -> None:
218+
"""Print hot water schedule information.
219+
220+
Args:
221+
hot_water_schedule (HotWaterSchedule): The hot water schedule information
222+
from the BSBLan device (time programs).
223+
224+
"""
225+
attributes = {
226+
"Monday": await get_attribute(
227+
hot_water_schedule.dhw_time_program_monday, "value", "N/A"
228+
),
229+
"Tuesday": await get_attribute(
230+
hot_water_schedule.dhw_time_program_tuesday, "value", "N/A"
180231
),
181-
"Time Program Tuesday": await get_attribute(
182-
hot_water_state.dhw_time_program_tuesday, "value", "N/A"
232+
"Wednesday": await get_attribute(
233+
hot_water_schedule.dhw_time_program_wednesday, "value", "N/A"
183234
),
184-
"Time Program Wednesday": await get_attribute(
185-
hot_water_state.dhw_time_program_wednesday, "value", "N/A"
235+
"Thursday": await get_attribute(
236+
hot_water_schedule.dhw_time_program_thursday, "value", "N/A"
186237
),
187-
"Time Program Thursday": await get_attribute(
188-
hot_water_state.dhw_time_program_thursday, "value", "N/A"
238+
"Friday": await get_attribute(
239+
hot_water_schedule.dhw_time_program_friday, "value", "N/A"
189240
),
190-
"Time Program Friday": await get_attribute(
191-
hot_water_state.dhw_time_program_friday, "value", "N/A"
241+
"Saturday": await get_attribute(
242+
hot_water_schedule.dhw_time_program_saturday, "value", "N/A"
192243
),
193-
"Time Program Saturday": await get_attribute(
194-
hot_water_state.dhw_time_program_saturday, "value", "N/A"
244+
"Sunday": await get_attribute(
245+
hot_water_schedule.dhw_time_program_sunday, "value", "N/A"
195246
),
196-
"Time Program Sunday": await get_attribute(
197-
hot_water_state.dhw_time_program_sunday, "value", "N/A"
247+
"Standard Values": await get_attribute(
248+
hot_water_schedule.dhw_time_program_standard_values, "value", "N/A"
198249
),
199250
}
200-
print_attributes("Hot Water State", attributes)
251+
print_attributes("Hot Water Schedule", attributes)
201252

202253

203254
async def main() -> None:
@@ -241,10 +292,24 @@ async def main() -> None:
241292
static_state: StaticState = await bsblan.static_values()
242293
await print_static_state(static_state)
243294

244-
# Get hot water state
295+
# Get hot water state (essential parameters for frequent polling)
245296
hot_water_state: HotWaterState = await bsblan.hot_water_state()
246297
await print_hot_water_state(hot_water_state)
247298

299+
# Get hot water configuration (checked less frequently)
300+
try:
301+
hot_water_config: HotWaterConfig = await bsblan.hot_water_config()
302+
await print_hot_water_config(hot_water_config)
303+
except Exception as e: # noqa: BLE001
304+
print(f"Hot water configuration not available: {e}")
305+
306+
# Get hot water schedule (time programs)
307+
try:
308+
hot_water_schedule: HotWaterSchedule = await bsblan.hot_water_schedule()
309+
await print_hot_water_schedule(hot_water_schedule)
310+
except Exception as e: # noqa: BLE001
311+
print(f"Hot water schedule not available: {e}")
312+
248313
# Example: Set DHW time program for Monday
249314
print("\nSetting DHW time program for Monday to 13:00-14:00")
250315

src/bsblan/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
Device,
77
DeviceTime,
88
DHWTimeSwitchPrograms,
9+
HotWaterConfig,
10+
HotWaterSchedule,
911
HotWaterState,
1012
Info,
1113
Sensor,
@@ -22,6 +24,8 @@
2224
"DHWTimeSwitchPrograms",
2325
"Device",
2426
"DeviceTime",
27+
"HotWaterConfig",
28+
"HotWaterSchedule",
2529
"HotWaterState",
2630
"Info",
2731
"Sensor",

src/bsblan/bsblan.py

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
API_VERSION_ERROR_MSG,
2424
API_VERSIONS,
2525
FIRMWARE_VERSION_ERROR_MSG,
26+
HOT_WATER_CONFIG_PARAMS,
27+
HOT_WATER_ESSENTIAL_PARAMS,
28+
HOT_WATER_SCHEDULE_PARAMS,
2629
HVAC_MODE_DICT,
2730
HVAC_MODE_DICT_REVERSE,
2831
MAX_VALID_YEAR,
@@ -45,6 +48,8 @@
4548
Device,
4649
DeviceTime,
4750
DHWTimeSwitchPrograms,
51+
HotWaterConfig,
52+
HotWaterSchedule,
4853
HotWaterState,
4954
Info,
5055
Sensor,
@@ -92,6 +97,7 @@ class BSBLAN:
9297
_initialized: bool = False
9398
_api_validator: APIValidator = field(init=False)
9499
_temperature_unit: str = "°C"
100+
_hot_water_param_cache: dict[str, str] = field(default_factory=dict)
95101

96102
async def __aenter__(self) -> Self:
97103
"""Enter the context manager.
@@ -189,12 +195,36 @@ async def _validate_api_section(self, section: SectionLiteral) -> None:
189195
# Update API data with validated configuration
190196
if self._api_data:
191197
self._api_data[section] = api_validator.get_section_params(section)
198+
199+
# Cache hot water parameters if this is the hot_water section
200+
if section == "hot_water":
201+
self._populate_hot_water_cache()
192202
except BSBLANError as err:
193203
logger.warning("Failed to validate section %s: %s", section, str(err))
194204
# Reset validation state for this section
195205
api_validator.reset_validation(section)
196206
raise
197207

208+
def _populate_hot_water_cache(self) -> None:
209+
"""Populate the hot water parameter cache with all available parameters."""
210+
if not self._api_validator:
211+
return
212+
213+
# Get all hot water parameters and cache them
214+
hotwater_params = self._api_validator.get_section_params("hot_water")
215+
self._hot_water_param_cache = hotwater_params.copy()
216+
logger.debug("Cached %d hot water parameters", len(self._hot_water_param_cache))
217+
218+
def set_hot_water_cache(self, params: dict[str, str]) -> None:
219+
"""Set the hot water parameter cache manually (for testing).
220+
221+
Args:
222+
params: Dictionary of parameter_id -> parameter_name mappings
223+
224+
"""
225+
self._hot_water_param_cache = params.copy()
226+
logger.debug("Manually set cache with %d hot water parameters", len(params))
227+
198228
async def _fetch_firmware_version(self) -> None:
199229
"""Fetch the firmware version if not already available."""
200230
if self._firmware_version is None:
@@ -683,18 +713,96 @@ async def _set_thermostat_state(self, state: dict[str, Any]) -> None:
683713
logger.debug("Response for setting: %s", response)
684714

685715
async def hot_water_state(self) -> HotWaterState:
686-
"""Get the current hot water state from BSBLAN device.
716+
"""Get essential hot water state for frequent polling.
717+
718+
This method returns only the most important hot water parameters
719+
that are typically checked frequently for monitoring purposes.
720+
This reduces API calls and improves performance for regular polling.
687721
688722
Returns:
689-
HotWaterState: The current hot water state.
723+
HotWaterState: Essential hot water state information.
690724
691725
"""
692-
hotwater_params = self._api_validator.get_section_params("hot_water")
693-
params = await self._extract_params_summary(hotwater_params)
726+
# Use cached parameters or fall back to API validator
727+
hotwater_params = (
728+
self._hot_water_param_cache
729+
or self._api_validator.get_section_params("hot_water")
730+
)
731+
essential_params = {
732+
param_id: param_name
733+
for param_id, param_name in hotwater_params.items()
734+
if param_id in HOT_WATER_ESSENTIAL_PARAMS
735+
}
736+
737+
if not essential_params:
738+
msg = "No essential hot water parameters available"
739+
raise BSBLANError(msg)
740+
741+
params = await self._extract_params_summary(essential_params)
694742
data = await self._request(params={"Parameter": params["string_par"]})
695743
data = dict(zip(params["list"], list(data.values()), strict=True))
696744
return HotWaterState.from_dict(data)
697745

746+
async def hot_water_config(self) -> HotWaterConfig:
747+
"""Get hot water configuration and advanced settings.
748+
749+
This method returns configuration parameters that are typically
750+
set once and checked less frequently.
751+
752+
Returns:
753+
HotWaterConfig: Hot water configuration information.
754+
755+
"""
756+
# Use cached parameters or fall back to API validator
757+
hotwater_params = (
758+
self._hot_water_param_cache
759+
or self._api_validator.get_section_params("hot_water")
760+
)
761+
config_params = {
762+
param_id: param_name
763+
for param_id, param_name in hotwater_params.items()
764+
if param_id in HOT_WATER_CONFIG_PARAMS
765+
}
766+
767+
if not config_params:
768+
msg = "No hot water configuration parameters available"
769+
raise BSBLANError(msg)
770+
771+
params = await self._extract_params_summary(config_params)
772+
data = await self._request(params={"Parameter": params["string_par"]})
773+
data = dict(zip(params["list"], list(data.values()), strict=True))
774+
return HotWaterConfig.from_dict(data)
775+
776+
async def hot_water_schedule(self) -> HotWaterSchedule:
777+
"""Get hot water time program schedules.
778+
779+
This method returns time program settings that are typically
780+
configured once and rarely changed.
781+
782+
Returns:
783+
HotWaterSchedule: Hot water schedule information.
784+
785+
"""
786+
# Use cached parameters or fall back to API validator
787+
hotwater_params = (
788+
self._hot_water_param_cache
789+
or self._api_validator.get_section_params("hot_water")
790+
)
791+
schedule_params = {
792+
param_id: param_name
793+
for param_id, param_name in hotwater_params.items()
794+
if param_id in HOT_WATER_SCHEDULE_PARAMS
795+
}
796+
797+
if not schedule_params:
798+
msg = "No hot water schedule parameters available"
799+
raise BSBLANError(msg)
800+
801+
params = await self._extract_params_summary(schedule_params)
802+
data = await self._request(params={"Parameter": params["string_par"]})
803+
data = dict(zip(params["list"], list(data.values()), strict=True))
804+
return HotWaterSchedule.from_dict(data)
805+
698806
async def set_hot_water( # noqa: PLR0913
699807
self,
700808
nominal_setpoint: float | None = None,

0 commit comments

Comments
 (0)