An S7F6 Came Back With No Recipe In It — Check the Body, Not the Function Number
S7F5 came back as S7F6 — body 21 01 00, an ack and not a PPBODY. A real capture, and the reply check most host parsers skip.
I wrote the host side that pulls a recipe body off the equipment. S7F5 went out with the PPID in it, and S7F6 came back. Stream 7, function 6, SystemBytes exactly what I sent. All of that is right. Then reading the PPBODY out of it blows up. The whole body is three bytes — 21 01 00 — and that is not a list, it is a B of one byte, value 0. An ack arrived where a recipe was supposed to be.
The shape E5 asks of an S7F6
Stream 7 in SEMI E5 is the process program transfer family, and it splits by direction. The push from host to equipment is S7F3 Process Program Send, whose body is a list of two items, PPID and PPBODY. The equipment answers S7F4 with a single ACKC7. ACKC7 0 means it took the body.
The pull is S7F5 Process Program Request. Its body is one PPID. The answer, S7F6 Process Program Data, carries a body that is a list of two items, PPID and PPBODY. So an S7F4 body and an S7F6 body have nothing in common except an even function number. One is a single code byte; the other is a list with a recipe in it.
Which means an S7F6 of one B byte is not "denied". E5 has no S7F6 of that shape. It is a parse failure.
One thing stated plainly: I did not verify what a conformant S7F6 — L,2 {PPID, PPBODY} — looks like byte for byte in this run. Everything in the capture below is the broken side, and the two-item list requirement comes from E5, not from a tool I drove. I also did not verify what equipment sends when it genuinely refuses the request; that varies by implementation.
The capture
I started the simulator's passive listener and pushed frames at it from outside over a plain socket. SessionID 11, port 5501. Only what the equipment side logged as RX is reproduced here.
Select first.
TX 00 00 00 0A 00 0B 00 00 00 01 00 00 02 01
RX 00 00 00 0A 00 0B 00 00 00 02 00 00 02 01
SType 1 Select.req, SType 2 Select.rsp. Byte 3 is 00, so it was accepted. SystemBytes 00 00 02 01 came back unchanged. Data messages are allowed from here on.
Then S1F13. It has to go first to clear the E30 communication gate — why a data message sent before S1F13 comes back as SxF0 covers that gate on its own.
TX 00 00 00 0C 00 0B 81 0D 00 00 00 00 02 02 01 00
RX 00 00 00 37 00 0B 01 0E 00 00 00 00 02 02
01 02 21 01 00 01 02 41 15 "VX-9000 Plasma Etcher" 41 0D "SECSGEM-1.4.1"
That reply has the right shape. It opens 01 02, so L,2; the first item is 21 01 00, a COMMACK 0; the second is another L,2 holding MDLN and SOFTREV. 41 is the format byte for ASCII and 15 is a length of 21 bytes. Worth holding on to: this same equipment does return a proper list in S1F14. It is the comparison for later.
Now S7F5.
TX 00 00 00 15 00 0B 87 05 00 00 00 00 02 03 41 09 45 54 43 48 5F 42 41 53 45
RX 00 00 00 0D 00 0B 07 06 00 00 00 00 02 03 21 01 00
The request body is 41 09 followed by nine bytes of ETCH_BASE, a PPID that really is loaded on the tool. The reply length is 13 — ten bytes of header, three of body. That body is 21 01 00. The top six bits of format byte 21 are format code 8, binary, and the bottom two are the count of length bytes, one. 01 is one byte of data and 00 is the value. A list has format code 0 and its format byte shows up as 01. The first body byte alone settles it. How to read a format byte is its own article: SECS-II item headers and length bytes.
Last, an S7F3, for the comparison.
TX 00 00 00 1B 00 0B 87 03 00 00 00 00 02 04 01 02 41 09 45 54 43 48 5F 46 41 53 54 21 02 00 01
RX 00 00 00 0D 00 0B 07 04 00 00 00 00 02 04 21 01 00
The body is byte-for-byte identical to the S7F6 body. And here it is correct. An S7F4 body is a single ACKC7, and 21 01 00 is ACKC7 0. The same three bytes are the right answer in S7F4 and an error in S7F6. "An ack came back, so it worked" does not hold anywhere in stream 7.
What answered me is the public simulator's listener, not a tool. I have no idea how many real pieces of equipment would ack an S7F5. What matters is what host code does when it meets a reply like this one.
The check to put in the host
The order I keep in an S7 parser:
- Is SType 0. If it is not a data message, stop there.
- Do the SystemBytes match what I sent. That is what pairs a reply to a request — pipelined requests match on SystemBytes.
- Is the function number the one I expected. Most code stops here and moves on. That is the bug.
- Is the top-level item an L,2. If the first body byte is not
01, there is no PPBODY in there. Filter it before indexing. - Is the second item's type something my PPBODY decoder can actually take.
I would nail step 4 down with an assert. The worst parser is the one that quietly hands back an empty recipe: the MES screen says "download complete" and the recipe table gains one blank row. You find that weeks later.
E5 has stream 9 for exactly this situation — S9F7 Illegal Data, sent when a received message cannot be interpreted. The direction rules, and which S9 message complains about what, are in SECS-II stream 9 error messages. Whether you send it is host policy, but the log must at minimum carry "the S7F6 body was not an L", with the bytes. Next time you are on the phone with the equipment vendor, those three bytes are the only evidence you have.
Every frame above is reproducible. Start the listener and send the same bytes over a socket: SECS/GEM simulator.