python-bsblan 3.1.6__tar.gz → 4.0.0__tar.gz

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.
Files changed (99) hide show
  1. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/copilot-instructions.md +48 -0
  2. python_bsblan-4.0.0/.github/prompts/add-parameter.prompt.md +32 -0
  3. python_bsblan-4.0.0/.github/prompts/code-review.prompt.md +40 -0
  4. python_bsblan-4.0.0/.github/skills/bsblan-parameters/SKILL.md +107 -0
  5. python_bsblan-4.0.0/.github/skills/bsblan-testing/SKILL.md +135 -0
  6. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/codeql.yaml +2 -2
  7. python_bsblan-4.0.0/.github/workflows/release-drafter.yaml +37 -0
  8. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/PKG-INFO +2 -1
  9. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/examples/control.py +12 -5
  10. python_bsblan-4.0.0/examples/discovery.py +266 -0
  11. python_bsblan-4.0.0/examples/profile_init.py +422 -0
  12. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/pyproject.toml +4 -3
  13. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/bsblan.py +209 -22
  14. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_api_validation.py +190 -1
  15. python_bsblan-4.0.0/tests/test_hot_water_additional.py +573 -0
  16. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_hotwater_state.py +3 -0
  17. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_initialization.py +57 -17
  18. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_state.py +1 -1
  19. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_temperature_validation.py +19 -9
  20. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/uv.lock +286 -264
  21. python_bsblan-3.1.6/.github/workflows/release-drafter.yaml +0 -19
  22. python_bsblan-3.1.6/tests/test_hot_water_additional.py +0 -289
  23. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.editorconfig +0 -0
  24. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.gitattributes +0 -0
  25. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/CODE_OF_CONDUCT.md +0 -0
  26. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/CONTRIBUTING.md +0 -0
  27. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  28. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  30. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/labels.yml +0 -0
  31. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/release-drafter.yml +0 -0
  32. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/renovate.json +0 -0
  33. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/auto-approve-renovate.yml +0 -0
  34. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/labels.yaml +0 -0
  35. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/linting.yaml +0 -0
  36. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/lock.yaml +0 -0
  37. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/pr-labels.yaml +0 -0
  38. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/release.yaml +0 -0
  39. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/stale.yaml +0 -0
  40. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/tests.yaml +0 -0
  41. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/typing.yaml +0 -0
  42. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.gitignore +0 -0
  43. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.nvmrc +0 -0
  44. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.pre-commit-config.yaml +0 -0
  45. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.prettierignore +0 -0
  46. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.yamllint +0 -0
  47. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/AGENTS.md +0 -0
  48. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/CLAUDE.md +0 -0
  49. {python_bsblan-3.1.6/.github → python_bsblan-4.0.0}/LICENSE.md +0 -0
  50. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/README.md +0 -0
  51. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/examples/ruff.toml +0 -0
  52. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/package-lock.json +0 -0
  53. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/package.json +0 -0
  54. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/sonar-project.properties +0 -0
  55. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/__init__.py +0 -0
  56. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/constants.py +0 -0
  57. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/exceptions.py +0 -0
  58. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/models.py +0 -0
  59. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/py.typed +0 -0
  60. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/utility.py +0 -0
  61. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/__init__.py +0 -0
  62. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/conftest.py +0 -0
  63. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/device.json +0 -0
  64. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/dict_version.json +0 -0
  65. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/hot_water_state.json +0 -0
  66. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/info.json +0 -0
  67. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/password.txt +0 -0
  68. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/sensor.json +0 -0
  69. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/state.json +0 -0
  70. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/static_state.json +0 -0
  71. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/thermostat_hvac.json +0 -0
  72. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/thermostat_temp.json +0 -0
  73. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/time.json +0 -0
  74. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/ruff.toml +0 -0
  75. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_api_initialization.py +0 -0
  76. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_auth.py +0 -0
  77. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_backoff_retry.py +0 -0
  78. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_bsblan.py +0 -0
  79. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_bsblan_edge_cases.py +0 -0
  80. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_configuration.py +0 -0
  81. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_constants.py +0 -0
  82. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_context_manager.py +0 -0
  83. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_device.py +0 -0
  84. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_dhw_time_switch.py +0 -0
  85. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_entity_info.py +0 -0
  86. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_info.py +0 -0
  87. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_reset_validation.py +0 -0
  88. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_schedule_models.py +0 -0
  89. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_sensor.py +0 -0
  90. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_set_hot_water_schedule.py +0 -0
  91. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_set_hotwater.py +0 -0
  92. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_static_state.py +0 -0
  93. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_temperature_unit.py +0 -0
  94. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_thermostat.py +0 -0
  95. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_time.py +0 -0
  96. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility.py +0 -0
  97. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility_additional.py +0 -0
  98. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility_edge_cases.py +0 -0
  99. {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_version_errors.py +0 -0
@@ -111,6 +111,21 @@ Parameters are organized into polling categories based on how frequently they ch
111
111
  - Device identification
112
112
  - Min/max temperature limits
113
113
 
114
+ ## Hot Water Parameter Groups
115
+
116
+ Hot water parameters are split into groups for granular lazy loading:
117
+
118
+ | Group | Params | Method | Use Case |
119
+ |-------|--------|--------|----------|
120
+ | essential | 5 | `hot_water_state()` | Frequent polling |
121
+ | config | 16 | `hot_water_config()` | Advanced settings |
122
+ | schedule | 8 | `hot_water_schedule()` | Time programs |
123
+
124
+ Defined in `constants.py`:
125
+ - `HOT_WATER_ESSENTIAL_PARAMS` - operating_mode, nominal_setpoint, etc.
126
+ - `HOT_WATER_CONFIG_PARAMS` - legionella settings, eco mode, etc.
127
+ - `HOT_WATER_SCHEDULE_PARAMS` - daily time programs
128
+
114
129
  ## Data Models
115
130
 
116
131
  ### Model Pattern
@@ -143,6 +158,39 @@ async with BSBLAN(host="192.168.1.100") as client:
143
158
  await client.set_hot_water(nominal_setpoint=55.0)
144
159
  ```
145
160
 
161
+ ### Lazy Loading Architecture
162
+ The library uses lazy loading for optimal performance:
163
+ - **Initialization**: Only fetches firmware version (fast startup)
164
+ - **Section validation**: Deferred until section is first accessed
165
+ - **Hot water granular loading**: Each method validates only its param group
166
+ - **Race condition prevention**: Per-section/group asyncio locks
167
+
168
+ ```python
169
+ # Initialize() is fast - only fetches firmware
170
+ await client.initialize() # ~0.02s
171
+
172
+ # Section validated on first access
173
+ await client.state() # Validates heating section on first call
174
+
175
+ # Hot water methods validate only their param groups:
176
+ await client.hot_water_state() # 5 essential params only
177
+ await client.hot_water_config() # 16 config params only
178
+ await client.hot_water_schedule() # 8 schedule params only
179
+ ```
180
+
181
+ ### Concurrency & Locking
182
+ The library uses asyncio locks to prevent race conditions during lazy loading:
183
+ - `_section_locks`: Per-section locks (heating, sensor, etc.)
184
+ - `_hot_water_group_locks`: Per-group locks (essential, config, schedule)
185
+
186
+ Double-checked locking pattern:
187
+ 1. Fast path: Check if validated (no lock)
188
+ 2. Acquire lock for specific section/group
189
+ 3. Double-check after acquiring lock
190
+ 4. Perform validation inside the lock
191
+
192
+ This prevents duplicate network requests when concurrent calls access the same section before validation completes.
193
+
146
194
  ### Error Handling
147
195
  - Use `BSBLANError` for general errors
148
196
  - Use `BSBLANConnectionError` for connection issues
@@ -0,0 +1,32 @@
1
+ ---
2
+ agent: agent
3
+ description: Add a new BSB-LAN parameter to the library
4
+ ---
5
+
6
+ # Add BSB-LAN Parameter
7
+
8
+ Add a new parameter to the python-bsblan library following the established patterns.
9
+
10
+ ## Required Information
11
+
12
+ - Parameter ID (numeric, e.g., "1645")
13
+ - Parameter name (snake_case, e.g., "legionella_function_setpoint")
14
+ - Category: state (fast poll), config (slow poll), or static
15
+ - Is it settable? (yes/no)
16
+ - Data type (float, int, string, enum)
17
+
18
+ ## Files to Modify
19
+
20
+ 1. `src/bsblan/constants.py` - Add parameter ID mapping
21
+ 2. `src/bsblan/models.py` - Add field to appropriate model
22
+ 3. `src/bsblan/bsblan.py` - Add to setter method if settable
23
+ 4. `tests/test_*.py` - Add tests for the new parameter
24
+
25
+ ## Validation
26
+
27
+ After changes, run:
28
+ ```bash
29
+ uv run pre-commit run --all-files
30
+ ```
31
+
32
+ Coverage must be 95%+ total and 100% for new code.
@@ -0,0 +1,40 @@
1
+ ---
2
+ agent: agent
3
+ description: Review code changes for python-bsblan library
4
+ ---
5
+
6
+ # Code Review Checklist
7
+
8
+ Review the changes against the python-bsblan coding standards.
9
+
10
+ ## Checklist
11
+
12
+ ### Code Quality
13
+ - [ ] Type hints on all functions
14
+ - [ ] Docstrings on public methods
15
+ - [ ] Line length under 88 characters
16
+ - [ ] Consistent parameter naming (snake_case)
17
+
18
+ ### Testing
19
+ - [ ] Tests added for new functionality
20
+ - [ ] Test coverage 95%+ total
21
+ - [ ] Patch coverage 100%
22
+
23
+ ### Patterns
24
+ - [ ] Uses `mashumaro` for JSON serialization
25
+ - [ ] Uses `aiohttp` for async HTTP
26
+ - [ ] Follows existing parameter naming conventions
27
+ - [ ] Error handling uses custom exceptions (`BSBLANError`, `BSBLANConnectionError`)
28
+
29
+ ### Pre-commit
30
+ - [ ] Ruff passes (linting + formatting)
31
+ - [ ] MyPy passes (type checking)
32
+ - [ ] Pylint passes (code analysis)
33
+ - [ ] Pytest passes (tests)
34
+
35
+ ## Run Validation
36
+
37
+ ```bash
38
+ uv run pre-commit run --all-files
39
+ uv run pytest --cov=src/bsblan --cov-report=term-missing
40
+ ```
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: bsblan-parameters
3
+ description: Add new BSB-LAN parameters to the python-bsblan library. Use this skill when adding parameter IDs, updating models, or extending the API to support new heating controller parameters.
4
+ ---
5
+
6
+ # Adding BSB-LAN Parameters
7
+
8
+ This skill guides you through adding new parameters to the python-bsblan library.
9
+
10
+ ## Parameter Naming Conventions
11
+
12
+ - Use `snake_case` for all parameter names
13
+ - Group related parameters with common prefixes
14
+ - Legionella-related parameters use `legionella_function_*` prefix
15
+ - DHW (Domestic Hot Water) parameters use `dhw_*` prefix
16
+
17
+ ## Steps to Add a New Parameter
18
+
19
+ ### 1. Add to `constants.py`
20
+
21
+ Add the parameter ID mapping:
22
+
23
+ ```python
24
+ BASE_HOT_WATER_PARAMS: Final[dict[str, str]] = {
25
+ "1645": "legionella_function_setpoint", # Parameter ID: name
26
+ }
27
+ ```
28
+
29
+ ### 2. Add to Model in `models.py`
30
+
31
+ Add the field to the appropriate dataclass:
32
+
33
+ ```python
34
+ @dataclass
35
+ class HotWaterConfig(DataClassORJSONMixin):
36
+ legionella_function_setpoint: ParameterValue | None = None
37
+ ```
38
+
39
+ ### 3. Update Method in `bsblan.py` (if settable)
40
+
41
+ Add parameter to the method signature:
42
+
43
+ ```python
44
+ async def set_hot_water(
45
+ self,
46
+ legionella_function_setpoint: float | None = None,
47
+ ) -> None:
48
+ ```
49
+
50
+ ### 4. Add Tests
51
+
52
+ Create tests in `tests/test_*.py`:
53
+
54
+ ```python
55
+ @pytest.mark.asyncio
56
+ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
57
+ """Test setting BSBLAN hot water state."""
58
+ await mock_bsblan.set_hot_water(nominal_setpoint=60.0)
59
+ mock_bsblan._request.assert_awaited_with(
60
+ base_path="/JS",
61
+ data={"Parameter": "1610", "Value": "60.0", "Type": "1"},
62
+ )
63
+ ```
64
+
65
+ ## Polling Categories
66
+
67
+ Parameters are organized by update frequency:
68
+
69
+ - **Fast Poll (State)**: Current temperatures, HVAC action/state, pump states
70
+ - **Slow Poll (Config)**: Operating modes, setpoints, legionella settings, time programs
71
+ - **Static**: Device identification, min/max temperature limits
72
+
73
+ ## Hot Water Parameter Groups
74
+
75
+ Hot water parameters use granular lazy loading. When adding a new hot water param, add it to the appropriate group in `constants.py`:
76
+
77
+ | Group | Constant | Method |
78
+ | -------------------- | ----------------------------- | ---------------------- |
79
+ | Essential (5 params) | `HOT_WATER_ESSENTIAL_PARAMS` | `hot_water_state()` |
80
+ | Config (16 params) | `HOT_WATER_CONFIG_PARAMS` | `hot_water_config()` |
81
+ | Schedule (8 params) | `HOT_WATER_SCHEDULE_PARAMS` | `hot_water_schedule()` |
82
+
83
+ ```python
84
+ # In constants.py - add to appropriate group set:
85
+ HOT_WATER_ESSENTIAL_PARAMS: Final[set[str]] = {"1600", "1610", ...}
86
+ HOT_WATER_CONFIG_PARAMS: Final[set[str]] = {"1601", "1614", ...}
87
+ ```
88
+
89
+ ## Concurrency Safety
90
+
91
+ The library uses asyncio locks to prevent race conditions:
92
+
93
+ - `_section_locks`: Per-section locks for lazy loading
94
+ - `_hot_water_group_locks`: Per-group locks for hot water validation
95
+
96
+ When adding new sections or groups, the lock is created automatically on first access.
97
+
98
+ ## Validation
99
+
100
+ Always run after changes:
101
+
102
+ ```bash
103
+ uv run pre-commit run --all-files
104
+ uv run pytest --cov=src/bsblan --cov-report=term-missing
105
+ ```
106
+
107
+ **Coverage requirements**: 95%+ total, 100% patch coverage.
@@ -0,0 +1,135 @@
1
+ ---
2
+ name: bsblan-testing
3
+ description: Write and run tests for the python-bsblan library. Use this skill when creating unit tests, working with fixtures, or ensuring code coverage requirements are met.
4
+ ---
5
+
6
+ # Testing python-bsblan
7
+
8
+ This skill guides you through testing practices for the python-bsblan library.
9
+
10
+ ## Test Structure
11
+
12
+ Tests are located in `tests/` and use pytest with async support.
13
+
14
+ ### Basic Test Pattern
15
+
16
+ ```python
17
+ import pytest
18
+ from bsblan import BSBLAN
19
+
20
+ @pytest.mark.asyncio
21
+ async def test_feature_name(mock_bsblan: BSBLAN) -> None:
22
+ """Test description."""
23
+ # Arrange
24
+ expected_value = "expected"
25
+
26
+ # Act
27
+ result = await mock_bsblan.some_method()
28
+
29
+ # Assert
30
+ assert result == expected_value
31
+ ```
32
+
33
+ ### Using Fixtures
34
+
35
+ Test fixtures (JSON responses) are in `tests/fixtures/`. Common fixtures:
36
+
37
+ - `device.json` - Device information
38
+ - `state.json` - Current state
39
+ - `hot_water_state.json` - Hot water state
40
+ - `sensor.json` - Sensor readings
41
+
42
+ Load fixtures using the `load_fixture` helper from `conftest.py`.
43
+
44
+ ## Coverage Requirements
45
+
46
+ - **Total coverage**: 95%+ required
47
+ - **Patch coverage**: 100% required (all new/modified code must be tested)
48
+
49
+ Check coverage:
50
+
51
+ ```bash
52
+ uv run pytest --cov=src/bsblan --cov-report=term-missing
53
+ ```
54
+
55
+ ## Running Tests
56
+
57
+ ```bash
58
+ # Run all tests
59
+ uv run pytest
60
+
61
+ # Run specific test file
62
+ uv run pytest tests/test_bsblan.py
63
+
64
+ # Run with verbose output
65
+ uv run pytest -v
66
+
67
+ # Run specific test
68
+ uv run pytest tests/test_bsblan.py::test_function_name
69
+ ```
70
+
71
+ ## Pre-commit Hooks
72
+
73
+ Always run before committing:
74
+
75
+ ```bash
76
+ uv run pre-commit run --all-files
77
+ ```
78
+
79
+ This runs:
80
+
81
+ - **Ruff**: Linting and formatting (88 char line limit)
82
+ - **MyPy**: Static type checking
83
+ - **Pylint**: Code analysis
84
+ - **Pytest**: Test execution with coverage
85
+
86
+ ## Mock Patterns
87
+
88
+ For API calls, use `mock_bsblan` fixture and verify calls:
89
+
90
+ ```python
91
+ mock_bsblan._request.assert_awaited_with(
92
+ base_path="/JS",
93
+ data={"Parameter": "1610", "Value": "60.0", "Type": "1"},
94
+ )
95
+ ```
96
+
97
+ ## Testing Lazy Loading
98
+
99
+ When testing hot water methods, mark param groups as validated to skip network calls:
100
+
101
+ ```python
102
+ @pytest.mark.asyncio
103
+ async def test_hot_water_no_params_error(monkeypatch: Any) -> None:
104
+ """Test error when no parameters available."""
105
+ bsblan = BSBLAN(config, session=session)
106
+
107
+ # Set empty cache and mark group as validated
108
+ bsblan.set_hot_water_cache({})
109
+ bsblan._validated_hot_water_groups.add("essential") # Skip validation
110
+
111
+ with pytest.raises(BSBLANError, match="No essential hot water"):
112
+ await bsblan.hot_water_state()
113
+ ```
114
+
115
+ For full integration tests with mocked responses:
116
+
117
+ ```python
118
+ # Mark group as validated to use cached params
119
+ bsblan._validated_hot_water_groups.add("config")
120
+ bsblan.set_hot_water_cache({"1601": "eco_mode_selection", ...})
121
+ ```
122
+
123
+ ## Testing Concurrent Access
124
+
125
+ The library uses asyncio locks for race condition prevention. When testing:
126
+
127
+ - Locks are created per-section/group automatically
128
+ - Access `_section_locks` and `_hot_water_group_locks` dicts if needed
129
+ - The double-checked locking pattern prevents duplicate validations
130
+
131
+ ```python
132
+ # Locks are stored in these dictionaries:
133
+ bsblan._section_locks # {"heating": Lock(), "sensor": Lock(), ...}
134
+ bsblan._hot_water_group_locks # {"essential": Lock(), ...}
135
+ ```
@@ -19,6 +19,6 @@ jobs:
19
19
  - name: ⤵️ Check out code from GitHub
20
20
  uses: actions/checkout@v6.0.1
21
21
  - name: 🏗 Initialize CodeQL
22
- uses: github/codeql-action/init@v3.31.9
22
+ uses: github/codeql-action/init@v4.31.9
23
23
  - name: 🚀 Perform CodeQL Analysis
24
- uses: github/codeql-action/analyze@v3.31.9
24
+ uses: github/codeql-action/analyze@v4.31.9
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: Release Drafter
3
+
4
+ # yamllint disable-line rule:truthy
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ workflow_dispatch:
10
+ inputs:
11
+ prerelease:
12
+ description: "Create a pre-release (beta)"
13
+ required: false
14
+ type: boolean
15
+ default: false
16
+ prerelease_identifier:
17
+ description: "Pre-release identifier"
18
+ required: false
19
+ type: choice
20
+ default: "beta"
21
+ options:
22
+ - beta
23
+ - alpha
24
+ - rc
25
+
26
+ jobs:
27
+ update_release_draft:
28
+ name: ✏️ Draft release
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - name: 🚀 Run Release Drafter
32
+ uses: release-drafter/release-drafter@v6.1.0
33
+ with:
34
+ prerelease: ${{ github.event.inputs.prerelease == 'true' }}
35
+ prerelease-identifier: ${{ github.event.inputs.prerelease_identifier }}
36
+ env:
37
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bsblan
3
- Version: 3.1.6
3
+ Version: 4.0.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
@@ -10,6 +10,7 @@ Project-URL: Changelog, https://github.com/liudger/python-bsblan/releases
10
10
  Author-email: Willem-Jan van Rootselaar <liudgervr@gmail.com>
11
11
  Maintainer-email: Willem-Jan van Rootselaar <liudgervr@gmail.com>
12
12
  License: MIT
13
+ License-File: LICENSE.md
13
14
  Keywords: api,async,bsblan,client,thermostat
14
15
  Classifier: Development Status :: 3 - Alpha
15
16
  Classifier: Framework :: AsyncIO
@@ -12,7 +12,6 @@ This three-tier approach reduces API calls by 79% for regular monitoring.
12
12
  from __future__ import annotations
13
13
 
14
14
  import asyncio
15
- import os
16
15
  from datetime import datetime
17
16
  from typing import Any
18
17
 
@@ -33,6 +32,7 @@ from bsblan import (
33
32
  get_hvac_action_category,
34
33
  )
35
34
  from bsblan.models import DHWTimeSwitchPrograms
35
+ from discovery import get_bsblan_host, get_config_from_env
36
36
 
37
37
 
38
38
  async def get_attribute(
@@ -302,12 +302,19 @@ async def print_hot_water_schedule(hot_water_schedule: HotWaterSchedule) -> None
302
302
 
303
303
  async def main() -> None:
304
304
  """Show example on controlling your BSBLan device."""
305
+ # Get host from environment variable or mDNS discovery
306
+ host, port = await get_bsblan_host()
307
+
308
+ # Get credentials from environment
309
+ env_config = get_config_from_env()
310
+
305
311
  # Create a configuration object
306
312
  config = BSBLANConfig(
307
- host="10.0.2.60",
308
- passkey=None,
309
- username=os.getenv("BSBLAN_USER"), # Compliant
310
- password=os.getenv("BSBLAN_PASS"), # Compliant
313
+ host=host,
314
+ port=port,
315
+ passkey=env_config.get("passkey"), # type: ignore[arg-type]
316
+ username=env_config.get("username"), # type: ignore[arg-type]
317
+ password=env_config.get("password"), # type: ignore[arg-type]
311
318
  )
312
319
 
313
320
  # Initialize BSBLAN with the configuration object