generate_ha: a command GA the actuator reports on is its own state address

Found on a real 1312-GA house (43 of 159 "switch without status" had a Read+Transmit
object on the command GA), measured on all seven corpus projects.

detect_missing_status already counted these as satisfied through pairing.self_reporting();
the generator did not and put them on the review list without a state_address. It now
uses the same function: no separate status found and the command GA has an R+T object
-> state_address = the command GA. Same for the on/off sibling of a dimmable light and
for brightness_state_address on a self-reporting brightness GA. Write-only objects still
go to review.

Corpus: no entity count changed, no address lost; review items 598 -> 555 on the house,
-36, -40, -12, -1, -32 on the others. Test C in test_real_house_fixes.py covers R+T,
write-only and a non-lighting switch.

Not verified on a live Home Assistant: state_address equal to address is accepted by the
KNX schema and xknx reads from it, but no running instance was used here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nikolay Miroshnichenko
2026-09-16 18:34:40 +02:00
co-authored by Claude Opus 5
parent 3968ce2cde
commit 76249469bb
4 changed files with 53 additions and 7 deletions
+30
View File
@@ -76,4 +76,34 @@ assert "1/0/1" in lightsB and lightsB["1/0/1"].get("state_address") == "1/1/1",
assert not any(s["address"] == "1/0/1" for s in knxB.get("switch", [])), knxB
print("OK: B — on/off lighting is a light with address + state_address, not a switch")
# ---------------------------------------------------------------- C. self-reporting status
def _proj_with_cos(rows, flags_by_addr):
gas, cos = {}, {}
for a, n, m, s in rows:
g = _ga(a, n, m, s)
if a in flags_by_addr:
cid = f"CO-{a}"
g["communication_object_ids"] = [cid]
cos[cid] = {"flags": flags_by_addr[a], "group_address_links": [a], "dpts": [], "text": "", "function_text": ""}
gas[a] = g
return build_loaded_from_raw({"group_addresses": gas, "communication_objects": cos,
"info": {"group_address_style": "ThreeLevel"}}, "mem")
pC = _proj_with_cos(
[("1/0/5", "1.02 Hall - Ceiling light - on/off", 1, 1),
("1/0/6", "1.03 Living - Floor lamp light - on/off", 1, 1),
("5/0/5", "Workshop socket - on/off", 1, 1)],
{"1/0/5": {"write": True, "read": True, "transmit": True},
"1/0/6": {"write": True, "read": False, "transmit": False},
"5/0/5": {"write": True, "read": True, "transmit": True}})
resC = generate_ha_yaml(pC)
knxC = yaml.safe_load(resC["yaml"].split("\n\n", 1)[1])["knx"]
ents = {e["address"]: e for plat in ("light", "switch") for e in knxC.get(plat, [])}
assert ents["1/0/5"].get("state_address") == "1/0/5", ents
assert ents["5/0/5"].get("state_address") == "5/0/5", ents
assert "state_address" not in ents["1/0/6"], ents
no_status = {r["address"] for r in resC["review"] if r["reason"] in ("light_without_status", "switch_without_status")}
assert no_status == {"1/0/6"}, no_status
print("OK: C — Read+Transmit object on the command GA is its own state; write-only stays in review")
print("\nALL REAL-HOUSE REGRESSION TESTS PASSED")