Skip to content

fix(alarm): start unknown, restore the last state and poll *#5*0## unless the OWNd profile refuses it - #803

Closed
gdluck wants to merge 6 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/alarm-unknown-restore
Closed

gdluck wants to merge 6 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/alarm-unknown-restore

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown

Requested by A

What is the problem

The burglar alarm panel is created as disarmed (_attr_alarm_state = STATE_DISARMED, raw_state: "disarmed") before any frame from the central unit has been seen, and it overrides async_added_to_hass without calling super(), so the RestoreEntity path of MyHOMEEntity never runs. After every restart the panel claims "disarmed" for an armed system until the next arm/disarm frame. No open or closed issue describes this; the motivation is the live F461 observation of the panel's default state while reviewing the entity. On top of that async_update polls *#5*0## through the entity's own gateway even when a shared-bus secondary or a warm standby is the one that carries WHO 5.

Related: #103, #151 (the WHO 5 panel requests this entity answers), #311, #619, #629 (physical F454 / F453AV captures with a central unit), #381 (profile-flag pattern), #564 / #576 (shared-bus delegation), #609 (armed_home removed), #766 (how a nack row is read on the F460 / F461 / MyHomeServer1 class), #799 (merged: the live F461 / MyHomeServer1 traces).


What is the solution

  1. alarm_control_panel.py:
    • The panel starts with alarm_state = None (HA shows unknown) and raw_state: None; a state only comes from a frame that names one (*5*2*0## / *5*9*0## / *5*0*0## disarmed, *5*8*0## armed, an alarm WHAT → triggered) or from the restored state.
    • The async_added_to_hass override is removed, so the base-class restore and deferred poll run. async_restore_last_state restores only disarmed, armed_away or triggered with the attribute restored: true (plus the stored raw_state / state_code); a stale armed_home (removed in feat(alarm): read-only alarm panel; arm and disarm through AUX frames (#564) #609), arming, pending, unknown or unavailable is not restored. restored is cleared only by a frame that sets the state; while it is set, activation (*5*1*0##), zone, battery and mains frames leave raw_state / state_code untouched, so a mismatched pair is never written or persisted.
    • async_update resolves the gateway that send_status_request would route through (_delegated_target(cmd), else _failover_target(cmd), else the entity's gateway, as MyHOMEExposeManager._effective_profile does) and sends through that target, so the route is resolved once. The poll is skipped (DEBUG, naming the frame actually skipped and the target's canonical profile.model_name) only when that target's OWNd profile states supports_alarm_status_request = False; None or no flag polls. There is no model table: see the oracle section for why. The flag belongs in OWNd (fix(gateway): close idle command sessions before the gateway drops them #381 pattern) and is proposed, not opened.
  2. docs/configuration/alarm.md: the unknown / restored behaviour, which frames confirm a restored state, the poll policy with the physical F454 / F453AV evidence, and the template-alarm availability comment (with the optional and not state_attr('alarm_control_panel.alarm_0', 'restored') clause).
  3. tests/test_alarm_restore_unknown.py (new, 46 cases) and the two updated assertions in tests/test_component_alarm.py.
  4. .github/release-notes/2.0.0b16.md: one line under "Other behaviour changes".

Companion PR: none in OWNd yet; the supports_alarm_status_request profile flag this PR reads is proposed, not opened (#381 pattern).


Review follow-up (review 5639821916)

  1. F454 in the refused table (blocking): gone, and the table with it. The physical tests/fixtures/plants/issue_311_f454/diagnostic_summary.json (firmware 2.0, lines 58–137: *#5*#1## → *5*11*#1##, startup *5*1*##, *5*5*##, *5*7*##, *5*9*##), the Central unit (WHERE #0) is marked "unresponsive" after two restarts, although it answers a plain status request #629 F454 1.0.34 sweep (*#5*0## → *5*0*##, *5*9*##, *5*5*##, *5*7*##, *5*11*#1..8##) and the Investigate lighting WHAT 19 status frames from BTicino F414 modular dimmers #619 F453AV 3.0.14 sweep (*#5*0## → *5*1*##, *5*9*##, *5*5*##, *5*7*##, zones) all answer on the bus, so F453AV had to go too. Checked tests/fixtures for the other listed models: there is no alarm capture for F450, F459 or F460, and the only F461 (2.0.11) and MyHomeServer1 (3.87.13) WHO 5 captures are the test(trace): ingest live F461 2.0.11 2026-10-23 capture and MyHomeServer1 3.87.13 reads behind OWNd #136-#142 and MyHOME #791-#798 #799 NACKs on plants without a central unit. A second reviewer's point was adopted: an emulated nack / silent row is not a model-capability fact, and a NACK on a plant without a central unit proves only that this plant NACKed; so the skip is now flag-only (explicit False on the OWNd profile), None polls, and silence is never converted into refusal. A per-plant cache of a real live NACK was considered and not implemented: the command session does not report a NACK back to the entity (the future resolves to the write time), so it needs gateway-session plumbing first. docs/configuration/alarm.md line 16 and the edited section agree now; the release note names no gateway as refusing.
  2. Delegation / failover / aliases in _status_request_refused: the effective target is resolved like send_status_request (delegated secondary, warm standby, own gateway) and the request is sent through it; the decision reads that target's profile; the log names cmd (*#5*#1## for a zone entry) and the target's canonical profile.model_name (MHS1 → MyHomeServer1, 573992 → F453AV), the configured name only for a Generic profile. Tests: MyHomeServer1 primary (profile refusing) + MH202 secondary carrying WHO 5 → the secondary is asked; the reverse → nothing sent; failover both ways; an explicit True polls whatever the configured alias, SKU, case or spacing; the log label for a short alias, a SKU and a Generic profile; a route result that is not a gateway handler falls back to the entity's gateway.
  3. State polish: raw_state / state_code are kept while restored is set and a non-verdict frame arrives (activation, *5*11*#1##, *5*18*#2##, battery, mains); restoration is restricted to {disarmed, armed_away, triggered}.
  4. Template availability comment: rewritten; the optional restored clause is given.
  5. BTICINO ALARM F4201 #177: removed from the title, body, commit messages (branch history rewritten), release note, code comment and tests. No issue describes the disarmed-after-restart behaviour (gh issue list --search "alarm disarmed restart" returns Review follow-up for #609: how each alarm review comment was handled #615 / feat(alarm): read-only alarm panel; arm and disarm through AUX frames (#564) #609 review threads and unrelated restart issues); the body says so. #799 is cited as merged, and the Skipping WHO=5 discovery: not supported by F461 profile line is attributed to _discovery_frames() + OWNd DEFAULT_SUPPORTED_WHO already on the base.

State Diagram

stateDiagram-v2
    direction TB
    state "1. Panel state" as Panel {
        [*] --> Unknown : entity created , before - disarmed from creation
        Unknown --> Restored : RestoreEntity, last disarmed / armed_away / triggered, attribute restored true
        Unknown --> Unknown : stored armed_home, arming, pending, unknown or unavailable - not restored
        Unknown --> Disarmed : *5*2*0## , *5*9*0## , *5*0*0##
        Unknown --> ArmedAway : *5*8*0##
        Unknown --> Triggered : alarm WHAT , is_alarm
        Restored --> Disarmed : *5*2*0## , *5*9*## , restored cleared, raw_state updated
        Restored --> ArmedAway : *5*8*0## , restored cleared
        Restored --> Triggered : alarm WHAT , restored cleared
        Restored --> Restored : *5*1*0## activation, zone, battery or mains frame - state and raw_state untouched
        Restored --> Restored : *#5*0## NACKed by the gateway - nothing reaches the panel
        Disarmed --> ArmedAway : *5*8*0##
        ArmedAway --> Disarmed : *5*2*0##
        ArmedAway --> Triggered : alarm WHAT
    }
    state "2. async_update poll" as Poll {
        [*] --> Target
        Target --> Decide : delegated secondary for WHO 5, else warm standby while the primary is down, else own gateway
        Decide --> Sent : profile.supports_alarm_status_request True or None, or no flag - *#5*WHERE## through the target
        Decide --> Skipped : profile.supports_alarm_status_request False - DEBUG with the frame and the target's canonical model
    }
Loading

Hardware Verification

  • Physical F454 2.0 with a BTicino central unit (Need help configuring Alarm feature (F454) #311 fixture, 2026-09-15): *#5*#1## answered by *5*11*#1## 61 ms later; the central unit reports *5*1*##, *5*5*##, *5*7*##, *5*9*##, zones and *5*15*#3## at startup, and *5*1*0## → *5*8*0## / *5*2*0## → *5*9*0## on arm / disarm.
  • Physical F454 1.0.34 (Central unit (WHERE #0) is marked "unresponsive" after two restarts, although it answers a plain status request #629 sweep, 2026-10-05) and F453AV 3.0.14 (Investigate lighting WHAT 19 status frames from BTicino F414 modular dimmers #619 sweep, 2026-10-04): *#5*0## answered with *5*0*## / *5*1*##, *5*9*##, *5*5*##, *5*7*## and the zone frames.
  • Physical MyHomeServer1 3.87.13 (*#13**16*3*87*13##, WHO 13 code 200, WHO 1013 model 67; read-only probe on 2026-10-24, no command sent to that plant): *#5*0## NACKed in 5 ms. No WHO 5 plant behind it.
  • Physical F461 2.0.11 (2026-10-23): *#5*0## and *#5*#1## NACKed in 3 ms. Per test(oracle): read empty-bus NACKs on F460/F461/MyHomeServer1 by verdict #766 a nack on the F460 / F461 / MyHomeServer1 class is the gateway's own refusal, not a bus reply; this plant has no central unit, so it proves that this plant NACKed, not what an F461 with a central unit does.
  • Live retest 2026-10-24 (report-15 consensus label entity path not exercised): no alarm panel is configured on that plant, so the unknown / restore path was not observable. Four startups sent no *#5*0##; that absence belongs to the startup gate of _discovery_frames() + OWNd DEFAULT_SUPPORTED_WHO already on v2-phase1-architecture (Skipping WHO=5 discovery: not supported by F461 profile), not to this PR.

How is it tested

  • tests/test_alarm_restore_unknown.py (new, 46 cases) and the two updated assertions in tests/test_component_alarm.py.
  • Fail-before / pass-after on the original source (alarm_control_panel.py from the base): 40 fail (38 in the new file, the 2 updated assertions), 17 pass; all pass after. Per review item, on the commit before each: item 1 (flag-only poll + replay tests) 13 fail, item 2 (target / label) 6 fail, item 3 (restore / raw_state guard) 9 fail.
  • Full suite against plain OWNd master (f04d8aa, the behaviour of the 2.0.0b11 pin), rebased tree on v2-phase1-architecture 31ea9fd: 4554 passed, 1 skipped (the base merge 31ea9fd alone is not re-run here; the previous base d8bac15 was 4418 passed, 1 skipped).
  • scripts/verify_ownd_coverage.py 100 % on all 73 modules, scripts/typing_ratchet.py 0 errors (baseline 0), ruff check ., scripts/verify_ha_standards.py, scripts/sync_documentation.py --check, scripts/manage_translations.py check, tests/test_fixture_privacy.py all pass.

Live retest (2026-10-24)

Consensus label: entity path not exercised (no alarm panel on the plant; the absence of *#5*0## at startup belongs to the base's discovery gate).


Where are the traces

Subsystem Capture Plant Frames verified by this PR
WHO 5 status request answered by a physical F454 tests/fixtures/plants/issue_311_f454/diagnostic_summary.json (#311) F454 2.0, BTicino central unit *#5*#1## → *5*11*#1##; startup *5*1*##, *5*5*##, *5*7*##, *5*9*##; *5*8*0##, *5*2*0##, *5*9*0## (replayed: *5*9*## confirms the restored state)
Same, second F454 tests/fixtures/traces/issue_629/myhome_sweep_F454_all_2026-10-05T10-51-55.json F454 1.0.34 *#5*0## → *5*0*##, *5*9*##, *5*5*##, *5*7*##, *5*11*#1..8## (replayed)
Same, F453AV tests/fixtures/traces/issue_619/myhome_sweep_F453AV_all_2026-10-04T19-45-11.json F453AV 3.0.14 *#5*0## → *5*1*##, *5*9*##, *5*5*##, *5*7*##, *5*18*#n##, *5*11*#2## (replayed)
WHO 5 status request refused by the gateway tests/fixtures/traces/f461_live_2026-10-23/ run run1_oracle_replay_readonly, step oracle_who5 (#799, merged) F461 2.0.11, no central unit *#5*0##, *#5*#1## → NACK after 3 ms (note: who5_refused_locally); replayed: nothing reaches the panel, the restored state is left alone
Same, second gateway family tests/fixtures/traces/myhomeserver1_live_2026-10-24/ run run2_reads_thermo_sound_alarm_lights, step read_who5 (#799, merged) MyHomeServer1 3.87.13, no WHO 5 *#5*0## → NACK after 5 ms; replayed as above
WHO 5 status request answered tests/fixtures/traces/issue_466/ (upstream, MH200N) MH200N the empty-WHERE reply set *5*0*##, *5*9*##, *5*5*##, *5*7*##, *5*11*#N##, the same frames the oracle ack / out rows show
Restore / unknown state handling No physical trace applies: state handling, no new frame; the unit tests replay *5*1*0##, zone, battery, mains and verdict frames through OWNEvent.parse — —

What does the Firmware Oracle / Encyclopedia say

Published rows (results/<P>/<V>/oracle/full/trace-corpus.tsv, index 53f9514), *#5*0##:

Gateway / firmware Verdict Reply
F450 020010, F453AV 030014, F454 020051, F459 020105, F460 020012, F461 020011, MyHomeServer1 028206 nack / silent –
F453 010100, H4684 020054, MH200 020102, MH200N 010108 ack / out *5*0*##, *5*9*##, *5*5*##, *5*7*##, *5*11*#1## … *5*11*#8##
MH202 010024 ack / out two bus frames

These rows are documented as compatibility, not used as a gate. The emulations are factory-blank images without a central unit; tools/expected_divergences.yaml already records the F454 case (020051 vs field 1.0.34 / 2.0 / 2.0.48, *#5*0## and *#5*#1## under silent_bus_vs_populated_trace), and the physical F454 and F453AV captures above answer the request their emulations NACK. The live F461 2.0.11 and MyHomeServer1 3.87.13 NACKs agree with their emulated rows but were taken on plants without a central unit, so they do not say what a plant with one does. Whether the request is answered cannot be inferred from the gateway model alone; the gate is therefore the OWNd profile flag only, and the oracle rows stay here as the record of what the emulations do.


Checklist

🤖 Generated with Claude Code

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@gdluck
gdluck marked this pull request as draft October 24, 2026 13:39
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
MyHomeServer1 README: "OWnd" typo; the alarm gate, the config-flow
identification and the liveness probe rows name MyHOME OpenWebNet-HA#803, OpenWebNet-HA#804, OpenWebNet-HA#806.
F461 README: the "What this capture backs" table extends to MyHOME OpenWebNet-HA#800-OpenWebNet-HA#806
(OpenWebNet-HA#802 the dimension 27 wire form, OpenWebNet-HA#803 *michnovka#5*0##, OpenWebNet-HA#804 the WHO 13 / 1013
identification replies, OpenWebNet-HA#806 the *OpenWebNet-HA#13**0## echo; OpenWebNet-HA#800 / OpenWebNet-HA#801 / OpenWebNet-HA#805 have no
frame in this capture); it states that f461_live_2026-10-15 holds the
dimension 22 reboot behind OWNd OpenWebNet-HA#136, the *4*311*2## / *4*311*#2## pair
behind OWNd OpenWebNet-HA#137 and probe_area0 behind OWNd OpenWebNet-HA#141 / MyHOME OpenWebNet-HA#798; and one
sentence says reply_time_s is measured while tx.t is reconstructed from the
probe log.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/alarm-unknown-restore branch from 1683fd3 to 4f7423d Compare October 24, 2026 15:46
GreenGrassBlueOcean pushed a commit that referenced this pull request Oct 24, 2026
MyHomeServer1 README: "OWnd" typo; the alarm gate, the config-flow
identification and the liveness probe rows name MyHOME #803, #804, #806.
F461 README: the "What this capture backs" table extends to MyHOME #800-#806
(#802 the dimension 27 wire form, #803 *#5*0##, #804 the WHO 13 / 1013
identification replies, #806 the *#13**0## echo; #800 / #801 / #805 have no
frame in this capture); it states that f461_live_2026-10-15 holds the
dimension 22 reboot behind OWNd #136, the *4*311*2## / *4*311*#2## pair
behind OWNd #137 and probe_area0 behind OWNd #141 / MyHOME #798; and one
sentence says reply_time_s is measured while tx.t is reconstructed from the
probe log.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/alarm-unknown-restore branch 2 times, most recently from d6b3f47 to 31d44a9 Compare October 24, 2026 19:40

@GreenGrassBlueOcean GreenGrassBlueOcean left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Requesting Changes

The core state-model fix here is solid and solves a real safety/UX bug: starting MyHOMEAlarmControlPanel as None (unknown) instead of disarmed, restoring MyHOMEEntity.async_added_to_hass() (RestoreEntity + DeviceHealth + post-discovery poll deferral), and clearing restored: True only on frames with a real arm/disarm/alarm verdict (*5*8*, *5*9*/*5*2*/*5*0*, *5*12/15/16/17/31*) rather than *5*1*0## (activation) or zone frames.

Before this moves out of draft / merges, please address the following issues (inline details attached):

  1. Physical F454 regression & doc contradiction (#311 vs oracle 020051): Adding "F454" unconditionally to ALARM_STATUS_REFUSED_MODELS contradicts our committed live F454 + burglar-alarm capture in tests/fixtures/plants/issue_311_f454/diagnostic_summary.json (lines 58–137, firmware: "2.0"), where [TX] *#5*#1## is answered on the bus by [RX] *5*11*#1## and the startup poll elicits *5*1*##, *5*5*##, *5*7*##, *5*9*## (routed by route_keys to CENTRAL_UNIT_KEY = "0", confirming alarm_0 immediately on restart). It also contradicts docs/configuration/alarm.md:16 right above the edited section and own-firmware-oracle/tools/expected_divergences.yaml (F454 020051 vs field 1.0.34/2.0/2.0.48 + silent_bus_vs_populated_trace).
  2. Shared-bus delegation, failover & alias normalization in _status_request_refused: send_status_request() routes through _delegated_target(cmd) and _failover_target(cmd), whereas _status_request_refused checks self._gateway_handler directly (suppressing *#5*0## on e.g. a MyHomeServer1 primary + MH202 secondary carrying WHO 5) and reads raw gateway.model (CONF_NAME) instead of canonical profile.model_name (missing SKU/short aliases like 573992 or MHS1).
  3. State/docs polish:
    • Guard raw_state / state_code updates while restored: True when a non-verdict frame (*5*1*0##, *5*11*#1##) arrives, and restrict async_restore_last_state to {STATE_DISARMED, STATE_ARMED_AWAY, STATE_TRIGGERED} so legacy pre-#609 armed_home entries cannot wedge the template alarm recipe.
    • Clarify the template alarm availability: comment in docs/configuration/alarm.md:67-69 now that RestoreEntity makes alarm_0 immediately available after a restart (using the restored state).
  4. Issue & PR citations:
    • #177 (@Manfredi87, 2025-04-08) is a pre-v2 feature request on a MyHomeServer1 before WHO 5 existed — neither F454 nor the quote "the state is always disarmed after a restart" appears in #177 (please clean up in PR title/body, commit message, release note, and comments).
    • In the PR body: #799 is already merged into base d8bac157 (not open), and Skipping WHO=5 discovery: not supported by F461 profile comes from _discovery_frames() + OWNd DEFAULT_SUPPORTED_WHO already on v2-phase1-architecture rather than #794.

Comment on lines +44 to +49
# Gateways that NACK the WHO 5 status request ``*#5*0##`` (own-firmware-oracle
# ``results/<model>/<fw>/oracle/full/trace-corpus.tsv``: F450 020010, F453AV 030014,
# F454 020051, F459 020105, F460 020012, F461 020011, MyHomeServer1 028206 answer
# ``nack`` / ``silent``). Fallback for OWNd profiles that carry no
# ``supports_alarm_status_request`` flag yet; the flag belongs in OWNd (#381 pattern).
ALARM_STATUS_REFUSED_MODELS = frozenset({"F450", "F453AV", "F454", "F459", "F460", "F461", "MYHOMESERVER1"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking — contradicts our live physical F454 trace (#311) and docs/configuration/alarm.md:16:

While emulated factory-blank F454 020051 returns nack / silent in own-firmware-oracle, our committed physical F454 (firmware: "2.0") + burglar-alarm trace in tests/fixtures/plants/issue_311_f454/diagnostic_summary.json (lines 58–137) proves a field F454 does forward WHO 5 status requests and return central-unit state:

  • Lines 124–137: [TX] *#5*#1## (14:17:38.658Z) → [RX] *5*11*#1## (14:17:38.719Z).
  • Lines 58–122: Startup poll returns *5*1*##, *5*5*##, *5*7*##, *5*9*##, *5*11*#1##..#4##, *5*18*#5##..#8##, *5*15*#3## — and route_keys() routes empty-WHERE *5*9*## to CENTRAL_UNIT_KEY = "0", immediately confirming alarm_control_panel.alarm_0's live state on restart!

own-firmware-oracle/tools/expected_divergences.yaml already records this divergence (F454 020051 vs field 1.0.34/2.0/2.0.48 on WHO 5/13, plus *#5*0## and *#5*#1## under silent_bus_vs_populated_trace), and docs/configuration/alarm.md:16 explicitly documents F454 answering polls with *5*1*##, *5*5*##, *5*7*##, *5*9*##.

Suppressing *#5*0## unconditionally for "F454" regresses live startup state confirmation on physical F454 plants where alarm_0 is already in the entity registry (leaving HA on an unconfirmed restored: True state if the alarm was armed/disarmed by transponder while HA was restarting). Since entity async_update() sends only a single command-session frame after initial discovery completes (and only when an alarm entity exists), please remove "F454" from ALARM_STATUS_REFUSED_MODELS (or gate by verified firmware revision if >= 2.0.51 genuinely dropped WHO 5 even with an alarm plant configured) and keep docs/configuration/alarm.md consistent.

# The central unit takes no code over the bus.
self._attr_code_arm_required = False
self._attr_alarm_state = STATE_DISARMED
# Unknown until a bus frame or the restored state says otherwise (#177).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misattributed #177 reference (here, in test_alarm_restore_unknown.py, .github/release-notes/2.0.0b16.md, commit message, and PR title/body): #177 (@Manfredi87, 2025-04-08) was a pre-v2 feature request on a MyHomeServer1 asking how to add an F4201 alarm before WHO 5 existed — neither F454 nor the quote "the state is always disarmed after a restart" appears in #177.

Comment on lines +180 to +186
async def async_restore_last_state(self, last_state: State) -> None:
"""Restore the last known state, flagged as restored until a bus frame confirms it."""
try:
state = AlarmControlPanelState(last_state.state)
except ValueError:
return
self._attr_alarm_state = state

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AlarmControlPanelState(last_state.state) accepts every HA core enum value (including armed_home, which MyHOME removed in #609, as well as arming/pending). Restoring a stale armed_home from .storage/core.restore_state would leave the documented template panel (states(...) in ['disarmed', 'armed_away', 'triggered']) stuck unavailable.

Please restrict restoration to the states this entity actually emits ({STATE_DISARMED, STATE_ARMED_AWAY, STATE_TRIGGERED}).

Comment on lines 194 to 217
def _status_request_refused(self) -> bool:
"""Whether this gateway NACKs ``*#5*0##``: the OWNd profile flag first, then the model table.

``None`` on the profile means unverified and falls through to the table; a gateway
in neither is polled as before.
"""
profile = getattr(self._gateway_handler, "profile", None)
flag = getattr(profile, "supports_alarm_status_request", None)
if isinstance(flag, bool):
return not flag
model = getattr(self._gateway_handler, "model", None)
return isinstance(model, str) and model.strip().upper().replace(" ", "") in ALARM_STATUS_REFUSED_MODELS

async def async_update(self) -> None:
"""Request status from the gateway."""
"""Request status from the gateway, unless the gateway refuses the request."""
if self._status_request_refused:
LOGGER.debug(
"%s %s: %s refuses *#5*0##; waiting for alarm frames on the bus",
self._gateway_handler.log_id,
self._display_name,
self._gateway_handler.model,
)
return
await self._gateway_handler.send_status_request(OWNAlarmCommand.status(self._where))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three issues in _status_request_refused / async_update:

  1. Shared-bus delegation & warm-standby failover bypassed: MyHOMEGatewayHandler.send_status_request(cmd) (gateway.py:638-644) routes through _delegated_target(cmd) and _failover_target(cmd), whereas _status_request_refused inspects self._gateway_handler directly. On a shared-bus plant such as MyHomeServer1 primary + MH202 secondary carrying WHO 5 (#564 / #576), an alarm entity registered on the primary (myhome.yaml or pre-delegation entity registry) has self._gateway_handler = MyHomeServer1 and skips *#5*0## even though send_status_request would send it through the connected MH202 secondary that does answer *#5*0##. Please resolve the effective target gateway (target = gw._delegated_target(cmd) or gw._failover_target(cmd) or gw, cf. MyHOMEExposeManager._effective_profile in expose.py:1042-1051) and check target's profile / model.
  2. SKU / alias normalization: gateway.model returns raw CONF_NAME, so model.strip().upper().replace(" ", "") misses hyphens and SKU/short aliases normalized by OWNd.profiles.get_gateway_profile() (e.g. "573992" → "F453AV", "MHS1" → "MyHomeServer1"). Prefer getattr(profile, "model_name", None) (when profile is not GenericGatewayProfile) before falling back to target.model.
  3. Log message frame & model: Line 211 hardcodes *#5*0## and self._gateway_handler.model even when self._where != "0" (e.g. a manual myhome.yaml zone entry sending *#5*#1##) or when routed to target. Log cmd and target.model instead.

Comment on lines 243 to 249
if live_state is not None:
self._attr_alarm_state = live_state
# Only a frame that sets the state confirms it; activation or zone frames do not.
self._attr_extra_state_attributes.pop("restored", None)

self._attr_extra_state_attributes["raw_state"] = message.state_name
self._attr_extra_state_attributes["state_code"] = message.state_code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When live_state is None (*5*1*0## activation, *5*11*#1## active zone, battery/mains events) while "restored" is still True, unconditionally overwriting raw_state and state_code leaves mismatched attributes (e.g. alarm_state: armed_away, restored: true, raw_state: "activation", state_code: 1), which would also be persisted across a second restart before confirmation. Consider either keeping the restored raw_state/state_code until live_state is not None or tracking bus telemetry separately.

Comment thread docs/configuration/alarm.md Outdated
Comment on lines +46 to +47
* A new panel is **unknown** until the central unit reports a state. After a restart it shows its last known state with the attribute `restored: true` until the next frame confirms it.
* F450, F453AV, F454, F459, F460, F461 and MyHomeServer1 refuse the status request `*#5*0##`, so the panel is not polled there and waits for the central unit's own frames.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Contradiction with line 16 above: Line 16 in this same file states "An F454 answers with system-level frames that have an empty WHERE (*5*1*##, *5*5*##, *5*7*##, *5*9*##, see Need help configuring Alarm feature (F454) #311)", whereas line 47 here says F454 refuses *#5*0##. Please reconcile with the #311 F454 trace finding.
  2. Template alarm availability recipe below (lines 67–69): The comment # Unavailable until the MyHOME panel has reported (availability: "{{ states('alarm_control_panel.alarm_0') in ['disarmed', 'armed_away', 'triggered'] }}") only holds on a blank first start now — after a restart, RestoreEntity restores alarm_0 immediately to disarmed/armed_away/triggered, making the template panel immediately available on the unconfirmed restored state. Please update that comment (and optionally note and not state_attr('alarm_control_panel.alarm_0', 'restored') for users who want the template panel unavailable until live bus confirmation).

Comment thread .github/release-notes/2.0.0b16.md Outdated

- **`myhome.send_message` and `myhome.sync_time` reject unknown keys.** A misspelt key such as `gw_mac` or a missing `message` raises a validation error instead of sending the frame to the default gateway; both services get text selectors in the visual editor (#189).
- **Climate zones show the setpoint in force (dimension 12).** When the thermostat regulates to another setpoint than the commanded one (dimension 14; 23.5 in force while 21.0 commanded on an F461), the entity and `local_target_temperature` now show the one in force instead of the commanded value plus the knob offset. Known limit: once a zone has reported dimension 12, a dimension 14 that is not the echo of a Home Assistant write does not move the slider until the next dimension 12 report or poll (see Known Limitations).
- **The burglar alarm panel starts `unknown` instead of `disarmed`.** It restores its last state after a restart (attribute `restored: true` until the central unit confirms it) and no longer polls `*#5*0##` on gateways whose firmware NACKs it (F450, F453AV, F454, F459, F460, F461, MyHomeServer1) (#177).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the gateway list (F454) and remove/fix the (#177) citation here once the review points on alarm_control_panel.py are addressed.

gdluck and others added 6 commits October 24, 2026 23:55
…*0## where the firmware refuses it

The panel claimed `disarmed` from creation and skipped the RestoreEntity
path, so an armed system showed disarmed after every restart. No issue
reports it; the motivation is the live F461 observation of the panel's
default state. Start with alarm_state None, restore the last valid state
flagged `restored: true` until a frame that names a state clears it, and
do not poll `*michnovka#5*0##` on gateways whose firmware NACKs it
(own-firmware-oracle trace-corpus rows). The gate reads an OWNd profile
flag first and falls back to a model table; the flag belongs in OWNd.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…teway refuses it

The model table ALARM_STATUS_REFUSED_MODELS turned emulator rows into a
model-capability fact, and the physical traces committed in
tests/fixtures disprove it: the OpenWebNet-HA#311 F454 (firmware 2.0) answers
`*michnovka#5*#1##` with `*5*11*#1##` and reports `*5*1*##`, `*5*5*##`,
`*5*7*##`, `*5*9*##`; the OpenWebNet-HA#629 F454 (1.0.34) and OpenWebNet-HA#619 F453AV (3.0.14)
sweeps answer `*michnovka#5*0##` with `*5*0*##` / `*5*1*##`, `*5*9*##` and the
zone frames. The own-firmware-oracle `nack` / `silent` rows (F450,
F453AV, F454, F459, F460, F461, MyHomeServer1) are factory-blank
emulations (tools/expected_divergences.yaml lists the F454 case under
silent_bus_vs_populated_trace), and the live F461 2.0.11 and
MyHomeServer1 3.87.13 NACKs (OpenWebNet-HA#799) were taken on plants without a
central unit: they prove those plants NACKed, not that every plant with
that gateway cannot answer. Silence is never converted into refusal.

The table is removed. Only an explicit `supports_alarm_status_request =
False` on the OWNd profile skips the poll; `None` or no flag polls. The
flag belongs in OWNd (OpenWebNet-HA#381 pattern). A per-plant cache of a live NACK
is not implemented here: the command session does not report a NACK
back to the entity (the future resolves to the write time), so it would
need gateway-session plumbing first.

Tests: every formerly listed model with no flag is polled, explicit
False skips, True / None poll; the three physical F454 / F453AV
fixtures prove the request is answered and that `*5*9*##` confirms the
restored state; the two live NACK traces prove nothing reaches the
panel and the restored state is left alone. docs/configuration/alarm.md
no longer contradicts its own point 1, and the release note describes
the policy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ies the request

`send_status_request()` routes through `_delegated_target(cmd)` and
`_failover_target(cmd)`, but the skip looked at the entity's own gateway.
On a shared bus such as a MyHomeServer1 primary with an MH202 secondary
carrying WHO 5 (OpenWebNet-HA#564 / OpenWebNet-HA#576), the panel registered on the primary would
have skipped a poll the secondary answers; in warm standby the
disconnected primary decided instead of the standby that sends.

Resolve the target the same way `send_status_request` does (delegated
secondary, then standby, then the gateway itself, cf.
MyHOMEExposeManager._effective_profile) and read that target's profile.
The DEBUG line names the frame actually skipped (`*michnovka#5*#1##` for a zone
entry, not a hard-coded `*michnovka#5*0##`) and the target's canonical
`profile.model_name` (`MHS1` -> `MyHomeServer1`, `573992` -> `F453AV`),
falling back to the configured name only for a Generic profile.

Tests: delegation in both directions, failover in both directions, an
explicit True polls whatever the configured alias, SKU, case or spacing,
and the log label for a short alias, a SKU and a Generic profile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…il a verdict

`AlarmControlPanelState(last_state.state)` accepted every core value,
so a stale `armed_home` (removed in OpenWebNet-HA#609) or a transient `arming` /
`pending` from .storage/core.restore_state would have been restored and
left the documented template panel unavailable. Restore only
`disarmed`, `armed_away` and `triggered`; anything else starts unknown.

While `restored` is set, frames that name no state (`*5*1*0##`
activation, `*5*11*#n##` / `*5*18*#n##` zones, `*5*5*##` battery,
`*5*7*##` mains) no longer overwrite `raw_state` / `state_code`, which
would have persisted a mismatched `armed_away` + `activation` pair
across a second restart. A verdict frame overwrites both and clears the
flag; once live, telemetry updates them as before.

Tests: each restorable state, six stale or transient states, five
non-verdict frames followed by a verdict. The doc lists the frames that
confirm a restored state.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The `availability` comment said the template panel is unavailable until
the MyHOME panel has reported. With RestoreEntity that holds only on a
blank first start: after a restart `alarm_0` is available at once on
its restored state (`restored: true`) until the central unit confirms
it. Say so, and give the `and not state_attr(..., 'restored')` clause
for users who want the template panel unavailable until the bus has
confirmed the state.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
OpenWebNet-HA#177 is a pre-v2 feature request about adding an F4201 central unit on
a MyHomeServer1, before WHO 5 existed; it names no F454 and contains no
sentence about the state after a restart. No open or closed issue
describes the disarmed-after-restart behaviour; the motivation is the
live F461 observation of the panel's default state. Remove the citation
from the code comment and the test comments (the release note and the
commit messages no longer carry it either).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/alarm-unknown-restore branch from 31d44a9 to 3b6b1bc Compare October 24, 2026 21:06
@gdluck gdluck changed the title fix(alarm): start unknown, restore the last state and skip *#5*0## where the firmware refuses it (#177) fix(alarm): start unknown, restore the last state and poll *#5*0## unless the OWNd profile refuses it Oct 24, 2026
@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

All five points are on the branch (head 3b6b1bc, history rewritten for the commit messages, rebased on the current base, still draft). While checking the F454 evidence I found two more physical contradictions in the corpus, so the fix went further than removing one model.

  1. F454 in ALARM_STATUS_REFUSED_MODELS: removed, and the table with it. Besides the Need help configuring Alarm feature (F454) #311 F454 2.0 trace you cited, the Central unit (WHERE #0) is marked "unresponsive" after two restarts, although it answers a plain status request #629 F454 1.0.34 and the Investigate lighting WHAT 19 status frames from BTicino F414 modular dimmers #619 F453AV 3.0.14 sweeps in tests/fixtures also answer *#5*0## / *#5*#1## on the bus; the emulated nack rows are factory-blank images, and the live F461 / MyHomeServer1 NACKs in the merged test(trace): ingest live F461 2.0.11 2026-10-23 capture and MyHomeServer1 3.87.13 reads behind OWNd #136-#142 and MyHOME #791-#798 #799 were on plants without a central unit, so neither is a model fact. The poll is now skipped only on an explicit supports_alarm_status_request = False on the OWNd profile; None polls. No alarm capture exists for F450 / F459 / F460. Replay tests from the three physical fixtures prove the poll is still sent and *5*9*## confirms the restored state; the two live NACK traces prove nothing reaches the panel. docs/configuration/alarm.md line 16 and the section now agree; release note updated. A per-plant live-NACK cache is deferred because the command session does not surface NACKs to entities (stated in the body).
  2. Target resolution: async_update resolves the target as send_status_request does (delegated secondary → standby → own gateway, the _effective_profile pattern), sends through it (route resolved once), decides on that target's profile, and the DEBUG line logs cmd and the target's canonical profile.model_name. Tests: MyHomeServer1 primary + MH202 secondary carrying WHO 5 (secondary asked) and the reverse, failover both ways with _failover_target called once, aliases / SKU / case / whitespace (MHS1, 573992, my-home-server-1, f461).
  3. Restored state: raw_state / state_code are no longer overwritten by non-verdict frames while restored: true; restoration is limited to disarmed / armed_away / triggered (stale armed_home, arming, pending → unknown). Tests added.
  4. Template availability comment rewritten for RestoreEntity, with the optional and not state_attr('alarm_control_panel.alarm_0', 'restored') clause.
  5. BTICINO ALARM F4201 #177 removed from the title, body, commit messages, release note, code comment and tests; no issue describes the disarmed-after-restart behaviour, so the body states the live F461 observation as the motivation. #799 (merged); the "Skipping WHO=5 discovery" line is attributed to _discovery_frames() + OWNd DEFAULT_SUPPORTED_WHO on the base.

A second-opinion review of the diff added three points that are in as well (route resolved once; only real gateway handlers accepted as a route result; two overclaiming sentences in the docs reworded). Suite 4554 passed, 100 % coverage, all repo checks green; test_alarm_restore_unknown.py now 46 cases, 40 of which fail on the base code.

@GreenGrassBlueOcean

GreenGrassBlueOcean commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

@TheDarkWizard would you please be so kind to check if this is still an issue in v2? or that these only occur in the v0.9.x. versions

@TheDarkWizard

Copy link
Copy Markdown

@TheDarkWizard would you please be so kind to check if this is still an issue in v2? or that these only occur in the v0.9.x. versions

Are you sure this tag is for me?

As you know, I have a BT4200 and not an alarm that responds to WHO=5.

@gdluck
gdluck marked this pull request as ready for review October 24, 2026 21:37
@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

All review items are addressed and CI is green on 3b6b1bc, so this is out of draft for your re-review (the branch stays open to the outcome of your question to the other reporter: if the symptom does not exist on v2, closing it is fine by me).

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@TheDarkWizard would you please be so kind to check if this is still an issue in v2? or that these only occur in the v0.9.x. versions

Are you sure this tag is for me?

As you know, I have a BT4200 and not an alarm that responds to WHO=5.

Yes that is probably the whole issue there is probably nobody with a who 5 alarm. Because all the alarms use the protocol of Ksenia

@gdluck I don't think we have traces of anyone operating a connected who5 alarm. I can be wrong of course

@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

You are right, and sorry for the noise: this came out of a review of every open and closed MyHOME issue where the only evidence I had for the alarm behaviour was the emulated firmware, and nobody has a trace of a connected WHO 5 alarm on v2 (the F461 and MyHomeServer1 plants I used have none either). A fix without a plant that shows the problem has no place here. Closing it and dropping the branch; if a WHO 5 trace ever appears, the unknown-start and restore idea can be reconsidered against it.

@gdluck gdluck closed this Oct 24, 2026
@gdluck
gdluck deleted the fix/alarm-unknown-restore branch October 24, 2026 21:47
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.

4 participants