Repository navigation
refactor(connection): remove _resolve_discovery_fn test shim and document MH201 retry semantics - #133
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
fedem95
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. Tested 99b7b85: 1780 passed locally, CI green including the MyHOME suite. OWNd/connection/gateway.py is back to exactly its pre-#132 content (same blob, 996c684), no test still patches OWNd.connection.find_gateways/get_gateway/get_port, and the two duplicated tests now exist once each.
I also checked the answer to my question in MyHOME (gateway_sessions.py on v2, lines 1242-1290): the worker closes the command session after 15 s idle, calls connect() itself (the full 5-attempt loop) when the session isn't open, and only then calls send(); a None from send() cancels the task without re-queuing. So on an MH201 the single-attempt path only covers a drop on an already-open session. Approving.
…penWebNet-HA#132, OpenWebNet-HA#133 to the changelog Brings in OpenWebNet-HA#132 (typed exceptions, fast send reconnect), OpenWebNet-HA#133 (discovery shim removal, MH201 retry notes) and OpenWebNet-HA#129 (expose builders, query helpers, tri-state profile flags). OWNd/__init__.py keeps master's exception re-exports with __version__ = "2.0.0b11". Changelog dated 2026-10-20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to #132 (review) by @fedem95.
1. What is the problem?
_resolve_discovery_fntest-shimming helper inOWNd/connection/gateway.py(Review Note 3):_resolve_discovery_fn,_ORIG_FIND_GATEWAYS,_ORIG_GET_GATEWAY,_ORIG_GET_PORT, andimport sysexisted inOWNd/connection/gateway.pysolely so legacy tests could patchOWNd.connection.{find_gateways,get_gateway,get_port}instead ofOWNd.connection.gateway.*.send()reconnect documentation (Review Note 1):OWNCommandSession._locked_send()switched its internal reconnect when streams areNoneto a single attempt (_connect_max_attempts_override = 1) with aSEND_RECONNECT_COOLDOWN = 2.0s window so queued commands behind_send_lockdo not stall for ~13.3 s of exponential backoff during a gateway outage.*99*0##negotiation when its session slot is occupied), the interaction betweenOWNd's single-attempt mid-flight reconnect insend()andMyHOME'sCommandWorkerPool.sending_loop()(which closes idle sockets after15.0sand callsawait session.connect()directly with the full 5-attempt / ~13.3 s retry window when reopening from idle) was not documented inOWNd/connection/command_session.py.tests/test_connection.py:test_written_non_query_with_status_request_flag_is_not_replayed_after_lost_ackandtest_disconnected_dimension_request_retries_once, and Codecov reported 4 partial branches on feat(connection): add typed exceptions, fast send reconnect, and API hardening #132 (session.pyandevent_session.py).2. How is it solved?
_resolve_discovery_fnfromOWNd/connection/gateway.py:find_gateways(),get_gateway(), andget_port()directly inOWNGateway.get_first_available_gateway(),OWNGateway.find_from_address(), andOWNGateway.build_from_discovery_info(), removing_resolve_discovery_fn,_ORIG_*, andimport sysfrom the runtime path (paired with OpenWebNet-HA/MyHOME#758 updatingMyHOME's test patches toOWNGateway.__module__).MyHOMEworker cross-reference inOWNd/connection/command_session.py:OWNCommandSession._locked_send()(matching the companion comment inMyHOME'scustom_components/myhome/gateway_sessions.pyin OpenWebNet-HA/MyHOME#758) explaining why callers that recycle idle sockets (sending_loop()) callawait session.connect()directly beforesend()(preserving the fullMAX_CONNECT_ATTEMPTS = 5/ ~13.3 s retry window when anMH201closes TCP during negotiation), whereas_locked_send()'s single-attempt reconnect only runs on directsend()calls with closed streams or on attempt 2 after a mid-flight transport drop.tests/test_connection.py& cover all 4 Codecov branch partials:patch("OWNd.connection.find_gateways", ...)test block intests/test_connection.py, and adds assertions covering_notify_auth_errorwithout a logger,test_connection(raise_on_error=False)on auth error,connect(raise_on_error=True)on fatal non-auth negotiation error, andevent_session.get_next(raise_on_auth_error=True)on non-auth reconnect failure.3. State Diagram
stateDiagram-v2 direction TB [*] --> IdleClosed: Session created / closed after 15.0s idle state "Closed (is_open == False)" as IdleClosed state "Direct connect() (MAX_CONNECT_ATTEMPTS = 5, ~13.3s backoff)" as DirectConnect state "Open (is_open == True)" as SessionOpen state "_locked_send() Attempt 1" as SendAttempt1 state "Internal Reconnect (_connect_max_attempts_override = 1)" as FastReconnect state "Cooldown Active (SEND_RECONNECT_COOLDOWN = 2.0s)" as Cooldown IdleClosed --> DirectConnect: MyHOME sending_loop() sees not is_open\ncalls await session.connect() DirectConnect --> SessionOpen: TCP + *99*0## + Auth -> *#*1## ACK\n(retries up to 5x if MH201 slot busy) DirectConnect --> IdleClosed: Exhausted 5 attempts -> returns failure IdleClosed --> FastReconnect: Caller invokes send() directly while closed SessionOpen --> SendAttempt1: await session.send(frame) SendAttempt1 --> SessionOpen: *#*1## ACK -> returns collected / True SendAttempt1 --> IdleClosed: *#*0## NACK or written non-query EOF\n(close streams, no replay, return None) SendAttempt1 --> FastReconnect: Pre-write EOF or idempotent query EOF\n(close streams -> attempt 2) FastReconnect --> SendAttempt1: 1-attempt connect() succeeds FastReconnect --> Cooldown: 1-attempt connect() fails\n(_last_reconnect_failure = monotonic()) Cooldown --> Cooldown: Subsequent send() within 2.0s\nfails fast without TCP connect Cooldown --> FastReconnect: Cooldown elapsed (>= 2.0s)4. Where are the traces (real ones)?
max_workers=1,15.0sidle close)MyHOME/tests/fixtures/traces/mh201-fresh-discovery-20260930.log4–10: worker 0 dequeues*#2*0##when session is closed, runs explicitOpening command sessionNegotiating command sessionReceived nonceCommand session established successfully. Lines331–332:Command session idle for 15.0s; closing socket to release gateway resource.WHO=1013code8(20000/tcp)MyHOME/tests/fixtures/traces/issue_674/home-assistant_2026-10-09T17-34-15.609Z.log161.3sworker lockup) demonstrating why_locked_send()must avoid compounding multi-attempt reconnect backoffs while holding_send_lock.2.0.11(WHO=13Dim 15 code200,134.15.5.0)MyHOME/tests/fixtures/traces/issue_667/myhome_trace_F461_impulse_intercom_2026-10-19.jsoncmd+evt) capture showing normal~90mscommandACKs and2.07sbus timeoutNACKs (*#*0##) on an open command session.5. What does the Firmware Oracle / Encyclopedia say?
protocol/sessions.md— Connection and Sessions):20000connection state machine:*#*1##(ACK) upon TCP accept.*99*9##/*99*0##for command/actions,*99*1##for event stream); server responds with open-range*#*1##, legacy challenge (*#<nonce>##), HMAC declaration (*98*1##/*98*2##),*#*0##(NACK), or connection close (EOF/IncompleteReadErrorwhen session slots are exhausted).open_connection) must not be equated with an active session until greeting, selector acceptance, and authentication succeed (session.is_connectedvssession.is_open).MyHOME/docs/configuration/gateways.md):15.0sof idle and using full 5-attemptconnect()on session reopen accommodates temporary slot contention without stalling in-flightsend()calls.openwebnet-mcp—lookup_firmware_verdict):F450 020010,F453 010100,F453AV 030014,F454 020051,F459 020105,F460 020012,F461 020011,H4684 020054,MH200 020102,MH200N 010108,MH202 010024,MyHomeServer1 028206), command frames and gateway model queries (*#13**15##) are processed only after the*99*0##/*99*9##negotiation handshake completes with*#*1##.