Skip to content

feat(gateway_sessions): end-to-end event-session liveness probe with non-blocking polls (#123) - #806

Closed
gdluck wants to merge 1 commit into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:feat/event-session-liveness-probe
Closed

gdluck wants to merge 1 commit into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:feat/event-session-liveness-probe

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown

Requested by A

Design feedback welcome: this is the active side of the "TCP up, bus dead" trade-off that #640 documented and left open.

What is the problem

Several reports describe the same failure: the event session stays connected, commands still go out and are answered, but bus updates stop arriving until the integration is reloaded (#123 on an F454, #164 on an F453; #135 and #158 describe the symptom without naming the gateway; the #123 reporter self-healed on Jeedom by reading the gateway time every 5 minutes). #640 (#637) rightly removed the passive inactivity timeout: a quiet plant is not a dead plant, and a 65-minute teardown on the MH201 produced reconnect churn and spurious warnings. OS TCP keepalive catches a dead socket, but not a live socket on which the gateway has stopped relaying the bus.

This PR adds an end-to-end probe that fires only after real silence and only draws a conclusion on gateways known to echo it. It does not reintroduce a passive timeout: silence alone never recycles anything, and a gateway that is not known to echo the probe is never recycled either.

Refs #123, #135, #158, #164, #640 (all closed).


What is the solution

  1. gateway_sessions.py:
    • EVENT_PROBE_IDLE = 600 s, EVENT_PROBE_TIMEOUT = 30 s, EVENT_PROBE_REPAIR_AFTER = 3, EVENT_PROBE_ECHO_MODELS = {"F461"}.
    • _read_event_session starts one _liveness_probe task per event session and cancels it with the session. The read loop (_dispatch_event_frames) stamps _last_rx_at for every frame that is not OWNSignaling, so the profile keepalive ACKs (*#*1##) keep TCP alive but do not count as bus traffic. The probe's echo is recognised by _is_time_echo: an OWNGatewayEvent with dimension 0 as OWNd parses *#13**0*HH*MM*SS*TZ## (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 rows carry ownd_class: OWNGatewayEvent), with the raw-string prefix *#13**0* as the fallback for a reply the pinned OWNd hands back unparsed. The keepalive itself is untouched.
    • After 600 s without a bus frame while connected, *#13**0## is queued on this gateway's own command pool with send_status_request_nowait (never blocks on a full queue; if it cannot be written there is no verdict). Once written, the probe waits 30 s for any frame on the event session:
      • a frame arrives: alive, misses reset, repair withdrawn. Only the time reply marks the gateway as "echoes".
      • no frame, and the gateway has echoed before or is known to echo: WARNING, the event session is expired through the existing watchdog and listening_loop recreates it with the normal 5 s backoff (_run_event_session logs DEBUG instead of "stalled"). After 3 consecutive misses a repair issue event_session_silent is raised; it clears on the next echo.
      • no frame on a gateway never seen to echo: DEBUG, nothing recycled.
    • "Known to echo" reads the OWNd profile first (getattr(profile, "echoes_event_session_probe", None), None = unverified) and falls back to EVENT_PROBE_ECHO_MODELS; a seen echo wins over both. The flag belongs in OWNd (fix(gateway): close idle command sessions before the gateway drops them #381 pattern); the table is the stop-gap.
    • _update_event_watchdog does not disarm the watchdog while a probe recycle is pending (a late frame cannot cancel the recycle after the probe decided).
    • CommandWorkerPool.send_status_request_nowait (+ _envelope helper shared with _enqueue); MyHOMEGatewayHandler.send_status_request_nowait with the same delegated / standby routing as send_status_request.
  2. gateway_events.py: the event-triggered WHO 4 zone poll (after a dimension 14 echo) uses the non-blocking path; a full queue skips the poll at DEBUG instead of blocking the event read loop (the zone's own pushes and the next poll still update it). This was the one place where the reader could wait on the sender.
  3. repairs.py, strings.json (+ translations/en.json via manage_translations.py sync-en), docs/diagnostics/repair-issues.md: the event_session_silent issue.
  4. tests/test_event_session_liveness.py (new, 22 cases): connected-but-silent F461 session is recycled and repaired after 2 misses (constants patched); an echoed probe keeps the session and marks the gateway; unrelated traffic keeps the session but does not prove an echo; a gateway that never echoed is not recycled; keepalive ACKs do not hide a dead bus; an unsent probe is not a miss; a full send queue does not block the event reader (failed before); the event-triggered poll is queued when there is room (guard); the profile flag decides before the table (4 cases) and a seen echo outweighs it; no probe while disconnected; a probe exception is logged and the probe keeps running; the first echo after a miss withdraws the repair; the repair issue is created / deleted in the issue registry; the non-blocking path follows the delegated / standby routing; _is_time_echo on the parsed OWNGatewayEvent (dimension 0 yes, the date dimension 1 no, a light event no) and the raw-string fallback. Updated: test_gateway.py::test_listening_loop_other_events and test_issue_674_f455_climate_trace.py::test_gateway_events_skips_zone_status_query_on_physical_gateways assert the non-blocking path.
  5. .github/release-notes/2.0.0b16.md: one line under "Other behaviour changes".

Companion PR: None in OWNd yet: the echoes_event_session_probe profile flag this PR reads first is proposed, not opened (#381 pattern).


State Diagram

stateDiagram-v2
    direction TB
    state "Event session liveness probe (per open event session)" as Probe {
        [*] --> Idle
        Idle --> Idle : any non-keepalive frame on the event session stamps _last_rx_at
        Idle --> Probing : 600 s without a bus frame while connected - *#13**0## queued nowait on the command session
        Probing --> Idle : queue full or session unavailable - no verdict , DEBUG, retried after the next idle period
        Probing --> Echoed : *#13**0*HH*MM*SS*TZ## , OWNGatewayEvent dimension 0 on the event session within 30 s - echo seen, misses 0, repair withdrawn
        Probing --> Alive : any other frame within 30 s - alive, misses 0 , does not prove an echo
        Probing --> Missed : no frame within 30 s
        Missed --> Idle : gateway never seen to echo, profile flag not True, model not in the table - DEBUG, session kept
        Missed --> Recycle : echo seen before, or profile flag True, or model in {F461} - WARNING, miss + 1, watchdog expired, listening_loop recreates the session
        Recycle --> Repair : 3 consecutive misses - repair issue event_session_silent
        Repair --> Echoed : next echo clears the issue
        Echoed --> Idle
        Alive --> Idle
    }
    state "Before this PR" as Before {
        [*] --> TcpOnly : OS TCP keepalive only - a live socket on which the gateway stopped relaying the bus is never noticed , #640 removed the passive timeout on purpose
    }
Loading

Hardware Verification

  • Physical F461 2.0.11 (2026-10-23): *#13**0## sent on the command session is answered there and the reply is also delivered on the event session, parsed by OWNd as OWNGatewayEvent (rows below). This is the echo the probe relies on, and the reason the fallback table holds only the F461.
  • Physical MyHomeServer1 3.87.13 (read-only probe 2026-10-24, no command sent): *#13**0## ACKed in 94 ms and 23 ms, the reply echoed on the event session both times (rows below). This unit behaves like the F461. The fallback table stays at {F461} (one unit, two observations); the runtime learns the echo on the first probe anyway, so a MyHomeServer1 is recycled from its second silent probe on without any table entry. Adding MyHomeServer1 to the table on the strength of this capture is a one-line change if you prefer it.
  • Live retest 2026-10-24 (report-15 consensus label core liveness not exercised): after a *#13**27## reply at 14:16:12 the local HA sent nothing for 12 min; the bus was silent until 14:20:00, when the F461 broadcast its own clock (*#13**#1*06*24*10*2026##, *#13**#0*17*20*00*##), which reset the silence window (the window then ran 8 min 38 s to the end of the test). HA sent no *#13**0## probe and performed no reconnect in the whole 12 min (expected: nothing before 600 s of silence). The echo path was seen at 14:09:13 in the services scenario (*#13**0## answered and echoed on the event session). The recycle path therefore has no live capture; a forced event-only stall (dropping the event flow on the HA host with a firewall rule) is the live test still to do, and a run on an F454 / MH200 / MH201 bench would be welcome.

How is it tested

  • tests/test_event_session_liveness.py (new, 22 cases, listed above) and the two updated tests.
  • Fail-before / pass-after, on the original source: 16 fail and 7 error (errors on the missing EVENT_PROBE_* constants; failures on the missing non-blocking sender and _is_time_echo, plus the 2 updated tests), 65 pass; all pass after.
  • Full suite against OWNd master (f04d8aa, the behaviour of the 2.0.0b11 pin): 4440 passed, 1 skipped (base v2-phase1-architecture ddc6d60: 4418 passed, 1 skipped).
  • scripts/verify_ownd_coverage.py 100 %, 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 all pass.

Live retest (2026-10-24)

Consensus label: core liveness not exercised (hourly F461 clock broadcasts reset the silence window); no spurious probe or reconnect in 12 min; the echo path was seen.


Where are the traces

Subsystem Capture Plant Frames verified by this PR
*#13**0## echoed on the event session tests/fixtures/traces/f461_live_2026-10-23/ run run1_oracle_replay_readonly step oracle_who13, and run run2_reviewer_reads (trace PR: #799, merged) F461 2.0.11 *#13**0## → ACK in 8 ms with *#13**0*23*38*50*999## on the command session and the same frame on the event session (note: who13_time_echoed_on_evt, ownd_class: OWNGatewayEvent); run 2: *#13**0*00*02*31*999## likewise
Same, second gateway family tests/fixtures/traces/myhomeserver1_live_2026-10-24/ runs run1_identify and run3_reads_lights_addresses_humidity (trace PR: #799, merged) MyHomeServer1 3.87.13 *#13**0## → ACK in 94 ms / 23 ms with *#13**0*16*19*49*999## / *#13**0*16*22*38*999## on both sessions (ownd_class: OWNGatewayEvent)
Keepalive ACKs excluded from "bus traffic" tests/fixtures/traces/f461_live_2026-10-23/ run run6_event_session_ha_fixed_integration (trace PR: #799, merged) F461 2.0.11 the gateway's own clock broadcasts *#13**#1*…## / *#13**#0*…## on the event session count as traffic; *#*1## does not
Recycle after a missed echo No physical trace: the plant was never silent for 600 s (unit tests only, see above) — —

What does the Firmware Oracle / Encyclopedia say

Published rows (results/<P>/<V>/oracle/full/who13-clock.tsv, index 53f9514), *#13**0##: ack / out with *#13**0*{UTC}*{M}*{S}*<tz>## on all 12 emulated gateways: time zone 001 on F450 020010, F454 020051, F459 020105; 000 on F453 010100, F453AV 030014, H4684 020054, MH200 020102, MH200N 010108, MH202 010024; 999 on F460 020012, F461 020011, MyHomeServer1 028206. The oracle sees the command session only, so it says the probe is harmless everywhere, not that it is echoed on the event session; the echo is a live fact for the F461 and the MyHomeServer1 only.

Note on #593: OWNd b9 raised ValueError on an event-side *#13**0##; the pinned OWNd parses the reply as OWNGatewayEvent, which is what _is_time_echo matches, with the raw prefix as the fallback.


Checklist

  • PR Description: Follows the structured sections above (What is the problem, What is the solution, State Diagram, How is it tested, Where are the traces, What does the Firmware Oracle / Encyclopedia say).
  • Trace-Backed (if protocol/device change): the echo is in 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 fixtures for both plants and the request is covered by the published oracle rows; the recycle path has not been exercised on a physical plant (said above).
  • V2 Architecture: N/A, no entity changes; session runner and repairs only.
  • Tests & Linters: pytest, ruff, and scripts/verify_ha_standards.py pass.

🤖 Generated with Claude Code

@gdluck
gdluck marked this pull request as draft October 24, 2026 13:39
@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 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 feat/event-session-liveness-probe branch from 1a42c6d to 6cb5b33 Compare October 24, 2026 15:50
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 feat/event-session-liveness-probe branch from 6cb5b33 to e58474b Compare October 24, 2026 18:26
…non-blocking polls (OpenWebNet-HA#123)

After 600 s without a bus frame (keepalive ACKs excluded) send
`*OpenWebNet-HA#13**0##` on the command session and expect its reply on the event
session within 30 s. A miss recycles the event session only on gateways
known to echo (seen echo, OWNd profile flag, then an F461 table: the
physical F461 2.0.11 delivers the reply on the event session); three
misses raise the `event_session_silent` repair. Silence alone never
recycles, so OpenWebNet-HA#640's position on quiet plants stands. The event-triggered
WHO 4 zone poll uses a non-blocking send so the reader never waits on a
full queue (OpenWebNet-HA#123, OpenWebNet-HA#135, OpenWebNet-HA#158, OpenWebNet-HA#164).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the feat/event-session-liveness-probe branch from e58474b to d35f5b9 Compare October 24, 2026 19:45

@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 & Protocol Review

The core direction here is well thought out: probing an idle (600 s) monitor session with *#13**0## on the command pool, excluding *#*1## OWNSignaling keepalive ACKs as bus evidence, gating recycles on proven echo behavior so quiet plants (#640) never cycle needlessly, and switching the event-triggered WHO 4 Dimension 14 poll to send_status_request_nowait so _process_message never blocks on a full queue. Verified locally against both editable OWNd and pinned OWNd==2.0.0b11 (96 passed, 0 strict-mypy regressions on pinned OWNd, docs/translations/HA standards checks green).

Requesting changes for a few functional regressions and repair-lifecycle edge cases uncovered during protocol and concurrency review:

  1. [High] Active *#13**0## polling triggers unconfigured_timezone repairs on MyHomeServer1 (and F460):
    • Before this PR, MyHOME never actively sent request_time() (*#13**0##) or request_datetime() (*#13**22##), so GatewayIdentityMixin._handle_gateway_diagnostics (identity.py:375-383) was purely passive.
    • Both the Firmware Oracle (F460, F461, MyHomeServer1) and PR #799's live MyHomeServer1 3.87.13 capture (tests/fixtures/traces/myhomeserver1_live_2026-10-24) return *#13**0*HH*MM*SS*999## on evt, yet identity.py:378 only exempts F461 (WHO 1013 code 134). As written, every quiet MyHomeServer1 plant will raise a false unconfigured_timezone repair after the first 10-minute idle probe.
  2. [Medium] event_session_silent repair stays stuck during active bus traffic and becomes orphaned across entry reloads:
    • _probe_misses and event_session_silent_{entry_id} are only cleared inside _probe_once(). Once raised, normal daytime bus frames update _last_rx_at in _dispatch_event_frames, postponing _liveness_probe() indefinitely while leaving "gateway stopped reporting bus events" stuck in the UI until 10 minutes of total silence occur.
    • If the user reloads the integration (or saves Options / Reconfigures) while the issue is open, async_unload_entry does not clear event_session_silent_{entry_id} and the new EventSessionRunner starts with _probe_misses = 0—so if self._probe_misses: skips _delete_probe_issue() on subsequent healthy probes and orphans the issue until HA restarts.
  3. [Medium] Pinned OWNd==2.0.0b11 logs WARNING: Data received is not a message every 10 minutes on 0530 / 124 timezones:
    • _is_time_echo handles the raw-string fallback when OWNd==2.0.0b11 fails to parse *#13**0*...*0530## / 124 (OWNd #144 is not yet in the pinned wheel), but _dispatch_event_frames still passes that raw str into _process_message, hitting gateway_events.py:274 (WARNING: Data received is not a message) every 10 minutes in half-hour timezone regions.
  4. [Medium] MYHOMESERVER1 missing from EVENT_PROBE_ECHO_MODELS, mid-probe socket-drop race, and _probe_echo_seen overriding echoes_event_session_probe = False — detailed inline below.

Non-blocking follow-ups

  • identity.py:583-589 (_request_object_model): Also runs synchronously inside _process_message and manually calls self.send_buffer.put_nowait(...) without cancelling the orphan written future on QueueFull or catching QueueShutDown; reusing self._command_pool.send_status_request_nowait(cmd) cleans up both.
  • diagnostics.py & gateway.py exports: Exporting _probe_echo_seen, _probe_misses, and profile.echoes_event_session_probe in config-entry diagnostics will make field reports immediately show which models echo *#13**0## in the wild; consider re-exporting EVENT_PROBE_* in gateway.py.__all__ alongside EVENT_STALL_TIMEOUT / EVENT_RESTART_BACKOFF_*.
  • PR description template: Before taking this out of Draft, please align the PR body sections with .github/pull_request_template.md (including ## State Diagram and ## What does the Firmware Oracle / Encyclopedia say).

self._last_probe_at = time.monotonic()
silent_for = self._last_probe_at - self._last_rx_at
# Never wait for room in a full queue: the probe is retried after the next idle period.
written = self.handler._command_pool.send_status_request_nowait(OWNGatewayCommand.request_time())

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.

High — Active *#13**0## probing triggers unconfigured_timezone (999) on MyHomeServer1:

Prior to this PR, OWNGatewayCommand.request_time() (*#13**0##) and request_datetime() (*#13**22##) were never actively sent by MyHOME. Sending *#13**0## every 10 minutes on idle plants feeds the echoed OWNGatewayEvent(dimension=0) into GatewayIdentityMixin._handle_gateway_diagnostics (custom_components/myhome/identity.py:375-383), which raises unconfigured_timezone whenever dim_val[3] == "999" and only exempts F461 (WHO 1013 code 134).

Both the Firmware Oracle (F460, F461, MyHomeServer1) and PR #799's live MyHomeServer1 3.87.13 capture (tests/fixtures/traces/myhomeserver1_live_2026-10-24: *#13**0*16*19*49*999##, *#13**0*16*22*38*999## on evt) return 999 in Dimension 0 on modern Home + Project firmware (which also has no legacy web clock UI at http://<gateway_ip>). Please ensure MyHomeServer1 (and F460 if applicable) is exempted or that internal liveness probes do not raise false unconfigured_timezone repairs every 10 minutes.

Comment on lines +301 to +305
if message is not None and not isinstance(message, OWNSignaling):
self._last_rx_at = time.monotonic()
if _is_time_echo(message):
self._last_echo_at = self._last_rx_at
self._rx_event.set()

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.

Medium — event_session_silent stays stuck while the bus is active and orphans across entry reloads:

  1. Stuck during active bus traffic: _probe_misses and event_session_silent_{entry_id} are only cleared inside _probe_once() (lines 387–390). If 3 missed probes raise event_session_silent overnight and the session recovers, normal daytime bus frames arriving every few minutes keep advancing self._last_rx_at here, postponing _liveness_probe() (delay = max(_last_rx_at, _last_probe_at) + 600 - now). The "gateway stopped reporting bus events" warning remains stuck in the UI while bus events are actively flowing, until 10 minutes of total silence allow a probe to run.
  2. Orphaned on reload: On config-entry reload (e.g. Options save / Reconfigure), async_unload_entry does not delete event_session_silent_{entry_id}, and the new EventSessionRunner initializes self._probe_misses = 0. When the next probe succeeds, if self._probe_misses: at line 387 is False, so _delete_probe_issue() is never called and the issue stays orphaned for the rest of HA's uptime.

Clearing _probe_misses (and withdrawing any raised probe issue via a dedicated _probe_issue_raised: bool flag or unconditional delete on first healthy frame + in async_unload_entry) right here when any non-signaling frame arrives resolves both issues cleanly.

Comment on lines +65 to +67
if isinstance(message, OWNGatewayEvent):
return message.dimension == 0
return str(message).startswith("*#13**0*")

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.

Medium — Pinned OWNd==2.0.0b11 logs WARNING every 10 minutes on unparsed 0530 / 124 echoes (+ strict-typing nit):

  1. Against manifest.json's pinned OWNd==2.0.0b11 (without OWNd Fix/invalid is on for light #144), *#13**0*20*42*07*0530## (seen in Firmware Oracle who13-clock-readback on F450) and *#13**0*...*124## raise ValueError in OWNMessage.parse(), so OWNEventSession.get_next() yields the raw str. Line 67 counts it for the probe, but _dispatch_event_frames (line 329) still passes that raw str to self.handler._process_message(message) → gateway_events.py:274, emitting WARNING: Data received is not a message: '*#13**0*20*42*07*0530##' every 10 minutes on quiet plants in half-hour timezones. Either suppress that warning in gateway_events.py when _is_time_echo(message) matches, or coordinate bumping manifest.json once OWNd Fix/invalid is on for light #144 is released.
  2. Nit: return bool(message.dimension == 0) on line 66 avoids mypy --strict [no-any-return] when running scripts/typing_ratchet.py in editable environments where OWNd is untyped.

Comment on lines +53 to +56
# Models seen to deliver the ``*#13**0##`` reply on the event session (physical F461
# 2.0.11). Fallback for OWNd profiles without an ``echoes_event_session_probe`` flag;
# the flag belongs in OWNd (#381 pattern).
EVENT_PROBE_ECHO_MODELS = frozenset({"F461"})

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.

Medium — MYHOMESERVER1 missing from EVENT_PROBE_ECHO_MODELS (and #123 / #164 coverage note):

  • PR 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 (tests/fixtures/traces/myhomeserver1_live_2026-10-24) proved on physical MyHomeServer1 3.87.13 that *#13**0## is echoed on evt (run1_identify, run3_reads_lights_addresses_humidity). Without "MYHOMESERVER1" in EVENT_PROBE_ECHO_MODELS (until OWNd carries echoes_event_session_probe), a MyHomeServer1 whose event session is already silent from startup/reload has _probe_echo_seen == False and will never be recycled or raise event_session_silent. Once the 999 timezone handling is updated, "MYHOMESERVER1" should be included here (and noted in .github/release-notes/2.0.0b16.md).
  • Also note for issue linking: #123 was reported on an F454 and #164 on an F453, whose existing traces in tests/fixtures/traces/ are merged BusMonitor captures that do not distinguish cmd vs evt delivery for *#13**0##. Worth noting in the PR body / release notes that F454/F453 are only covered if they echo *#13**0## on evt.

Comment on lines +360 to +363
if self._probe_echo_seen:
return True
if isinstance(flag, bool):
return flag

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.

Medium — Explicit profile.echoes_event_session_probe is False should not be overridden by _probe_echo_seen:

Per OpenWebNet Encyclopedia ownkb:chunk:r000309, WHO 13 Dimension 0 value frames can appear on monitor sessions as server-originated or external-supervisor traffic. If a gateway whose OWNd profile is hardware-verified not to echo its own command-session *#13**0## (flag is False) happens to deliver a spontaneous/supervisor *#13**0*...## frame inside a 30 s probe window once, _probe_echo_seen latches True for the lifetime of EventSessionRunner and will falsely recycle that gateway every 10 minutes on quiet nights.

Consider checking if isinstance(flag, bool): return flag before if self._probe_echo_seen: return True so _probe_echo_seen only resolves unverified (flag is None) profiles.

Comment on lines +379 to +399
while self._last_rx_at <= written_at and (remaining := deadline - time.monotonic()) > 0:
self._rx_event.clear()
with contextlib.suppress(TimeoutError):
await asyncio.wait_for(self._rx_event.wait(), timeout=remaining)
if self._last_rx_at > written_at:
# Any frame proves the event path; only the time reply proves this gateway echoes.
if self._last_echo_at > written_at:
self._probe_echo_seen = True
if self._probe_misses:
LOGGER.info("%s Event session answers the liveness probe again.", self.log_id)
self._delete_probe_issue()
self._probe_misses = 0
return True
if not self._probe_echo_expected():
LOGGER.debug(
"%s `*#13**0##` was not echoed on the event session; this gateway has never "
"echoed it, so the session is kept.",
self.log_id,
)
return True
self._probe_misses += 1

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.

Medium — Re-check self.is_connected after waiting EVENT_PROBE_TIMEOUT:

_liveness_probe checks if not self.is_connected: before calling _probe_once(), which then waits up to 30 s here. If the TCP socket drops a few seconds after written_at (so self.is_connected flips False and OWNEventSession.get_next() is already reconnecting under the stall watchdog), waking at deadline without re-checking self.is_connected counts a false _probe_misses += 1 and forces _probe_recycle = True mid-reconnect.

Adding if not self.is_connected: return True before line 392 keeps socket drops strictly under OWNd and the stall watchdog.

2. Restart the gateway.

### How it clears
The issue is withdrawn as soon as a probe is echoed again.

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.

Nit — Wording mismatch with _probe_once recovery condition (repairs.py:124 & repair-issues.md:58):

Both this section and async_delete_event_session_silent_issue's docstring say the issue clears "once a probe is echoed again", whereas _probe_once() clears on any bus frame (self._last_rx_at > written_at, matching strings.json's "when the gateway answers again"). Updating both to reflect that any received bus event or probe echo clears the issue keeps the docs and code aligned.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck I cannot keep asking myself if this is really a problem. The issues mentioned in this pr are based upon 0.9.x. versions not on the revised v2. I cannot stop thinking that this a medicine for a cure that does not exist...

@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

Fair point: the issues this PR cites (#123, #135, #158, #164) were reported on 0.9.x, and nothing on v2 shows the problem (the only live observation today was that the probe never fired in 12 minutes because the F461 broadcasts its clock hourly). Closing it and dropping the branch; no medicine without a diagnosis. If a v2 trace ever shows a silently dead event session, the capture can reopen the question.

@gdluck gdluck closed this Oct 24, 2026
@gdluck
gdluck deleted the feat/event-session-liveness-probe branch October 24, 2026 21:07
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