python-bsblan 5.2.0__tar.gz → 5.2.1__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 (120) hide show
  1. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/codeql.yaml +2 -2
  2. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/docs.yaml +1 -1
  3. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/linting.yaml +2 -2
  4. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/release-drafter.yaml +1 -1
  5. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/release.yaml +1 -1
  6. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/scorecard.yml +1 -1
  7. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/tests.yaml +3 -3
  8. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/typing.yaml +1 -1
  9. python_bsblan-5.2.1/.nvmrc +1 -0
  10. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/PKG-INFO +1 -1
  11. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/pyproject.toml +3 -3
  12. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/bsblan.py +16 -42
  13. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/models.py +3 -3
  14. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_circuit.py +49 -120
  15. python_bsblan-5.2.0/.nvmrc +0 -1
  16. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.editorconfig +0 -0
  17. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.gitattributes +0 -0
  18. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/CODE_OF_CONDUCT.md +0 -0
  19. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/CONTRIBUTING.md +0 -0
  20. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  23. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/SECURITY.md +0 -0
  24. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/copilot-instructions.md +0 -0
  25. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/labels.yml +0 -0
  26. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/prompts/add-parameter.prompt.md +0 -0
  27. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/prompts/code-review.prompt.md +0 -0
  28. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/release-drafter.yml +0 -0
  29. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/renovate.json +0 -0
  30. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/skills/bsblan-parameters/SKILL.md +0 -0
  31. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/skills/bsblan-testing/SKILL.md +0 -0
  32. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/auto-approve-renovate.yml +0 -0
  33. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/dependency-review.yaml +0 -0
  34. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/labels.yaml +0 -0
  35. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/lock.yaml +0 -0
  36. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/pr-labels.yaml +0 -0
  37. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/stale.yaml +0 -0
  38. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/zizmor.yml +0 -0
  39. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.gitignore +0 -0
  40. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.pre-commit-config.yaml +0 -0
  41. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.prettierignore +0 -0
  42. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.yamllint +0 -0
  43. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/AGENTS.md +0 -0
  44. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/CLAUDE.md +0 -0
  45. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/LICENSE.md +0 -0
  46. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/Makefile +0 -0
  47. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/README.md +0 -0
  48. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/client.md +0 -0
  49. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/constants.md +0 -0
  50. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/exceptions.md +0 -0
  51. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/models.md +0 -0
  52. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/getting-started.md +0 -0
  53. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/index.md +0 -0
  54. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/control.py +0 -0
  55. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/discovery.py +0 -0
  56. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/fetch_param.py +0 -0
  57. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/profile_init.py +0 -0
  58. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/ruff.toml +0 -0
  59. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/speed_test.py +0 -0
  60. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/mkdocs.yml +0 -0
  61. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/package-lock.json +0 -0
  62. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/package.json +0 -0
  63. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/sonar-project.properties +0 -0
  64. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/__init__.py +0 -0
  65. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/constants.py +0 -0
  66. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/exceptions.py +0 -0
  67. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/py.typed +0 -0
  68. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/utility.py +0 -0
  69. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/__init__.py +0 -0
  70. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/conftest.py +0 -0
  71. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/device.json +0 -0
  72. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/dict_version.json +0 -0
  73. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/hot_water_state.json +0 -0
  74. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/info.json +0 -0
  75. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/password.txt +0 -0
  76. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/sensor.json +0 -0
  77. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/state.json +0 -0
  78. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/state_circuit2.json +0 -0
  79. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/static_state.json +0 -0
  80. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/static_state_circuit2.json +0 -0
  81. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/thermostat_hvac.json +0 -0
  82. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/thermostat_temp.json +0 -0
  83. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/time.json +0 -0
  84. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/ruff.toml +0 -0
  85. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_api_initialization.py +0 -0
  86. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_api_validation.py +0 -0
  87. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_auth.py +0 -0
  88. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_backoff_retry.py +0 -0
  89. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_bsblan.py +0 -0
  90. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_bsblan_edge_cases.py +0 -0
  91. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_configuration.py +0 -0
  92. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_constants.py +0 -0
  93. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_context_manager.py +0 -0
  94. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_device.py +0 -0
  95. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_dhw_time_switch.py +0 -0
  96. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_entity_info.py +0 -0
  97. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_entity_info_ha.py +0 -0
  98. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_heating_schedule.py +0 -0
  99. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_hot_water_additional.py +0 -0
  100. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_hotwater_state.py +0 -0
  101. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_include_parameter.py +0 -0
  102. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_info.py +0 -0
  103. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_initialization.py +0 -0
  104. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_read_parameters.py +0 -0
  105. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_reset_validation.py +0 -0
  106. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_schedule_models.py +0 -0
  107. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_sensor.py +0 -0
  108. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_heating_schedule.py +0 -0
  109. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_hot_water_schedule.py +0 -0
  110. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_hotwater.py +0 -0
  111. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_state.py +0 -0
  112. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_static_state.py +0 -0
  113. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_temperature_unit.py +0 -0
  114. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_temperature_validation.py +0 -0
  115. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_thermostat.py +0 -0
  116. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_time.py +0 -0
  117. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility.py +0 -0
  118. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility_additional.py +0 -0
  119. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility_edge_cases.py +0 -0
  120. {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_version_errors.py +0 -0
@@ -27,6 +27,6 @@ jobs:
27
27
  with:
28
28
  persist-credentials: false
29
29
  - name: 🏗 Initialize CodeQL
30
- uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
30
+ uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
31
31
  - name: 🚀 Perform CodeQL Analysis
32
- uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
32
+ uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
@@ -27,7 +27,7 @@ jobs:
27
27
  with:
28
28
  persist-credentials: false
29
29
  - name: 🏗 Set up uv
30
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
30
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
31
31
  with:
32
32
  enable-cache: true
33
33
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -25,7 +25,7 @@ jobs:
25
25
  with:
26
26
  persist-credentials: false
27
27
  - name: 🏗 Set up uv
28
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
28
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
29
29
  with:
30
30
  enable-cache: true
31
31
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -49,7 +49,7 @@ jobs:
49
49
  with:
50
50
  persist-credentials: false
51
51
  - name: 🏗 Set up uv
52
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
52
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
53
53
  with:
54
54
  enable-cache: true
55
55
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -36,7 +36,7 @@ jobs:
36
36
  steps:
37
37
  - name: 🚀 Run Release Drafter
38
38
  # yamllint disable-line rule:line-length
39
- uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
39
+ uses: release-drafter/release-drafter@563bf132657a13ded0b01fcb723c5a58cdd824e2 # v7.2.1
40
40
  with:
41
41
  prerelease: ${{ github.event.inputs.prerelease == 'true' }}
42
42
  prerelease-identifier: ${{ github.event.inputs.prerelease_identifier }}
@@ -29,7 +29,7 @@ jobs:
29
29
  with:
30
30
  persist-credentials: false
31
31
  - name: 🏗 Set up uv
32
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
32
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
33
33
  with:
34
34
  enable-cache: false
35
35
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -66,6 +66,6 @@ jobs:
66
66
  # Upload the results to GitHub's code scanning dashboard (optional).
67
67
  - name: "Upload to code-scanning"
68
68
  # yamllint disable-line rule:line-length
69
- uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
69
+ uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
70
70
  with:
71
71
  sarif_file: results.sarif
@@ -33,7 +33,7 @@ jobs:
33
33
  with:
34
34
  persist-credentials: false
35
35
  - name: 🏗 Set up uv
36
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
36
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
37
37
  with:
38
38
  enable-cache: true
39
39
  - name: 🏗 Set up Python ${{ matrix.python }}
@@ -68,7 +68,7 @@ jobs:
68
68
  - name: ⬇️ Download coverage data
69
69
  uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
70
70
  - name: 🏗 Set up uv
71
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
71
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
72
72
  with:
73
73
  enable-cache: true
74
74
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -93,7 +93,7 @@ jobs:
93
93
  - name: SonarQube Cloud Scan
94
94
  if: env.HAS_SONAR_TOKEN == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
95
95
  # yamllint disable-line rule:line-length
96
- uses: SonarSource/sonarqube-scan-action@299e4b793aaa83bf2aba7c9c14bedbb485688ec4 # v7.1.0
96
+ uses: SonarSource/sonarqube-scan-action@59db25f34e16620e48ab4bb9e4a5dce155cb5432 # v8.0
97
97
  env:
98
98
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
99
99
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -27,7 +27,7 @@ jobs:
27
27
  persist-credentials: false
28
28
  - name: 🏗 Set up uv
29
29
  # yamllint disable-line rule:line-length
30
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
30
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
31
31
  with:
32
32
  enable-cache: true
33
33
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
@@ -0,0 +1 @@
1
+ 24.15.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bsblan
3
- Version: 5.2.0
3
+ Version: 5.2.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-bsblan"
3
- version = "5.2.0"
3
+ version = "5.2.1"
4
4
  description = "Asynchronous Python client for BSBLAN API"
5
5
  authors = [
6
6
  {name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
@@ -196,7 +196,7 @@ dev = [
196
196
  # hatch is required to support type hinting and proper packaging of the py.typed file.
197
197
  "hatch>=1.14.1",
198
198
  "isort==8.0.1",
199
- "ty==0.0.32",
199
+ "ty==0.0.34",
200
200
  "prek>=0.3.3",
201
201
  "pre-commit-hooks==6.0.0",
202
202
  "pylint==4.0.5",
@@ -205,7 +205,7 @@ dev = [
205
205
  "pytest-cov==7.1.0",
206
206
  "pytest-xdist>=3.8.0",
207
207
  "pyupgrade==3.21.2",
208
- "ruff==0.15.11",
208
+ "ruff==0.15.12",
209
209
  "safety==3.7.0",
210
210
  "vulture==2.16",
211
211
  "yamllint==1.38.0",
@@ -152,13 +152,10 @@ class BSBLAN:
152
152
  async def get_available_circuits(self) -> list[int]:
153
153
  """Detect which heating circuits are available on the device.
154
154
 
155
- Uses a two-step probe for each circuit (1, 2):
156
- 1. Query the operating mode parameter — the response must be
157
- non-empty and contain actual data.
158
- 2. Query the status parameter (8000/8001) an inactive
159
- circuit returns ``value="0"`` with ``desc="---"``.
160
-
161
- A circuit is only considered available when both checks pass.
155
+ Uses the configured operating mode probe parameters from
156
+ CircuitConfig.PROBE_PARAMS as the only discovery signal. Status
157
+ parameters are not queried during discovery to keep setup lightweight
158
+ and avoid excluding valid circuits when status data is unavailable.
162
159
 
163
160
  This is useful for integration setup flows (e.g., Home Assistant
164
161
  config flow) to discover how many circuits the user's controller
@@ -179,46 +176,23 @@ class BSBLAN:
179
176
  response = await self._request(
180
177
  params={"Parameter": param_id},
181
178
  )
182
- # A circuit exists if the response contains the param_id key
183
- # with actual data (not an empty dict)
184
- if not response.get(param_id):
185
- continue
186
-
187
- # Secondary check: query the status parameter.
188
- # Inactive circuits either:
189
- # - return value="0" and desc="---"
190
- # - return an empty dict {} (param not supported)
191
- status_id = CircuitConfig.STATUS_PARAMS[circuit]
192
- status_resp = await self._request(
193
- params={"Parameter": status_id},
179
+ except BSBLANError:
180
+ logger.debug(
181
+ "Circuit %d not available (operating mode request failed)",
182
+ circuit,
194
183
  )
195
- status_data = status_resp.get(status_id, {})
196
-
197
- # Empty response means the parameter doesn't exist
198
- if not status_data or not isinstance(status_data, dict):
199
- logger.debug(
200
- "Circuit %d has no status data (not supported)",
201
- circuit,
202
- )
203
- continue
204
-
205
- # value="0" + desc="---" means inactive
206
- if (
207
- status_data.get("desc") == CircuitConfig.INACTIVE_MARKER
208
- and str(status_data.get("value", "")) == "0"
209
- ):
210
- logger.debug(
211
- "Circuit %d has status '---' (inactive)",
212
- circuit,
213
- )
214
- continue
184
+ continue
215
185
 
216
- available.append(circuit)
217
- except BSBLANError:
186
+ # A circuit exists if the response contains the operating mode key
187
+ # with actual data (not an empty dict).
188
+ if not response.get(param_id):
218
189
  logger.debug(
219
- "Circuit %d not available (request failed)",
190
+ "Circuit %d has no operating mode data (not supported)",
220
191
  circuit,
221
192
  )
193
+ continue
194
+
195
+ available.append(circuit)
222
196
  return sorted(available)
223
197
 
224
198
  async def _setup_api_validator(self) -> None:
@@ -326,13 +326,13 @@ class EntityInfo(BaseModel, Generic[T]):
326
326
  unit: str
327
327
  desc: str
328
328
  value: T | None = None
329
- data_type: int = Field(alias="dataType", default=0)
329
+ data_type: int = Field(validation_alias="dataType", default=0)
330
330
  error: int = 0
331
331
  readonly: int = 0
332
332
  readwrite: int = 0
333
333
  precision: float | None = None
334
- data_type_name: str = Field(default="", alias="dataType_name")
335
- data_type_family: str = Field(default="", alias="dataType_family")
334
+ data_type_name: str = Field(default="", validation_alias="dataType_name")
335
+ data_type_family: str = Field(default="", validation_alias="dataType_family")
336
336
 
337
337
  @model_validator(mode="before")
338
338
  @classmethod
@@ -13,7 +13,7 @@ import pytest
13
13
  from aresponses import Response, ResponsesMockServer
14
14
 
15
15
  from bsblan import BSBLAN, BSBLANConfig, State, StaticState
16
- from bsblan.constants import ErrorMsg, build_api_config
16
+ from bsblan.constants import CircuitConfig, ErrorMsg, build_api_config
17
17
  from bsblan.exceptions import BSBLANError, BSBLANInvalidParameterError
18
18
  from bsblan.utility import APIValidator
19
19
 
@@ -544,26 +544,20 @@ async def test_get_available_circuits_two_circuits(
544
544
  # HC1 operating mode
545
545
  if param_id == "700":
546
546
  return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
547
- # HC1 status - active
548
- if param_id == "8000":
549
- return {
550
- "8000": {
551
- "value": "114",
552
- "desc": "Heating mode Comfort",
553
- }
554
- }
555
547
  # HC2 operating mode
556
548
  if param_id == "1000":
557
549
  return {"1000": {"value": "1", "unit": "", "desc": "Automatic"}}
558
- # HC2 status - active
559
- if param_id == "8001":
560
- return {"8001": {"value": "114", "desc": "Heating mode Comfort"}}
561
- return {}
550
+ msg = f"Unexpected parameter probe: {param_id}"
551
+ raise AssertionError(msg)
562
552
 
563
- bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
553
+ request_mock = AsyncMock(side_effect=mock_request)
554
+ bsblan._request = request_mock # type: ignore[method-assign]
564
555
 
565
556
  circuits = await bsblan.get_available_circuits()
566
557
  assert circuits == [1, 2]
558
+ assert [
559
+ call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
560
+ ] == ["700", "1000"]
567
561
 
568
562
 
569
563
  @pytest.mark.asyncio
@@ -580,33 +574,27 @@ async def test_get_available_circuits_only_one(
580
574
  param_id = params.get("Parameter", "")
581
575
  if param_id == "700":
582
576
  return {"700": {"value": "3", "unit": "", "desc": "Comfort"}}
583
- if param_id == "8000":
584
- return {
585
- "8000": {
586
- "value": "114",
587
- "desc": "Heating mode Comfort",
588
- }
589
- }
590
577
  # HC2 operating mode - return empty
591
578
  if param_id == "1000":
592
579
  return {param_id: {}}
593
- return {}
580
+ msg = f"Unexpected parameter probe: {param_id}"
581
+ raise AssertionError(msg)
594
582
 
595
- bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
583
+ request_mock = AsyncMock(side_effect=mock_request)
584
+ bsblan._request = request_mock # type: ignore[method-assign]
596
585
 
597
586
  circuits = await bsblan.get_available_circuits()
598
587
  assert circuits == [1]
588
+ assert [
589
+ call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
590
+ ] == ["700", "1000"]
599
591
 
600
592
 
601
593
  @pytest.mark.asyncio
602
- async def test_get_available_circuits_inactive_by_status(
594
+ async def test_get_available_circuits_does_not_probe_status_params(
603
595
  mock_bsblan_circuit: BSBLAN,
604
596
  ) -> None:
605
- """Test that circuits with status '---' are detected as inactive.
606
-
607
- This is the real-world scenario: the device returns a valid operating
608
- mode for all circuits, but status param shows '---' for HC2.
609
- """
597
+ """Test discovery only queries probe params, not status params."""
610
598
  bsblan = mock_bsblan_circuit
611
599
 
612
600
  async def mock_request(
@@ -617,61 +605,20 @@ async def test_get_available_circuits_inactive_by_status(
617
605
  # All circuits return valid operating mode
618
606
  if param_id in {"700", "1000"}:
619
607
  return {param_id: {"value": "1", "unit": "", "desc": "Automatic"}}
620
- # HC1 status - active
621
- if param_id == "8000":
622
- return {
623
- "8000": {
624
- "value": "114",
625
- "desc": "Heating mode Comfort",
626
- }
627
- }
628
- # HC2 status - inactive (value=0, desc=---)
629
- if param_id == "8001":
630
- return {"8001": {"value": "0", "desc": "---"}}
631
- return {}
608
+ msg = f"Unexpected status parameter probe: {param_id}"
609
+ raise AssertionError(msg)
632
610
 
633
- bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
611
+ request_mock = AsyncMock(side_effect=mock_request)
612
+ bsblan._request = request_mock # type: ignore[method-assign]
634
613
 
635
614
  circuits = await bsblan.get_available_circuits()
636
- assert circuits == [1]
637
-
638
-
639
- @pytest.mark.asyncio
640
- async def test_get_available_circuits_inactive_empty_status(
641
- mock_bsblan_circuit: BSBLAN,
642
- ) -> None:
643
- """Test that circuits with empty status response are inactive.
644
-
645
- Some controllers return an empty dict for status params of circuits
646
- that don't exist (e.g., HC2 status 8001 returns {}).
647
- """
648
- bsblan = mock_bsblan_circuit
649
-
650
- async def mock_request(
651
- **kwargs: Any,
652
- ) -> dict[str, Any]:
653
- params = kwargs.get("params", {})
654
- param_id = params.get("Parameter", "")
655
- # All circuits return valid operating mode
656
- if param_id in {"700", "1000"}:
657
- return {param_id: {"value": "1", "unit": "", "desc": "Automatic"}}
658
- # HC1 status - active
659
- if param_id == "8000":
660
- return {
661
- "8000": {
662
- "value": "114",
663
- "desc": "Heating mode Comfort",
664
- }
665
- }
666
- # HC2 status - empty response (param not supported)
667
- if param_id == "8001":
668
- return {}
669
- return {}
670
-
671
- bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
672
-
673
- circuits = await bsblan.get_available_circuits()
674
- assert circuits == [1]
615
+ assert circuits == [1, 2]
616
+ assert {
617
+ call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
618
+ } == {
619
+ "700",
620
+ "1000",
621
+ }
675
622
 
676
623
 
677
624
  @pytest.mark.asyncio
@@ -681,27 +628,19 @@ async def test_get_available_circuits_request_failure(
681
628
  """Test circuit detection when some requests fail."""
682
629
  bsblan = mock_bsblan_circuit
683
630
 
684
- call_count = 0
685
-
686
631
  async def mock_request(
687
632
  **kwargs: Any,
688
633
  ) -> dict[str, Any]:
689
- nonlocal call_count
690
- call_count += 1
691
634
  params = kwargs.get("params", {})
692
635
  param_id = params.get("Parameter", "")
693
636
  if param_id == "700":
694
637
  return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
695
- if param_id == "8000":
696
- return {
697
- "8000": {
698
- "value": "114",
699
- "desc": "Heating mode Comfort",
700
- }
701
- }
702
638
  # HC2 fail with connection error
703
- msg = "Connection failed"
704
- raise BSBLANError(msg)
639
+ if param_id == "1000":
640
+ msg = "Connection failed"
641
+ raise BSBLANError(msg)
642
+ msg = f"Unexpected parameter probe: {param_id}"
643
+ raise AssertionError(msg)
705
644
 
706
645
  bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
707
646
 
@@ -723,15 +662,11 @@ async def test_get_available_circuits_param_not_in_response(
723
662
  param_id = params.get("Parameter", "")
724
663
  if param_id == "700":
725
664
  return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
726
- if param_id == "8000":
727
- return {
728
- "8000": {
729
- "value": "114",
730
- "desc": "Heating mode Comfort",
731
- }
732
- }
733
665
  # Returns a response but without the expected param key
734
- return {"other_key": {"value": "1"}}
666
+ if param_id == "1000":
667
+ return {"other_key": {"value": "1"}}
668
+ msg = f"Unexpected parameter probe: {param_id}"
669
+ raise AssertionError(msg)
735
670
 
736
671
  bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
737
672
 
@@ -740,37 +675,31 @@ async def test_get_available_circuits_param_not_in_response(
740
675
 
741
676
 
742
677
  @pytest.mark.asyncio
743
- async def test_get_available_circuits_status_failure_excludes_circuit(
678
+ async def test_get_available_circuits_all_probes_missing(
744
679
  mock_bsblan_circuit: BSBLAN,
745
680
  ) -> None:
746
- """Test that a circuit is excluded if the status request fails.
747
-
748
- If the operating mode returns valid data but the status request fails,
749
- the circuit should be excluded (fail-safe).
750
- """
681
+ """Test no circuits are detected when operating mode probes are missing."""
751
682
  bsblan = mock_bsblan_circuit
683
+ expected_params = list(CircuitConfig.PROBE_PARAMS.values())
752
684
 
753
685
  async def mock_request(
754
686
  **kwargs: Any,
755
687
  ) -> dict[str, Any]:
756
688
  params = kwargs.get("params", {})
757
689
  param_id = params.get("Parameter", "")
758
- if param_id == "700":
759
- return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
760
- # Status request fails
761
- if param_id == "8000":
762
- msg = "Connection failed"
763
- raise BSBLANError(msg)
764
- # HC2 return empty
765
- if param_id == "1000":
766
- return {param_id: {}}
767
- return {}
690
+ if param_id in expected_params:
691
+ return {}
692
+ msg = f"Unexpected parameter probe: {param_id}"
693
+ raise AssertionError(msg)
768
694
 
769
- bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
695
+ request_mock = AsyncMock(side_effect=mock_request)
696
+ bsblan._request = request_mock # type: ignore[method-assign]
770
697
 
771
- # HC1 status request fails -> entire circuit probe fails -> excluded
772
698
  circuits = await bsblan.get_available_circuits()
773
699
  assert circuits == []
700
+ assert [
701
+ call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
702
+ ] == expected_params
774
703
 
775
704
 
776
705
  @pytest.mark.asyncio
@@ -1 +0,0 @@
1
- 24.14.1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes