You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
USend / URecv and BSend with up to 65,534 bytes #981
Thanks, @hs2bws, this sounds really useful. USend/URecv and fragmented BSend/BRecv are gaps in partner.py today, and testing against an S7-1500 is great.
Could you open it as pull requests rather than an attached file? That way it can be reviewed and tested in CI, and you keep the credit. Smaller PRs are much easier to review, so ideally one per feature:
fragmented BSend/BRecv up to 65,534 bytes;
USend/URecv;
the helpers (set_socket_timeout, check_status, set_send_data, get_recv_data_r_id).
Tests can use two Partner instances talking to each other over localhost; tests/test_partner.py has examples. Each PR also needs an entry under Unreleased in CHANGES.md. CLAUDE.md in the repo lists the checks to run.
Thanks for opening #983–#986, @hs2bws. One note on CI first: the checks on those PRs show green, but the test workflows haven't run yet. GitHub holds workflows on a first-time contributor's PRs until a maintainer approves them. So here is what the diffs show, all in one place:
The four PRs aren't independent yet. Each is a slice of your rewritten partner.py, cut against master, so they overlap and depend on each other:
Update partner.py Urcv + Brcv fragmented #984 also changes the public set_recv_callback callback from one argument to five. That would break existing users, so it needs to be a new, opt-in callback (or keep the old one working).
Every PR still needs:
tests: two Partner instances on localhost, for example a 2,000-byte BSend arriving intact at BRecv, and a USend with 1 and with 4 data blocks;
an entry under Unreleased in CHANGES.md;
uv run pre-commit run --all-files, which will fix the trailing whitespace and flag the == False comparisons. mypy snap7 must pass too.
Suggested order, each PR building on the one before:
fragmented BSend and BRecv together, with a round-trip test;
USend/URecv on top of that, keeping set_send_data(data) / get_recv_data() working for single-block BSend;
the helpers.
It would be easiest to close #983–#986 and open the first of these, rebased on master. Happy to help if anything about the test setup is unclear. The fragmentation and USend support are welcome, and the S7-1500 testing is especially valuable.
CI now runs on #983–#986. I've approved the workflows, so each PR shows its real test and pre-commit results.
Backwards compatibility is a hard requirement. Existing Partner users must keep working unchanged. Specifically:
set_recv_callback (Update partner.py Urcv + Brcv fragmented #984): a callback registered today takes one bytes argument. After the change it would be called with five arguments, and existing callbacks would raise TypeError. Please leave that callback as it is, and add the multi-block data some other way. A separate set_recv_callback_ex() (or similar) for the five-value form would work, or get_recv_data_r_id() after the callback fires.
set_send_data(data) / get_recv_data() / b_send() / b_recv(): these keep their signatures in your PRs, good. A single-block BSend must still behave exactly as before.
The BSend frame for an unfragmented send (Update partner.py -> fragmented BSend up to 65,534 bytes #983): the sequence number, data-unit reference and last-data-unit fields change even when nothing is fragmented. If the S7-1500 needs those values, a short Wireshark capture of a CPU's own BSEND would justify it. Without one, please keep today's frame for sends that fit in one PDU, and use the new fields only for fragmented transfers.
New features are additions:u_send(), the fragmentation, and the helpers as new methods or new optional parameters, with no existing public method changing its signature or return type.
A test that registers a one-argument callback and does a plain BSend/BRecv round trip on top of your changes would show this stays intact.
I am not a professional programmer; this is just a hobby. And this is my first experience using GitHub, so...
I understand the requirement for backward compatibility. The code adjustments were originally intended for my own personal use, where that wasn't a priority. I will try to implement the necessary changes.
The approach using set_recv_callback_ex() with separate handlers for usend and urcv definitely makes sense.
Creating independent pull requests (PRs) for fragmented bsend/brcv and for usend/urcv will be difficult, as both the _parse_partner_data_pdu function and the _recv_listener would need to be modified for both enhancements.
Furthermore, a separate listener/task for urcv does not seem practical to me. The advantage of a single listener (or recv function) is that the PLC can send a mix of bsend and usend messages, and Snap7 can handle both seamlessly. This is why b_recv also responds to urcv transmissions.
Regarding the sequence number, data-unit reference, and "last-data-unit" flag:
I set up two PLCs to communicate, captured the network packets, and analyzed them using Wireshark. I then attempted to replicate the packet structure as accurately as possible. packet capture.zip
-1 First Bsend frame (fragmented or unfragmented):
Sequence number = 0
Data-unit reference -> increments from 0x01 to 0xff
Last-data-unit -> 0x00 = yes (unfragmented), 0x01 = no (fragmented)
-2 Ack from partner:
sequence number -> increments from 0x01 to 0xff
data-unit reference = 0
-3 Next Bsend frame
sequence number = remains the same as in the Ack for all frames
data-unit reference -> same as in the first frame
last-data-unit -> 0x00 = yes, 0x01 = no
-4 Next Ack from partner:
sequence number -> remains the same as in the first Ack for all Acks
data-unit reference = 0
...
Thanks, @hs2bws, and no worries about being new to GitHub. The work is useful and the capture is exactly what was missing.
Splitting. The PRs don't have to be independent, only ordered. The first one changes _parse_partner_data_pdu and _recv_listener for fragmented BSend/BRecv. The second one starts from that branch and extends the same functions for USend/URecv. Two PRs is fine; the helpers can go in whichever one they belong to.
One listener. Agreed, keep a single listener that handles both. I didn't mean a second thread, only a second way to hand the data to the caller: set_recv_callback() keeps getting one bytes argument, and set_recv_callback_ex() gets the blocks, the block count and the R-ID. b_recv() returning USend data as well is fine, as long as a plain BSend still comes back exactly as it does today.
Frame fields. I went through the capture and it justifies the change: the CPU's own unfragmented BSend has sequence number 0, an incrementing data-unit reference and last-data-unit 0, so only the data-unit reference differs from today's frame. The fragmented transfer matches your description too, with one detail: in bsend fragmented.pcap the receiving CPU's ack sequence number doesn't count up. It is 0xc4 for one whole transfer and 0x44 for the next. The 0xa4, 0xa5, … values are in the capture where the Pi sends the acks. So the sender should echo whatever sequence number the ack carries and not expect a particular value.
Please put a few of the capture's frames into tests as raw bytes (a first, a middle and a last fragment, an ack, and the one-block and four-block USend), so the frames stay pinned to what the CPUs sent.
Interesting; I hadn’t even noticed that the sequence number alternates between these two values. Now the question arises: are these always the values 0xc4 and 0x44? Is it a coincidence that the two values differ by only a single bit (1100 0100 vs. 0100 0100)? What values should b_recv() use for an ack? I will attempt to generate a packet capture using a different PLC configuration to see if that provides any answers.
By the way, the file bsend fragmented.pcap contains the communication between two S7 PLCs - an S7-300 (192.168.0.180) and an S7-1500 (192.168.0.100) - that are in use within a real-world industrial environment.
Activity
gijzelaerr commentedon Nov 9, 2026
Thanks, @hs2bws, this sounds really useful. USend/URecv and fragmented BSend/BRecv are gaps in
partner.pytoday, and testing against an S7-1500 is great.Could you open it as pull requests rather than an attached file? That way it can be reviewed and tested in CI, and you keep the credit. Smaller PRs are much easier to review, so ideally one per feature:
set_socket_timeout,check_status,set_send_data,get_recv_data_r_id).Tests can use two
Partnerinstances talking to each other over localhost;tests/test_partner.pyhas examples. Each PR also needs an entry underUnreleasedinCHANGES.md.CLAUDE.mdin the repo lists the checks to run.gijzelaerr commentedon Nov 11, 2026
Thanks for opening #983–#986, @hs2bws. One note on CI first: the checks on those PRs show green, but the test workflows haven't run yet. GitHub holds workflows on a first-time contributor's PRs until a maintainer approves them. So here is what the diffs show, all in one place:
The four PRs aren't independent yet. Each is a slice of your rewritten
partner.py, cut againstmaster, so they overlap and depend on each other:_send_datato_send_data1and add the same__init__lines, so whichever merges second conflicts.get_recv_data_r_id()reads_recv_data_count, which only Update partner.py Urcv + Brcv fragmented #984 sets.They break existing tests, which CI will show once it runs:
_build_partner_data_pdufour new required arguments and makes it return a tuple.tests/test_partner.pycalls it as_build_partner_data_pdu(data)in six places._parse_partner_data_pdureturn an 11-tuple. Seven tests unpack three values.set_recv_callbackcallback from one argument to five. That would break existing users, so it needs to be a new, opt-in callback (or keep the old one working).Every PR still needs:
Partnerinstances on localhost, for example a 2,000-byte BSend arriving intact at BRecv, and a USend with 1 and with 4 data blocks;UnreleasedinCHANGES.md;uv run pre-commit run --all-files, which will fix the trailing whitespace and flag the== Falsecomparisons.mypy snap7must pass too.Suggested order, each PR building on the one before:
set_send_data(data)/get_recv_data()working for single-block BSend;It would be easiest to close #983–#986 and open the first of these, rebased on
master. Happy to help if anything about the test setup is unclear. The fragmentation and USend support are welcome, and the S7-1500 testing is especially valuable.gijzelaerr commentedon Nov 11, 2026
Two follow-ups, @hs2bws:
CI now runs on #983–#986. I've approved the workflows, so each PR shows its real test and pre-commit results.
Backwards compatibility is a hard requirement. Existing
Partnerusers must keep working unchanged. Specifically:set_recv_callback(Update partner.py Urcv + Brcv fragmented #984): a callback registered today takes onebytesargument. After the change it would be called with five arguments, and existing callbacks would raiseTypeError. Please leave that callback as it is, and add the multi-block data some other way. A separateset_recv_callback_ex()(or similar) for the five-value form would work, orget_recv_data_r_id()after the callback fires.set_send_data(data)/get_recv_data()/b_send()/b_recv(): these keep their signatures in your PRs, good. A single-block BSend must still behave exactly as before.u_send(), the fragmentation, and the helpers as new methods or new optional parameters, with no existing public method changing its signature or return type.A test that registers a one-argument callback and does a plain BSend/BRecv round trip on top of your changes would show this stays intact.
hs2bws commentedon Nov 11, 2026
Thank you for the reviews and comments.
I am not a professional programmer; this is just a hobby. And this is my first experience using GitHub, so...
I understand the requirement for backward compatibility. The code adjustments were originally intended for my own personal use, where that wasn't a priority. I will try to implement the necessary changes.
The approach using
set_recv_callback_ex()with separate handlers forusendandurcvdefinitely makes sense.Creating independent pull requests (PRs) for fragmented
bsend/brcvand forusend/urcvwill be difficult, as both the_parse_partner_data_pdufunction and the_recv_listenerwould need to be modified for both enhancements.Furthermore, a separate listener/task for
urcvdoes not seem practical to me. The advantage of a single listener (orrecvfunction) is that the PLC can send a mix ofbsendandusendmessages, and Snap7 can handle both seamlessly. This is whyb_recvalso responds tourcvtransmissions.Regarding the sequence number, data-unit reference, and "last-data-unit" flag:
I set up two PLCs to communicate, captured the network packets, and analyzed them using Wireshark. I then attempted to replicate the packet structure as accurately as possible.
packet capture.zip
-1 First Bsend frame (fragmented or unfragmented):
Sequence number = 0
Data-unit reference -> increments from 0x01 to 0xff
Last-data-unit -> 0x00 = yes (unfragmented), 0x01 = no (fragmented)
-2 Ack from partner:
sequence number -> increments from 0x01 to 0xff
data-unit reference = 0
-3 Next Bsend frame
sequence number = remains the same as in the Ack for all frames
data-unit reference -> same as in the first frame
last-data-unit -> 0x00 = yes, 0x01 = no
-4 Next Ack from partner:
sequence number -> remains the same as in the first Ack for all Acks
data-unit reference = 0
...
gijzelaerr commentedon Nov 12, 2026
Thanks, @hs2bws, and no worries about being new to GitHub. The work is useful and the capture is exactly what was missing.
Splitting. The PRs don't have to be independent, only ordered. The first one changes
_parse_partner_data_pduand_recv_listenerfor fragmented BSend/BRecv. The second one starts from that branch and extends the same functions for USend/URecv. Two PRs is fine; the helpers can go in whichever one they belong to.One listener. Agreed, keep a single listener that handles both. I didn't mean a second thread, only a second way to hand the data to the caller:
set_recv_callback()keeps getting onebytesargument, andset_recv_callback_ex()gets the blocks, the block count and the R-ID.b_recv()returning USend data as well is fine, as long as a plain BSend still comes back exactly as it does today.Frame fields. I went through the capture and it justifies the change: the CPU's own unfragmented BSend has sequence number 0, an incrementing data-unit reference and last-data-unit 0, so only the data-unit reference differs from today's frame. The fragmented transfer matches your description too, with one detail: in
bsend fragmented.pcapthe receiving CPU's ack sequence number doesn't count up. It is0xc4for one whole transfer and0x44for the next. The0xa4,0xa5, … values are in the capture where the Pi sends the acks. So the sender should echo whatever sequence number the ack carries and not expect a particular value.Please put a few of the capture's frames into tests as raw bytes (a first, a middle and a last fragment, an ack, and the one-block and four-block USend), so the frames stay pinned to what the CPUs sent.
hs2bws commentedon Nov 12, 2026
Interesting; I hadn’t even noticed that the sequence number alternates between these two values. Now the question arises: are these always the values 0xc4 and 0x44? Is it a coincidence that the two values differ by only a single bit (1100 0100 vs. 0100 0100)? What values should b_recv() use for an ack? I will attempt to generate a packet capture using a different PLC configuration to see if that provides any answers.
By the way, the file
bsend fragmented.pcapcontains the communication between two S7 PLCs - an S7-300 (192.168.0.180) and an S7-1500 (192.168.0.100) - that are in use within a real-world industrial environment.