python-bsblan 6.1.10__tar.gz → 6.2.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 (181) hide show
  1. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/codeql.yaml +2 -2
  2. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/release.yaml +1 -1
  3. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/scorecard.yml +2 -2
  4. python_bsblan-6.2.0/.nvmrc +1 -0
  5. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/PKG-INFO +1 -1
  6. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/getting-started.md +38 -0
  7. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/control.py +3 -5
  8. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/fake_bsblan_server.py +6 -2
  9. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/package-lock.json +4 -4
  10. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/package.json +1 -1
  11. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/pyproject.toml +5 -5
  12. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_thermostat.py +44 -9
  13. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_validation.py +1 -1
  14. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/bsblan.py +58 -32
  15. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/constants.py +22 -9
  16. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/models.py +3 -0
  17. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/utility.py +0 -60
  18. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/hot_water_state.json +13 -0
  19. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/state.json +13 -0
  20. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/state_circuit2.json +13 -0
  21. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_circuit.py +35 -0
  22. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_constants.py +9 -3
  23. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_context_manager.py +76 -0
  24. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_hotwater_state.py +6 -2
  25. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_pps.py +53 -2
  26. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_state.py +5 -1
  27. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_temperature_unit.py +14 -0
  28. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_thermostat.py +131 -1
  29. python_bsblan-6.2.0/tests/test_time.py +112 -0
  30. python_bsblan-6.2.0/tests/test_utility.py +173 -0
  31. python_bsblan-6.1.10/.nvmrc +0 -1
  32. python_bsblan-6.1.10/tests/test_time.py +0 -182
  33. python_bsblan-6.1.10/tests/test_utility.py +0 -398
  34. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.editorconfig +0 -0
  35. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.gitattributes +0 -0
  36. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/CODE_OF_CONDUCT.md +0 -0
  37. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/CONTRIBUTING.md +0 -0
  38. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  39. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/SECURITY.md +0 -0
  42. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/agents/pr-review.agent.md +0 -0
  43. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/agents/security-reviewer.agent.md +0 -0
  44. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/agents/tdd-refactor.agent.md +0 -0
  45. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/copilot-instructions.md +0 -0
  46. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/labels.yml +0 -0
  47. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/add-parameter.prompt.md +0 -0
  48. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/code-review.prompt.md +0 -0
  49. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/opsx-apply.prompt.md +0 -0
  50. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/opsx-archive.prompt.md +0 -0
  51. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/opsx-explore.prompt.md +0 -0
  52. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/opsx-propose.prompt.md +0 -0
  53. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/prompts/opsx-sync.prompt.md +0 -0
  54. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/release-drafter.yml +0 -0
  55. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/renovate.json +0 -0
  56. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/bsblan-parameters/SKILL.md +0 -0
  57. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/bsblan-testing/SKILL.md +0 -0
  58. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/feature-doc-updates/SKILL.md +0 -0
  59. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/openspec-apply-change/SKILL.md +0 -0
  60. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/openspec-archive-change/SKILL.md +0 -0
  61. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/openspec-explore/SKILL.md +0 -0
  62. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/openspec-propose/SKILL.md +0 -0
  63. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/skills/openspec-sync-specs/SKILL.md +0 -0
  64. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/auto-approve-renovate.yml +0 -0
  65. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/dependency-review.yaml +0 -0
  66. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/docs.yaml +0 -0
  67. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/labels.yaml +0 -0
  68. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/linting.yaml +0 -0
  69. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/lock.yaml +0 -0
  70. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/pr-labels.yaml +0 -0
  71. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/release-drafter.yaml +0 -0
  72. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/stale.yaml +0 -0
  73. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/tests.yaml +0 -0
  74. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/workflows/typing.yaml +0 -0
  75. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.github/zizmor.yml +0 -0
  76. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.gitignore +0 -0
  77. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.pre-commit-config.yaml +0 -0
  78. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.prettierignore +0 -0
  79. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/.yamllint +0 -0
  80. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/AGENTS.md +0 -0
  81. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/CLAUDE.md +0 -0
  82. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/LICENSE.md +0 -0
  83. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/Makefile +0 -0
  84. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/README.md +0 -0
  85. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/api/client.md +0 -0
  86. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/api/constants.md +0 -0
  87. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/api/exceptions.md +0 -0
  88. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/api/models.md +0 -0
  89. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/docs/index.md +0 -0
  90. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/discovery.py +0 -0
  91. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/fetch_param.py +0 -0
  92. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/profile_init.py +0 -0
  93. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/ruff.toml +0 -0
  94. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/examples/speed_test.py +0 -0
  95. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/mkdocs.yml +0 -0
  96. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/.openspec.yaml +0 -0
  97. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/design.md +0 -0
  98. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/proposal.md +0 -0
  99. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/specs/cooling-operating-mode/spec.md +0 -0
  100. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/tasks.md +0 -0
  101. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/.openspec.yaml +0 -0
  102. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/design.md +0 -0
  103. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/proposal.md +0 -0
  104. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/specs/circuit-discovery/spec.md +0 -0
  105. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/tasks.md +0 -0
  106. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/.openspec.yaml +0 -0
  107. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/design.md +0 -0
  108. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/proposal.md +0 -0
  109. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/specs/client-retry-policy/spec.md +0 -0
  110. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/tasks.md +0 -0
  111. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/.openspec.yaml +0 -0
  112. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/design.md +0 -0
  113. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/proposal.md +0 -0
  114. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/specs/error-classification/spec.md +0 -0
  115. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/tasks.md +0 -0
  116. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/.openspec.yaml +0 -0
  117. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/design.md +0 -0
  118. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/proposal.md +0 -0
  119. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/specs/lazy-validation-completeness/spec.md +0 -0
  120. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/tasks.md +0 -0
  121. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/config.yaml +0 -0
  122. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/specs/circuit-discovery/spec.md +0 -0
  123. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/specs/client-retry-policy/spec.md +0 -0
  124. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/specs/cooling-operating-mode/spec.md +0 -0
  125. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/specs/error-classification/spec.md +0 -0
  126. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/openspec/specs/lazy-validation-completeness/spec.md +0 -0
  127. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/sonar-project.properties +0 -0
  128. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/__init__.py +0 -0
  129. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_hot_water.py +0 -0
  130. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_parameters.py +0 -0
  131. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_schedules.py +0 -0
  132. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_temperature.py +0 -0
  133. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_transport.py +0 -0
  134. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/_version.py +0 -0
  135. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/exceptions.py +0 -0
  136. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/src/bsblan/py.typed +0 -0
  137. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/__init__.py +0 -0
  138. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/conftest.py +0 -0
  139. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/device.json +0 -0
  140. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/dict_version.json +0 -0
  141. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/info.json +0 -0
  142. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/password.txt +0 -0
  143. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/pps_device.json +0 -0
  144. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/pps_state.json +0 -0
  145. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/pps_static_values.json +0 -0
  146. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/sensor.json +0 -0
  147. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/static_state.json +0 -0
  148. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/static_state_circuit2.json +0 -0
  149. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/thermostat_hvac.json +0 -0
  150. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/thermostat_temp.json +0 -0
  151. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/fixtures/time.json +0 -0
  152. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/ruff.toml +0 -0
  153. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_api_initialization.py +0 -0
  154. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_api_validation.py +0 -0
  155. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_auth.py +0 -0
  156. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_backoff_retry.py +0 -0
  157. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_bsblan.py +0 -0
  158. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_bsblan_edge_cases.py +0 -0
  159. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_configuration.py +0 -0
  160. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_device.py +0 -0
  161. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_dhw_time_switch.py +0 -0
  162. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_entity_info.py +0 -0
  163. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_entity_info_ha.py +0 -0
  164. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_heating_schedule.py +0 -0
  165. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_hot_water_additional.py +0 -0
  166. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_include_parameter.py +0 -0
  167. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_info.py +0 -0
  168. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_initialization.py +0 -0
  169. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_read_parameters.py +0 -0
  170. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_reset_validation.py +0 -0
  171. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_response_encoding.py +0 -0
  172. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_schedule_models.py +0 -0
  173. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_sensor.py +0 -0
  174. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_set_heating_schedule.py +0 -0
  175. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_set_hot_water_schedule.py +0 -0
  176. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_set_hotwater.py +0 -0
  177. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_static_state.py +0 -0
  178. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_temperature_validation.py +0 -0
  179. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_utility_additional.py +0 -0
  180. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/tests/test_utility_edge_cases.py +0 -0
  181. {python_bsblan-6.1.10 → python_bsblan-6.2.0}/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@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
30
+ uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
31
31
  - name: 🚀 Perform CodeQL Analysis
32
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
32
+ uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
@@ -54,7 +54,7 @@ jobs:
54
54
  with:
55
55
  subject-path: ./dist/*
56
56
  - name: 🚀 Publish to PyPi
57
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
57
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
58
58
  with:
59
59
  verbose: true
60
60
  print-hash: true
@@ -40,7 +40,7 @@ jobs:
40
40
 
41
41
  - name: "Run analysis"
42
42
  # yamllint disable-line rule:line-length
43
- uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
43
+ uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
44
44
  with:
45
45
  results_file: results.sarif
46
46
  results_format: 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@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
69
+ uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
70
70
  with:
71
71
  sarif_file: results.sarif
@@ -0,0 +1 @@
1
+ 24.22.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-bsblan
3
- Version: 6.1.10
3
+ Version: 6.2.0
4
4
  Summary: Asynchronous Python client for BSBLAN API
5
5
  Project-URL: Homepage, https://github.com/liudger/python-bsblan
6
6
  Project-URL: Repository, https://github.com/liudger/python-bsblan
@@ -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
@@ -409,11 +409,9 @@ async def main() -> None:
409
409
  # Example: Set device time
410
410
  if bsblan.supports_time_sync:
411
411
  print("\nSetting device time to current system time")
412
- # Get current local system time and format it for BSB-LAN.
413
- current_time = datetime.now().replace(microsecond=0) # noqa: DTZ005
414
- formatted_time = current_time.strftime("%d.%m.%Y %H:%M:%S")
415
- print(f"Current system time: {formatted_time}")
416
- await bsblan.set_time(formatted_time)
412
+ current_time = datetime.now() # noqa: DTZ005
413
+ print(f"Current system time: {current_time:%d.%m.%Y %H:%M:%S}")
414
+ await bsblan.set_time(current_time)
417
415
  else:
418
416
  print("\nSkipping device time sync for this bus type")
419
417
 
@@ -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.10"
3
+ version = "6.2.0"
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.86",
200
+ "ty==0.0.90",
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
208
  "pyupgrade==3.22.0",
209
- "ruff==0.16.10",
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))
@@ -233,7 +233,7 @@ class SectionValidator:
233
233
 
234
234
  This enables more granular lazy loading for hot water - instead of
235
235
  validating all 29 hot water parameters at once, we only validate
236
- the specific group needed (essential: 5, config: 16, schedule: 8).
236
+ the specific group needed (essential: 6, config: 16, schedule: 8).
237
237
 
238
238
  Uses a per-group lock to prevent concurrent validation of the same
239
239
  group, which could cause duplicate network requests.
@@ -51,9 +51,10 @@ from .models import (
51
51
  State,
52
52
  StaticState,
53
53
  )
54
- from .utility import is_param_value_active, validate_time_format
54
+ from .utility import is_param_value_active
55
55
 
56
56
  if TYPE_CHECKING:
57
+ from datetime import datetime
57
58
  from typing import Self
58
59
 
59
60
  from aiohttp.client import ClientSession
@@ -171,7 +172,20 @@ class BSBLAN:
171
172
  if self.session is None:
172
173
  self.session = aiohttp.ClientSession()
173
174
  self._close_session = True
174
- await self.initialize()
175
+ try:
176
+ await self.initialize()
177
+ except BaseException:
178
+ if self._close_session and self.session:
179
+ session = self.session
180
+ self.session = None
181
+ self._close_session = False
182
+ try:
183
+ await session.close()
184
+ except Exception:
185
+ logger.exception(
186
+ "Failed to close session after initialization failure"
187
+ )
188
+ raise
175
189
  return self
176
190
 
177
191
  async def __aexit__(self, *args: object) -> None:
@@ -332,7 +346,7 @@ class BSBLAN:
332
346
 
333
347
  This enables more granular lazy loading for hot water - instead of
334
348
  validating all 29 hot water parameters at once, we only validate
335
- the specific group needed (essential: 5, config: 16, schedule: 8).
349
+ the specific group needed (essential: 6, config: 16, schedule: 8).
336
350
 
337
351
  Uses a per-group lock to prevent concurrent validation of the same
338
352
  group, which could cause duplicate network requests.
@@ -720,7 +734,7 @@ class BSBLAN:
720
734
  include: Optional list of parameter names to fetch. If None,
721
735
  fetches all state parameters. Valid names include:
722
736
  hvac_mode, target_temperature, hvac_action,
723
- hvac_mode_changeover, current_temperature,
737
+ hvac_mode_changeover, current_temperature, current_setpoint,
724
738
  room1_temp_setpoint_boost.
725
739
  circuit: The heating circuit number (1 or 2). Defaults to 1.
726
740
  Circuit 2 uses separate parameter IDs but returns the
@@ -782,15 +796,17 @@ class BSBLAN:
782
796
  Args:
783
797
  include: Optional list of parameter names to fetch. If None,
784
798
  fetches all static parameters. Valid names include:
785
- min_temp, max_temp.
799
+ temp_reduced_setpoint, heating_protective_setpoint,
800
+ comfort_setpoint_max, cooling_comfort_setpoint_min,
801
+ cooling_reduced_setpoint, and min_temp, max_temp on PPS.
786
802
  circuit: The heating circuit number (1 or 2). Defaults to 1.
787
803
 
788
804
  Returns:
789
805
  StaticState: The static information from the BSBLAN device.
790
806
 
791
807
  Example:
792
- # Fetch only min_temp
793
- static = await client.static_values(include=["min_temp"])
808
+ # Fetch only the reduced setpoint
809
+ static = await client.static_values(include=["temp_reduced_setpoint"])
794
810
 
795
811
  # Fetch static values for heating circuit 2
796
812
  static_hc2 = await client.static_values(circuit=2)
@@ -847,26 +863,33 @@ class BSBLAN:
847
863
  time_data = {"time": data["0"]}
848
864
  return DeviceTime.model_validate(time_data)
849
865
 
850
- async def set_time(self, time_value: str) -> None:
866
+ async def set_time(self, value: datetime) -> None:
851
867
  """Set the time on the BSB-LAN device.
852
868
 
853
869
  Args:
854
- time_value (str): The time value to set in format "DD.MM.YYYY HH:MM:SS"
855
- (e.g., "13.08.2025 10:25:55").
870
+ value (datetime): The local time of the heating system. Its date and
871
+ time are written as they are, without timezone conversion.
856
872
 
857
873
  Raises:
858
- BSBLANInvalidParameterError: If the time format is invalid.
874
+ BSBLANInvalidParameterError: If the year is out of range.
875
+ BSBLANError: If the device does not accept the new time.
859
876
 
860
877
  """
861
878
  await self._ensure_device_metadata()
862
879
  self._validate_time_sync_supported()
863
- self._validate_time_format(time_value)
880
+ if not Validation.MIN_YEAR <= value.year <= Validation.MAX_YEAR:
881
+ msg = f"Invalid year: {value.year}"
882
+ raise BSBLANInvalidParameterError(msg)
883
+ # BSB-LAN 4.1 rejects a space between the date and the time
864
884
  response = await self._request(
865
- base_path="/JS", data=self._set_payload("0", time_value)
885
+ base_path="/JS",
886
+ data=self._set_payload("0", value.strftime("%d.%m.%Y_%H:%M:%S")),
866
887
  )
867
888
  logger.debug("Response for setting time: %s", response)
889
+ if response.get("0", {}).get("status") != 1:
890
+ raise BSBLANError(ErrorMsg.TIME_SYNC_FAILED)
868
891
 
869
- async def thermostat( # pylint: disable=too-many-arguments
892
+ async def thermostat( # noqa: PLR0913 # pylint: disable=too-many-arguments
870
893
  self,
871
894
  target_temperature: str | None = None,
872
895
  hvac_mode: int | None = None,
@@ -874,6 +897,8 @@ class BSBLAN:
874
897
  target_temperature_high: str | float | None = None,
875
898
  *,
876
899
  cooling_operating_mode: int | None = None,
900
+ temp_reduced_setpoint: str | float | None = None,
901
+ temporary_mode: int | None = None,
877
902
  ) -> None:
878
903
  """Change the state of the thermostat through BSB-Lan.
879
904
 
@@ -888,6 +913,12 @@ class BSBLAN:
888
913
  cooling_operating_mode: The cooling circuit operating mode to set
889
914
  as raw integer value: 0=Protection, 1=Automatic, 2=Reduced,
890
915
  3=Comfort. Not supported on PPS devices.
916
+ temp_reduced_setpoint: The reduced (eco) room setpoint to set.
917
+ Not supported on PPS devices.
918
+ temporary_mode: Temporary operating mode override, active until
919
+ the next time program switch point. Raw integer value:
920
+ 0=No override, 1=Reduced, 2=Comfort. Only has an effect in
921
+ automatic mode. Not supported on PPS devices.
891
922
 
892
923
  Example:
893
924
  # Set HC1 temperature
@@ -899,6 +930,12 @@ class BSBLAN:
899
930
  # Set HC1 cooling operating mode
900
931
  await client.thermostat(cooling_operating_mode=1)
901
932
 
933
+ # Set HC1 reduced setpoint
934
+ await client.thermostat(temp_reduced_setpoint="18.0")
935
+
936
+ # Switch HC1 to comfort until the next time program switch point
937
+ await client.thermostat(temporary_mode=2)
938
+
902
939
  # Set HC2 mode
903
940
  await client.thermostat(hvac_mode=1, circuit=2)
904
941
 
@@ -906,13 +943,14 @@ class BSBLAN:
906
943
  self._validate_circuit(circuit)
907
944
  if self._uses_pps_bus:
908
945
  self._validate_bus_write_supported()
909
- await self._initialize_temperature_range(circuit)
910
946
 
911
947
  self._validate_single_parameter(
912
948
  target_temperature,
913
949
  hvac_mode,
914
950
  target_temperature_high,
915
951
  cooling_operating_mode,
952
+ temp_reduced_setpoint,
953
+ temporary_mode,
916
954
  error_msg=ErrorMsg.MULTI_PARAMETER,
917
955
  )
918
956
 
@@ -922,6 +960,8 @@ class BSBLAN:
922
960
  circuit,
923
961
  target_temperature_high,
924
962
  cooling_operating_mode=cooling_operating_mode,
963
+ temp_reduced_setpoint=temp_reduced_setpoint,
964
+ temporary_mode=temporary_mode,
925
965
  )
926
966
  await self._set_device_state(state)
927
967
 
@@ -960,21 +1000,6 @@ class BSBLAN:
960
1000
  circuit,
961
1001
  )
962
1002
 
963
- def _validate_time_format(self, time_value: str) -> None:
964
- """Validate the time format.
965
-
966
- Args:
967
- time_value (str): The time value to validate.
968
-
969
- Raises:
970
- BSBLANInvalidParameterError: If the time format is invalid.
971
-
972
- """
973
- try:
974
- validate_time_format(time_value, Validation.MIN_YEAR, Validation.MAX_YEAR)
975
- except ValueError as err:
976
- raise BSBLANInvalidParameterError(str(err)) from err
977
-
978
1003
  def _set_payload(
979
1004
  self, parameter: str, value: str, type_: str = "1"
980
1005
  ) -> dict[str, Any]:
@@ -1010,14 +1035,15 @@ class BSBLAN:
1010
1035
  that are typically checked frequently for monitoring purposes.
1011
1036
  This reduces API calls and improves performance for regular polling.
1012
1037
 
1013
- Uses granular lazy loading - only validates the 5 essential params,
1038
+ Uses granular lazy loading - only validates the 6 essential params,
1014
1039
  not all 29 hot water parameters.
1015
1040
 
1016
1041
  Args:
1017
1042
  include: Optional list of parameter names to fetch. If None,
1018
1043
  fetches all essential hot water parameters. Valid names include:
1019
1044
  operating_mode, nominal_setpoint, release,
1020
- dhw_actual_value_top_temperature, state_dhw_pump.
1045
+ dhw_actual_value_top_temperature, dhw_current_setpoint,
1046
+ state_dhw_pump.
1021
1047
 
1022
1048
  Returns:
1023
1049
  HotWaterState: Essential hot water state information.
@@ -45,6 +45,7 @@ BASE_HEATING_PARAMS: Final[dict[str, str]] = {
45
45
  # -------
46
46
  "8000": "hvac_action",
47
47
  "8740": "current_temperature",
48
+ "8741": "current_setpoint",
48
49
  "770": "room1_temp_setpoint_boost",
49
50
  }
50
51
 
@@ -90,6 +91,7 @@ BASE_HOT_WATER_PARAMS: Final[dict[str, str]] = {
90
91
  "1680": "operating_mode_changeover",
91
92
  # -------
92
93
  "8830": "dhw_actual_value_top_temperature",
94
+ "8831": "dhw_current_setpoint",
93
95
  "8820": "state_dhw_pump",
94
96
  # -------
95
97
  "561": "dhw_time_program_monday",
@@ -113,6 +115,7 @@ BASE_HEATING_CIRCUIT2_PARAMS: Final[dict[str, str]] = {
113
115
  # -------
114
116
  "8001": "hvac_action",
115
117
  "8770": "current_temperature",
118
+ "8771": "current_setpoint",
116
119
  "1070": "room1_temp_setpoint_boost",
117
120
  }
118
121
 
@@ -181,12 +184,16 @@ class CircuitConfig:
181
184
  "target_temperature_high": "902",
182
185
  "hvac_mode": "700",
183
186
  "cooling_operating_mode": "901",
187
+ "temp_reduced_setpoint": "712",
188
+ "temporary_mode": "10110",
184
189
  },
185
190
  2: {
186
191
  "target_temperature": "1010",
187
192
  "target_temperature_high": "1202",
188
193
  "hvac_mode": "1000",
189
194
  "cooling_operating_mode": "1201",
195
+ "temp_reduced_setpoint": "1012",
196
+ "temporary_mode": "10111",
190
197
  },
191
198
  }
192
199
  PROBE_PARAMS: Final[dict[int, str]] = {
@@ -252,6 +259,9 @@ class Validation:
252
259
  # 901/1201 cooling circuit operating mode (ENUM, verified on device):
253
260
  # 0=Protection, 1=Automatic, 2=Reduced, 3=Comfort
254
261
  COOLING_OPERATING_MODES: Final[set[int]] = {0, 1, 2, 3}
262
+ # 10110/10111 temporary operating mode (write-only, verified on device):
263
+ # 0=No override, 1=Reduced, 2=Comfort, until the next time program switch
264
+ TEMPORARY_MODES: Final[set[int]] = {0, 1, 2}
255
265
  PPS_HVAC_MODES: Final[set[int]] = {0, 1, 3}
256
266
  PPS_HVAC_MODE_TO_BSBLAN: Final[dict[int, str]] = {
257
267
  0: "2", # off
@@ -415,19 +425,20 @@ _STATUS_TO_CATEGORY: dict[HeatingCircuitStatus, HVACActionCategory] = {
415
425
  # Off states
416
426
  HeatingCircuitStatus.FAULT_ERROR: HVACActionCategory.OFF,
417
427
  HeatingCircuitStatus.STANDBY_1: HVACActionCategory.OFF,
418
- HeatingCircuitStatus.OFF: HVACActionCategory.OFF,
419
428
  HeatingCircuitStatus.STANDBY_2: HVACActionCategory.OFF,
420
429
  HeatingCircuitStatus.COOLING_OFF: HVACActionCategory.OFF,
421
- HeatingCircuitStatus.COOLING_LOCKED: HVACActionCategory.OFF,
422
430
  HeatingCircuitStatus.HEATING_GENERATOR_OFF: HVACActionCategory.OFF,
423
431
  HeatingCircuitStatus.HEATING_OFF: HVACActionCategory.OFF,
424
- HeatingCircuitStatus.LOCKED_HEATING_MODE: HVACActionCategory.OFF,
425
- HeatingCircuitStatus.LOCKED_GENERATOR: HVACActionCategory.OFF,
426
- HeatingCircuitStatus.LOCKED_BUFFER: HVACActionCategory.OFF,
427
- HeatingCircuitStatus.DAY_ECO: HVACActionCategory.OFF,
428
- HeatingCircuitStatus.SETBACK_REDUCED: HVACActionCategory.OFF,
429
- HeatingCircuitStatus.SETBACK_FROST_PROTECTION: HVACActionCategory.OFF,
430
- HeatingCircuitStatus.ROOM_TEMPERATURE_LIMIT: HVACActionCategory.OFF,
432
+ # Idle states: the circuit runs but currently needs no heat
433
+ HeatingCircuitStatus.OFF: HVACActionCategory.IDLE, # summer operation
434
+ HeatingCircuitStatus.COOLING_LOCKED: HVACActionCategory.IDLE,
435
+ HeatingCircuitStatus.LOCKED_HEATING_MODE: HVACActionCategory.IDLE,
436
+ HeatingCircuitStatus.LOCKED_GENERATOR: HVACActionCategory.IDLE,
437
+ HeatingCircuitStatus.LOCKED_BUFFER: HVACActionCategory.IDLE,
438
+ HeatingCircuitStatus.DAY_ECO: HVACActionCategory.IDLE,
439
+ HeatingCircuitStatus.SETBACK_REDUCED: HVACActionCategory.IDLE,
440
+ HeatingCircuitStatus.SETBACK_FROST_PROTECTION: HVACActionCategory.IDLE,
441
+ HeatingCircuitStatus.ROOM_TEMPERATURE_LIMIT: HVACActionCategory.IDLE,
431
442
  # Heating states
432
443
  HeatingCircuitStatus.MANUAL_CONTROL: HVACActionCategory.HEATING,
433
444
  HeatingCircuitStatus.OVERRUN: HVACActionCategory.HEATING,
@@ -550,6 +561,7 @@ class ErrorMsg:
550
561
  )
551
562
  NO_HEATING_SCHEDULE_PARAMS = "No heating schedule parameters available"
552
563
  TIME_SYNC_NOT_SUPPORTED = "Time synchronization is not supported by this device"
564
+ TIME_SYNC_FAILED = "The device did not accept the new time"
553
565
  BUS_WRITE_NOT_SUPPORTED = "Writing parameters is not supported by this device"
554
566
 
555
567
 
@@ -642,6 +654,7 @@ class HotWaterParams:
642
654
  "nominal_setpoint",
643
655
  "release",
644
656
  "dhw_actual_value_top_temperature",
657
+ "dhw_current_setpoint",
645
658
  "state_dhw_pump",
646
659
  }
647
660
  }
@@ -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