python-bsblan 6.1.9__tar.gz → 6.1.11__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 (179) hide show
  1. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/codeql.yaml +2 -2
  2. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/release-drafter.yaml +1 -1
  3. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/scorecard.yml +2 -2
  4. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/tests.yaml +1 -1
  5. python_bsblan-6.1.11/.nvmrc +1 -0
  6. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/PKG-INFO +1 -1
  7. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/getting-started.md +44 -0
  8. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/fake_bsblan_server.py +6 -2
  9. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/package-lock.json +4 -4
  10. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/package.json +1 -1
  11. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/pyproject.toml +6 -6
  12. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_thermostat.py +44 -9
  13. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_validation.py +8 -2
  14. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/bsblan.py +43 -10
  15. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/constants.py +26 -9
  16. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/models.py +3 -0
  17. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/hot_water_state.json +13 -0
  18. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/state.json +13 -0
  19. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/state_circuit2.json +13 -0
  20. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_circuit.py +35 -0
  21. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_constants.py +9 -3
  22. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_context_manager.py +76 -0
  23. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_hotwater_state.py +6 -2
  24. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_pps.py +84 -0
  25. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_state.py +5 -1
  26. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_temperature_unit.py +14 -0
  27. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_thermostat.py +131 -1
  28. python_bsblan-6.1.9/.nvmrc +0 -1
  29. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.editorconfig +0 -0
  30. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.gitattributes +0 -0
  31. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/CODE_OF_CONDUCT.md +0 -0
  32. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/CONTRIBUTING.md +0 -0
  33. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  34. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  36. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/SECURITY.md +0 -0
  37. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/agents/pr-review.agent.md +0 -0
  38. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/agents/security-reviewer.agent.md +0 -0
  39. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/agents/tdd-refactor.agent.md +0 -0
  40. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/copilot-instructions.md +0 -0
  41. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/labels.yml +0 -0
  42. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/add-parameter.prompt.md +0 -0
  43. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/code-review.prompt.md +0 -0
  44. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/opsx-apply.prompt.md +0 -0
  45. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/opsx-archive.prompt.md +0 -0
  46. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/opsx-explore.prompt.md +0 -0
  47. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/opsx-propose.prompt.md +0 -0
  48. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/prompts/opsx-sync.prompt.md +0 -0
  49. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/release-drafter.yml +0 -0
  50. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/renovate.json +0 -0
  51. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/bsblan-parameters/SKILL.md +0 -0
  52. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/bsblan-testing/SKILL.md +0 -0
  53. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/feature-doc-updates/SKILL.md +0 -0
  54. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/openspec-apply-change/SKILL.md +0 -0
  55. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/openspec-archive-change/SKILL.md +0 -0
  56. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/openspec-explore/SKILL.md +0 -0
  57. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/openspec-propose/SKILL.md +0 -0
  58. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/skills/openspec-sync-specs/SKILL.md +0 -0
  59. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/auto-approve-renovate.yml +0 -0
  60. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/dependency-review.yaml +0 -0
  61. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/docs.yaml +0 -0
  62. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/labels.yaml +0 -0
  63. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/linting.yaml +0 -0
  64. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/lock.yaml +0 -0
  65. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/pr-labels.yaml +0 -0
  66. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/release.yaml +0 -0
  67. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/stale.yaml +0 -0
  68. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/workflows/typing.yaml +0 -0
  69. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.github/zizmor.yml +0 -0
  70. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.gitignore +0 -0
  71. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.pre-commit-config.yaml +0 -0
  72. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.prettierignore +0 -0
  73. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/.yamllint +0 -0
  74. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/AGENTS.md +0 -0
  75. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/CLAUDE.md +0 -0
  76. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/LICENSE.md +0 -0
  77. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/Makefile +0 -0
  78. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/README.md +0 -0
  79. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/api/client.md +0 -0
  80. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/api/constants.md +0 -0
  81. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/api/exceptions.md +0 -0
  82. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/api/models.md +0 -0
  83. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/docs/index.md +0 -0
  84. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/control.py +0 -0
  85. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/discovery.py +0 -0
  86. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/fetch_param.py +0 -0
  87. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/profile_init.py +0 -0
  88. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/ruff.toml +0 -0
  89. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/examples/speed_test.py +0 -0
  90. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/mkdocs.yml +0 -0
  91. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/.openspec.yaml +0 -0
  92. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/design.md +0 -0
  93. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/proposal.md +0 -0
  94. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/specs/cooling-operating-mode/spec.md +0 -0
  95. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/tasks.md +0 -0
  96. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/.openspec.yaml +0 -0
  97. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/design.md +0 -0
  98. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/proposal.md +0 -0
  99. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/specs/circuit-discovery/spec.md +0 -0
  100. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/tasks.md +0 -0
  101. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/.openspec.yaml +0 -0
  102. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/design.md +0 -0
  103. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/proposal.md +0 -0
  104. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/specs/client-retry-policy/spec.md +0 -0
  105. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/tasks.md +0 -0
  106. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/.openspec.yaml +0 -0
  107. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/design.md +0 -0
  108. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/proposal.md +0 -0
  109. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/specs/error-classification/spec.md +0 -0
  110. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/tasks.md +0 -0
  111. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/.openspec.yaml +0 -0
  112. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/design.md +0 -0
  113. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/proposal.md +0 -0
  114. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/specs/lazy-validation-completeness/spec.md +0 -0
  115. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/tasks.md +0 -0
  116. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/config.yaml +0 -0
  117. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/specs/circuit-discovery/spec.md +0 -0
  118. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/specs/client-retry-policy/spec.md +0 -0
  119. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/specs/cooling-operating-mode/spec.md +0 -0
  120. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/specs/error-classification/spec.md +0 -0
  121. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/openspec/specs/lazy-validation-completeness/spec.md +0 -0
  122. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/sonar-project.properties +0 -0
  123. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/__init__.py +0 -0
  124. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_hot_water.py +0 -0
  125. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_parameters.py +0 -0
  126. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_schedules.py +0 -0
  127. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_temperature.py +0 -0
  128. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_transport.py +0 -0
  129. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/_version.py +0 -0
  130. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/exceptions.py +0 -0
  131. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/py.typed +0 -0
  132. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/src/bsblan/utility.py +0 -0
  133. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/__init__.py +0 -0
  134. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/conftest.py +0 -0
  135. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/device.json +0 -0
  136. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/dict_version.json +0 -0
  137. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/info.json +0 -0
  138. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/password.txt +0 -0
  139. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/pps_device.json +0 -0
  140. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/pps_state.json +0 -0
  141. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/pps_static_values.json +0 -0
  142. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/sensor.json +0 -0
  143. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/static_state.json +0 -0
  144. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/static_state_circuit2.json +0 -0
  145. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/thermostat_hvac.json +0 -0
  146. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/thermostat_temp.json +0 -0
  147. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/fixtures/time.json +0 -0
  148. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/ruff.toml +0 -0
  149. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_api_initialization.py +0 -0
  150. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_api_validation.py +0 -0
  151. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_auth.py +0 -0
  152. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_backoff_retry.py +0 -0
  153. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_bsblan.py +0 -0
  154. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_bsblan_edge_cases.py +0 -0
  155. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_configuration.py +0 -0
  156. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_device.py +0 -0
  157. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_dhw_time_switch.py +0 -0
  158. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_entity_info.py +0 -0
  159. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_entity_info_ha.py +0 -0
  160. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_heating_schedule.py +0 -0
  161. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_hot_water_additional.py +0 -0
  162. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_include_parameter.py +0 -0
  163. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_info.py +0 -0
  164. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_initialization.py +0 -0
  165. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_read_parameters.py +0 -0
  166. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_reset_validation.py +0 -0
  167. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_response_encoding.py +0 -0
  168. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_schedule_models.py +0 -0
  169. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_sensor.py +0 -0
  170. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_set_heating_schedule.py +0 -0
  171. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_set_hot_water_schedule.py +0 -0
  172. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_set_hotwater.py +0 -0
  173. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_static_state.py +0 -0
  174. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_temperature_validation.py +0 -0
  175. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_time.py +0 -0
  176. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_utility.py +0 -0
  177. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_utility_additional.py +0 -0
  178. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/tests/test_utility_edge_cases.py +0 -0
  179. {python_bsblan-6.1.9 → python_bsblan-6.1.11}/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@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
30
+ uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
31
31
  - name: 🚀 Perform CodeQL Analysis
32
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
32
+ uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
@@ -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@72967cdc98ddd3160f1fc3dff619de365e137dd0 # v7.9.0
39
+ uses: release-drafter/release-drafter@978bb0b30c5ee8cdd332ced253758157150d43f0 # v7.10.0
40
40
  with:
41
41
  prerelease: ${{ github.event.inputs.prerelease == 'true' }}
42
42
  prerelease-identifier: ${{ github.event.inputs.prerelease_identifier }}
@@ -57,7 +57,7 @@ jobs:
57
57
  # Upload the results as artifacts (optional).
58
58
  - name: "Upload artifact"
59
59
  # yamllint disable-line rule:line-length
60
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
60
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
61
61
  with:
62
62
  name: SARIF file
63
63
  path: results.sarif
@@ -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@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
69
+ uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
70
70
  with:
71
71
  sarif_file: results.sarif
@@ -46,7 +46,7 @@ jobs:
46
46
  - name: 🚀 Run pytest
47
47
  run: uv run pytest --cov=bsblan tests
48
48
  - name: ⬆️ Upload coverage artifact
49
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
49
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
50
50
  with:
51
51
  name: coverage-${{ matrix.python }}
52
52
  include-hidden-files: true
@@ -0,0 +1 @@
1
+ 24.22.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-bsblan
3
- Version: 6.1.9
3
+ Version: 6.1.11
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
@@ -95,6 +95,26 @@ example, on PPS devices). The upper heating bound is the comfort maximum (`716`
95
95
  for circuit 1 and `1016` for circuit 2) when the device exposes those
96
96
  parameters.
97
97
 
98
+ ## Reduced setpoint and temporary override
99
+
100
+ The reduced room setpoint (`712` for circuit 1, `1012` for circuit 2) can be
101
+ written with `temp_reduced_setpoint`. It uses the same bounds as
102
+ `target_temperature`.
103
+
104
+ To switch a circuit to comfort or reduced only until the next time program
105
+ switch point, write `temporary_mode` (`10110` for circuit 1, `10111` for circuit
106
+ 2): `0` (no override), `1` (reduced), or `2` (comfort). The override only has an
107
+ effect in automatic mode and cannot be read back.
108
+
109
+ ```python
110
+ async with BSBLAN(config) as client:
111
+ await client.thermostat(temp_reduced_setpoint="18.0")
112
+ await client.thermostat(temporary_mode=2)
113
+ ```
114
+
115
+ Setting `temp_reduced_setpoint` and `temporary_mode` is not supported on PPS
116
+ devices.
117
+
98
118
  ## Cooling setpoint support
99
119
 
100
120
  Some BSB/LPB controllers expose a cooling comfort setpoint for each heating
@@ -147,6 +167,24 @@ async with BSBLAN(config) as client:
147
167
 
148
168
  Setting `cooling_operating_mode` is not supported on PPS devices.
149
169
 
170
+ ## Active setpoints
171
+
172
+ In automatic mode the controller switches between the comfort setpoint
173
+ (`710`/`1010`) and the reduced setpoint (`712`/`1012`) following the time
174
+ program, and room units can shift it temporarily. `target_temperature` always
175
+ holds the comfort setpoint. The setpoint the controller currently applies is
176
+ exposed as `current_setpoint` (`8741` for circuit 1, `8771` for circuit 2).
177
+
178
+ Hot water works the same way: `nominal_setpoint` (`1610`) applies during the
179
+ release periods (`1620`) and `reduced_setpoint` (`1612`) outside them. The
180
+ setpoint currently applied is exposed as `dhw_current_setpoint` (`8831`).
181
+
182
+ ```python
183
+ async with BSBLAN(config) as client:
184
+ state = await client.state(include=["target_temperature", "current_setpoint"])
185
+ hw_state = await client.hot_water_state(include=["dhw_current_setpoint"])
186
+ ```
187
+
150
188
  ## PPS bus support
151
189
 
152
190
  PPS bus devices are detected from the device metadata returned by BSB-LAN. The
@@ -157,11 +195,17 @@ Supported PPS climate operations:
157
195
 
158
196
  - `state()` for `hvac_mode`, `target_temperature`, and `current_temperature`
159
197
  - `static_values()` for `min_temp` and `max_temp`
198
+ - `sensor()` for `outside_temperature` (`15030`) and `current_temperature` (`15008`)
160
199
  - `thermostat()` for target temperature and HVAC mode
161
200
  - `get_available_circuits()`, which returns `[1]` when the single PPS
162
201
  climate circuit is available, otherwise `[]`; PPS devices only ever expose
163
202
  circuit `1`
164
203
 
204
+ PPS sensor parameters are selected only when device metadata identifies the PPS
205
+ bus. BSB/LPB devices retain their existing sensor mappings. Sensor reads remain
206
+ on demand; `sensor(include=["outside_temperature"])` requests only `15030` on PPS
207
+ and only `8700` otherwise, without probing both parameter ranges.
208
+
165
209
  PPS devices currently have these limitations:
166
210
 
167
211
  - Only circuit `1` is supported.
@@ -128,6 +128,7 @@ def build_parameter_db(
128
128
  # ---- Sensor params ----
129
129
  db["8700"] = _param("Outside temp sensor local", "6.1", unit="°C", readonly=1)
130
130
  db["8740"] = _param("Room temp 1 actual value", "21.3", unit="°C", readonly=1)
131
+ db["8741"] = _param("Room setpoint 1", "21.0", unit="°C", readonly=1)
131
132
  db["3113"] = _param("Total energy", "7968", unit="kWh", readonly=1)
132
133
 
133
134
  # ---- Time param (0) ----
@@ -161,6 +162,7 @@ def build_parameter_db(
161
162
  db["1663"] = _param("DHW circulation setpoint", "40.0", unit="°C")
162
163
  db["1680"] = _param("DHW changeover", "0", desc="---", data_type=1)
163
164
  db["8830"] = _param("DHW actual top temp", "48.2", unit="°C", readonly=1)
165
+ db["8831"] = _param("DHW current setpoint", "50.0", unit="°C", readonly=1)
164
166
  db["8820"] = _param(
165
167
  "DHW pump state", "255", desc="Off", data_type=1, readonly=1
166
168
  )
@@ -219,9 +221,10 @@ def build_parameter_db(
219
221
  data_type=1,
220
222
  readonly=1,
221
223
  )
222
- db["8741"] = _param(
224
+ db["8770"] = _param(
223
225
  "Room temp 2 actual value", "19.8", unit="°C", readonly=1
224
226
  )
227
+ db["8771"] = _param("Room setpoint 2", "19.0", unit="°C", readonly=1)
225
228
  db["8750"] = _param(
226
229
  "HC2 Room1 thermostat mode",
227
230
  "1",
@@ -250,7 +253,7 @@ def build_parameter_db(
250
253
  data_type=1,
251
254
  readonly=1,
252
255
  )
253
- db["8742"] = _param(
256
+ db["8800"] = _param(
254
257
  "Room temp 3 actual value", "17.5", unit="°C", readonly=1
255
258
  )
256
259
  db["8751"] = _param(
@@ -340,6 +343,7 @@ class FakeBSBLAN:
340
343
  "1680",
341
344
  "8820",
342
345
  "8830",
346
+ "8831",
343
347
  }
344
348
  )
345
349
 
@@ -12,13 +12,13 @@
12
12
  "url-parse": "1.5.10"
13
13
  },
14
14
  "devDependencies": {
15
- "prettier": "3.9.9"
15
+ "prettier": "3.9.10"
16
16
  }
17
17
  },
18
18
  "node_modules/prettier": {
19
- "version": "3.9.9",
20
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz",
21
- "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==",
19
+ "version": "3.9.10",
20
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
21
+ "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
22
22
  "dev": true,
23
23
  "license": "MIT",
24
24
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "author": "WJL van Rootselaar <liudgervr@gmail.com>",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "prettier": "3.9.9"
12
+ "prettier": "3.9.10"
13
13
  },
14
14
  "dependencies": {
15
15
  "url-parse": "1.5.10"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-bsblan"
3
- version = "6.1.9"
3
+ version = "6.1.11"
4
4
  description = "Asynchronous Python client for BSBLAN API"
5
5
  authors = [
6
6
  {name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
@@ -186,7 +186,7 @@ build-backend = "hatchling.build"
186
186
  dev = [
187
187
  "aresponses==3.0.0",
188
188
  "bandit==1.9.4",
189
- "black==26.10.0",
189
+ "black==26.10.1",
190
190
  "blacken-docs==1.20.0",
191
191
  "codespell==2.4.3",
192
192
  "covdefaults==2.3.0",
@@ -197,16 +197,16 @@ dev = [
197
197
  # hatch is required to support type hinting and proper packaging of the py.typed file.
198
198
  "hatch>=1.14.1",
199
199
  "isort==8.0.1",
200
- "ty==0.0.85",
200
+ "ty==0.0.89",
201
201
  "prek>=0.3.3",
202
202
  "pre-commit-hooks==6.0.0",
203
- "pylint==4.1.2",
203
+ "pylint==4.1.3",
204
204
  "pytest>=8.3.5",
205
205
  "pytest-asyncio==1.4.0",
206
206
  "pytest-cov==7.1.0",
207
207
  "pytest-xdist>=3.8.0",
208
- "pyupgrade==3.21.2",
209
- "ruff==0.16.10",
208
+ "pyupgrade==3.22.0",
209
+ "ruff==0.17.1",
210
210
  "safety==3.8.1",
211
211
  "vulture==2.16",
212
212
  "yamllint==1.38.0",
@@ -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))
@@ -17,7 +17,12 @@ import asyncio
17
17
  import logging
18
18
  from typing import TYPE_CHECKING, cast
19
19
 
20
- from .constants import PPS_HEATING_PARAMS, PPS_STATIC_VALUES_PARAMS, ErrorMsg
20
+ from .constants import (
21
+ PPS_HEATING_PARAMS,
22
+ PPS_SENSOR_PARAMS,
23
+ PPS_STATIC_VALUES_PARAMS,
24
+ ErrorMsg,
25
+ )
21
26
  from .exceptions import BSBLANError
22
27
  from .utility import APIValidator
23
28
 
@@ -117,6 +122,7 @@ class SectionValidator:
117
122
 
118
123
  api_data["heating"] = PPS_HEATING_PARAMS.copy()
119
124
  api_data["staticValues"] = PPS_STATIC_VALUES_PARAMS.copy()
125
+ api_data["sensor"] = PPS_SENSOR_PARAMS.copy()
120
126
  api_data["heating_circuit2"] = {}
121
127
  api_data["staticValues_circuit2"] = {}
122
128
 
@@ -227,7 +233,7 @@ class SectionValidator:
227
233
 
228
234
  This enables more granular lazy loading for hot water - instead of
229
235
  validating all 29 hot water parameters at once, we only validate
230
- the specific group needed (essential: 5, config: 16, schedule: 8).
236
+ the specific group needed (essential: 6, config: 16, schedule: 8).
231
237
 
232
238
  Uses a per-group lock to prevent concurrent validation of the same
233
239
  group, which could cause duplicate network requests.
@@ -171,7 +171,20 @@ class BSBLAN:
171
171
  if self.session is None:
172
172
  self.session = aiohttp.ClientSession()
173
173
  self._close_session = True
174
- await self.initialize()
174
+ try:
175
+ await self.initialize()
176
+ except BaseException:
177
+ if self._close_session and self.session:
178
+ session = self.session
179
+ self.session = None
180
+ self._close_session = False
181
+ try:
182
+ await session.close()
183
+ except Exception:
184
+ logger.exception(
185
+ "Failed to close session after initialization failure"
186
+ )
187
+ raise
175
188
  return self
176
189
 
177
190
  async def __aexit__(self, *args: object) -> None:
@@ -332,7 +345,7 @@ class BSBLAN:
332
345
 
333
346
  This enables more granular lazy loading for hot water - instead of
334
347
  validating all 29 hot water parameters at once, we only validate
335
- the specific group needed (essential: 5, config: 16, schedule: 8).
348
+ the specific group needed (essential: 6, config: 16, schedule: 8).
336
349
 
337
350
  Uses a per-group lock to prevent concurrent validation of the same
338
351
  group, which could cause duplicate network requests.
@@ -720,7 +733,7 @@ class BSBLAN:
720
733
  include: Optional list of parameter names to fetch. If None,
721
734
  fetches all state parameters. Valid names include:
722
735
  hvac_mode, target_temperature, hvac_action,
723
- hvac_mode_changeover, current_temperature,
736
+ hvac_mode_changeover, current_temperature, current_setpoint,
724
737
  room1_temp_setpoint_boost.
725
738
  circuit: The heating circuit number (1 or 2). Defaults to 1.
726
739
  Circuit 2 uses separate parameter IDs but returns the
@@ -782,15 +795,17 @@ class BSBLAN:
782
795
  Args:
783
796
  include: Optional list of parameter names to fetch. If None,
784
797
  fetches all static parameters. Valid names include:
785
- min_temp, max_temp.
798
+ temp_reduced_setpoint, heating_protective_setpoint,
799
+ comfort_setpoint_max, cooling_comfort_setpoint_min,
800
+ cooling_reduced_setpoint, and min_temp, max_temp on PPS.
786
801
  circuit: The heating circuit number (1 or 2). Defaults to 1.
787
802
 
788
803
  Returns:
789
804
  StaticState: The static information from the BSBLAN device.
790
805
 
791
806
  Example:
792
- # Fetch only min_temp
793
- static = await client.static_values(include=["min_temp"])
807
+ # Fetch only the reduced setpoint
808
+ static = await client.static_values(include=["temp_reduced_setpoint"])
794
809
 
795
810
  # Fetch static values for heating circuit 2
796
811
  static_hc2 = await client.static_values(circuit=2)
@@ -866,7 +881,7 @@ class BSBLAN:
866
881
  )
867
882
  logger.debug("Response for setting time: %s", response)
868
883
 
869
- async def thermostat( # pylint: disable=too-many-arguments
884
+ async def thermostat( # noqa: PLR0913 # pylint: disable=too-many-arguments
870
885
  self,
871
886
  target_temperature: str | None = None,
872
887
  hvac_mode: int | None = None,
@@ -874,6 +889,8 @@ class BSBLAN:
874
889
  target_temperature_high: str | float | None = None,
875
890
  *,
876
891
  cooling_operating_mode: int | None = None,
892
+ temp_reduced_setpoint: str | float | None = None,
893
+ temporary_mode: int | None = None,
877
894
  ) -> None:
878
895
  """Change the state of the thermostat through BSB-Lan.
879
896
 
@@ -888,6 +905,12 @@ class BSBLAN:
888
905
  cooling_operating_mode: The cooling circuit operating mode to set
889
906
  as raw integer value: 0=Protection, 1=Automatic, 2=Reduced,
890
907
  3=Comfort. Not supported on PPS devices.
908
+ temp_reduced_setpoint: The reduced (eco) room setpoint to set.
909
+ Not supported on PPS devices.
910
+ temporary_mode: Temporary operating mode override, active until
911
+ the next time program switch point. Raw integer value:
912
+ 0=No override, 1=Reduced, 2=Comfort. Only has an effect in
913
+ automatic mode. Not supported on PPS devices.
891
914
 
892
915
  Example:
893
916
  # Set HC1 temperature
@@ -899,6 +922,12 @@ class BSBLAN:
899
922
  # Set HC1 cooling operating mode
900
923
  await client.thermostat(cooling_operating_mode=1)
901
924
 
925
+ # Set HC1 reduced setpoint
926
+ await client.thermostat(temp_reduced_setpoint="18.0")
927
+
928
+ # Switch HC1 to comfort until the next time program switch point
929
+ await client.thermostat(temporary_mode=2)
930
+
902
931
  # Set HC2 mode
903
932
  await client.thermostat(hvac_mode=1, circuit=2)
904
933
 
@@ -906,13 +935,14 @@ class BSBLAN:
906
935
  self._validate_circuit(circuit)
907
936
  if self._uses_pps_bus:
908
937
  self._validate_bus_write_supported()
909
- await self._initialize_temperature_range(circuit)
910
938
 
911
939
  self._validate_single_parameter(
912
940
  target_temperature,
913
941
  hvac_mode,
914
942
  target_temperature_high,
915
943
  cooling_operating_mode,
944
+ temp_reduced_setpoint,
945
+ temporary_mode,
916
946
  error_msg=ErrorMsg.MULTI_PARAMETER,
917
947
  )
918
948
 
@@ -922,6 +952,8 @@ class BSBLAN:
922
952
  circuit,
923
953
  target_temperature_high,
924
954
  cooling_operating_mode=cooling_operating_mode,
955
+ temp_reduced_setpoint=temp_reduced_setpoint,
956
+ temporary_mode=temporary_mode,
925
957
  )
926
958
  await self._set_device_state(state)
927
959
 
@@ -1010,14 +1042,15 @@ class BSBLAN:
1010
1042
  that are typically checked frequently for monitoring purposes.
1011
1043
  This reduces API calls and improves performance for regular polling.
1012
1044
 
1013
- Uses granular lazy loading - only validates the 5 essential params,
1045
+ Uses granular lazy loading - only validates the 6 essential params,
1014
1046
  not all 29 hot water parameters.
1015
1047
 
1016
1048
  Args:
1017
1049
  include: Optional list of parameter names to fetch. If None,
1018
1050
  fetches all essential hot water parameters. Valid names include:
1019
1051
  operating_mode, nominal_setpoint, release,
1020
- dhw_actual_value_top_temperature, state_dhw_pump.
1052
+ dhw_actual_value_top_temperature, dhw_current_setpoint,
1053
+ state_dhw_pump.
1021
1054
 
1022
1055
  Returns:
1023
1056
  HotWaterState: Essential hot water state information.
@@ -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
 
@@ -156,6 +159,11 @@ PPS_STATIC_VALUES_PARAMS: Final[dict[str, str]] = {
156
159
  "15007": "max_temp",
157
160
  }
158
161
 
162
+ PPS_SENSOR_PARAMS: Final[dict[str, str]] = {
163
+ "15030": "outside_temperature",
164
+ "15008": "current_temperature",
165
+ }
166
+
159
167
 
160
168
  # Circuit configuration
161
169
  class CircuitConfig:
@@ -176,12 +184,16 @@ class CircuitConfig:
176
184
  "target_temperature_high": "902",
177
185
  "hvac_mode": "700",
178
186
  "cooling_operating_mode": "901",
187
+ "temp_reduced_setpoint": "712",
188
+ "temporary_mode": "10110",
179
189
  },
180
190
  2: {
181
191
  "target_temperature": "1010",
182
192
  "target_temperature_high": "1202",
183
193
  "hvac_mode": "1000",
184
194
  "cooling_operating_mode": "1201",
195
+ "temp_reduced_setpoint": "1012",
196
+ "temporary_mode": "10111",
185
197
  },
186
198
  }
187
199
  PROBE_PARAMS: Final[dict[int, str]] = {
@@ -247,6 +259,9 @@ class Validation:
247
259
  # 901/1201 cooling circuit operating mode (ENUM, verified on device):
248
260
  # 0=Protection, 1=Automatic, 2=Reduced, 3=Comfort
249
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}
250
265
  PPS_HVAC_MODES: Final[set[int]] = {0, 1, 3}
251
266
  PPS_HVAC_MODE_TO_BSBLAN: Final[dict[int, str]] = {
252
267
  0: "2", # off
@@ -410,19 +425,20 @@ _STATUS_TO_CATEGORY: dict[HeatingCircuitStatus, HVACActionCategory] = {
410
425
  # Off states
411
426
  HeatingCircuitStatus.FAULT_ERROR: HVACActionCategory.OFF,
412
427
  HeatingCircuitStatus.STANDBY_1: HVACActionCategory.OFF,
413
- HeatingCircuitStatus.OFF: HVACActionCategory.OFF,
414
428
  HeatingCircuitStatus.STANDBY_2: HVACActionCategory.OFF,
415
429
  HeatingCircuitStatus.COOLING_OFF: HVACActionCategory.OFF,
416
- HeatingCircuitStatus.COOLING_LOCKED: HVACActionCategory.OFF,
417
430
  HeatingCircuitStatus.HEATING_GENERATOR_OFF: HVACActionCategory.OFF,
418
431
  HeatingCircuitStatus.HEATING_OFF: HVACActionCategory.OFF,
419
- HeatingCircuitStatus.LOCKED_HEATING_MODE: HVACActionCategory.OFF,
420
- HeatingCircuitStatus.LOCKED_GENERATOR: HVACActionCategory.OFF,
421
- HeatingCircuitStatus.LOCKED_BUFFER: HVACActionCategory.OFF,
422
- HeatingCircuitStatus.DAY_ECO: HVACActionCategory.OFF,
423
- HeatingCircuitStatus.SETBACK_REDUCED: HVACActionCategory.OFF,
424
- HeatingCircuitStatus.SETBACK_FROST_PROTECTION: HVACActionCategory.OFF,
425
- 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,
426
442
  # Heating states
427
443
  HeatingCircuitStatus.MANUAL_CONTROL: HVACActionCategory.HEATING,
428
444
  HeatingCircuitStatus.OVERRUN: HVACActionCategory.HEATING,
@@ -637,6 +653,7 @@ class HotWaterParams:
637
653
  "nominal_setpoint",
638
654
  "release",
639
655
  "dhw_actual_value_top_temperature",
656
+ "dhw_current_setpoint",
640
657
  "state_dhw_pump",
641
658
  }
642
659
  }
@@ -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
 
@@ -249,6 +249,19 @@
249
249
  "readwrite": 1,
250
250
  "unit": "°C"
251
251
  },
252
+ "8831": {
253
+ "name": "DHW temperature setpoint",
254
+ "dataType_name": "TEMP",
255
+ "dataType_family": "VALS",
256
+ "error": 0,
257
+ "value": "45.0",
258
+ "desc": "",
259
+ "precision": 0.1,
260
+ "dataType": 0,
261
+ "readonly": 1,
262
+ "readwrite": 1,
263
+ "unit": "°C"
264
+ },
252
265
  "8820": {
253
266
  "name": "State DHW pump",
254
267
  "dataType_name": "ONOFF",