refactor(management): add composable connection-based procedure cores - #1832
refactor(management): add composable connection-based procedure cores#1832kewde wants to merge 11 commits into
Conversation
|
|
||
| async def nm_individual_address_write( | ||
| xknx: XKNX, individual_address: IndividualAddressableType | ||
| manager: ConnectionManager, |
There was a problem hiding this comment.
Problem: procedures that need multiple connections
Some procedures require sequential A_Connects to different addresses:
- NM_IndividualAddress_Write — 2 serial A_Connects (occupancy check + verify/restart).
- NM_Router_Scan — loop of up to 256 A_Connects, one per subnet address 0–255.
- NM_DomainAddress_Scan / NM_DomainAddress_Scan2 — same pattern.
These can't take conn: P2PConnection — they need a connection factory.
Options
1. Move address into connect()
P2PConnection becomes a reusable session object; address is passed at connect-time:
# procedure signature
async def nm_router_scan(conn: P2PConnection) -> list[IndividualAddress]:
for i in range(256):
async with conn.connect(IndividualAddress(f"1.{i}.0")):
...
# caller
conn = P2PConnection(xknx)
await nm_router_scan(conn)2. Pass ConnectionManager to scanning procedures
Remove the raise guard in Management.connect() — allow reconnecting to the same or a new
address — and let scanning procedures own their loop:
async def nm_router_scan(manager: ConnectionManager) -> list[IndividualAddress]:
for i in range(256):
async with manager.connection(IndividualAddress(f"1.{i}.0")) as conn:
...
# caller
await nm_router_scan(xknx.management)This is already the pattern for nm_individual_address_write — scanning procedures are just
another case of a procedure that manages its own connection lifecycle.
There was a problem hiding this comment.
In my opinion Option 2 is a cleaner API.
Regarding removing the raise:
if address in self._connections:
raise ManagementConnectionError(f"Connection to {address} already exists.")We remove the adddress from self._connections on disconnect. Parallel connections aren't possible iirc. So I think we can - and should - keep this. Connections to new addresses are possible anyway.
I could imagine to have some kind of backoff retry mechanism if an address is already connected by another procedure. But that's out of scope for this PR. And maybe better handled by the user of the API.
| self._response_waiter.set_result(telegram) | ||
| self._expected_sequence_number = self._expected_sequence_number + 1 & 0xF | ||
|
|
||
| async def send_data_no_ack(self, payload: APCI) -> None: |
There was a problem hiding this comment.
to provide a clean way to do restarts
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1832 +/- ##
==========================================
+ Coverage 97.76% 97.79% +0.03%
==========================================
Files 181 181
Lines 13195 13202 +7
==========================================
+ Hits 12900 12911 +11
+ Misses 295 291 -4
🚀 New features to boost your workflow:
|
|
Claude Code 🤖: I like where this is heading — splitting "procedure logic" from "connection lifecycle" is the right move, and 1. Drop
|
|
Personally I'd drop the Protocols and the Legacy shims. It should be fine to break these APIs and do a major release when we are ready. |
4d4e09f to
26036e5
Compare
… CLAUDE.md Finish adding the Management Procedures 03.05.02 spec version to the remaining files PR #1832 didn't touch (nm_individual_address_read, nm_individual_address_serial_number_read/write, and their tests). Add CLAUDE.md documenting the "KNX v<version> - <Document Title> <number> - <paragraph>" citation format, why the version matters (different KNX documents carry different versions - Transport Layer is v01.02.02, Management Procedures is v02.01.02), and the hard rule to never guess a document's version/title/number - ask first. Lists the documents with confirmed versions so far and the ones still needing input (Application Layer 03.03.07, LTE Application Layer, Routing 3.8.5, Communication Medium KNX IP 3.2.6).
f4e3fb3 to
7c0150c
Compare
… CLAUDE.md Finish adding the Management Procedures 03.05.02 spec version to the remaining files PR #1832 didn't touch (nm_individual_address_read, nm_individual_address_serial_number_read/write, and their tests). Add CLAUDE.md documenting the "KNX v<version> - <Document Title> <number> - <paragraph>" citation format, why the version matters (different KNX documents carry different versions - Transport Layer is v01.02.02, Management Procedures is v02.01.02), and the hard rule to never guess a document's version/title/number - ask first. Lists the documents with confirmed versions so far and the ones still needing input (Application Layer 03.03.07, LTE Application Layer, Routing 3.8.5, Communication Medium KNX IP 3.2.6).
3d4c9b7 to
574e855
Compare
… CLAUDE.md Finish adding the Management Procedures 03.05.02 spec version to the remaining files PR #1832 didn't touch (nm_individual_address_read, nm_individual_address_serial_number_read/write, and their tests). Add CLAUDE.md documenting the "KNX v<version> - <Document Title> <number> - <paragraph>" citation format, why the version matters (different KNX documents carry different versions - Transport Layer is v01.02.02, Management Procedures is v02.01.02), and the hard rule to never guess a document's version/title/number - ask first. Lists the documents with confirmed versions so far and the ones still needing input (Application Layer 03.03.07, LTE Application Layer, Routing 3.8.5, Communication Medium KNX IP 3.2.6).
- Simple procedures (dm_restart, nm_individual_address_check) now take conn: P2PConnection directly; callers own the connection lifecycle - Complex procedures (nm_individual_address_write) keep ConnectionManager + Broadcaster signatures to manage their own connection lifecycle - Legacy wrappers open/close connections and delegate to clean functions - Add legacy tests for dm_restart and all nm_individual_address_* wrappers - Document T_Connect-PDU = A_Connect at wire level (KNX 03.03.07 §3.5.1)
- Remove unnecessary ellipsis constants after docstrings in Protocol method bodies (W2301) - Replace reimport aliasing pattern in procedures/__init__.py with explicit assignments to avoid W0404
…m_individual_address_write Add test for the case where the device sends TDisconnect during the initial address check, causing nm_individual_address_check to return True internally while the context manager's finally raises ManagementConnectionRefused from disconnect() — which the outer except swallows per KNX 03.05.02 §2.3 step 1.
…back Per farmio's review on this PR: drop xknx.management.protocols entirely and annotate procedures with the concrete Management/P2PConnection classes instead, and drop the XKNX-based legacy wrappers rather than carrying a deprecation layer - the API can just break with a major release. - dm_restart_r_co(conn) keeps its spec name (KNX 03.05.02 §3.7.3); a new dm_restart(management, address) convenience wrapper owns the connection. - nm_individual_address_check(conn) is renamed to nm_individual_address_check_conn(conn) - not a spec name, just an xknx convention for the composable core - freeing nm_individual_address_check for a new (management, address)-owning wrapper that keeps the occupied-on-disconnect semantics in one place. - nm_individual_address_write now takes a single management: Management argument instead of (manager, broadcaster), and calls the _conn cores directly on its own already-open connections. This also fixes a latent UnboundLocalError risk: address_found is now always initialized before the try/except that can swallow ManagementConnectionRefused. - nm_individual_address_read/nm_individual_address_serial_number_read/ nm_individual_address_serial_number_write retyped from Broadcaster to Management (no behavior change, Management already satisfied it). - Removed device/legacy.py, network/legacy.py, and the nm_invididual_address_write typo alias. - Updated examples and changelog (with before/after snippets) to match.
…procedures Management.__init__ requires an XKNX instance anyway, so typing the connection/broadcast-owning procedures narrowly with Management didn't buy real decoupling - it just made callers write xknx.management everywhere instead of the xknx they already have. Revert those six procedures (dm_restart, nm_individual_address_check, nm_individual_address_write, nm_individual_address_read, nm_individual_address_serial_number_read/write) back to taking xknx: XKNX, using xknx.management internally. This actually makes the top-level, single-device procedures' call signature identical to the pre-PR API for anyone who was just calling dm_restart(xknx, address) etc. - the only remaining breaking changes are nm_individual_address_write dropping its duplicate xknx.management argument, the protocols module removal, and the _conn/_r_co composable core renames, all covered in the changelog entry. The composable conn-based cores (dm_restart_r_co, nm_individual_address_check_conn) are unaffected - they still take conn: P2PConnection since they operate on an already-open connection, not the whole xknx instance.
The occupancy check in nm_individual_address_write was needlessly duplicating nm_individual_address_check's own connection-open and ManagementConnectionRefused handling instead of just calling it directly - a leftover from an earlier iteration of this branch that used to type this function differently. Call nm_individual_address_check(xknx, address) directly, matching main's structure exactly for that part. The changelog was overstating this PR's breaking changes by comparing against this branch's own earlier, since-reverted commits (Broadcaster/ ConnectionManager protocols, dual manager+broadcaster arguments) rather than against current main. Diffed every touched procedure file against main directly: every existing top-level procedure signature is actually unchanged. The only real breaking change is dropping the nm_invididual_address_write typo alias; everything else is additive (dm_restart_r_co, nm_individual_address_check_conn, P2PConnection.send_data_no_ack) or internal refactoring.
Comments/docstrings referencing "KNX 03.05.02" or "KNX 03.03.07" didn't specify which release of the KNX Standard those document numbers belong to. Add "v02.01.02 - <document title> <number>" consistently, scoped to the files this PR already touches.
…d guards test_nm_individual_address_write_address_found_other_in_programming_mode advanced the clock past nm_individual_address_read's 3s broadcast window before delivering the programming-mode reply, so the reply was silently dropped and the test actually exercised the "no device in programming mode" branch instead of the address-conflict branch its name and the generic pytest.raises(ManagementConnectionError) implied. Deliver the reply while the broadcast is still listening, then advance the clock via time_travel to close it out without a real 3s wait, and assert on the specific error message so this can't silently regress again. Add test_send_on_disconnected_connection covering the three identical "if not self._connected: raise ManagementConnectionRefused" guards in P2PConnection (send_data_no_ack, request, _send_data). _send_data's was previously only covered incidentally through unrelated procedure tests (and stopped being covered at all once the write test above was fixed), which is exactly the kind of coverage that silently breaks when unrelated tests change; test it directly instead.
…wait_for_ack) send_data_no_ack sitting right next to the private _send_data read like an internal helper that leaked into the public API, and the two were near-duplicates of the same telegram construction. Merge them into one public send_data(payload, wait_for_ack=True) - the ack-waiting path is unchanged, the no-ack path just sends and returns.
This branch predates the 3.16.0 and 3.17.0 releases, and rebasing had kept this PR's changelog entries textually anchored where they were originally written - which by now was inside the already-shipped "# 3.16.0 Complex schema" section instead of "# Unreleased changes" at the top. Move the Breaking Changes/New Features entries to Unreleased, and drop the stale "Internals" bullet describing the abandoned Broadcaster/ConnectionManager protocol design (superseded several commits ago) instead of leaving it duplicated in both places.
… CLAUDE.md Finish adding the Management Procedures 03.05.02 spec version to the remaining files PR #1832 didn't touch (nm_individual_address_read, nm_individual_address_serial_number_read/write, and their tests). Add CLAUDE.md documenting the "KNX v<version> - <Document Title> <number> - <paragraph>" citation format, why the version matters (different KNX documents carry different versions - Transport Layer is v01.02.02, Management Procedures is v02.01.02), and the hard rule to never guess a document's version/title/number - ask first. Lists the documents with confirmed versions so far and the ones still needing input (Application Layer 03.03.07, LTE Application Layer, Routing 3.8.5, Communication Medium KNX IP 3.2.6).
574e855 to
96a0b31
Compare
Description
Adds composable "conn" variants of two management procedures, so several procedures can be chained over one already-open
P2PConnectioninstead of each one opening and closing its own:dm_restart_r_co(conn)— the actual KNX v02.01.02 - Management Procedures 03.05.02 - §3.7.3 procedure name for the connection-based variant of DM_Restart.dm_restart(xknx, address)now just opens a connection and delegates to it.nm_individual_address_check_conn(conn)— an xknx-only naming convention (not a KNX spec name) for the connection-based core ofnm_individual_address_check.nm_individual_address_writenow calls this directly on its own already-open connections instead of duplicating the DeviceDescriptorRead/timeout handling inline.All existing top-level procedures (
dm_restart,nm_individual_address_check,nm_individual_address_write,nm_individual_address_read,nm_individual_address_serial_number_read/_write) keep their familiar(xknx, ...)signature — no breaking change to any of those call sites.Also:
P2PConnection.send_data(payload, wait_for_ack=True), replacing the near-duplicatesend_data_no_ack/private_send_datapair, sodm_restart_r_coand the request-based procedures share one send path instead of two copies of the sameTDataConnectedconstruction.nm_invididual_address_writetypo alias — the misspelled name is no longer exported. This is the only breaking change in this PR.CLAUDE.mddocumenting how to cite the KNX Standard in code (document + version, since different KNX documents version independently), the management procedure naming convention, changelog conventions, and PR template usage.Fixes # (issue)
Type of change
Checklist