python-bsblan 5.1.4__py3-none-any.whl → 5.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/__init__.py CHANGED
@@ -17,6 +17,8 @@ from .models import (
17
17
  DHWTimeSwitchPrograms,
18
18
  EntityInfo,
19
19
  EntityValue,
20
+ HeatingSchedule,
21
+ HeatingTimeSwitchPrograms,
20
22
  HotWaterConfig,
21
23
  HotWaterSchedule,
22
24
  HotWaterState,
@@ -45,6 +47,8 @@ __all__ = [
45
47
  "EntityValue",
46
48
  "HVACActionCategory",
47
49
  "HeatingCircuitStatus",
50
+ "HeatingSchedule",
51
+ "HeatingTimeSwitchPrograms",
48
52
  "HotWaterConfig",
49
53
  "HotWaterSchedule",
50
54
  "HotWaterState",
bsblan/bsblan.py CHANGED
@@ -22,6 +22,7 @@ from .constants import (
22
22
  APIConfig,
23
23
  CircuitConfig,
24
24
  ErrorMsg,
25
+ HeatingScheduleParams,
25
26
  HotWaterParams,
26
27
  Validation,
27
28
  )
@@ -38,6 +39,8 @@ from .models import (
38
39
  DeviceTime,
39
40
  DHWSchedule,
40
41
  EntityInfo,
42
+ HeatingSchedule,
43
+ HeatingTimeSwitchPrograms,
41
44
  HotWaterConfig,
42
45
  HotWaterSchedule,
43
46
  HotWaterState,
@@ -1380,6 +1383,91 @@ class BSBLAN:
1380
1383
  include=include,
1381
1384
  )
1382
1385
 
1386
+ async def heating_schedule(
1387
+ self,
1388
+ include: list[str] | None = None,
1389
+ circuit: int = 1,
1390
+ ) -> HeatingTimeSwitchPrograms:
1391
+ """Get heating time switch programs for a specific circuit.
1392
+
1393
+ Args:
1394
+ include: Optional list of day names to fetch. If None,
1395
+ fetches all schedule parameters. Valid names include:
1396
+ monday, tuesday, wednesday, thursday,
1397
+ friday, saturday, sunday, standard_values.
1398
+ circuit: The heating circuit number (1 or 2). Defaults to 1.
1399
+
1400
+ Returns:
1401
+ HeatingTimeSwitchPrograms: Heating schedule information.
1402
+
1403
+ """
1404
+ self._validate_circuit(circuit)
1405
+ time_program_params = HeatingScheduleParams.TIME_PROGRAMS[circuit]
1406
+
1407
+ filtered_params = time_program_params
1408
+ if include is not None:
1409
+ if not include:
1410
+ raise BSBLANError(ErrorMsg.EMPTY_INCLUDE_LIST)
1411
+ filtered_params = {
1412
+ param_id: name
1413
+ for param_id, name in time_program_params.items()
1414
+ if name in include
1415
+ }
1416
+ if not filtered_params:
1417
+ raise BSBLANError(ErrorMsg.INVALID_INCLUDE_PARAMS)
1418
+
1419
+ params = self._extract_params_summary(filtered_params)
1420
+ data = await self._request(params={"Parameter": params["string_par"]})
1421
+ mapped_data = {
1422
+ name: data[param_id]
1423
+ for param_id, name in filtered_params.items()
1424
+ if param_id in data
1425
+ }
1426
+
1427
+ if not mapped_data:
1428
+ raise BSBLANError(ErrorMsg.NO_HEATING_SCHEDULE_PARAMS)
1429
+
1430
+ return HeatingTimeSwitchPrograms.model_validate(mapped_data)
1431
+
1432
+ async def set_heating_schedule(
1433
+ self,
1434
+ schedule: HeatingSchedule,
1435
+ circuit: int = 1,
1436
+ ) -> None:
1437
+ """Set heating time switch programs for a specific circuit.
1438
+
1439
+ This method allows setting weekly heating schedules using a type-safe
1440
+ interface with TimeSlot and DaySchedule objects.
1441
+
1442
+ Args:
1443
+ schedule: HeatingSchedule object containing the weekly schedule.
1444
+ circuit: The heating circuit number (1 or 2). Defaults to 1.
1445
+
1446
+ Raises:
1447
+ BSBLANError: If no schedule is provided.
1448
+
1449
+ """
1450
+ self._validate_circuit(circuit)
1451
+
1452
+ if not schedule.has_any_schedule():
1453
+ raise BSBLANError(ErrorMsg.NO_SCHEDULE)
1454
+
1455
+ day_param_map = {
1456
+ v: k
1457
+ for k, v in HeatingScheduleParams.TIME_PROGRAMS[circuit].items()
1458
+ if v != "standard_values"
1459
+ }
1460
+
1461
+ for day_name, param_id in day_param_map.items():
1462
+ day_schedule: DaySchedule | None = getattr(schedule, day_name)
1463
+ if day_schedule is not None:
1464
+ state = {
1465
+ "Parameter": param_id,
1466
+ "Value": day_schedule.to_bsblan_format(),
1467
+ "Type": "1",
1468
+ }
1469
+ await self._set_device_state(state)
1470
+
1383
1471
  async def set_hot_water(self, params: SetHotWaterParam) -> None:
1384
1472
  """Change the state of the hot water system through BSB-Lan.
1385
1473
 
bsblan/constants.py CHANGED
@@ -491,6 +491,7 @@ class ErrorMsg:
491
491
  EMPTY_INCLUDE_LIST = (
492
492
  "Empty include list provided. Use None to fetch all parameters."
493
493
  )
494
+ NO_HEATING_SCHEDULE_PARAMS = "No heating schedule parameters available"
494
495
 
495
496
 
496
497
  # Handle both ASCII and Unicode degree symbols
@@ -655,3 +656,30 @@ class HotWaterParams:
655
656
  "567": "sunday",
656
657
  "576": "standard_values",
657
658
  }
659
+
660
+
661
+ class HeatingScheduleParams:
662
+ """Heating schedule parameter mappings per circuit."""
663
+
664
+ TIME_PROGRAMS: Final[dict[int, dict[str, str]]] = {
665
+ 1: {
666
+ "501": "monday",
667
+ "502": "tuesday",
668
+ "503": "wednesday",
669
+ "504": "thursday",
670
+ "505": "friday",
671
+ "506": "saturday",
672
+ "507": "sunday",
673
+ "516": "standard_values",
674
+ },
675
+ 2: {
676
+ "521": "monday",
677
+ "522": "tuesday",
678
+ "523": "wednesday",
679
+ "524": "thursday",
680
+ "525": "friday",
681
+ "526": "saturday",
682
+ "527": "sunday",
683
+ "536": "standard_values",
684
+ },
685
+ }
bsblan/models.py CHANGED
@@ -141,23 +141,10 @@ class DaySchedule:
141
141
 
142
142
 
143
143
  @dataclass
144
- class DHWSchedule:
145
- """Weekly hot water schedule for setting time programs.
144
+ class WeeklySchedule:
145
+ """Base weekly schedule with optional day schedules.
146
146
 
147
- Use this dataclass to set DHW time programs via set_hot_water_schedule().
148
- Each day can have up to 3 time slots.
149
-
150
- Example:
151
- >>> schedule = DHWSchedule(
152
- ... monday=DaySchedule(slots=[
153
- ... TimeSlot(time(6, 0), time(8, 0)),
154
- ... TimeSlot(time(17, 0), time(21, 0)),
155
- ... ]),
156
- ... tuesday=DaySchedule(slots=[
157
- ... TimeSlot(time(6, 0), time(8, 0)),
158
- ... ])
159
- ... )
160
- >>> await client.set_hot_water_schedule(schedule)
147
+ Each day can have up to 3 time slots (validated by DaySchedule).
161
148
 
162
149
  """
163
150
 
@@ -190,6 +177,38 @@ class DHWSchedule:
190
177
  )
191
178
 
192
179
 
180
+ @dataclass
181
+ class DHWSchedule(WeeklySchedule):
182
+ """Weekly hot water schedule for setting time programs.
183
+
184
+ Use this dataclass to set DHW time programs via set_hot_water_schedule().
185
+ Each day can have up to 3 time slots.
186
+
187
+ Example:
188
+ >>> schedule = DHWSchedule(
189
+ ... monday=DaySchedule(slots=[
190
+ ... TimeSlot(time(6, 0), time(8, 0)),
191
+ ... TimeSlot(time(17, 0), time(21, 0)),
192
+ ... ]),
193
+ ... tuesday=DaySchedule(slots=[
194
+ ... TimeSlot(time(6, 0), time(8, 0)),
195
+ ... ])
196
+ ... )
197
+ >>> await client.set_hot_water_schedule(schedule)
198
+
199
+ """
200
+
201
+
202
+ @dataclass
203
+ class HeatingSchedule(WeeklySchedule):
204
+ """Weekly heating schedule for setting time programs.
205
+
206
+ Use this dataclass to set heating time programs via set_heating_schedule().
207
+ Each day can have up to 3 time slots.
208
+
209
+ """
210
+
211
+
193
212
  @dataclass
194
213
  class DHWTimeSwitchPrograms:
195
214
  """Dataclass for DHW time switch programs."""
@@ -554,6 +573,29 @@ class HotWaterSchedule(BaseModel):
554
573
  dhw_time_program_standard_values: EntityInfo[int] | None = None
555
574
 
556
575
 
576
+ class HeatingTimeSwitchPrograms(BaseModel):
577
+ """Heating time switch programs for a specific heating circuit (READ).
578
+
579
+ The daily time programs (Monday-Sunday) use BSB-LAN dataType 9
580
+ (TIMEPROG) and return schedule strings like
581
+ ``"13:00-15:00 ##:##-##:## ##:##-##:##"`` where ``##:##`` marks
582
+ unused time slots.
583
+
584
+ ``standard_values`` is a YESNO enum (0=No, 1=Yes) that resets
585
+ all daily schedules back to the controller's factory defaults.
586
+
587
+ """
588
+
589
+ monday: EntityInfo[str | int] | None = None
590
+ tuesday: EntityInfo[str | int] | None = None
591
+ wednesday: EntityInfo[str | int] | None = None
592
+ thursday: EntityInfo[str | int] | None = None
593
+ friday: EntityInfo[str | int] | None = None
594
+ saturday: EntityInfo[str | int] | None = None
595
+ sunday: EntityInfo[str | int] | None = None
596
+ standard_values: EntityInfo[int] | None = None
597
+
598
+
557
599
  class DeviceTime(BaseModel):
558
600
  """Object holds device time information."""
559
601
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bsblan
3
- Version: 5.1.4
3
+ Version: 5.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
7
- Project-URL: Documentation, https://github.com/liudger/python-bsblan
7
+ Project-URL: Documentation, https://liudger.github.io/python-bsblan
8
8
  Project-URL: Bug Tracker, https://github.com/liudger/python-bsblan/issues
9
9
  Project-URL: Changelog, https://github.com/liudger/python-bsblan/releases
10
10
  Author-email: Willem-Jan van Rootselaar <liudgervr@gmail.com>
@@ -41,8 +41,9 @@ Description-Content-Type: text/markdown
41
41
  [![Build Status][build-shield]][build]
42
42
  [![Code Coverage][codecov-shield]][codecov]
43
43
  [![Quality Gate Status][sonarcloud-shield]][sonarcloud]
44
-
45
- [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee]
44
+ [![OpenSSF Scorecard][scorecard-shield]][scorecard]
45
+ [![OpenSSF Best Practices][bestpractices-shield]][bestpractices]
46
+ [![Documentation][docs-shield]][docs]
46
47
 
47
48
  Asynchronous Python client for BSBLan.
48
49
 
@@ -52,97 +53,36 @@ This package allows you to control and monitor a BSBLan device
52
53
  programmatically. It is mainly created to allow third-party programs to automate
53
54
  the behavior of [BSBLan][bsblanmodule].
54
55
 
56
+ **[Full documentation](https://liudger.github.io/python-bsblan)**
57
+
55
58
  ## Installation
56
59
 
57
60
  ```bash
58
61
  pip install python-bsblan
59
62
  ```
60
63
 
61
- ## Usage
64
+ ## Quick start
62
65
 
63
66
  ```python
64
- # pylint: disable=W0621
65
- """Asynchronous Python client for BSBLan."""
66
-
67
67
  import asyncio
68
- import os
69
-
70
- from bsblan import BSBLAN, BSBLANConfig, Device, Info, Sensor, State, StaticState
71
-
72
-
73
- async def print_state(state: State) -> None:
74
- """Print the current state of the BSBLan device."""
75
- print(f"HVAC Action: {state.hvac_action.desc}")
76
- print(f"HVAC Mode: {state.hvac_mode.desc}")
77
- print(f"Current Temperature: {state.current_temperature.value}")
78
-
79
-
80
- async def print_sensor(sensor: Sensor) -> None:
81
- """Print sensor information from the BSBLan device."""
82
- print(f"Outside Temperature: {sensor.outside_temperature.value}")
83
-
84
-
85
- async def print_device_info(device: Device, info: Info) -> None:
86
- """Print device and general information."""
87
- print(f"Device Name: {device.name}")
88
- print(f"Version: {device.version}")
89
- print(f"Device Identification: {info.device_identification.value}")
90
-
91
-
92
- async def print_static_state(static_state: StaticState) -> None:
93
- """Print static state information."""
94
- print(f"Min Temperature: {static_state.min_temp.value}")
95
- print(f"Max Temperature: {static_state.max_temp.value}")
96
-
68
+ from bsblan import BSBLAN, BSBLANConfig
97
69
 
98
70
  async def main() -> None:
99
- """Show example on controlling your BSBLan device.
100
-
101
- Options:
102
- - passkey (http://url/"passkey"/) if your device is setup for passkey authentication
103
- - username and password if your device is setup for username/password authentication
104
-
105
- """
106
- # Create a configuration object
107
- config = BSBLANConfig(
108
- host="192.0.2.1",
109
- passkey=None,
110
- username=os.getenv("USERNAME"), # Compliant
111
- password=os.getenv("PASSWORD"), # Compliant
112
- )
113
-
114
- # Initialize BSBLAN with the configuration object
115
- async with BSBLAN(config) as bsblan:
116
- # Get and print state
117
- state: State = await bsblan.state()
118
- await print_state(state)
71
+ config = BSBLANConfig(host="192.168.1.100")
72
+ async with BSBLAN(config) as client:
73
+ # Read current state
74
+ state = await client.state()
75
+ print(f"Current temperature: {state.current_temperature.value}")
119
76
 
120
77
  # Set thermostat temperature
121
- print("\nSetting temperature to 18°C")
122
- await bsblan.thermostat(target_temperature="18")
123
-
124
- # Set HVAC mode (using raw integer: 0=off, 1=auto, 2=eco, 3=heat)
125
- print("Setting HVAC mode to heat")
126
- await bsblan.thermostat(hvac_mode=3)
127
-
128
- # Get and print sensor information
129
- sensor: Sensor = await bsblan.sensor()
130
- await print_sensor(sensor)
131
-
132
- # Get and print device and general info
133
- device: Device = await bsblan.device()
134
- info: Info = await bsblan.info()
135
- await print_device_info(device, info)
136
-
137
- # Get and print static state
138
- static_state: StaticState = await bsblan.static_values()
139
- await print_static_state(static_state)
78
+ await client.thermostat(target_temperature="21.5")
140
79
 
141
-
142
- if __name__ == "__main__":
143
- asyncio.run(main())
80
+ asyncio.run(main())
144
81
  ```
145
82
 
83
+ For more examples, including hot water control, multi-circuit support, and
84
+ authentication setup, see the [Getting Started][docs-getting-started] guide.
85
+
146
86
  ## Changelog & Releases
147
87
 
148
88
  This repository keeps a change log using [GitHub's releases][releases]
@@ -190,12 +130,14 @@ make setup
190
130
  A `Makefile` is provided for common development tasks. Run `make help` to
191
131
  see all available targets:
192
132
 
193
- | Command | Description |
194
- |-----------------|--------------------------------------|
195
- | `make setup` | Install dev dependencies & git hooks |
196
- | `make lint` | Run all pre-commit hooks |
197
- | `make test` | Run tests |
198
- | `make coverage` | Run tests with coverage report |
133
+ | Command | Description |
134
+ |--------------------|--------------------------------------|
135
+ | `make setup` | Install dev dependencies & git hooks |
136
+ | `make lint` | Run all pre-commit hooks |
137
+ | `make test` | Run tests |
138
+ | `make coverage` | Run tests with coverage report |
139
+ | `make docs` | Build documentation |
140
+ | `make docs-serve` | Serve documentation locally |
199
141
 
200
142
  As this repository uses [prek][prek] (a faster, Rust-based drop-in replacement
201
143
  for pre-commit), all changes are linted and tested with each commit. You can
@@ -243,9 +185,16 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
243
185
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
244
186
  SOFTWARE.
245
187
 
188
+ ---
189
+
190
+ [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee]
191
+
246
192
  [bsblanmodule]: https://github.com/fredlcore/bsb_lan
247
193
  [build-shield]: https://github.com/liudger/python-bsblan/actions/workflows/tests.yaml/badge.svg
248
194
  [build]: https://github.com/liudger/python-bsblan/actions
195
+ [docs]: https://liudger.github.io/python-bsblan
196
+ [docs-getting-started]: https://liudger.github.io/python-bsblan/getting-started/
197
+ [docs-shield]: https://img.shields.io/badge/docs-GitHub%20Pages-blue
249
198
  [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg
250
199
  [buymeacoffee]: https://www.buymeacoffee.com/liudger
251
200
  [codecov-shield]: https://codecov.io/gh/liudger/python-bsblan/branch/main/graph/badge.svg?token=ypos87GGxv
@@ -259,11 +208,15 @@ SOFTWARE.
259
208
  [uv]: https://docs.astral.sh/uv/
260
209
  [uv-install]: https://docs.astral.sh/uv/getting-started/installation/
261
210
  [prek]: https://github.com/j178/prek
262
- [project-stage-shield]: https://img.shields.io/badge/project%20stage-experimental-yellow.svg
211
+ [project-stage-shield]: https://img.shields.io/badge/project%20stage-stable-green.svg
263
212
  [pypi]: https://pypi.org/project/python-bsblan/
264
213
  [python-versions-shield]: https://img.shields.io/pypi/pyversions/python-bsblan
265
214
  [releases-shield]: https://img.shields.io/github/v/release/liudger/python-bsblan.svg
266
215
  [releases]: https://github.com/liudger/python-bsblan/releases
267
216
  [semver]: http://semver.org/spec/v2.0.0.html
217
+ [scorecard-shield]: https://api.scorecard.dev/projects/github.com/liudger/python-bsblan/badge
218
+ [scorecard]: https://scorecard.dev/viewer/?uri=github.com/liudger/python-bsblan
219
+ [bestpractices-shield]: https://www.bestpractices.dev/projects/12561/badge
220
+ [bestpractices]: https://www.bestpractices.dev/projects/12561
268
221
  [sonarcloud-shield]: https://sonarcloud.io/api/project_badges/measure?project=liudger_python-bsblan&metric=alert_status
269
222
  [sonarcloud]: https://sonarcloud.io/summary/new_code?id=liudger_python-bsblan
@@ -0,0 +1,11 @@
1
+ bsblan/__init__.py,sha256=YD_edvxHL5ocUE2iLWhdinqpsRYWJkVrcds0b6aKp9U,1288
2
+ bsblan/bsblan.py,sha256=PCNForRgeJFHwZRE_mEcvySQxiYLA2JCIzMJu429Fq4,63034
3
+ bsblan/constants.py,sha256=LoMSrEdZj2zohDFxOQ2JT7J4jIH9clXh7sfkNnnjl5A,22864
4
+ bsblan/exceptions.py,sha256=jL7qohIMmuVTsdWBB_trKPg5Yzim6JxaOT13h6EJPlk,1770
5
+ bsblan/models.py,sha256=qEycc2eVkCK2CXWDXBSqtTyM-to6pH7jX6aKvXeH3R8,20705
6
+ bsblan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ bsblan/utility.py,sha256=sS0wWJoqvLAHzwaSLIqQEQ-boHsYFLKAHw8KNTSmdX8,5772
8
+ python_bsblan-5.2.0.dist-info/METADATA,sha256=MjhQBz4cjiRwunT2OH4X-gRFsprNSIpLBMNu7kam24Y,8529
9
+ python_bsblan-5.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
10
+ python_bsblan-5.2.0.dist-info/licenses/LICENSE.md,sha256=Shv8HPcD1WbZjBPvfb5r3h_cwaPeVaUZMUqU_XQGwGw,1092
11
+ python_bsblan-5.2.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- bsblan/__init__.py,sha256=t_RF_90ManstXJBdoL8n98KK9tnTTvwezntJxk7GfBY,1180
2
- bsblan/bsblan.py,sha256=Pn240e7BXjWGS60Zq8jYBlR_umXuiVYDxoGCu_Usdlk,59973
3
- bsblan/constants.py,sha256=Q6lqnx1YFlPZZYpZrm8Ytx8HgmhTtO4U88EZmnPZTds,22089
4
- bsblan/exceptions.py,sha256=jL7qohIMmuVTsdWBB_trKPg5Yzim6JxaOT13h6EJPlk,1770
5
- bsblan/models.py,sha256=ZqztmI90aDODKJ06SHue2cU5AMqp0chjT8r_B1FUbxg,19409
6
- bsblan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- bsblan/utility.py,sha256=sS0wWJoqvLAHzwaSLIqQEQ-boHsYFLKAHw8KNTSmdX8,5772
8
- python_bsblan-5.1.4.dist-info/METADATA,sha256=0SguR6h4-8P6HHJdzlCeZMsxJe36N7BC21MAMsfUtso,9728
9
- python_bsblan-5.1.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
10
- python_bsblan-5.1.4.dist-info/licenses/LICENSE.md,sha256=Shv8HPcD1WbZjBPvfb5r3h_cwaPeVaUZMUqU_XQGwGw,1092
11
- python_bsblan-5.1.4.dist-info/RECORD,,