HSMS Select Timeout: Three Causes That Look Identical in the Host Log
Real captures of the three ways a Select gets no usable answer — silence, mismatched SystemBytes, and a wrong SessionID — and the SEMI E37 timers T5, T6 and T7 that decide what your log shows.
What the host logs is usually one line.
[HSMS] EQ ETCH-01 10.20.4.11:5000 select timeout, retrying (3)
Mail that line to the equipment vendor and the answer is normally "we did reply" — and sometimes that is true. TCP is up, the equipment wrote a Select.rsp to the socket, the frame reached the host's NIC. The host stack then threw it away. Why it threw it away is not in the log. "Timeout" is.
What separates the three is whether a reply frame arrived at all, and if it did, which field is wrong. The diagram below is that order of checks; the rest of this article is the hex, taken off a real socket.
Every hex string below was actually on a socket. EQ1 on the SECS/GEM simulator ran as a passive listener on 127.0.0.1:5501, driven from outside by a plain Python socket client. One fault knob was switched on per cause, each over a fresh TCP connection, and only frames the equipment side logged as RX are shown. The times are measured at the client, and the raw export is committed at content/demos/hsms-select-no-answer-three-failures.json. Captured 2026-09-17.
What a good Select looks like
Select.req and Select.rsp carry no body. Four length bytes, then the 10-byte SEMI E37 message header, and that is the whole message — which is why the length field on a control message is always 10.
This round trip was taken with faults at {}.
06:03:00.213 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 11 01
06:03:00.225 RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 11 01 11.9 ms
00 00 00 0A Length = 10 (header only, no body)
00 0B bytes 0-1 SessionID = 11
00 byte 2 zero, because this is a control message
00 byte 3 Select Status in a Select.rsp. 0 = accepted
00 byte 4 PType = 0 (SECS-II)
01 / 02 byte 5 SType = 1 Select.req / 2 Select.rsp
00 00 11 01 bytes 6-9 SystemBytes = 0x00001101, echoed unchanged
E37 numbers the header bytes from zero: the SessionID is bytes 0–1 and the SystemBytes are bytes 6–9. Count them from one and the checklist at the end of this article points you at the wrong byte.
The 11.9 ms is the first connection after the listener came up; the same round trip later in the same capture took 2.9 ms. Either way it is milliseconds.
Two things in the reply matter to the host: is the SessionID the one I sent, and are the SystemBytes echoed unchanged. SystemBytes are what E37 uses to pair a transaction. If either field is off, a correctly written host stack refuses to treat the frame as the answer to its request — which means it drops it, and very few stacks log a drop.
A successful Select starts nothing on the GEM side. Select is an E37 transaction; E30's communication establishment (S1F13/S1F14) only happens once the session is SELECTED. That distinction is written up in SELECTED is not the same as COMMUNICATING.
My Select.req uses SessionID 11, the number configured on the equipment. Whether E37 wants FF FF in the SessionID of Select.req and Select.rsp is not something these captures can settle — I am leaving it marked unverified. What is confirmed is the Linktest case, where E37 uses the session-independent FF FF (a tool that answers Linktest but never completes Select). Use the number your interface spec gives you.
Cause 1 — nothing actually comes back
Taken with the silentAfterSelect fault on. The client waited 12 seconds.
06:03:00.442 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 11 02
RX (none — the client gave up after 12,011.7 ms)
00 00 00 0A / 00 0B / 00 / 00 / 00 / 01 / 00 00 11 02
Length 10, SessionID 11, byte 2 = 0, byte 3 = 0, PType 0, SType 1, SystemBytes 0x00001102
The equipment-side log has the RX. The frame arrived; it just was not acted on.
2026-09-17T06:03:00.445Z RX 00 00 00 0A 00 0B 00 00 00 01 00 00 11 02
2026-09-17T06:03:00.445Z FAULT Select.req ignored (silentAfterSelect)
In those 12 seconds the listener never closed the socket either. On E37's defaults both T6 (5 s) and T7 (10 s) had already run out. This simulator runs neither timer — read that as evidence that a simulator is not a substitute for the standard. Real equipment should have dropped the connection by then. The 12,011.7 ms is my client's own patience, not a value of T6 or T7.
In the field this shape usually means one of three things: the equipment's comms process is up but not finished initialising, a previous session was never torn down and the tool still believes it is SELECTED, or a firewall that permits the TCP handshake and drops the payload direction. That last one comes up more than you would expect — if the connect succeeds and nothing ever comes back after it, check the firewall before you check the tool.
For the last two of those, the silence is itself a spec violation. E37 answers a refused Select with a Select.rsp anyway, carrying a non-zero Select Status in byte 3. No answer at all is an equipment bug, or a frame dying somewhere upstream of it. Putting that sentence in the vendor ticket changes the reply you get.
The host-side fix is the retry interval, not the retry count. That is what E37's T5 (Connect Separation Timeout, 10 s default) exists for. Hammering reconnects in a tight loop burns socket resources on the equipment and makes a tool that was mid-initialisation come up later, not sooner. Select is a control transaction, so the timer the host is actually waiting on is T6 (Control Transaction Timeout, 5 s default, range 1–240 s). When T6 expires, drop the connection and wait out T5 before trying again. T7 (NOT SELECTED Timeout, 10 s default, range 1–240 s) is the backstop behind it, and the side running T7 is usually the passive equipment waiting for a Select.req that never comes. What each timer does on expiry is in which HSMS timer just fired.
If the bytes stop mid-frame rather than never starting, the timer at issue is T8 (Network Intercharacter Timeout, 5 s default), not T6. Length prefixes and frame reassembly are their own subject.
Cause 2 — the reply arrives with the wrong SystemBytes
The wrongSystemBytes fault. This knob adds one to the request's SystemBytes and sends that back.
06:03:12.663 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 11 03
06:03:12.665 RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 11 04 1.9 ms
^^^^^^^^^^^
00 00 00 0A Length 10
00 0B bytes 0-1 SessionID 11 — matches what was sent
00 byte 2 0
00 byte 3 Select Status 0 = accepted
00 byte 4 PType 0
02 byte 5 SType 2 Select.rsp
00 00 11 04 bytes 6-9 SystemBytes — sent 0x00001103, received 0x00001104
A structurally perfect Select.rsp: SType 2, right SessionID, Select Status 0. It pairs with nothing. The equipment log records it as a normal TX ... Select.rsp.
The thing that separates this from cause 1 is time. The reply landed in 1.9 ms. Cause 1 makes the host sit there until T6 expires. Both produce the same "select timeout" line in the application log, but at the socket level one is an immediate frame and the other is silence. That difference is the entire diagnosis.
The usual reason a tool gets SystemBytes wrong is that its stack generates the response from its own counter instead of echoing the request — older firmware, or an implementation running one global counter across several host sessions. The simulator's fault does exactly that, +1. From the vendor's side "we sent a response" is genuinely true, so the conversation does not move until you attach the capture.
Cause 3 — the SessionID does not match
The wrongSessionId fault, which replies with the equipment's SessionID plus one.
06:03:12.873 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 11 04
06:03:12.875 RX Select.rsp 00 00 00 0A 00 0C 00 00 00 02 00 00 11 04 1.7 ms
^^^^^
00 00 00 0A Length 10
00 0C bytes 0-1 SessionID 12 — 11 went out
00 byte 2 0
00 byte 3 Select Status 0 = accepted
00 byte 4 PType 0
02 byte 5 SType 2 Select.rsp
00 00 11 04 bytes 6-9 SystemBytes 0x00001104, echoed exactly
The SystemBytes are right and only the SessionID is off. The cause is normally mundane: the tool has several devices defined and the number configured on the host is not the number the equipment puts in its replies. Copying one tool's configuration to a second tool during a line expansion and changing only half of it does it too.
Host stacks disagree about this one. Some check the SessionID strictly and drop the frame; others match control messages on SType and SystemBytes alone and let it through. If yours lets it through, Select succeeds and the failure moves downstream — the equipment keeps sending 12, the host cannot map it to a configured tool, and you get some flavour of "unknown device" instead. Either way, the first symptom line does not tell you which. Once you've narrowed it to the SystemBytes case, that one has its own walkthrough — healthy and broken sessions side by side.
An explicit refusal is none of the three
A non-zero byte 3 is a different story. Captured with selectRefuse: 1:
06:03:13.084 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 11 05
06:03:13.086 RX Select.rsp 00 00 00 0A 00 0B 00 01 00 02 00 00 11 05 2.0 ms
^^
Byte 3 is 01 and everything else is correct: SessionID 11, SystemBytes echoed, SType 2. E37 names the low Select Status values — 0 accepted, 1 Communication Already Active, 2 Connection Not Ready, 3 Connect Exhaust, 4 and above reserved. That table is cited from the standard, not proved by the capture. What the capture shows is only that whatever the equipment puts in byte 3 arrives unchanged. Reading the values one by one is an accepted Select and a refused one differ by a single byte.
One more thing the equipment side records. After the Select Status 1 refusal the listener's HSMS state stayed NOT CONNECTED, while the accepted round trip moved it to SELECTED. The socket was open in both cases. An open socket is not a session.
This article is about the cases where the host sees nothing at all. A refusal at least reaches your log with a reason attached.
Telling them apart without equipment
- Run tcpdump on the host itself.
tcpdump -i any -X -s0 'tcp port 5000'. No inbound frame at all is cause 1. An inbound frame means your own stack discarded it, so read the hex. - Read the four length bytes first.
00 00 00 0Ais a control message with no body; only the next 10 bytes matter. - Compare bytes 0–1 (SessionID) and bytes 6–9 (SystemBytes) against what you sent. SessionID differs → cause 3. SystemBytes differ → cause 2. The header byte numbering is E37's, from zero.
- Read byte 3. Non-zero is not silence, it is a refusal, and that number is the reason.
- Look at the elapsed time. It is the only clue available when all you have is the host log. Something arriving a few milliseconds after the request, followed by a timeout, is not silence. In these captures that was 1.7–2.9 ms, while the silent case produced nothing in 12 seconds.
And if you are writing the host stack yourself, which most MES projects are: log received frames before transaction matching, not after. Logging only what matched is the real reason this class of problem is undiagnosable. One line with the 10 header bytes in hex is enough — all four cases above separate immediately on that line. Byte 3 is the easiest one to lose, because a control message borrows the slot an E5 data message spends on its Function number.
Attaching a capture and writing "we sent 0x00001103 and received 0x00001104" ends the vendor thread in one mail. "There is no response" takes three round trips.
To reproduce any of this, start a passive listener at scadathings.com/secs-gem-simulator, set silentAfterSelect, wrongSystemBytes, wrongSessionId and selectRefuse one at a time, and point your own host stack at each. Every hex string above came from there.