feat(s7commplus): full per-tag browse() — symbol tree with access-sequences#751
Conversation
gijzelaerr
left a comment
There was a problem hiding this comment.
Review Summary
The typeinfo.py module is well-structured: clean dataclass hierarchy, faithful port of the C# reference's type system, pure-function parsers, and a clear parse→tree-build→flatten pipeline. The test suite (449 lines) covers all offset-info variants, the tree builder (scalars, structs, basic/struct/mdim arrays, BBOOL alignment), and the EXPLORE response parsing. The codec DWORD/LWORD fix (fixed big-endian vs VLQ) is a genuine bug fix.
No malicious code. No network calls, no file I/O, no subprocess spawning in typeinfo.py — it's purely bytes-in/objects-out parsing.
PLC compatibility: All changes are in s7/ (S7CommPlus). No snap7/ files touched, so S7-300/400/1200 legacy PUT/GET is unaffected.
However, there are two blocking issues:
1. CRITICAL: Async browse() not updated — sync/async parity broken
The sync S7CommPlusClient.browse() is completely rewritten with the 5-phase orchestration (list DBs → read type-info RID → add native areas → explore type-info container → build tree), returning the new format with access_sequence, opt_address, etc. But S7CommPlusAsyncClient.browse() still uses the old _parse_explore_fields path and returns a different (inferior) dict shape. This means:
- Sync and async
browse()return incompatible result formats - Async users get the old, less complete symbol tree
- The unified
s7.AsyncClientwraps the async client, so it's also affected
This must be updated before merge.
2. LGPL-3.0 licensing question
python-snap7 is MIT-licensed. typeinfo.py header says it "ports the type-system structures from the thomas-v2/S7CommPlusDriver C# reference (LGPL-3.0)". Porting LGPL-3.0 code into an MIT project has licensing implications — the LGPL requires the derived work either remain LGPL-compatible or be structured so the LGPL portion can be replaced. A clean-room reimplementation from the protocol spec would be fine under MIT, but a direct "port" likely is not. This needs a licensing decision from the maintainer before merge.
Minor issues:
_add_subnodesmdim odometer:xx[dim + 1] += 1whendim == 5would indexxx[6](out of bounds). In practice this only triggers aftern > array_element_countexits the while-loop, but it's brittle — add a guard.- The
_parse_explore_fieldsfunction is now dead code for the sync path (only used by asyncbrowse()). Once async is updated, it can be removed.
Not ready to merge — needs async parity and a licensing decision.
2a10cb6 to
adbdc0a
Compare
adbdc0a to
7c12e0f
Compare
|
Updated, and rebased onto current
Validation: sync + async |
ValueDWord / ValueLWord are fixed 4-/8-byte big-endian values, unlike UDInt/ULInt which are VLQ. They were wrongly grouped with the VLQ ints in decode_pvalue_to_bytes, encode_typed_value and skip_typed_value; split them out as fixed-width. (16-bit Word/UInt/Int were already fixed; only the 32/64-bit *Int* types are VLQ.) browse() reads DWORD/LWORD tags, so this is a prerequisite.
Implemented independently from the S7CommPlus wire-protocol layout (PVartypeList / POffsetInfoType / the PObject tree / VarnameList) — written against a protocol spec and the unit tests rather than by translating any existing driver. Parses the EXPLORE type-info container and flattens it into per-tag records: symbolic name, access-sequence (LID path), datatype, and optimized/non-optimized byte+bit offsets. Handles scalars, structs/UDTs, 1-D / struct / multi-dimensional arrays (incl. BBOOL byte-aligned bit indexing) and multi-block vartype lists.
Enumerate DBs, resolve each DB's type-info RID via a LID=1 read, add the native process
areas, explore the type-info container, and recombine into the flat symbol tree. Returns
a list of {name, access_sequence, data_type, opt/nonopt byte+bit offsets}.
async _send_request gains integrity_tail + multi-fragment reassembly; async browse() mirrors the sync per-tag tree with the identical result schema. Removes the old shallow _parse_explore_fields path.
7c12e0f to
017878b
Compare
gijzelaerr
left a comment
There was a problem hiding this comment.
Re-review after rebase (both prior blockers resolved)
Blocker #1: Async parity — RESOLVED
Async browse() now uses the full typeinfo-based orchestration (_read_typeinfo_rid, _explore_type_info_container, typeinfo.build_tree, typeinfo.build_flat_list). The old _parse_explore_fields import is removed. Async _send_request also gains integrity_tail + reassembly. Sync and async now return the identical schema.
Blocker #2: LGPL licensing — RESOLVED
typeinfo.py header no longer references "porting" from the C# driver. It now says: "follows the S7CommPlus wire protocol … as observed on an S7-1500 and documented publicly." The PR description confirms: "written against a protocol spec and the unit tests rather than by translating any existing driver."
Diff is clean after rebase
8 files (down from 11 before rebase). The #745/#747/#749 base changes (connection.py, _s7commplus_server.py, protocol.py, test_s7_tls.py) are gone — only browse-specific content remains.
DWORD/LWORD codec fix still present
ValueDWord (fixed 4-byte BE) and ValueLWord (fixed 8-byte BE) correctly split from VLQ-encoded UDINT/ULINT.
Security: ✅ No issues. typeinfo.py is pure parsing — no I/O, no network, no subprocess.
PLC compatibility: S7CommPlus only. No snap7/ touched.
Ready to merge.
Full per-tag
browse()— symbol tree with access-sequencesReturns the full per-tag symbol tree: each variable as
{name, access_sequence (LID path), data_type, optimized/non-optimized byte+bit offsets}, convertible to asnap7.tags.Tagand readable viaread_tag()/read_symbolic().Rebased onto current
masternow that the prerequisite PRs (#745, #747, #749) have merged — the diff is the browse contribution only.What's here
s7/typeinfo.py— a type-info parser + symbol-tree builder implemented independently from the S7CommPlus wire-protocol layout (PVartypeList / POffsetInfoType / the PObject tree / VarnameList): written against a protocol spec and the unit tests rather than by translating any existing driver; the module header credits the protocol. Handles scalars, structs/UDTs, 1-D / struct / multi-dimensional arrays (incl. BBOOL byte-aligned bit indexing) and multi-block vartype lists.browse()orchestration: enumerate DBs -> resolve each DB's type-info RID via a LID=1 read -> explore the type-info container -> recombine into the flat tree._send_requestgainsintegrity_tail+ multi-fragment reassembly, and asyncbrowse()returns the identical per-tag schema as sync.ValueDWord/ValueLWordare fixed-width big-endian, not VLQ.Validation
Against a real S7-1500 (V2, TLS): sync and async
browse()return a byte-for-byte identical symbol set (verified by hashing all names / access-sequences / offsets), and spot-reads via a browsed access-sequence match directread_symbolic(). Full unit suite green.(System datatypes —
HW_*,CONN_*, ... — are intentionally not surfaced yet; tracked separately.)