The Equipment Stamps Your S6F11, Not Your Host — Checking That Clock With S2F17 and S2F31
Equipment clock drift reorders S6F11 events while HSMS stays green. Reading the clock with S2F17, setting it with S2F31, and why TIACK 0 proves nothing.
You reconstruct one lot and Process Start is stamped 40 seconds after Process Complete. The host log shows no socket drop and every Linktest passed. HSMS says SELECTED, GEM says COMMUNICATING. Nothing anywhere is red.
It is usually the clock. The time on an S6F11 event report is often the time the equipment stamped, not the time your host received it, and equipment clocks run for months without ever seeing NTP. An industrial PC losing two seconds a day is six minutes off after half a year. Six minutes is enough to invert the order of reports.
Two message pairs give the host a way in. S2F17/S2F18 reads the clock, S2F31/S2F32 sets it.
A drifting clock keeps the link green
That is the shape of this problem. HSMS never validates a time. None of T3, T5, T6, T7 or T8 looks at the equipment clock — every one of those timers counts elapsed time, never an absolute instant. A tool six minutes out is still SELECTED and still COMMUNICATING.
What breaks is further down, where the MES lays events on a time axis. If tool A is accurate and tool B runs six minutes fast, the history of a wafer that passed through both is stored out of order. Start suspecting the report definitions and you will read the whole event report link structure and find nothing wrong, because nothing there is wrong.
So the host-side rule is short. One S2F17 right after S1F13, then one on a schedule. I use eight hours. Even if you only record the difference against the host clock and act on none of it, you end up with the line that settles the argument later: this tool was three minutes fast that week.
The bytes that actually went over
This is one socket opened against the HSMS listener of the SECS/GEM simulator and run straight through. Not synthesised — these are the frames that crossed the socket. The simulator is our own tool, published for exactly this, and it plays the equipment here. The two things it does below — answering S2F18 in ISO 8601, and returning TIACK 0 in S2F32 without touching the RTC — are both things real equipment does. This capture is where you get to see them on the wire.
Select.req and S1F13 just get you into position. S1F13 needs the W-bit to move the state to COMMUNICATING — SELECTED and COMMUNICATING are different states.
TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 04 01
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 04 01
TX S1F13 W 00 00 00 0C 00 0B 81 0D 00 00 00 00 04 02 01 00
RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 04 02 01 02 21 01 00 …
Reading the header: SessionID 00 0B (11), byte 2 carries the stream with the W-bit (81 is stream 1, W-bit set), byte 3 the function (0D is 13), and the last four bytes are the SystemBytes. That is the SEMI E37 header layout.
Now read the clock.
TX S2F17 W 00 00 00 0A 00 0B 82 11 00 00 00 00 04 03
RX S2F18 00 00 00 24 00 0B 02 12 00 00 00 00 04 03
41 18 32 30 32 36 2D 30 39 2D 30 37 54 30 30 3A 30 35 3A 34 32 2E 31 37 36 5A
S2F17 has no body. The length is 00 00 00 0A — the ten header bytes and nothing else. 82 is stream 2 with the W-bit, 11 is function 17.
The body that comes back is the point of this article. 41 is the SECS-II item format byte for ASCII with one length byte, 18 is the length: 24. Read those 24 bytes as latin1 and you get 2026-09-07T00:05:42.176Z.
Twenty-four characters. It is a well-formed ISO 8601 string, and it is not a length a host parser expects from a SECS-II TIME item. What implementations actually accept is YYMMDDhhmmss at 12 characters and YYYYMMDDhhmmsscc at 16. I have not verified those two lengths against the SEMI E5 standard text, so treat them as unverified. What is certain is the 24 bytes in this capture, and that a parser slicing a fixed 12 or 16 characters off the front will insist 2026-09-07T0 is a timestamp.
The host-side move: branch on the length before you parse. If it is neither 12 nor 16, do not interpret it as a time — log it verbatim and raise. Better than quietly parsing garbage into the MES.
TIACK 0 in S2F32 does not mean the clock moved
Having read it, try setting it. S2F31 carrying a 16-character TIME.
TX S2F31 W 00 00 00 1C 00 0B 82 1F 00 00 00 00 04 04
41 10 32 30 32 36 30 39 30 37 31 30 31 35 30 30 30 30
RX S2F32 00 00 00 0D 00 0B 02 20 00 00 00 00 04 04 21 01 00
41 10 is ASCII, 16 bytes, and the content is 2026090710150000 — 10:15, more than ten hours ahead of the 00:05 the equipment had just reported. The answer is 21 01 00: a binary item, one byte, value 0. TIACK 0, accepted.
Then read it again immediately.
TX S2F17 W 00 00 00 0A 00 0B 82 11 00 00 00 00 04 05
RX S2F18 00 00 00 24 00 0B 02 12 00 00 00 00 04 05
41 18 32 30 32 36 2D 30 39 2D 30 37 54 30 30 3A 30 35 3A 34 32 2E 36 37 38 5A
00:05:42.678Z. The first read said 00:05:42.176Z, so 502 ms passed and nothing else. No jump to 10:15. TIACK 0 came back and the clock is unchanged.
This is the mine people step on. The equipment answers TIACK 0 meaning "syntactically received" and never touches the RTC. The host log records "clock set OK" and the event ordering breaks again the following week.
So after every S2F31, send another S2F17 and compare the value. That is not defensive coding, it is the only verification available. TIACK is the equipment's claim; the second S2F18 is the evidence.
One more thing checked here. An S2F31 carrying 14 characters — neither 12 nor 16 — draws the same answer.
TX S2F31 W 00 00 00 1A 00 0B 82 1F 00 00 00 00 04 06
41 0E 32 30 32 36 30 39 30 37 31 30 31 35 30 30
RX S2F32 00 00 00 0D 00 0B 02 20 00 00 00 00 04 06 21 01 00
41 0E is ASCII, 14 bytes. TIACK 0. No length validation at all. TIACK 0 is not evidence that your format was right.
Break the encoding and S2F32 stops coming
Damage the item itself and the answer changes shape. Here the length byte declares 12 and only two bytes follow.
TX S2F31 W 00 00 00 0E 00 0B 82 1F 00 00 00 00 04 07 41 0C 32 30
RX S9F7 00 00 00 16 00 0B 09 07 00 00 00 00 03 EA
21 0A 00 0B 82 1F 00 00 00 00 04 07
Not S2F32 but S9F7 Illegal Data. The body is a ten-byte binary item (21 0A), and those ten bytes are the offending message's own header: 00 0B 82 1F 00 00 00 00 04 07. That is MHEAD.
Look at the SystemBytes: the request carried 00 00 04 07 and the S9F7 arrived with 00 00 03 EA. That is correct, not a defect. An S9Fx is not a reply — it is a new primary message the equipment opens, so it carries its own SystemBytes, and the pointer back to the offending message lives in the MHEAD body item rather than in the header. Stream 9 error messages are built that way.
Which means that from the host's side the S2F31 transaction is still unanswered, and its T3 runs all the way out. A host with no stream 9 handling ends up with one line in the log saying "no response", when the message explaining why had already arrived.
So take stream 9 on a path separate from SystemBytes matching: pull the stream, function and SystemBytes out of the ten MHEAD bytes and fail the waiting transaction on the spot. Then there is nothing to wait for T3 to tell you.
Timezone is not on the wire
Neither S2F18 nor S2F31 has a timezone field. At 12 or 16 characters there is no room for one. The equipment sends its local time and the host has no way to know which zone that is.
The value in this capture ends in Z, so it is UTC — but only because this implementation writes ISO 8601, not because SECS-II carries a zone. The moment a tool answers in the standard format, the zone is gone.
What that produces in practice:
- Host on UTC, equipment on KST. Nine hours apart, caught within a day.
- Both on local time in a fab that observes DST. Twice a year you get a one-hour hole and a one-hour duplicate. A lot that finished during that hour in March has a start time later than its end time.
- Equipment reporting UTC while the host reads it as local. The offset is identical every day, so it never looks like drift — it becomes "that tool is just like that".
One answer on the host side. Keep the zone per equipment as configuration. Do not infer what is not on the wire; put timezone: Asia/Seoul in the equipment table and apply it to the S2F18 value. Use an IANA name rather than a fixed offset for any zone with DST — +09:00 is right in Korea and wrong the moment this code is copied to another fab.
Host-side checklist
- Send one S2F17 as soon as S1F13 comes back with COMMACK 0. If there is no reply, or an SxF0, drop that tool from clock sync and record that you did.
- Look at the length of the S2F18 body first. Branch on 12, 16, other. Anything else does not get parsed; it gets logged verbatim.
- Compute the difference against the host clock and store it per tool. The threshold depends on the line, but if you report wafer-level events, anything past two seconds is worth looking at.
- Only send S2F31 to tools you are allowed to. What happens to a running timer when the clock jumps mid-recipe is something only the equipment vendor knows.
- Always follow S2F31 with another S2F17 and confirm the value actually moved. Never trust TIACK 0 alone.
- Handle S9F7 in a stream 9 handler and use MHEAD to fail the original transaction. Otherwise an encoding error disguises itself as a T3 timeout.
- If a tool is confirmed to ignore S2F31, correct with an offset on the host side. The MES has to keep running while you wait for the vendor.
Do not live on item 7 for long. An offset correction only holds while the drift is linear, and the rate an RTC loses time changes with temperature. When the season turns, so does your correction.
To reproduce any of this, point a socket at the HSMS listener in the SECS/GEM simulator and send the frames above verbatim. Every hex line here came from there.