Skip to content

feat(s7commplus): add multi-write APIs - #819

Merged
gijzelaerr merged 6 commits into
masterfrom
feat/795-s7commplus-multi-write
Sep 8, 2026
Merged

feat(s7commplus): add multi-write APIs#819
gijzelaerr merged 6 commits into
masterfrom
feat/795-s7commplus-multi-write

Conversation

@gijzelaerr

@gijzelaerr gijzelaerr commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • add db_write_multi() to the synchronous and asynchronous S7CommPlus clients
  • add the requested write_multi() convenience alias
  • encode regular-session writes in one existing SET_MULTI_VARIABLES request
  • preserve V1-initial/substreamed compatibility by issuing one SET_VAR_SUBSTREAMED request per item
  • allow an explicit DataType as a fourth tuple element while preserving three-tuple BLOB writes
  • encode variable-length integer PValues according to the S7CommPlus wire format
  • leave conditional IntegrityId insertion to the connection layer and its negotiated counters
  • cover disconnected behavior, cross-DB sync/async integration, typed values, integrity framing, and the substreamed fallback

The explicit REAL form has completed a successful write/read/restore round trip against a real S7-1500 V2 PLC. A typed integer round trip would provide additional hardware confirmation of the VLQ encoding.

Verification

  • uv run --frozen --extra test --extra s7commplus pytest -q (1,905 passed, 78 skipped)
  • uv run --frozen pre-commit run --all-files
  • uv build --no-sources

Fixes #795

@gijzelaerr

Copy link
Copy Markdown
Owner Author

@bvanelli I refreshed this branch onto current master, added coverage for the substreamed fallback, and requested your review.

Since you have both current V2 and legacy/session-key PLC coverage, could you also try one db_write_multi() round trip on each available protocol path? The useful result is simply the PLC model/protocol, whether all values were written, and whether a following read returns them; no raw capture is needed unless it fails.

pip install --upgrade "python-snap7 @ git+https://github.com/gijzelaerr/python-snap7.git@feat/795-s7commplus-multi-write"

The current head passes 1,826 tests, the complete pre-commit suite, and the package build.

@gijzelaerr
gijzelaerr requested review from spreeker and removed request for spreeker September 5, 2026 08:26
@gijzelaerr

Copy link
Copy Markdown
Owner Author

@bvanelli Following up on the earlier hardware-testing request: could you also review the current multi-write implementation and API shape when you have a moment?

@bvanelli

bvanelli commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Hello @gijzelaerr ,

I did take a look during the week. The plc returns an error, so I was instead trying to report exactly what was failing: there were some theories like pvalue implementation, integrity id handling, etc.

I'll report it whenever I have time next week again.

@bvanelli

bvanelli commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

I'll post the traceback tomorrow, but I instructed one agent to figure out what was causing the problem. Here is what it did:

  • It wrote a _probe_datatypes: the PLC rejects a value whose datatype does not match it. The reason is that encode_pvalue_blob writes a BLOB, but individual variables might require different datatypes. It wrote instead encode_pvalue_typed(DataType.WORD, b"\x02\x00") # -> b"\x00\x0b\x02\x00"
  • It also tried to get rid of the encode_uint32_vlq(1) for _build_area_read_payload, _build_area_write_payload, _build_symbolic_write_payload. This is the same behaviour regarding a previous PR fix: Fix IntegrityId and response parsing for Browse. #764, where encode_uint32_vlq(1) should instead (possibly??) provide the integrity id.
  • _parse_read_response was turned into _parse_read_response_typed, where the validation and conversion of the datatype was done.

Here was the final run from the agent (you can ignore some of the output, since it's assuming things):

Result

  Static variable chosen: LGF_SinusCI_DB.amplitude — DB7, REAL, reads 1.0. It's an input parameter of a sine-generator FB (LGF library): the program reads it,
  never writes it, and it feeds only a simulated value, no actuator.

  Baseline — latest merged tree, fix stashed: the multi-variable path is dead on this V2 PLC. read_symbolic works, but db_read_multi returns [] (the
  duplicate-IntegrityId bug corrupts the request), so db_write_multi can't be round-tripped at all. That's the "test latest" answer: the feature doesn't
  function against a real S7-1500 without the fix.

  With the stashed fix applied: clean full round trip, value persists —

  db_read_multi amplitude       = 3f800000  (1.0)
  db_write_multi NO-OP          -> read back 3f800000
  db_write_multi CHANGE -> 2.0  -> read back 40000000  (2.0)  ✓ sticks
  RESTORE original              -> read back 3f800000  (1.0)  ✓

  CPU stayed in RUN throughout; original value restored.

Would you rather have me try to find a BLOB datatype to write instead, then tackle datatype handling in a different PR?

Co-Authored-By: Codex <noreply@openai.com>
@gijzelaerr

Copy link
Copy Markdown
Owner Author

Implemented the hardware-test findings in 6ad0fa5.

The fixed literal after ObjectQualifier was acting as a second IntegrityId once the connection inserted its tracked counter. All multi/area/symbolic read and write builders now leave IntegrityId insertion exclusively to the connection.

Multi-write items can now specify the PLC datatype as a fourth tuple element, for example:

client.db_write_multi([
    (7, 0, struct.pack(">f", 2.0), DataType.REAL),
    (7, 4, b"\x02\x00", DataType.WORD),
])

Existing three-tuples remain BLOB writes for compatibility. Explicit fixed-width types are length-validated, and the same typed encoding is used by the legacy substreamed fallback.

Validation: 1,779 passed, 82 skipped; full pre-commit suite and source/wheel build pass. A real-PLC round trip with the explicit datatype form would be the remaining useful confirmation.

Co-Authored-By: Codex <noreply@openai.com>

@bvanelli bvanelli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested the code against a live PLC. It worked for writing a REAL. I can try writing an integer, but according to the reference, it needs different encoding.

Regarding the WithIntegrityId flag, I wonder if we could do some testing with the TIA portal to confirm communication behaviour.

Comment thread tests/test_s7_codec.py
Comment thread s7commplus/client.py
@gijzelaerr
gijzelaerr requested a review from bvanelli September 8, 2026 05:45
@bvanelli

bvanelli commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

LGTM. Some details from hardware testing:

  • Datatype does not seem to be optional, and the PLC refuses the DataType.BLOB. Multi-write cannot be done DB-wide, so the datatype should be mandatory. Testing from writing a BLOB to:
    • INT target → return value 0xa2028a000675ffe2, low word −30 (type mismatch)
    • BYTE target → 0xa201be000162ff79, low word −135
  • I have hardware tested writing a INT, DINT, UDINT, and WORD, and all four went through.

@gijzelaerr
gijzelaerr merged commit 383852f into master Sep 8, 2026
20 checks passed
@gijzelaerr

Copy link
Copy Markdown
Owner Author

Thanks @bvanelli — the INT, DINT, UDINT, and WORD results provide the integer hardware confirmation we were missing. Your BLOB rejection result also means the three-tuple default is a misleading contract for scalar targets.

I have implemented a follow-up locally that requires (db_number, start_offset, data, datatype) in both multi-write clients and rejects missing datatypes before sending, including before the first request in the synchronous substreamed fallback. Explicit BLOB remains possible for targets that actually support it; this does not imply DB-wide BLOB writes work on the tested PLC.

The same follow-up adds datatype= to sync/async symbolic and area writes, addressing the related type-mismatch lead in #844. Existing single-write signatures and their BLOB defaults remain compatible, while the examples now specify the target type.

Local validation: 1,944 tests passed, 78 skipped; all pre-commit hooks and source/wheel builds passed. The broader mypy run has 207 existing errors versus 208 on unchanged master, with no new diagnostics. The branch is not published yet; I will link the reviewable PR when it is available.

@gijzelaerr

Copy link
Copy Markdown
Owner Author

The datatype follow-up is now available in #873, commit 232f0d4. It requires explicit four-element multi-write tuples and adds typed symbolic/area writes to both clients.

Install the tested revision with:

python -m pip install 'python-snap7[s7commplus] @ git+https://github.com/gijzelaerr/python-snap7.git@232f0d41d66ef76762f6d5c5b71fc2082792ecf7'

@bvanelli could you review the explicit-type contract and, when convenient, confirm an existing typed integer test still works on this revision? Your previous INT/DINT/UDINT/WORD hardware results remain recorded; the new regression checks cover the public sync/async APIs and reject missing types before any multi-write is sent.

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.

S7CommPlus multi-write (SetMultiVariables for data)

2 participants