python-bsblan 6.1.10__py3-none-any.whl → 6.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bsblan/_thermostat.py CHANGED
@@ -35,7 +35,7 @@ class ThermostatWriter:
35
35
  self._temperature = temperature
36
36
  self._set_payload = set_payload
37
37
 
38
- async def prepare_state( # pylint: disable=too-many-arguments
38
+ async def prepare_state( # noqa: PLR0913 # pylint: disable=too-many-arguments
39
39
  self,
40
40
  target_temperature: str | None,
41
41
  hvac_mode: int | None,
@@ -43,6 +43,8 @@ class ThermostatWriter:
43
43
  target_temperature_high: str | float | None = None,
44
44
  *,
45
45
  cooling_operating_mode: int | None = None,
46
+ temp_reduced_setpoint: str | float | None = None,
47
+ temporary_mode: int | None = None,
46
48
  ) -> dict[str, Any]:
47
49
  """Prepare the thermostat state for setting.
48
50
 
@@ -53,6 +55,9 @@ class ThermostatWriter:
53
55
  target_temperature_high: The cooling comfort setpoint to set.
54
56
  cooling_operating_mode: The cooling circuit operating mode to set
55
57
  as raw integer.
58
+ temp_reduced_setpoint: The reduced (eco) room setpoint to set.
59
+ temporary_mode: The temporary operating mode override to set as
60
+ raw integer.
56
61
 
57
62
  Returns:
58
63
  dict[str, Any]: The prepared state for the thermostat.
@@ -69,10 +74,7 @@ class ThermostatWriter:
69
74
  self._set_payload(param_ids["target_temperature"], target_temperature),
70
75
  )
71
76
  if target_temperature_high is not None:
72
- param_id = param_ids.get("target_temperature_high")
73
- if param_id is None:
74
- parameter_name = "target_temperature_high"
75
- raise BSBLANInvalidParameterError(parameter_name)
77
+ param_id = self._param_id(param_ids, "target_temperature_high")
76
78
  await self._temperature.validate_target_temperature_high(
77
79
  target_temperature_high,
78
80
  circuit,
@@ -89,16 +91,35 @@ class ThermostatWriter:
89
91
  self._set_payload(param_ids["hvac_mode"], hvac_value),
90
92
  )
91
93
  if cooling_operating_mode is not None:
92
- param_id = param_ids.get("cooling_operating_mode")
93
- if param_id is None:
94
- parameter_name = "cooling_operating_mode"
95
- raise BSBLANInvalidParameterError(parameter_name)
94
+ param_id = self._param_id(param_ids, "cooling_operating_mode")
96
95
  self.validate_cooling_operating_mode(cooling_operating_mode)
97
96
  state.update(
98
97
  self._set_payload(param_id, str(cooling_operating_mode)),
99
98
  )
99
+ if temp_reduced_setpoint is not None:
100
+ param_id = self._param_id(param_ids, "temp_reduced_setpoint")
101
+ await self._temperature.validate_target_temperature(
102
+ str(temp_reduced_setpoint),
103
+ circuit,
104
+ )
105
+ state.update(
106
+ self._set_payload(param_id, str(temp_reduced_setpoint)),
107
+ )
108
+ if temporary_mode is not None:
109
+ param_id = self._param_id(param_ids, "temporary_mode")
110
+ self.validate_temporary_mode(temporary_mode)
111
+ state.update(
112
+ self._set_payload(param_id, str(temporary_mode)),
113
+ )
100
114
  return state
101
115
 
116
+ @staticmethod
117
+ def _param_id(param_ids: dict[str, str], parameter_name: str) -> str:
118
+ """Return the parameter ID for a write, or raise if unsupported."""
119
+ if (param_id := param_ids.get(parameter_name)) is None:
120
+ raise BSBLANInvalidParameterError(parameter_name)
121
+ return param_id
122
+
102
123
  def thermostat_params(self, circuit: int) -> dict[str, str]:
103
124
  """Return thermostat write parameters for the active bus type."""
104
125
  if self._uses_pps_bus():
@@ -135,3 +156,17 @@ class ThermostatWriter:
135
156
  """
136
157
  if cooling_operating_mode not in Validation.COOLING_OPERATING_MODES:
137
158
  raise BSBLANInvalidParameterError(str(cooling_operating_mode))
159
+
160
+ def validate_temporary_mode(self, temporary_mode: int) -> None:
161
+ """Validate the temporary operating mode override.
162
+
163
+ Args:
164
+ temporary_mode (int): The mode to validate. Valid values are
165
+ 0=No override, 1=Reduced, 2=Comfort.
166
+
167
+ Raises:
168
+ BSBLANInvalidParameterError: If the mode is invalid.
169
+
170
+ """
171
+ if temporary_mode not in Validation.TEMPORARY_MODES:
172
+ raise BSBLANInvalidParameterError(str(temporary_mode))
bsblan/_validation.py CHANGED
@@ -233,7 +233,7 @@ class SectionValidator:
233
233
 
234
234
  This enables more granular lazy loading for hot water - instead of
235
235
  validating all 29 hot water parameters at once, we only validate
236
- the specific group needed (essential: 5, config: 16, schedule: 8).
236
+ the specific group needed (essential: 6, config: 16, schedule: 8).
237
237
 
238
238
  Uses a per-group lock to prevent concurrent validation of the same
239
239
  group, which could cause duplicate network requests.
bsblan/bsblan.py CHANGED
@@ -51,9 +51,10 @@ from .models import (
51
51
  State,
52
52
  StaticState,
53
53
  )
54
- from .utility import is_param_value_active, validate_time_format
54
+ from .utility import is_param_value_active
55
55
 
56
56
  if TYPE_CHECKING:
57
+ from datetime import datetime
57
58
  from typing import Self
58
59
 
59
60
  from aiohttp.client import ClientSession
@@ -171,7 +172,20 @@ class BSBLAN:
171
172
  if self.session is None:
172
173
  self.session = aiohttp.ClientSession()
173
174
  self._close_session = True
174
- await self.initialize()
175
+ try:
176
+ await self.initialize()
177
+ except BaseException:
178
+ if self._close_session and self.session:
179
+ session = self.session
180
+ self.session = None
181
+ self._close_session = False
182
+ try:
183
+ await session.close()
184
+ except Exception:
185
+ logger.exception(
186
+ "Failed to close session after initialization failure"
187
+ )
188
+ raise
175
189
  return self
176
190
 
177
191
  async def __aexit__(self, *args: object) -> None:
@@ -332,7 +346,7 @@ class BSBLAN:
332
346
 
333
347
  This enables more granular lazy loading for hot water - instead of
334
348
  validating all 29 hot water parameters at once, we only validate
335
- the specific group needed (essential: 5, config: 16, schedule: 8).
349
+ the specific group needed (essential: 6, config: 16, schedule: 8).
336
350
 
337
351
  Uses a per-group lock to prevent concurrent validation of the same
338
352
  group, which could cause duplicate network requests.
@@ -720,7 +734,7 @@ class BSBLAN:
720
734
  include: Optional list of parameter names to fetch. If None,
721
735
  fetches all state parameters. Valid names include:
722
736
  hvac_mode, target_temperature, hvac_action,
723
- hvac_mode_changeover, current_temperature,
737
+ hvac_mode_changeover, current_temperature, current_setpoint,
724
738
  room1_temp_setpoint_boost.
725
739
  circuit: The heating circuit number (1 or 2). Defaults to 1.
726
740
  Circuit 2 uses separate parameter IDs but returns the
@@ -782,15 +796,17 @@ class BSBLAN:
782
796
  Args:
783
797
  include: Optional list of parameter names to fetch. If None,
784
798
  fetches all static parameters. Valid names include:
785
- min_temp, max_temp.
799
+ temp_reduced_setpoint, heating_protective_setpoint,
800
+ comfort_setpoint_max, cooling_comfort_setpoint_min,
801
+ cooling_reduced_setpoint, and min_temp, max_temp on PPS.
786
802
  circuit: The heating circuit number (1 or 2). Defaults to 1.
787
803
 
788
804
  Returns:
789
805
  StaticState: The static information from the BSBLAN device.
790
806
 
791
807
  Example:
792
- # Fetch only min_temp
793
- static = await client.static_values(include=["min_temp"])
808
+ # Fetch only the reduced setpoint
809
+ static = await client.static_values(include=["temp_reduced_setpoint"])
794
810
 
795
811
  # Fetch static values for heating circuit 2
796
812
  static_hc2 = await client.static_values(circuit=2)
@@ -847,26 +863,33 @@ class BSBLAN:
847
863
  time_data = {"time": data["0"]}
848
864
  return DeviceTime.model_validate(time_data)
849
865
 
850
- async def set_time(self, time_value: str) -> None:
866
+ async def set_time(self, value: datetime) -> None:
851
867
  """Set the time on the BSB-LAN device.
852
868
 
853
869
  Args:
854
- time_value (str): The time value to set in format "DD.MM.YYYY HH:MM:SS"
855
- (e.g., "13.08.2025 10:25:55").
870
+ value (datetime): The local time of the heating system. Its date and
871
+ time are written as they are, without timezone conversion.
856
872
 
857
873
  Raises:
858
- BSBLANInvalidParameterError: If the time format is invalid.
874
+ BSBLANInvalidParameterError: If the year is out of range.
875
+ BSBLANError: If the device does not accept the new time.
859
876
 
860
877
  """
861
878
  await self._ensure_device_metadata()
862
879
  self._validate_time_sync_supported()
863
- self._validate_time_format(time_value)
880
+ if not Validation.MIN_YEAR <= value.year <= Validation.MAX_YEAR:
881
+ msg = f"Invalid year: {value.year}"
882
+ raise BSBLANInvalidParameterError(msg)
883
+ # BSB-LAN 4.1 rejects a space between the date and the time
864
884
  response = await self._request(
865
- base_path="/JS", data=self._set_payload("0", time_value)
885
+ base_path="/JS",
886
+ data=self._set_payload("0", value.strftime("%d.%m.%Y_%H:%M:%S")),
866
887
  )
867
888
  logger.debug("Response for setting time: %s", response)
889
+ if response.get("0", {}).get("status") != 1:
890
+ raise BSBLANError(ErrorMsg.TIME_SYNC_FAILED)
868
891
 
869
- async def thermostat( # pylint: disable=too-many-arguments
892
+ async def thermostat( # noqa: PLR0913 # pylint: disable=too-many-arguments
870
893
  self,
871
894
  target_temperature: str | None = None,
872
895
  hvac_mode: int | None = None,
@@ -874,6 +897,8 @@ class BSBLAN:
874
897
  target_temperature_high: str | float | None = None,
875
898
  *,
876
899
  cooling_operating_mode: int | None = None,
900
+ temp_reduced_setpoint: str | float | None = None,
901
+ temporary_mode: int | None = None,
877
902
  ) -> None:
878
903
  """Change the state of the thermostat through BSB-Lan.
879
904
 
@@ -888,6 +913,12 @@ class BSBLAN:
888
913
  cooling_operating_mode: The cooling circuit operating mode to set
889
914
  as raw integer value: 0=Protection, 1=Automatic, 2=Reduced,
890
915
  3=Comfort. Not supported on PPS devices.
916
+ temp_reduced_setpoint: The reduced (eco) room setpoint to set.
917
+ Not supported on PPS devices.
918
+ temporary_mode: Temporary operating mode override, active until
919
+ the next time program switch point. Raw integer value:
920
+ 0=No override, 1=Reduced, 2=Comfort. Only has an effect in
921
+ automatic mode. Not supported on PPS devices.
891
922
 
892
923
  Example:
893
924
  # Set HC1 temperature
@@ -899,6 +930,12 @@ class BSBLAN:
899
930
  # Set HC1 cooling operating mode
900
931
  await client.thermostat(cooling_operating_mode=1)
901
932
 
933
+ # Set HC1 reduced setpoint
934
+ await client.thermostat(temp_reduced_setpoint="18.0")
935
+
936
+ # Switch HC1 to comfort until the next time program switch point
937
+ await client.thermostat(temporary_mode=2)
938
+
902
939
  # Set HC2 mode
903
940
  await client.thermostat(hvac_mode=1, circuit=2)
904
941
 
@@ -906,13 +943,14 @@ class BSBLAN:
906
943
  self._validate_circuit(circuit)
907
944
  if self._uses_pps_bus:
908
945
  self._validate_bus_write_supported()
909
- await self._initialize_temperature_range(circuit)
910
946
 
911
947
  self._validate_single_parameter(
912
948
  target_temperature,
913
949
  hvac_mode,
914
950
  target_temperature_high,
915
951
  cooling_operating_mode,
952
+ temp_reduced_setpoint,
953
+ temporary_mode,
916
954
  error_msg=ErrorMsg.MULTI_PARAMETER,
917
955
  )
918
956
 
@@ -922,6 +960,8 @@ class BSBLAN:
922
960
  circuit,
923
961
  target_temperature_high,
924
962
  cooling_operating_mode=cooling_operating_mode,
963
+ temp_reduced_setpoint=temp_reduced_setpoint,
964
+ temporary_mode=temporary_mode,
925
965
  )
926
966
  await self._set_device_state(state)
927
967
 
@@ -960,21 +1000,6 @@ class BSBLAN:
960
1000
  circuit,
961
1001
  )
962
1002
 
963
- def _validate_time_format(self, time_value: str) -> None:
964
- """Validate the time format.
965
-
966
- Args:
967
- time_value (str): The time value to validate.
968
-
969
- Raises:
970
- BSBLANInvalidParameterError: If the time format is invalid.
971
-
972
- """
973
- try:
974
- validate_time_format(time_value, Validation.MIN_YEAR, Validation.MAX_YEAR)
975
- except ValueError as err:
976
- raise BSBLANInvalidParameterError(str(err)) from err
977
-
978
1003
  def _set_payload(
979
1004
  self, parameter: str, value: str, type_: str = "1"
980
1005
  ) -> dict[str, Any]:
@@ -1010,14 +1035,15 @@ class BSBLAN:
1010
1035
  that are typically checked frequently for monitoring purposes.
1011
1036
  This reduces API calls and improves performance for regular polling.
1012
1037
 
1013
- Uses granular lazy loading - only validates the 5 essential params,
1038
+ Uses granular lazy loading - only validates the 6 essential params,
1014
1039
  not all 29 hot water parameters.
1015
1040
 
1016
1041
  Args:
1017
1042
  include: Optional list of parameter names to fetch. If None,
1018
1043
  fetches all essential hot water parameters. Valid names include:
1019
1044
  operating_mode, nominal_setpoint, release,
1020
- dhw_actual_value_top_temperature, state_dhw_pump.
1045
+ dhw_actual_value_top_temperature, dhw_current_setpoint,
1046
+ state_dhw_pump.
1021
1047
 
1022
1048
  Returns:
1023
1049
  HotWaterState: Essential hot water state information.
bsblan/constants.py CHANGED
@@ -45,6 +45,7 @@ BASE_HEATING_PARAMS: Final[dict[str, str]] = {
45
45
  # -------
46
46
  "8000": "hvac_action",
47
47
  "8740": "current_temperature",
48
+ "8741": "current_setpoint",
48
49
  "770": "room1_temp_setpoint_boost",
49
50
  }
50
51
 
@@ -90,6 +91,7 @@ BASE_HOT_WATER_PARAMS: Final[dict[str, str]] = {
90
91
  "1680": "operating_mode_changeover",
91
92
  # -------
92
93
  "8830": "dhw_actual_value_top_temperature",
94
+ "8831": "dhw_current_setpoint",
93
95
  "8820": "state_dhw_pump",
94
96
  # -------
95
97
  "561": "dhw_time_program_monday",
@@ -113,6 +115,7 @@ BASE_HEATING_CIRCUIT2_PARAMS: Final[dict[str, str]] = {
113
115
  # -------
114
116
  "8001": "hvac_action",
115
117
  "8770": "current_temperature",
118
+ "8771": "current_setpoint",
116
119
  "1070": "room1_temp_setpoint_boost",
117
120
  }
118
121
 
@@ -181,12 +184,16 @@ class CircuitConfig:
181
184
  "target_temperature_high": "902",
182
185
  "hvac_mode": "700",
183
186
  "cooling_operating_mode": "901",
187
+ "temp_reduced_setpoint": "712",
188
+ "temporary_mode": "10110",
184
189
  },
185
190
  2: {
186
191
  "target_temperature": "1010",
187
192
  "target_temperature_high": "1202",
188
193
  "hvac_mode": "1000",
189
194
  "cooling_operating_mode": "1201",
195
+ "temp_reduced_setpoint": "1012",
196
+ "temporary_mode": "10111",
190
197
  },
191
198
  }
192
199
  PROBE_PARAMS: Final[dict[int, str]] = {
@@ -252,6 +259,9 @@ class Validation:
252
259
  # 901/1201 cooling circuit operating mode (ENUM, verified on device):
253
260
  # 0=Protection, 1=Automatic, 2=Reduced, 3=Comfort
254
261
  COOLING_OPERATING_MODES: Final[set[int]] = {0, 1, 2, 3}
262
+ # 10110/10111 temporary operating mode (write-only, verified on device):
263
+ # 0=No override, 1=Reduced, 2=Comfort, until the next time program switch
264
+ TEMPORARY_MODES: Final[set[int]] = {0, 1, 2}
255
265
  PPS_HVAC_MODES: Final[set[int]] = {0, 1, 3}
256
266
  PPS_HVAC_MODE_TO_BSBLAN: Final[dict[int, str]] = {
257
267
  0: "2", # off
@@ -415,19 +425,20 @@ _STATUS_TO_CATEGORY: dict[HeatingCircuitStatus, HVACActionCategory] = {
415
425
  # Off states
416
426
  HeatingCircuitStatus.FAULT_ERROR: HVACActionCategory.OFF,
417
427
  HeatingCircuitStatus.STANDBY_1: HVACActionCategory.OFF,
418
- HeatingCircuitStatus.OFF: HVACActionCategory.OFF,
419
428
  HeatingCircuitStatus.STANDBY_2: HVACActionCategory.OFF,
420
429
  HeatingCircuitStatus.COOLING_OFF: HVACActionCategory.OFF,
421
- HeatingCircuitStatus.COOLING_LOCKED: HVACActionCategory.OFF,
422
430
  HeatingCircuitStatus.HEATING_GENERATOR_OFF: HVACActionCategory.OFF,
423
431
  HeatingCircuitStatus.HEATING_OFF: HVACActionCategory.OFF,
424
- HeatingCircuitStatus.LOCKED_HEATING_MODE: HVACActionCategory.OFF,
425
- HeatingCircuitStatus.LOCKED_GENERATOR: HVACActionCategory.OFF,
426
- HeatingCircuitStatus.LOCKED_BUFFER: HVACActionCategory.OFF,
427
- HeatingCircuitStatus.DAY_ECO: HVACActionCategory.OFF,
428
- HeatingCircuitStatus.SETBACK_REDUCED: HVACActionCategory.OFF,
429
- HeatingCircuitStatus.SETBACK_FROST_PROTECTION: HVACActionCategory.OFF,
430
- HeatingCircuitStatus.ROOM_TEMPERATURE_LIMIT: HVACActionCategory.OFF,
432
+ # Idle states: the circuit runs but currently needs no heat
433
+ HeatingCircuitStatus.OFF: HVACActionCategory.IDLE, # summer operation
434
+ HeatingCircuitStatus.COOLING_LOCKED: HVACActionCategory.IDLE,
435
+ HeatingCircuitStatus.LOCKED_HEATING_MODE: HVACActionCategory.IDLE,
436
+ HeatingCircuitStatus.LOCKED_GENERATOR: HVACActionCategory.IDLE,
437
+ HeatingCircuitStatus.LOCKED_BUFFER: HVACActionCategory.IDLE,
438
+ HeatingCircuitStatus.DAY_ECO: HVACActionCategory.IDLE,
439
+ HeatingCircuitStatus.SETBACK_REDUCED: HVACActionCategory.IDLE,
440
+ HeatingCircuitStatus.SETBACK_FROST_PROTECTION: HVACActionCategory.IDLE,
441
+ HeatingCircuitStatus.ROOM_TEMPERATURE_LIMIT: HVACActionCategory.IDLE,
431
442
  # Heating states
432
443
  HeatingCircuitStatus.MANUAL_CONTROL: HVACActionCategory.HEATING,
433
444
  HeatingCircuitStatus.OVERRUN: HVACActionCategory.HEATING,
@@ -550,6 +561,7 @@ class ErrorMsg:
550
561
  )
551
562
  NO_HEATING_SCHEDULE_PARAMS = "No heating schedule parameters available"
552
563
  TIME_SYNC_NOT_SUPPORTED = "Time synchronization is not supported by this device"
564
+ TIME_SYNC_FAILED = "The device did not accept the new time"
553
565
  BUS_WRITE_NOT_SUPPORTED = "Writing parameters is not supported by this device"
554
566
 
555
567
 
@@ -642,6 +654,7 @@ class HotWaterParams:
642
654
  "nominal_setpoint",
643
655
  "release",
644
656
  "dhw_actual_value_top_temperature",
657
+ "dhw_current_setpoint",
645
658
  "state_dhw_pump",
646
659
  }
647
660
  }
bsblan/models.py CHANGED
@@ -481,6 +481,8 @@ class State(BaseModel):
481
481
  # (0=Protection, 1=Automatic, 2=Reduced, 3=Comfort)
482
482
  cooling_operating_mode: EntityInfo[int] | None = None
483
483
  current_temperature: EntityInfo[float] | None = None
484
+ # 8741/8771: room setpoint currently applied by the controller
485
+ current_setpoint: EntityInfo[float] | None = None
484
486
  room1_temp_setpoint_boost: EntityInfo[float] | None = None
485
487
 
486
488
 
@@ -525,6 +527,7 @@ class HotWaterState(BaseModel):
525
527
  nominal_setpoint: EntityInfo[float] | None = None
526
528
  release: EntityInfo[int] | None = None
527
529
  dhw_actual_value_top_temperature: EntityInfo[float] | None = None
530
+ dhw_current_setpoint: EntityInfo[float] | None = None
528
531
  state_dhw_pump: EntityInfo[int] | None = None
529
532
 
530
533
 
bsblan/utility.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- import re
7
6
  from dataclasses import dataclass, field
8
7
  from typing import Any
9
8
 
@@ -12,65 +11,6 @@ from .constants import CircuitConfig
12
11
  logger = logging.getLogger(__name__)
13
12
 
14
13
 
15
- def validate_time_format(
16
- time_value: str,
17
- min_year: int,
18
- max_year: int,
19
- ) -> None:
20
- """Validate the BSB-LAN time format.
21
-
22
- Args:
23
- time_value: The time value to validate in format DD.MM.YYYY HH:MM:SS.
24
- min_year: Minimum valid year.
25
- max_year: Maximum valid year.
26
-
27
- Raises:
28
- ValueError: If the time format is invalid.
29
-
30
- """
31
- # BSB-LAN supports format: DD.MM.YYYY HH:MM:SS (e.g., "13.08.2025 10:25:55")
32
- pattern = r"^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2}):(\d{2})$"
33
-
34
- match = re.match(pattern, time_value)
35
- if not match:
36
- msg = f"Invalid time format: {time_value}. Expected DD.MM.YYYY HH:MM:SS"
37
- raise ValueError(msg)
38
-
39
- day, month, year, hour, minute, second = map(int, match.groups())
40
-
41
- # Validate ranges
42
- if not (1 <= day <= 31):
43
- msg = f"Invalid day: {day}"
44
- raise ValueError(msg)
45
- if not (1 <= month <= 12):
46
- msg = f"Invalid month: {month}"
47
- raise ValueError(msg)
48
- if not (min_year <= year <= max_year):
49
- msg = f"Invalid year: {year}"
50
- raise ValueError(msg)
51
- if not (0 <= hour <= 23):
52
- msg = f"Invalid hour: {hour}"
53
- raise ValueError(msg)
54
- if not (0 <= minute <= 59):
55
- msg = f"Invalid minute: {minute}"
56
- raise ValueError(msg)
57
- if not (0 <= second <= 59):
58
- msg = f"Invalid second: {second}"
59
- raise ValueError(msg)
60
-
61
- # Additional validation for days per month
62
- if month in (4, 6, 9, 11) and day > 30:
63
- msg = f"Invalid day {day} for month {month}"
64
- raise ValueError(msg)
65
- if month == 2:
66
- # Leap year check
67
- is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
68
- max_day = 29 if is_leap else 28
69
- if day > max_day:
70
- msg = f"Invalid day {day} for February in year {year}"
71
- raise ValueError(msg)
72
-
73
-
74
14
  def is_param_value_active(param: dict[str, Any] | None) -> bool:
75
15
  """Check whether a parameter payload carries an active value.
76
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-bsblan
3
- Version: 6.1.10
3
+ Version: 6.2.0
4
4
  Summary: Asynchronous Python client for BSBLAN API
5
5
  Project-URL: Homepage, https://github.com/liudger/python-bsblan
6
6
  Project-URL: Repository, https://github.com/liudger/python-bsblan
@@ -0,0 +1,19 @@
1
+ bsblan/__init__.py,sha256=bGIxaWUq-NleDngb-EYphvtAdotxH08RZ2nWqAJB8Vs,1531
2
+ bsblan/_hot_water.py,sha256=PfYj8Yf6GYprIQ0AXjvEKWMdCojGAh6CMRcmHE9ZYwg,9114
3
+ bsblan/_parameters.py,sha256=oT1zm-HUP6oo23V46PEe4aumSYWuXMwJaxsEbw13a7g,5010
4
+ bsblan/_schedules.py,sha256=Pb0TrhoZM3Wlf83zZOo2d31xDLI0178TNWRqhljJJmU,5273
5
+ bsblan/_temperature.py,sha256=DsxFd6f1EMYKHcrf74ScQk6YqaYuUy0zBPHhiP-C0nY,11366
6
+ bsblan/_thermostat.py,sha256=Yl-AM4gJikwSQwcpYH7fHH6I1lEVggnendf28-d0IAE,6520
7
+ bsblan/_transport.py,sha256=qNtV_8uJrfYfHlbaExPln4jCaJxqE5sypq5OHyxG8KE,8266
8
+ bsblan/_validation.py,sha256=w93Hg5sqeJKXOozwWV4Hjh7_aR197tOpHn6LgxL1Pek,17836
9
+ bsblan/_version.py,sha256=hpeIg9JUswHSorY3iSUERcMC1MzwM9Rpdo1wqeh5rc8,2799
10
+ bsblan/bsblan.py,sha256=XOzwrThvxT6NQcaKz7BKdpeZPkZRBbgnXd8plcrU-X4,48987
11
+ bsblan/constants.py,sha256=xcL43NpsyrJ6NRJHO2x2iH9J97Dq6DsVsFn-CzpBxoo,26373
12
+ bsblan/exceptions.py,sha256=Z9pXtVc8zhy9FTBkT8leJ1UE9zdOdLHF7pE7C4GjBmg,2795
13
+ bsblan/models.py,sha256=UJbCoVr7_ohtKG5n_jSzoYDzBpl9y4OWj3bqGYRvf_0,23111
14
+ bsblan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ bsblan/utility.py,sha256=V5OjZieC6NpQHMKdfrUMJkjSrbrMvpnT7A5k5bQnmgM,5543
16
+ python_bsblan-6.2.0.dist-info/METADATA,sha256=DyrUwQh3bu9ueQKDpOkBipZzpIIAPo1OG-q0bn2ZFt8,9585
17
+ python_bsblan-6.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
18
+ python_bsblan-6.2.0.dist-info/licenses/LICENSE.md,sha256=Shv8HPcD1WbZjBPvfb5r3h_cwaPeVaUZMUqU_XQGwGw,1092
19
+ python_bsblan-6.2.0.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- bsblan/__init__.py,sha256=bGIxaWUq-NleDngb-EYphvtAdotxH08RZ2nWqAJB8Vs,1531
2
- bsblan/_hot_water.py,sha256=PfYj8Yf6GYprIQ0AXjvEKWMdCojGAh6CMRcmHE9ZYwg,9114
3
- bsblan/_parameters.py,sha256=oT1zm-HUP6oo23V46PEe4aumSYWuXMwJaxsEbw13a7g,5010
4
- bsblan/_schedules.py,sha256=Pb0TrhoZM3Wlf83zZOo2d31xDLI0178TNWRqhljJJmU,5273
5
- bsblan/_temperature.py,sha256=DsxFd6f1EMYKHcrf74ScQk6YqaYuUy0zBPHhiP-C0nY,11366
6
- bsblan/_thermostat.py,sha256=_Dgvt1iVLGJT2H5i4z9R1RUU44NZCJJwnP1D9rSXDHk,5052
7
- bsblan/_transport.py,sha256=qNtV_8uJrfYfHlbaExPln4jCaJxqE5sypq5OHyxG8KE,8266
8
- bsblan/_validation.py,sha256=LxuewBmGDaPAtdBNo9B8LY6_q6rX4BkfbA7EZA5NL4Q,17836
9
- bsblan/_version.py,sha256=hpeIg9JUswHSorY3iSUERcMC1MzwM9Rpdo1wqeh5rc8,2799
10
- bsblan/bsblan.py,sha256=v91kmq3gd62xZcqtg40dVWDI0MUPPO8yE7-eY0W3UdA,47423
11
- bsblan/constants.py,sha256=qVagiPIWgP_hnkC9WRc-zQj0-gwfdu63hsYfxlUcXpk,25709
12
- bsblan/exceptions.py,sha256=Z9pXtVc8zhy9FTBkT8leJ1UE9zdOdLHF7pE7C4GjBmg,2795
13
- bsblan/models.py,sha256=nbQjmkLEdM_MtazTTDS1KISZs9h1iestoZg8byVB9kM,22932
14
- bsblan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- bsblan/utility.py,sha256=5oD3f84ke_r7pTQN2jjhhgtVjLjq1Ohld3LlHN7m8xg,7421
16
- python_bsblan-6.1.10.dist-info/METADATA,sha256=iPG3zb1OKuOQaPHKUe6NZw55N0dpUHHkwBlhQp4LYsk,9586
17
- python_bsblan-6.1.10.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
18
- python_bsblan-6.1.10.dist-info/licenses/LICENSE.md,sha256=Shv8HPcD1WbZjBPvfb5r3h_cwaPeVaUZMUqU_XQGwGw,1092
19
- python_bsblan-6.1.10.dist-info/RECORD,,