Skip to content

feat(alarm): read-only alarm panel; arm and disarm through AUX frames (#564) - #609

Merged
GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/alarm-read-only-aux
Oct 3, 2026
Merged

GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/alarm-read-only-aux

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The plant owner in #564 (comment) reports, quoting BTicino support, that current central-unit firmware rejects arm/disarm sent as WHO 5 frames over SCS, whichever gateway sends them. No capture shows such a command being accepted. They arm with a WHO 9 AUX frame (e.g. *9*1*7##) plus an automation programmed on the central unit; which AUX channels arm or disarm is the installer's choice.

The panel still advertised arm away / arm home / trigger and sent:

Action Frame sent What it is
arm away, arm home *5*1*0## "activation": the F454 trace (#311) shows it on every disarm, so it is not arming
disarm *5*2*0## rejected per #564
trigger *5*17*0## no capture of it being accepted

Changes

  • Entity (alarm_control_panel.py):
    • No supported features, so core refuses arm/trigger services. Disarm has no feature flag in core, so the entity refuses it with a translated ServiceValidationError (alarm_read_only) instead of sending a frame.
    • The armed_home branch in handle_event and the STATE_ARMED_HOME constant are gone. feat(alarm): complete WHO 5 burglar alarm implementation and gateway profiles OWNd#66 makes is_armed_home always False (home and away arming look the same on the bus), so the branch could not be reached and failed the 100 % coverage gate against OWNd master. The other state transitions are unchanged.
  • Docs:
    • alarm.md: read-only state table (armed_away = *5*8*##, WHAT 1 explained, armed_home never reported), and a note that discovery depends on the gateway: an F454 answers a poll with empty-WHERE frames, so there the panel appears at the first real arm or disarm.
    • alarm.md: a core template alarm panel recipe that takes its state from the MyHOME panel and sends the AUX frames with myhome.send_message. It requires a code to disarm (code_format: number plus a condition on code), is unavailable until the MyHOME panel has reported (no disarmed fallback), and explains how to keep the code in secrets.yaml: !secret must hold the whole template.
    • supported_functions.md, use case 7 and the README domain table (via scripts/update_supported_domains.py) no longer promise arm/disarm.
  • Translations: alarm_read_only in strings.json, synced to en.json. Italian is left to @fedem95's follow-up to fix(translations): placeholder mismatches in it/fr/nl and Italian CEN trigger names #577, as offered in the review.

Breaking change

Automations that call alarm_control_panel.alarm_arm_away / alarm_arm_home / alarm_trigger / alarm_disarm on the MyHOME panel now fail instead of sending a WHO 5 frame. Per #564 those frames did nothing on current firmware. Anyone whose central unit does accept them can still send them with myhome.send_message, or from a template panel.

Review follow-ups

Each of @fedem95's review points and how it was handled is listed in #615.

Tests

  • tests/test_alarm_read_only.py (new): full config-entry setup; each of the four services raises and nothing is sent to the gateway.

  • test_component_alarm.py: features are empty, disarm raises with the translation key, status requests are unchanged, and an active-zone frame (*5*11*0##) leaves the panel's state alone.

  • Branch merged with v2-phase1-architecture (includes test(alarm): arm with WHAT 8, not WHAT 1 (OWNd#66 conformance) #608).

  • Full suite against OWNd master (with Support for audio control / sound system (WHO 16)? #66): 3129 passed, 100 % coverage. Alarm tests also pass against OWNd 2.0.0b9 from PyPI (the manifest pin).

  • CI green; ruff clean.

  • V2 Architecture: no new entities; handle_event() only loses the unreachable armed_home branch.

🤖 Generated with Claude Code

The plant owner in OpenWebNet-HA#564 (comment 5913248544) reports, quoting BTicino
support, that current central-unit firmware rejects arm/disarm sent as WHO 5
frames over SCS, whichever gateway sends them. No capture shows one accepted.
They arm with a WHO 9 AUX frame (e.g. *9*1*7##) that an automation programmed
on the central unit maps to zones, so the frames are the installer's choice.

The panel advertised arm away/home and trigger and sent *5*1*0##,
*5*2*0## and *5*17*0##; the first is "activation", not arming. It now has
no arm/trigger features (core refuses those services) and refuses disarm,
which core cannot gate, with a translated ServiceValidationError. State
handling is unchanged.

The docs show a core template alarm panel that takes its state from the
MyHOME panel and sends the AUX frames with myhome.send_message. The state
table now maps armed_away to *5*8*## and explains WHAT 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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!

fedem95
fedem95 previously approved these changes Oct 2, 2026

@fedem95 fedem95 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Read-only is the right call given #564, and the state table now matches what the F454 trace (#311) shows for WHAT 1. Code looks good: no features, disarm refused with a translated error, handle_event untouched.

A few things on the docs and around it:

  1. The template recipe disarms without a code. With code_arm_required: false and no check on code, anyone who can reach Home Assistant disarms the burglar alarm with one tap, including from the alarm card. I'd at least say so in the doc, and ideally show the pattern, e.g. code_format: number and a condition on code in disarm: before the AUX frame.
  2. Discovery depends on the gateway. The doc says the panel is registered by "the *5*9*0## a disarmed panel sends when polled". On the MH202 (#564) that's true. In the F454 trace from #311, though, the system-level frames come with an empty WHERE (*5*1*##, *5*5*##, *5*7*##, *5*9*##), and MyHOME deliberately doesn't create an entity from "" (OWNd#66 discussion). On that gateway the panel would only appear at the first live *5*8*0## / *5*9*0##, i.e. the first real arm or disarm after a blank start. Worth a sentence in the doc, or a follow-up to adopt the existing 0 panel from an empty-WHERE status.
  3. alarm_read_only is English-only. For it.json I'd suggest: "{name} è di sola lettura: inserisci e disinserisci l'allarme con i comandi AUX programmati sulla centrale". Happy to add it, together with the other missing Italian keys, in the follow-up to #577.

Minor: the template's state passes unknown/unavailable straight through when the MyHOME panel hasn't reported yet; the template panel logs those as invalid states. A default ({{ states(...) if states(...) in ['disarmed','armed_away','triggered'] else 'disarmed' }}, or availability) would keep the log clean.

…teway-dependent discovery

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

@fedem95 fedem95 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks, 1 and 2 are covered well.

CI: the coverage failure isn't from this commit. pypi_standards.yml installs OWNd from master, and OWNd#66 was merged at 14:17Z, four minutes before f7c82c3. Since then is_armed_home is always False, so alarm_control_panel.py:193 (self._attr_alarm_state = STATE_ARMED_HOME) can't be reached and the 100 % gate fails. It will fail on every open MyHOME PR, not just this one. Dropping that elif matches the new doc ("armed_home is never reported"), and this PR seems the natural place for it.

I'd revert my own suggestion on the fallback. Falling back to disarmed while the MyHOME panel is unknown/unavailable makes the template panel show "Disarmed" after every restart until the central unit reports, even if the alarm is armed. For a burglar alarm that's the wrong default. availability is the better of the two options I offered:

        availability: "{{ states('alarm_control_panel.alarm_0') in ['disarmed', 'armed_away', 'triggered'] }}"
        state: "{{ states('alarm_control_panel.alarm_0') }}"

!secret and the code check. !secret can't be interpolated inside the template string, so {{ code == !secret … }} won't work. The whole template has to be the secret (value_template: !secret alarm_disarm_check, with alarm_disarm_check: "{{ code == '1234' }}" in secrets.yaml), or the code can be compared against a helper. Worth spelling out, since the warning tells people to do it.

GreenGrassBlueOcean and others added 2 commits October 3, 2026 11:48
…ses availability

OWNd#66 makes is_armed_home always False (home and away arming look the
same on the bus), so the elif in handle_event could not be reached and
failed the 100 % coverage gate against OWNd master.

Docs, per review: the template panel is unavailable until the MyHOME
panel reports instead of falling back to disarmed (which would show an
armed alarm as disarmed after a restart), and the code check explains
that !secret must hold the whole template.

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

Copy link
Copy Markdown
Contributor Author

Thanks @fedem95, all points from both reviews are handled. The full point-by-point write-up is in #615; short version:

First review

  1. Disarm without a code: alarm.md now warns that the template panel is a separate entity that the MyHOME refusal does not protect, and the recipe uses code_format: number plus a condition on code before the AUX frame (f7c82c3).
  2. Gateway-dependent discovery: documented: MH202 shows the panel at once; on an F454 (empty-WHERE status, Need help configuring Alarm feature (F454) #311) it appears at the first real arm or disarm (f7c82c3). Adopting the 0 panel from an empty-WHERE status is left as a follow-up.
  3. Italian alarm_read_only: left to your fix(translations): placeholder mismatches in it/fr/nl and Italian CEN trigger names #577 follow-up, as you offered.
  4. unknown/unavailable passthrough: superseded by B below.

Second review

  • A. CI coverage: you were right about OWNd#66. Dropped the unreachable armed_home branch and the STATE_ARMED_HOME constant; the test now checks that *5*11*0## leaves the panel's state alone. Merged v2-phase1-architecture (brings in test(alarm): arm with WHAT 8, not WHAT 1 (OWNd#66 conformance) #608). Full suite against OWNd master: 3129 passed, 100 % coverage; alarm tests also pass on PyPI 2.0.0b9. CI is green (7a5818f, ef6f934).
  • B. availability instead of the disarmed fallback: recipe now uses your snippet, with a comment on why there is no fallback (ef6f934).
  • C. !secret inside the template: the doc explains that !secret replaces a whole value, shows value_template: !secret alarm_disarm_check with the template in secrets.yaml, and mentions comparing against a helper instead (ef6f934).

The PR description is updated to match.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 9da01ad into OpenWebNet-HA:v2-phase1-architecture Oct 3, 2026
16 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the feat/alarm-read-only-aux branch October 3, 2026 19:07
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 3, 2026
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
…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>
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.

3 participants