← 전체 글
SECS/GEM/약 18분 읽기/— 조회

화면은 ONLINE인데 S2F41이 HCACK 2로 튕긴다 — E30 제어 상태 세 가지

화면은 ONLINE인데 S2F41 START가 HCACK 2로 거절된다. E30 통신 상태와 제어 상태를 나누고 HCACK이 0이 되는 자리를 capture로 짚는다.

SECS/GEMMESSCADA문제 해결체크리스트

Host가 S2F41에 RCMD START를 실어 보냈다. S2F42가 1 ms 만에 돌아온다. HCACK은 2. 설비 앞에 서 있는 사람은 화면에 ONLINE이라고 떠 있다고 말한다. 회의실에서 30분이 그냥 간다.

HCACK 2는 SEMI E5에서 "지금은 수행할 수 없음"이다. 명령이 없다는 뜻도, 파라미터가 틀렸다는 뜻도 아니다. 상태가 아니라는 뜻이다. 그리고 설비 화면의 "ONLINE"과 SEMI E30이 말하는 상태는 같은 것이 아니다.

E30에는 state machine이 두 개 있고, 둘은 서로 다른 질문에 답한다.

E30의 두 state machine: S1F13/S1F14로 넘어가는 통신 상태와 S1F17/S1F15로 움직이는 제어 상태, S2F41이 통하는 자리는 ON-LINE REMOTE 하나 GEM 통신 상태GEM 제어 상태 NOT COMMUNICATING COMMUNICATING OFF-LINE ON-LINE LOCAL ON-LINE REMOTE S1F13 S1F14 Separate.req socket close S1F17 S1F15 operator 패널 S1F13 이전 · 모든 message → SxF0 OFF-LINE · LOCAL → HCACK 2 ON-LINE REMOTE → HCACK 0

통신 상태와 제어 상태는 다른 질문에 답한다

**통신 상태(communication state)**는 "SECS-II 대화가 열렸는가"에 답한다. 값은 NOT COMMUNICATING과 COMMUNICATING 두 개뿐이다. Host가 S1F13을 보내고 설비가 COMMACK 0을 실은 S1F14로 답하면 COMMUNICATING으로 넘어간다. SEMI E5의 S1F14 COMMACK은 값이 두 개다 — 0은 accepted, 1은 denied, try again. Socket이 끊기거나 Separate.req가 오면 다시 NOT COMMUNICATING이다.

**제어 상태(control state)**는 "지금 이 설비를 누가 지시하는가"에 답한다. 값은 OFF-LINE, ON-LINE LOCAL, ON-LINE REMOTE 세 개다. SEMI E30에서 host의 remote command를 받아 주는 자리는 마지막 하나뿐이다.

두 machine은 독립이 아니라 순서가 있다. 통신이 열리지 않았으면 제어 상태를 물어볼 일도 없다. 그래서 증상이 네 가지로 갈린다.

지금 상태S2F41을 보내면화면에는
NOT COMMUNICATING (S1F13 이전)S2F0 — function 0짜리 abort. 요청 자체가 없던 일이 된다대개 아무것도 안 뜬다
COMMUNICATING + OFF-LINES2F42, HCACK 2OFFLINE 또는 공백
COMMUNICATING + ON-LINE LOCALS2F42, HCACK 2ONLINE — 여기가 함정이다
COMMUNICATING + ON-LINE REMOTES2F42, HCACK 0ONLINE

1·2·4행은 아래 capture에서 byte로 찍었다. 3행 ON-LINE LOCAL은 SEMI E30의 규칙이고 시뮬레이터 코드도 "ON-LINE REMOTE가 아니면 HCACK 2"로 한 줄이지만, 이번 실행에서 LOCAL을 직접 통과시켜 보지는 않았다. 그 줄은 unverified로 둔다.

세 번째 줄이 회의를 길게 만드는 줄이다. Operator가 설비 panel을 LOCAL로 돌려 놓으면 설비는 여전히 online이다. 화면도 ONLINE이라고 쓴다. 그런데 host 명령은 전부 HCACK 2로 튕긴다. Operator 입장에서는 아무것도 안 건드렸고, host 입장에서는 어제까지 되던 게 안 된다.

HCACK 값 전체가 필요하면 HCACK 0이 실제로 보장하는 것 쪽에 SEMI E5의 S2F42 HCACK 표가 정리돼 있다. 여기서 쓰는 건 0(수행함)과 2(지금은 수행할 수 없음) 두 개, 그리고 1(그런 command 없음)까지다.

SxF0을 "무응답"으로 읽지 말 것

S1F13 이전에 보낸 message는 거절당하는 게 아니라 abort된다. SECS-II에서 function 0은 transaction을 없던 것으로 만드는 응답이고, 설비는 같은 stream에 그걸 붙여 돌려준다. S2F41에는 S2F0, S1F3에는 S1F0이다.

이걸 T3 timeout으로 오독하는 host driver를 자주 본다. Function 0은 body가 없고 W-bit도 없으니, function 필드를 안 보고 "기대한 S2F42가 아니면 응답 없음"으로 처리하면 T3(대개 45 s)를 다 기다린 뒤 없는 network 문제를 찾으러 간다. Socket은 멀쩡하고 설비는 1 ms 만에 답했다.

HSMS가 selected인데 여기서 막히는 경우는 selected와 communicating을 구분하는 글에 따로 적어 뒀다.

제어 상태를 움직이는 message

Host 쪽에서 제어 상태를 바꾸는 방법은 두 개다.

  • S1F17 Request ON-LINE → 설비가 S1F18 ONLACK으로 답한다. SEMI E5의 ONLACK은 0 = ON-LINE accepted, 1 = ON-LINE not allowed, 2 = equipment already ON-LINE이다.
  • S1F15 Request OFF-LINE → S1F16 OFLACK. SEMI E5에서 OFLACK은 0 = off-line acknowledge 하나뿐이다. Host가 스스로 물러나는 message고, 유지보수 전에 host 명령이 끼어들지 않게 만들 때 쓴다.

값이 세 개라고 세 개가 다 나오는 건 아니다. 아래 capture에서 시뮬레이터가 돌려준 ONLACK은 0 하나뿐이고, 시뮬레이터 코드는 S1F17에 고정된 B 0을 답한다 — 상태와 무관하게 1도 2도 나오지 않는다(코드로 확인, capture에는 S1F17이 한 번뿐이다). 그러니 "2가 오면 이미 online"은 SEMI E5가 정의한 의미지 이 실행이 보여 준 동작이 아니다. 실제 설비에서 2를 받아 본 적이 없다면, host 코드에서 2를 성공으로 처리하는 분기는 아직 한 번도 실행돼 본 적 없는 코드다.

여기서 놓치기 쉬운 게 하나 더 있다. S1F17이 성공해도 ON-LINE LOCAL로 갈지 ON-LINE REMOTE로 갈지는 설비가 정한다. Panel이 LOCAL이면 S1F17에 ONLACK 0을 주고 LOCAL로 올라가는 설비가 있다. Host 로그에는 "online 성공"이 찍히고, 다음 S2F41은 HCACK 2다. 그러니 online 여부의 최종 판정은 ONLACK이 아니라 실제 S2F41의 HCACK이다.

Socket 하나에 message 일곱 개

여기부터는 방금 돌린 실행이다. SECS/GEM 시뮬레이터의 EQ1 passive listener(127.0.0.1:5501)에 TCP socket을 직접 열고, HSMS frame을 손으로 조립해 밀어 넣었다. 시작 전 상태는 제어 상태 OFF-LINE, fault 없음. SystemBytes는 0x00007001부터 하나씩 올렸다 — 설비 쪽 packet 목록에서 내 message만 골라내려고 그렇게 했다.

1. Select.req → Select.rsp

H→E  00 00 00 0A 00 0B 00 00 00 01 00 00 70 01
E→H  00 00 00 0A 00 0B 00 00 00 02 00 00 70 01

SessionID 00 0B(11), PType 0, SType 1이 나가고 SType 2가 돌아온다. SystemBytes 00 00 70 01은 그대로 복사돼 온다. Header Byte 3이 00이니 Select Status 0, HSMS는 SELECTED다.

2. S1F13보다 먼저 던진 S2F41 → S2F0

H→E  S2F41 W=1  (wire 44 bytes)
00 00 00 28 00 0B 82 29 00 00 00 00 70 02
01 02 41 05 53 54 41 52 54 01 01 01 02 41
04 50 50 49 44 41 09 45 54 43 48 5F 42 41
53 45

E→H  00 00 00 0A 00 0B 02 00 00 00 00 00 70 02

요청 쪽 Byte 2 82는 W-bit 1 + stream 2, Byte 3 29는 function 41. Body 30 byte는 01 02(L,2) → 41 05 START → 01 01(L,1) → 01 02(L,2) → 41 04 PPID + 41 09 ETCH_BASE, RCMD 하나에 CPNAME/CPVAL 한 쌍이다. 답으로 온 건 14 byte짜리 header 하나 — Byte 2 02는 stream 2에 W-bit 없음, Byte 3 00이 function 0이다. S2F0, body 없음. SystemBytes는 00 00 70 02로 요청과 짝이 맞으니 이건 timeout이 아니라 명시적인 abort다.

3. S1F13 → S1F14 COMMACK 0

H→E  00 00 00 17 00 0B 81 0D 00 00 00 00 70 03
     01 02 41 04 48 4F 53 54 41 03 31 2E 30

E→H  00 00 00 37 00 0B 01 0E 00 00 00 00 70 03
     01 02 21 01 00 01 02 41 15 56 58 2D 39 30 30 30
     20 50 6C 61 73 6D 61 20 45 74 63 68 65 72 41 0D
     53 45 43 53 47 45 4D 2D 31 2E 34 2E 31

Body는 L[2]{A "HOST", A "1.0"}, MDLN과 SOFTREV 자리다. 답은 L[2]{B 0, L[2]{A "VX-9000 Plasma Etcher", A "SECSGEM-1.4.1"}} — 앞의 21 01 00이 COMMACK 0이다. 21은 format code 8(binary)에 length byte 1개, 01이 길이, 00이 값. 이 한 byte로 통신 상태가 COMMUNICATING이 됐고, 2번에서 abort되던 gate가 열렸다.

4. 같은 S2F41, 이번엔 대답이 온다 → HCACK 2

H→E  00 00 00 28 00 0B 82 29 00 00 00 00 70 04  (+ 2번과 동일한 body 30 byte)

E→H  00 00 00 11 00 0B 02 2A 00 00 00 00 70 04
     01 02 21 01 02 01 00

보낸 건 2번과 완전히 같은 44 byte, SystemBytes만 70 04다. 이번엔 S2F0이 아니라 S2F42가 온다 — Byte 3 2A = 42. Body는 L[2]{B 2, L[0]}, HCACK 2와 빈 CPACK list다. HCACK이 사는 자리는 이 21 byte frame의 offset 18 — 앞 4 byte 길이 prefix, 10 byte header, 01 02(L,2), 21 01(B,1) 다음의 한 byte다. Host driver가 봐야 하는 건 그 byte 하나고, 설비 화면이 아니다.

5. S1F17 → S1F18 ONLACK 0

H→E  00 00 00 0A 00 0B 81 11 00 00 00 00 70 05
E→H  00 00 00 0D 00 0B 01 12 00 00 00 00 70 05 21 01 00

요청은 body가 아예 없는 14 byte header다. Byte 3 11 = 17. 답은 3 byte body 21 01 00 — list가 아니라 binary item 하나, ONLACK 0이다. S1F14처럼 L[2]가 올 거라고 가정하고 무조건 첫 item을 list로 풀어 보는 parser는 여기서 21을 만나 깨진다. 제어 상태가 움직인 자리는 이 두 줄이다. 설비는 S1F18을 내보낸 뒤 제어 상태를 ON-LINE REMOTE로 올린다.

6. 세 번째 S2F41 → HCACK 0

H→E  00 00 00 28 00 0B 82 29 00 00 00 00 70 06  (+ 동일한 body 30 byte)

E→H  00 00 00 11 00 0B 02 2A 00 00 00 00 70 06
     01 02 21 01 00 01 00

4번의 응답과 이 응답은 byte 두 개만 다르다. SystemBytes 끝자리, 그리고 offset 18의 02 → 00. 명령도 같고 body도 같고 socket도 같다. 바뀐 건 그 사이에 낀 header 14 byte짜리 S1F17 하나뿐이다.

7. Separate.req

H→E  00 00 00 0A 00 0B 00 00 00 09 00 00 70 07

SType 9, 답은 없다. 설비는 통신 상태를 NOT COMMUNICATING으로 되돌리고 socket을 닫는다. 제어 상태는 안 건드린다 — 다음 절이 그 얘기다.

일곱 개 모두 설비 쪽 packet 목록에 RX로 남았고, 위 hex는 /api/export/pcap이 뱉은 것 그대로다.

재접속은 제어 상태를 초기화하지 않는다

7번에서 socket을 닫아도 설비는 ON-LINE REMOTE에 그대로 남는다. 다시 붙어서 S1F13만 하고 S2F41을 던지면 S1F17 없이도 HCACK 0이 나온다. 이 실행에서는 그 재접속을 찍지 않았으므로 — 시뮬레이터 코드상 socket close handler가 commState만 되돌린다 — 여기서는 코드로 확인한 것이지 capture로 증명한 것은 아니다. 실제로 이 글의 capture를 끝낸 뒤 제어 상태를 OFF-LINE으로 되돌리는 데 API 호출이 한 번 더 필요했다.

이게 현장에서 두 번째로 시간을 잡아먹는 오해다. Host를 재기동했으니 설비도 처음 상태일 거라고 가정하고 짠 startup 순서는, 설비가 어제 저녁 상태 그대로 남아 있을 때 어긋난다. S1F13 다음에 S1F17을 조건 없이 한 번 보내는 편이 싸다. 거꾸로 같은 이유로, 같은 test를 두 번 연속 돌리면 두 번째 실행의 HCACK 2 단계는 통과하지 못한다. 버그가 아니라 상태가 남아 있는 것이다.

이건 core 모델이고, 실제 설비는 더 있다

여기까지가 SEMI E30의 뼈대다. 실제 설비는 이 위에 sub-state를 얹는다. HOST OFF-LINE과 EQUIPMENT OFF-LINE의 구분, 전원을 켰을 때 ATTEMPT ON-LINE으로 들어가 host와의 통신을 먼저 시도하는 동작, 그 시도를 관리하는 재시도 타이머 — 벤더 문서에 각각 다른 이름으로 적혀 있다.

시뮬레이터에는 그게 없다. 두 machine, sub-state 없음, 그게 전부다. 그래서 위 일곱 message는 host driver가 core 규칙을 지키는지를 증명하지, 설비가 어떻게 동작할지를 증명하지 않는다. 실제 설비의 sub-state 전이는 이 capture로 뒷받침할 수 없으므로 unverified다. 붙일 설비가 생기면 그때 각 sub-state에서 S2F41을 한 번씩 던져 보고 HCACK을 표로 남기는 게 맞다.

Startup 전체 — S1F13부터 S2F31까지 — 를 같은 방식으로 훑는 쪽은 E30 startup을 명령 하나로 통과시키기에 있다.

다음에 HCACK 2를 보면

  1. 설비 화면 말고 S2F42의 HCACK byte를 본다. 위 frame이면 offset 18이다. 2면 상태 문제, 1이면 그 RCMD 자체가 없는 것이다.
  2. S1F17을 보내고 ONLACK을 본다. 0이 와도 아직 아무것도 증명되지 않았다.
  3. S2F41을 다시 던진다. HCACK 0이면 끝, 또 2면 panel이 LOCAL이다. 그때는 코드가 아니라 설비 앞에 있는 사람에게 전화할 차례다.

시뮬레이터의 GEM Control 패널에 OFF-LINE / ON-LINE LOCAL / ON-LINE REMOTE 세 상태가 그대로 떠 있고, host가 보낸 S1F17·S1F15가 그 표시를 실시간으로 움직인다. 오가는 byte는 packet 목록에 그대로 남으니, 위 hex를 그대로 다시 밀어 넣어 자기 driver의 frame과 맞춰 보면 된다. SECS/GEM 시뮬레이터는 지금 떠 있다.