Skip to content

fix(climate): keep AUTO zones on season frames, write generic setpoint mode - #649

Closed
gdluck wants to merge 3 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/firmware-validated-climate
Closed

gdluck wants to merge 3 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/firmware-validated-climate

Conversation

@gdluck

@gdluck gdluck commented Oct 7, 2026 •

Copy link
Copy Markdown

Requested by A · project thread

Depends on OpenWebNet-HA/OWNd#94, which decodes *4*1*Z## / *4*0*Z## as hvac_season with a typed season attribute; this change reads that attribute and touches no WHAT. With the pinned OWNd 2.0.0b10 the frame still arrives as hvac_mode and takes the unchanged path, so the two can merge in either order. The other OWNd firmware-validated fixes are in OpenWebNet-HA/OWNd#87 to #93.

Before: a zone in AUTO (or in manual HEAT / COOL) switched to the season's mode whenever the gateway reported *4*1*Z## / *4*0*Z##, and setpoint writes sent mode digit 1 (heat) or 2 (cool).

After: on the season frame a zone that is AUTO, HEAT or COOL keeps its mode and records season as an attribute; a zone that was OFF or not yet known takes the season's mode (it is running again); a central unit shows the season as its mode, as before. Zone setpoint writes use mode digit 3 (generic), as BTicino's own client does. The central-unit write keeps digits 1/2, because OWNd's set_central_temperature has no generic digit.

Legrand WHO 4 v2.0.0 (p. 13, 16, 19, 63) lists *4*0/1*Z## as the zone's operation-mode (season) frame, and bt_termo emits it next to the dimension 12 setpoint report (512 of the 534 dimension-12 reports in the emitted corpus carry one). The firmware forwards mode digits 1/2/3 as three different bus codes. Checked live on a MyHomeServer1 (3.87.13) plant on 2026-10-07: *#4*60*#14*0230*3## (digit 3, the zone's current setpoint) was ACKed and the zone kept reporting *4*1*60## with its setpoint and actuators unchanged, so the generic digit is accepted and leaves the season alone, which is the behaviour this change wants; BTicino's own client writes 3 as well.

Validation data: firmware_validation_climate.zip (17 KB): the report and the WHO 4, energy and sound replays this change relies on; the full set with raw logs and harness is linked from OpenWebNet-HA/OWNd#87 to #94. The firmware images themselves are not included.

How: custom_components/myhome/climate.py (a MESSAGE_TYPE_SEASON branch, no WHAT read; the central-unit write keeps digits 1/2 because OWNd's set_central_temperature has no generic digit), the six assertions in tests/test_component_climate.py that encoded digit 1/2 for zone writes (now 3), and tests/test_climate_season_frame.py (skipped on the pinned OWNd). Works with the pinned OWNd 2.0.0b10 (falls back to _what). climate.py compiles; the Home Assistant test suite was not run locally.

  • V2 Architecture: no new entities or subscriptions.

🤖 Generated with Claude Code
https://claude.ai/code/session_018oHp1P4vxxoFnaLUUZpbpZ

…t mode

- *4*0*Z## / *4*1*Z## is the zone's season (Legrand WHO 4 p. 13/16/19/63);
  bt_termo emits it with every dimension 12 report, so an AUTO zone no
  longer flips to HEAT/COOL on it.
- Setpoint writes use mode digit 3 (generic), as BTicino's own client does;
  the firmware forwards 1/2/3 as three different bus codes.

Evidence: OpenWebNet-HA/OWNd docs/firmware_validation (emulator replays on
MyHomeServer1, F454, F459, F461, F450).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

Thanks for the firmware work. CI is red on this PR, and I think the approach should change before it lands.

Why CI fails

  • Five tests in test_component_climate.py still expect mode digit 1 or 2 on setpoint writes. They now get 3.
  • test_p4_central_unit_3550... gets *#4*#0*#14*0215*2##. OWNd's set_central_temperature only knows heat (1) and everything else (2), so CLIMATE_MODE_AUTO writes a conditioning setpoint to the central unit, the opposite of the intent.
  • The new "season only, stay AUTO" branch has no test.

Why I'd rewrite rather than patch

  1. Digit 3 is only verified for zones. In the oracle, F454 and the other images ACK *#4*#23#1*#14*T*1/2/3##. No central-unit (#0) setpoint write exists in any result, so we shouldn't change the central path on that basis.
  2. OWNMessage.what (OWNd#94) is the wrong abstraction. It exposes the raw _what for every WHO, but 1 means ON for WHO 1 and "heating season" for WHO 4. A bare integer invites exactly the misreading this PR tries to fix. A WHO 4-specific field on OWNHeatingEvent (for example season, separate from mode) would be safer, and it removes the getattr(message, "what", getattr(message, "_what")) fallback here.
  3. The AUTO check depends on prior state. *4*1*Z## is ignored only if the zone is already AUTO. The event itself should say what it is, and OWNHeatingEvent.mode should stop reporting season frames as a mode change in the first place.
  4. The OWNd side is eight stacked PRs (F422 BUS-BUS Interface #87 to Message Sending not working #94). We now have the firmware oracle and the encyclopedia in-tree. I'd rather regenerate these fixes against oracle results, with each behaviour pinned by a conformance test, than merge a split-out series.

I'll prepare a replacement for this change, and OWNd#94, that keeps your findings and adds the tests. Happy to credit you as co-author.

🤖 Generated with Claude Code

…r zones

OWNd's set_central_temperature only knows digits 1 and 2, so the central
unit write keeps the season-specific digit; zone writes use 3. The climate
tests that asserted digit 1/2 for zone writes now expect 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck check also here https://github.com/OpenWebNet-HA/own-firmware-oracle/blob/main/findings/MyHomeServer1/thermo-ts10.md

the firmware oracle tests among all major gateways of which we have firmware and I am also now adding devices with firmware like e.g. L4561N and the 8 inch color screens to have an up to date picture of gateway and device behaviour accros the entire plant (device and gateway) More than happy to add also the thermo device as well. Please let me know which device you are using

@gdluck

gdluck commented Oct 7, 2026

Copy link
Copy Markdown
Author

Read it. That document is an independent replay of the same TS10 frames on the same MyHomeServer1 firmware (028206) with a different harness, and it agrees with mine byte for byte everywhere the two overlap: compound zone commands (*4*311*#23#1## → D1 01 03 02 C2 17 00 00, off …06 00, protection …07 00), the compound setpoint (…C2 17 02 32 for 25.0 °C, mine …02 2C for 22.0 °C), fan speed on the plain zone (0B 0S), programs and scenarios (zero-based index in byte 7), weekend C1 04, holiday C1 05 DD PP, the three calendar dimensions, and the timed manual C1 09 TT HH. It also confirms my section 5 result that F454's bt_termo emits identical telegrams.

What it adds that I had not replayed:

  • 112#… and 212#… (season-specific timed manual) are accepted.
  • Holiday without a day count (*4*33#3101*#0##) is refused; the 33DDD form is mandatory. OWNd's set_central_holiday already requires days 1..255, so no change.
  • MH200N refuses every WHO 4 command over OpenWebNet unless it is programmed as a scenario on the gateway. That matters for MyHOME users on an MH200N: the climate entity cannot drive zones there, which the PR bodies do not mention.

Nothing in it contradicts the nine PRs or asks for a code change. The compound-address and plain-zone fan-coil rules it verifies are the PR 82/83 work already merged in OWNd; my PRs build on that. I'd suggest replying to the commenter that the results match and asking whether their oracle can also run the WHO 0 / 25 / 18 lists from firmware_validation.zip, since their MH200N image is one I do not have.

@gdluck
gdluck force-pushed the fix/firmware-validated-climate branch from 8dc5e8d to eeb9a49 Compare October 7, 2026 17:33
OpenWebNet-HA/OWNd#94 decodes *4*1*Z## / *4*0*Z## as hvac_season with a
season attribute. A zone that is AUTO, HEAT or COOL keeps its mode on it;
a zone that was OFF or unknown takes the season's mode; a central unit
shows the season as its mode. No WHAT is read anywhere. With the pinned
OWNd 2.0.0b10 the frame still arrives as hvac_mode and takes the old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck, do you happen to have a physical trace from a bus monitor? That would be incredibly helpful. As we saw with the venetian covers, one of the main takeaways from this project is that official documentation doesn't always reflect reality, and the firmware only tells half the story. Having an actual physical trace would give me much more confidence than relying on the firmware alone.

@gdluck

gdluck commented Oct 7, 2026

Copy link
Copy Markdown
Author

Live frame test on the real MyHomeServer1 (192.168.0.218), 2026-10-07 20:06-20:13 UTC

User-authorised. 37 frames were sent on a COMMAND session from the minsky box (own_live_test.py, 5-12 s apart)
while the gateway's MONITOR session was captured (live_test_monitor_2026-10-07.log, 107 frames, handshake lines
removed). Targets: thermostat zone 60 (heating, manual 23.0 °C before the test), energy meter 51, light 43 (a relay).
Zone 60 was put back to manual 23.0 °C heating at the end and confirmed (*#4*60*12*0230*3##, *4*1*60##).
The gateway password was used only to connect and is in no attached file.

The maintainer's doubt (MyHOME#649 comment): "the firmware only tells half the story", a physical trace would be
more convincing. This test is not a bus-byte capture (the scsgate on minsky still hears nothing), but it is the
real gateway on the real plant, which is the half the emulator could not show.

1. Live versus emulator, frame by frame

Reply column: ACK = *#*1##, NACK = *#*0##. "Report" = what the monitor showed within 3 s. Emulator = the
mhs1 bt_termo / bt_supervisione / bt_luci replay of the same frame on test addresses (zone 23, meter 51, light 11).

Frame sent Live reply Live report on the monitor Emulator Match
*#4*60## ACK, 5 frames dim 0, dim 12 0230*3, *4*1*60##, dim 14, dim 13 NACK, no bus frame differs, see 2.1
*#4*60*11## NACK – request sent, no device = (no fan coil on 60)
*#4*60*19## ACK *#4*60*19*5*0## – request sent =
*#4*60*20## ACK *#4*60#1*20*0## – request sent =
*#4*#60## NACK – not replayed plant has no central unit
*#4*60*15##, 15#1 NACK – request sent, no device = (no slave probe)
*#4*60*14##, 13## ACK with value – request sent =
*#4*60*#14*0230*3## ACK dim 12 0230*3 + *4*1*60## (season unchanged) ACK, 92 17 30 2C =
*#4*60*#14*0230*2## ACK *4*0*60## + dim 12 (season → conditioning) ACK, 91 17 30 2C =
*#4*60*#14*0230*1## ACK *4*1*60## + dim 12 (season → heating), actuator 60#2*20*5 ACK, 90 17 30 2C =
*#4*60*#11*2##, #11*0 ACK nothing (no fan coil) ACK =
*4*303*60## ACK *4*303*60## + dim 12 ACK, 93 17 30 0F =
*4*311*60## NACK – NACK, nothing =
*4*311*#60## ACK nothing, zone unchanged ACK, D1 00 03 02 C2 17 00 00 = on acceptance, see 2.2
*4*102*60## ACK *4*102*60## + dim 12 0070*3 (antifreeze, 7.0 °C) ACK =
*4*303*#60## ACK nothing ACK = on acceptance, see 2.2
*4*311*#60## (again) ACK nothing; *#4*60## 2 min later still antifreeze ACK see 2.2
*#18*51*51##, 54, 53, 113 ACK – ACK / "silent" (waiting for the bus) =
*#18*51*#1200#1*255## ACK – ACK =
*#18*51*1200## NACK – NACK, nothing =
*18*510#9*51##, *#18*51*513#9##, *18*59#9*51##, *#18*51*516## ACK – ACK / silent =
*#1*43##, *#1*43*1## ACK *1*0*43## – request sent =
*1*11*43## ACK *1*1*43## (light on; switched off by me 6 s later) ACK, 11 00 12 16 =
*#1*43*#2*0*0*5## ACK *1*1*43## at +0.1 s, *1*0*43## at +5.2 s ACK, D1 11 01 42 06 … = (5-s timer ran)
*#1*43*2## ACK *#1*43*2*0*0*0## – request sent =
*1*2*43## ACK nothing (relay, not a dimmer) ACK, 11 00 12 1D =

24 of the 28 frames that have an emulator row match it exactly; 3 "silent" emulator rows are ACK live because a
device answered (the emulator's bus had none); 1 differs (2.1). No live result contradicts any of OWNd #87-#94 or
MyHOME #649.

2. What the live plant adds

2.1 *#4*Z## is answered by the gateway itself

The emulated bt_termo produced no bus frame for the bare zone status request; the real gateway answers it in
~1 s with the five-frame list (dim 0, dim 12, season *4*1*Z##, dim 14, dim 13), exactly the Legrand p. 19 list.
Deduction: the answer comes from the gateway's own state, not from a bus query. This corrects the note in
REPORT.md section 4 ("bare zone status produces no bus frame … MyHOME polls with a frame that never reaches the
bus"): MyHOME's poll status(where) works on the real gateway.

2.2 #Z commands are accepted but do nothing on a plant without a central unit

*4*311*#60## and *4*303*#60## are ACKed, yet the zone never reports and its state does not change (it stayed in
antifreeze after 102 until I wrote the setpoint again). *#4*#0## and *#4*#60## are NACK: this plant has no
central unit, and #Z addressing targets the central. The plain-zone forms (303, 102, #14, #11) are the
ones that act here. Consequence for MyHOME: on such a plant HVACMode.AUTO (OWNd sends *4*311*#Z##) is
accepted by the gateway and has no effect; the firmware audit's A3 finding ("311 only as #Z") is about
acceptance, not effect. Not a defect in the PRs, but worth a note in MyHOME's docs.

2.3 Season frame and setpoint digits (confirms #94 and #649)

Every setpoint report carries the season frame right next to dimension 12; digit 3 leaves the season as it is,
digit 2 switches the zone to conditioning (*4*0*60##) and digit 1 back to heating. These three writes were mine.

2.4 Variable timer and fixed timer on a relay (confirms the WHO 1 builders in #93)

*#1*43*#2*0*0*5## turned the light on and the gateway reported it off 5.2 s later; *1*11*43## turned it on
(1-minute timer, cut short by my off). *#1*43*2## reads back 0*0*0 once the timer has run.

2.5 Energy requests

All the forms the PRs use are accepted on the real meter 51, including *18*510#9*51## and *#18*51*513#9##;
*#18*51*1200## is refused, as the emulator said (and as #92's body says). The data frames of the graph requests
were not captured in the 11-s monitor windows; acceptance only.

3. Still not done

A physical SCS byte trace: the scsgate attached to minsky (/dev/ttyACM0, scsmonitor 1.05, same settings as the
September capture) reported no bus frame in three sessions today while the gateway reported hundreds. That is a
wiring or hardware state on the minsky side.

Attached logs

live_test_monitor_2026-10-07.log

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck, thank you for capturing this! Having an authentic live trace from a physical MyHomeServer1 on an actual plant is invaluable. The side-by-side comparison with the emulator is fantastic work—especially confirming that *#4*Z## is answered directly from the gateway's cached state, verifying the setpoint digit 3 behavior, and capturing the 5.2 s relay timer.

In MyHOME, we treat physical traces as immutable facts and preserve them in-tree with their own test fixtures. To land this cleanly and keep each step reviewable, here is the roadmap I propose:

  1. Dedicated MyHOME Trace PR (Ground Truth):
    Let's check in live_test_monitor_2026-10-07.log under tests/fixtures/traces/ along with a README.md (documenting the hardware profile and findings) and a deterministic replay test. Having the raw physical plant capture committed in-tree locks in this empirical ground truth immediately and permanently, independent of any library releases—exactly like we did in #648 for the MH200/F422 capture and #646 for the F454 step trace. (Would you like to open this PR so you get full commit attribution, or should I stage it?)

  2. Consolidated WHO 4 Heating PR in OWNd:
    Instead of managing 8 separate stacked PRs across different subsystems, let's group the heating work (First installation issue #89, First Setup issue #90, Message Sending not working #94) into a single, cohesive WHO 4 PR in OWNd. That keeps the diff domain-focused, validates directly against the trace and oracle, and lets us cut a clean OWNd release for climate.

  3. Finalize MyHOME Climate (fix(climate): keep AUTO zones on season frames, write generic setpoint mode #649):
    Once the OWNd heating release is cut and the trace PR is in, we update fix(climate): keep AUTO zones on season frames, write generic setpoint mode #649 to bump the OWNd pin and ensure all climate tests pass against the new trace replay fixture.

  4. Remaining Domains (Lighting & Energy):
    We follow up with dedicated domain PRs for WHO 1 (MyHome: Failed to set up #93) and WHO 18 (Event listener crashes #91, Using thermostat ""4691 to control conditioning system #92) afterwards.

How does that workflow sound to you?

@gdluck

gdluck commented Oct 7, 2026 •

Copy link
Copy Markdown
Author

Thanks, the roadmap sounds right to me, and I'd rather you stage the restructuring since you know how you want the tree and the releases laid out. Please go ahead and refactor the PRs the way you propose.

  1. Trace PR in OpenWebNet-HA/MyHOME: please stage it. Take live_test_monitor_2026-10-07.log as attached above, together with the comparison table from my comment for the README (hardware profile: MyHomeServer1 3.87.13, thermostat zone 60 heating with no central unit, F520 meter 51, relay actuator 43). I'm happy with attribution going to the trace itself; if you prefer me as author on the commit, say so and I'll open it instead.

  2. Consolidated WHO 4 PR in OpenWebNet-HA/OWNd: agreed. One correction to the grouping: the heating fixes are fix(heating): read the 115#/215#/315# parameter as a program, not a temperature OWNd#87, fix(heating): 13xxx and 23xxx are holiday days in heating / conditioning mode OWNd#88, fix(heating): name the central unit status WHATs 22, 23, 24, 30 and 31 OWNd#89 and fix(heating): decode WHAT 1 / 0 as the season, not an operating mode OWNd#94. fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states OWNd#90 is WHO 0 (scenario-module programming and lock states) and has nothing to do with climate, so it should stay with the non-heating set. Feel free to squash those four into one branch; everything in them is backed by the firmware replays in firmware_validation.zip and by today's live run.

  3. fix(climate): keep AUTO zones on season frames, write generic setpoint mode #649: I'll leave it open and rebase it onto the OWNd climate release once you cut it, bumping the pin and running the climate tests against the new trace fixture.

  4. Remaining domains in OWNd: fine to follow with one PR each for lighting (feat(lighting): builders for presets, step, fixed timers and variable timer OWNd#93), energy (feat(cen): parse and build WHO 25 scenario-plus frames (WHAT 11-15) OWNd#91, fix(energy): parse the WHO 18 dimension 1200 automatic-update reply OWNd#92) and scenario/CEN (fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states OWNd#90). Tell me if you'd rather I open those consolidated branches myself.

I'll close the single-fix OWNd PRs as soon as the consolidated branches that replace them are up, so nothing is lost in between.

GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 7, 2026
…es, and raw log parity (OpenWebNet-HA#649)

- Accept live_test_monitor_2026-10-07.log as authentic ground truth
- Clarify JSON archive as derived fixture under proposed OWNd season decoding
- Mark 37-command comparison table and F520 frames as comment evidence
- Document unmentioned zone 39, zone 50, and dimensions 40/48/59 traffic
- Clarify actuator 2 vs fan speed semantics on zone 60
- Add test_myhomeserver1_live_trace_log_to_json_raw_equality for 1:1 parity
GreenGrassBlueOcean added a commit that referenced this pull request Oct 7, 2026
…lay test (#649)

Captured from a physical BTicino MyHomeServer1 gateway (FW 3.87.13)
by @gdluck on 2026-10-07 across thermostat zone 60, energy meter 51,
and lighting relay actuator 43.

Co-authored-by: gdluck <9041273+gdluck@users.noreply.github.com>
GreenGrassBlueOcean added a commit that referenced this pull request Oct 7, 2026
…eserver1-live-trace

test(trace): add authentic MyHomeServer1 physical plant trace and replay test (#649)
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

CI Failure & Architectural Audit Summary

The CI failure on commit 6ba824d is caused by a Ruff static analysis violation I001 [*] Import block is un-sorted or un-formatted in tests/test_climate_season_frame.py:6:1:

from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from homeassistant.components.climate.const import HVACMode
import OWNd.message as own_message  # <--- Unsorted import
from OWNd.message import OWNEvent

This triggers an immediate failure in scripts/verify_ha_standards.py (checked by Audit quality_scale.yaml) and tests/test_ha_standards.py::test_ruff_static_analysis_standards (checked by Run Pytest Suite & Coverage).

Additionally, our code audit identified key protocol and compatibility items:

  1. Wrong Setpoint Mode in async_set_hvac_mode:
    In PR fix(climate): keep AUTO zones on season frames, write generic setpoint mode #649, setting HVACMode.HEAT or HVACMode.COOL was changed to send mode=SETPOINT_WRITE_MODE (digit 3). As confirmed by physical bus captures and Legrand WHO 4 specs, digit 3 instructs the gateway to leave the active season unchanged. Switching mode explicitly to HEAT or COOL must send mode 1 (CLIMATE_MODE_HEAT) or mode 2 (CLIMATE_MODE_COOL). Generic digit 3 belongs strictly in async_set_temperature() to prevent accidental season flips during temperature setpoint adjustments.
  2. Dual Compatibility with Released OWNd 2.0.0b10 and Dev OWNd:
    tests/test_heating.py directly asserted msg.message_type == MESSAGE_TYPE_MODE on *4*1*1##, which fails under OWNd branches that decode season frames. Dual compatibility (hvac_season vs hvac_mode) ensures clean test runs across both environments.
  3. Holiday Mode Assertions:
    tests/test_message_edges.py needed dual compatibility (CLIMATE_MODE_AUTO, CLIMATE_MODE_HEAT/COOL) for 13xxx/23xxx frames.

All fixes, rebased cleanly on v2-phase1-architecture (with authentic plant trace fixture #655 merged), have been verified and consolidated with co-authorship in #657.

GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 8, 2026
…penWebNet-HA#649)

- Decode WHO 4 *4*1*Z## and *4*0*Z## season frames without altering running zone HVAC mode
- Write generic mode 3 in async_set_temperature to preserve active plant season
- Retain explicit mode 1 (heat) and 2 (cool) in async_set_hvac_mode
- Maintain dual backward compatibility with released OWNd 2.0.0b10 and dev OWNd
- Ensure 100% test coverage across both pinned OWNd 2.0.0b10 and newer OWNd releases

Co-authored-by: gdluck <9041273+gdluck@users.noreply.github.com>
GreenGrassBlueOcean added a commit that referenced this pull request Oct 8, 2026
…657)

Supercedes and resolves #649 and #650.

Co-authored-by: gdluck <9041273+gdluck@users.noreply.github.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

Closed in favor of #657, which has been merged into v2-phase1-architecture with all 16 CI checks passing and co-authorship preserved. Thank you @gdluck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants