Skip to content

Add dual-dialect Tag parsing (PLC4X + nodeS7)#701

Merged
gijzelaerr merged 1 commit into
masterfrom
tag-subtyping-and-nodes7
Apr 21, 2026
Merged

Add dual-dialect Tag parsing (PLC4X + nodeS7)#701
gijzelaerr merged 1 commit into
masterfrom
tag-subtyping-and-nodes7

Conversation

@gijzelaerr
Copy link
Copy Markdown
Owner

Summary

  • PLC4XTag and NodeS7Tag are new subtypes of Tag, each with their own .parse() classmethod and a __str__ that round-trips to the source dialect.
  • parse_tag(s, *, strict=True) is a dispatcher that autodetects dialect from syntax markers: , selects nodeS7, :TYPE selects PLC4X. In strict mode, bare short forms like M7.1 or IW22 raise; strict=False dispatches them to the nodeS7 parser (which accepts bareness).
  • Tag.from_string stays as an alias for PLC4XTag.parse for backwards compatibility. All existing callers and tests continue to work.

Why nodeS7?

pyS7 and the Node-RED ecosystem use DB1,X0.0 / DB1,R4 / M10.5 / IW22. Accepting that syntax alongside PLC4X makes it painless for existing users of those tools (including the competing ha-s7plc HA integration) to migrate to python-snap7 4.0. The canonical in-memory representation is unchanged; only the edges (parse + __str__) know about dialect.

New API

from snap7 import PLC4XTag, NodeS7Tag, parse_tag

PLC4XTag.parse("DB1.DBD0:REAL")        # strict PLC4X
NodeS7Tag.parse("DB1,R0")              # strict nodeS7

parse_tag("DB1.DBD0:REAL")             # → PLC4XTag
parse_tag("DB1,R0")                    # → NodeS7Tag
parse_tag("M7.1")                      # raises (ambiguous)
parse_tag("M7.1", strict=False)        # → NodeS7Tag (BOOL, bit 1 of byte 7)
parse_tag("IW22", strict=False)        # → NodeS7Tag (WORD)

str(PLC4XTag.parse("DB1,DBX0.0"))      # not valid; pass :BOOL
str(NodeS7Tag.parse("DB1,R4"))         # "DB1,R4" (round-trip)

Test plan

  • 91 tag tests pass (new + existing)
  • Full suite: 1497 passed, 91 skipped
  • mypy clean across snap7/s7/tests/example
  • ruff check and format clean
  • pre-commit all hooks pass

🤖 Generated with Claude Code

Introduces PLC4XTag and NodeS7Tag as subtypes of Tag, each with their
own .parse() classmethod and __str__ that round-trips to the source
dialect. Adds parse_tag(s, *, strict=True) as a dispatcher that
autodetects dialect from syntax markers (',' -> nodeS7, ':TYPE' -> PLC4X).

Why: lets users of pyS7 / Node-RED bring their existing addressing
conventions (DB1,X0.0, M7.1, IW22) into ha-s7 and other integrations
without learning PLC4X syntax, while keeping PLC4X as the default in
docs and examples. Subtypes make the source dialect visible in types
and debuggers; isinstance(tag, Tag) still works internally.

Tag.from_string stays as an alias for PLC4XTag.parse for backwards
compatibility. Canonical protocol-layer code continues to work with
the base Tag type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gijzelaerr gijzelaerr merged commit 7358d20 into master Apr 21, 2026
39 checks passed
@gijzelaerr gijzelaerr deleted the tag-subtyping-and-nodes7 branch April 21, 2026 08:12
gijzelaerr added a commit that referenced this pull request Apr 21, 2026
)

The post-merge CI for #701 hit Errno 98 ("Address already in use") on
ubuntu-24.04 / py3.14 inside test_server_context_manager. Root cause:
tests picked ports via random.randint over narrow ranges (5k-20k
ports) with no collision check, so two concurrent server starts could
grab the same port, and re-runs on the same runner hit TIME_WAIT
lingers.

Fix: bind a throwaway socket to port 0, read the OS-assigned
ephemeral port, close, and pass it to the server. The ephemeral pool
is tens of thousands wide and the OS guarantees the port is free at
the moment of pick — much smaller collision window than a 1-in-5000
random draw. snap7.server already sets SO_REUSEADDR so TIME_WAIT
lingers don't bite either.

One helper (`get_free_tcp_port`) lives in tests/conftest.py and is
reused by test_s7_unified, test_optimizer, and test_stress. Added
tests/__init__.py so `from .conftest import ...` works.

Co-authored-by: Claude Opus 4.7 (1M context) <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.

1 participant