Skip to content

Unified Tag API replacing SymbolTable#697

Merged
gijzelaerr merged 4 commits intomasterfrom
feature/tag-unification
Apr 20, 2026
Merged

Unified Tag API replacing SymbolTable#697
gijzelaerr merged 4 commits intomasterfrom
feature/tag-unification

Conversation

@gijzelaerr
Copy link
Copy Markdown
Owner

@gijzelaerr gijzelaerr commented Apr 17, 2026

Summary

Unified Tag API for reading and writing typed PLC values, using PLC4X / Siemens STEP7 address syntax familiar to every TIA Portal user. Includes symbolic (LID-based) access for S7-1200/1500 optimized DBs.

Tag API

A Tag describes a typed value at a specific S7 address. Tags can be created ad-hoc, loaded from files, or discovered from the PLC.

from s7 import Client, Tag, load_csv, load_json, load_tia_xml, from_browse

client = Client()
client.connect("192.168.1.10", 0, 1)

# Ad-hoc access with PLC4X-style strings
speed = client.read_tag("DB1.DBD0:REAL")
running = client.read_tag("DB1.DBX4.0:BOOL")
client.write_tag("DB1.DBW6:INT", 1500)

# Batch read (uses optimizer when enabled)
values = client.read_tags(["DB1.DBD0:REAL", "DB1.DBW6:INT"])

# Load named tags from a file
tags = load_tia_xml("db1.xml")  # dict[str, Tag]
temperature = client.read_tag(tags["Motor.Temperature"])

Address syntax

DB1.DBX0.0:BOOL          bit in data block
DB1.DBB10:BYTE           byte
DB1.DBW10:INT            word (2 bytes)
DB1.DBD10:REAL           double word (4 bytes)
DB1:10:INT               short form
DB1:10:STRING[20]        variable-length string
DB1:10:REAL[5]           array of 5 REALs
M10.5:BOOL               Merker bit
MW20:WORD                Merker word
I0.0:BOOL                input bit
Q0.0:BOOL                output bit

Leading % is optional. All fixed-size types support [count] arrays.

Symbolic access for optimized DBs (experimental)

S7-1200/1500 DBs with "Optimized block access" enabled (the TIA Portal V13+ default) do not use fixed byte offsets. The PLC relocates variables internally between downloads, so byte addresses like DB1.DBX0.0 are unreliable. Symbolic access navigates the PLC's symbol tree using LIDs (Local IDs).

# Discover LIDs from the PLC
variables = client.browse()
tags = from_browse(variables)  # Tags configured for symbolic access

# Read/write routes through S7CommPlus LID-based access
speed = client.read_tag(tags["Motor.Speed"])
client.write_tag(tags["Motor.Speed"], 1500.0)

# Or construct symbolic Tags manually
tag = Tag.from_access_string(
    "8A0E0001.A",           # AccessArea.LID (hex, dot-separated)
    datatype="REAL",
    name="Motor.Speed",
    symbol_crc=0x12345678,  # optional layout version check
)

The wire encoding follows the S7CommPlusDriver reference but has not yet been validated against a real optimized-DB PLC.

API additions

  • snap7.tags.Tag — canonical dataclass (area, db_number, byte_offset, datatype, bit, count, name, access_sequence, symbol_crc, is_symbolic)
  • Tag.from_string(address) — PLC4X address parser
  • Tag.from_access_string(hex_path, datatype, ...) — symbolic access parser
  • load_csv, load_json, load_tia_xml — return dict[str, Tag]
  • from_browse(variables) — convert client.browse() results to Tag dict
  • Client.read_tag(tag_or_str) — read typed value
  • Client.write_tag(tag_or_str, value) — write typed value
  • Client.read_tags([tags]) — batch read via optimizer
  • S7CommPlusClient.read_symbolic, write_symbolic — LID-based PDU builders

Replace the homegrown SymbolTable class with a simpler Tag-based API
using PLC4X / Siemens STEP7 address syntax that's familiar to every
TIA Portal user.

New API:
- snap7.tags.Tag dataclass: area, db_number, byte_offset, bit, datatype,
  count, name
- Tag.from_string("DB1.DBX0.0:BOOL") — full PLC4X parser
- load_csv / load_json / load_tia_xml — return dict[str, Tag]
- Client.read_tag(tag_or_str) — read typed value
- Client.write_tag(tag_or_str, value) — write typed value
- Client.read_tags([tag, ...]) — batch read via optimizer

Address syntax supported:
- DB1.DBX0.0:BOOL, DB1.DBB10:BYTE, DB1.DBW10:INT, DB1.DBD10:REAL
- DB1:10:INT (short form), DB1:10:STRING[20], DB1:10:REAL[5] (array)
- M10.5:BOOL, MW20:WORD (Merker)
- I0.0:BOOL, Q0.0:BOOL (I/O)
- Leading %% optional

Removed:
- snap7/util/symbols.py (SymbolTable, TagAddress)
- tests/test_symbols.py
- doc/API/symbols.rst

SymbolTable was experimental, unreleased, and its homegrown dict syntax
was not aligned with industry convention. The new Tag string syntax
matches what TIA Portal watch tables use.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gijzelaerr gijzelaerr added this to the 4.0 milestone Apr 17, 2026
gijzelaerr and others added 3 commits April 17, 2026 15:45
S7-1200/1500 DBs with "Optimized block access" enabled (the TIA Portal
V13+ default) do not use fixed byte offsets — the PLC relocates
variables internally between downloads. Symbolic access navigates the
PLC's symbol tree using LIDs (Local IDs) instead.

Tag extensions:
- access_sequence: list[int] — LID path through the symbol tree
- symbol_crc: int — layout version validation (0 = skip)
- is_symbolic property — True when access_sequence is set
- Tag.from_access_string("8A0E0001.A", "REAL") classmethod using the
  S7CommPlusDriver dot-separated hex format (AccessArea.LID.LID...)

S7CommPlus client:
- read_symbolic(access_area, lids, symbol_crc) — GetMultiVariables with
  LID-based ItemAddress
- write_symbolic(...) — SetMultiVariables equivalent
- Module-level _build_symbolic_read/write_payload helpers

s7.Client routing:
- read_tag/write_tag detect Tag.is_symbolic and route to S7CommPlus
  symbolic access; classic byte-offset tags continue to use legacy
- read_tags falls back to sequential when any tag is symbolic
  (batching symbolic reads via optimizer is future work)
- snap7.Client.read_tag raises NotImplementedError for symbolic tags
  (legacy S7 has no symbolic support)

Experimental — the wire implementation follows S7CommPlusDriver but
has not been validated against a real optimized-DB PLC yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete the symbolic access loop: browse() now captures the LID (from
the object RID in EXPLORE responses) and symbol_crc for each variable.
These can be fed into Tag objects for optimized-block access.

New:
- snap7.tags.from_browse(variables) — converts browse() results to
  dict[str, Tag], producing symbolic Tags when LIDs are present
- _parse_explore_fields now captures "lid" and "symbol_crc" keys

Workflow for optimized DBs:

    variables = client.browse()
    tags = from_browse(variables)
    value = client.read_tag(tags["Motor.Speed"])  # symbolic access

Still experimental — the CRC handling in particular needs real PLC
validation. If browse doesn't provide a CRC, the Tag uses CRC=0
(skip check) which the PLC may or may not accept depending on config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gijzelaerr gijzelaerr merged commit e609837 into master Apr 20, 2026
39 checks passed
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