HSMS
HSMS(High-Speed SECS Message Services, SEMI E37)는 SECS-II 메시지를 TCP/IP로 주고받는 방법입니다. 직렬 회선용 SECS-I를 대신하며, 지금 만드는 장비는 대부분 HSMS를 씁니다. SEMI E37.1(HSMS-SS)은 연결 하나에 세션(session) 하나만 쓰는 단일 세션 방식을 정합니다. PDComm은 HSMS-SS를 구현합니다.
메시지 형식
섹션 제목: “메시지 형식”HSMS는 TCP 바이트 스트림 위에 메시지를 하나씩 이어 붙여 보냅니다. 각 메시지는 4바이트 길이, 10바이트 헤더, 본문 순서입니다(SEMI E37, HSMS 메시지 형식).
+--------------------+--------------------------------+------------------+| message length | header (10 bytes) | body (SECS-II) || 4 bytes, big-endian| see below | optional |+--------------------+--------------------------------+------------------+
header byte 0..1 2 3 4 5 6..9 session ID W-bit + stream function PType SType system bytes- 메시지 길이(message length)는 헤더와 본문을 합한 바이트 수입니다. 본문이 없으면 10입니다.
- 세션 ID(session ID)는 데이터 메시지가 어느 장비의 것인지 나타냅니다. SECS-II의 장치 ID에 해당합니다.
- 바이트 2–3은 데이터 메시지에서 W 비트, 스트림, 펑션입니다. 제어 메시지에서는 상태 코드나 거부 이유를 담습니다.
- PType은 본문의 표현 방식입니다. 0은 SECS-II입니다. PDComm은 PType 0만 받습니다.
- SType은 메시지 종류입니다.
PDComm 옵션의 max_frame_bytes는 이 길이 필드와 헤더 14바이트를 포함한 프레임 크기입니다. 예를 들어 튜토리얼 C++ 예제는 4096바이트 메시지를 위해 max_frame_bytes를 4110으로 정합니다.
메시지 종류(SType)
섹션 제목: “메시지 종류(SType)”| SType | 메시지 | 용도 |
|---|---|---|
| 0 | 데이터 메시지(data message) | SECS-II 메시지를 전달합니다. |
| 1, 2 | Select.req, Select.rsp | 연결을 통신 가능한 상태로 만듭니다. |
| 3, 4 | Deselect.req, Deselect.rsp | 연결을 끊지 않고 통신을 끝냅니다. |
| 5, 6 | Linktest.req, Linktest.rsp | 상대가 살아 있는지 확인합니다. |
| 7 | Reject.req | 받을 수 없는 메시지를 거부합니다. |
| 9 | Separate.req | 응답 없이 통신을 끝내고 연결을 닫습니다. |
HSMS-SS의 제어 메시지는 세션 ID로 0xFFFF를 씁니다. PDComm은 Reject.req를 뺀 제어 메시지의 세션 ID가 0xFFFF가 아니면 프로토콜 오류로 처리합니다.
Active와 Passive
섹션 제목: “Active와 Passive”- Passive 쪽은 TCP 포트를 열고 연결을 기다립니다.
- Active 쪽은 Passive 쪽에 TCP 연결을 시작합니다. 연결되면 Select.req를 보냅니다.
호스트와 장비 중 어느 쪽이 Active가 될지는 역할과 별개로 정합니다. 자세한 조합은 배경 지식 개요를 참고합니다.
PDComm에서는 끝점(endpoint)을 만들 때 모드를 정합니다(PDCOMM_TCP_ACTIVE 또는 PDCOMM_TCP_PASSIVE, .NET HsmsMode).
- 주소는
127.0.0.1:5000처럼 숫자 IPv4/IPv6 주소와 포트입니다. PDComm은 DNS 이름을 해석하지 않습니다. - Passive 끝점은 만들 때 바로 포트에 바인드(bind)합니다. 포트 0을 쓰면 운영체제가 빈 포트를 고르고, 로컬 주소 함수로 실제 포트를 읽습니다.
- Active 끝점은 TCP 연결이 맺어지면 Select.req를 자동으로 보냅니다.
- Passive 끝점은 받은 Select.req에 상태 0(성공)으로 답합니다. 이미 Selected 상태이면 상태 1로 답합니다.
연결 상태
섹션 제목: “연결 상태”SEMI E37의 HSMS 상태 머신(state machine)은 세 상태로 이뤄집니다.
TCP connected NOT CONNECTED ---------------------------> CONNECTED: NOT SELECTED ^ | ^ | Select | | Deselect | TCP closed, Separate.req, v | | T6 / T7 / T8 expired CONNECTED: SELECTED +-----------------------------------------+TCP 연결만으로는 데이터 메시지를 보낼 수 없습니다. Select 절차가 끝나 SELECTED 상태가 되어야 SECS-II 메시지를 주고받습니다.
PDComm은 이 상태를 다음 번호로 알려 줍니다(PDCOMM_TCP_*, .NET HsmsConnectionState).
| 값 | PDComm 상태 | .NET 이름 | E37 상태 |
|---|---|---|---|
| 0 | PDCOMM_TCP_STOPPED |
Stopped |
끝점이 멈춤 |
| 1 | PDCOMM_TCP_CONNECTING |
Connecting |
NOT CONNECTED, Active가 연결 시도 중 |
| 2 | PDCOMM_TCP_LISTENING |
Listening |
NOT CONNECTED, Passive가 대기 중 |
| 3 | PDCOMM_TCP_NOT_SELECTED |
NotSelected |
CONNECTED: NOT SELECTED |
| 4 | PDCOMM_TCP_SELECTED |
Selected |
CONNECTED: SELECTED |
| 5 | PDCOMM_TCP_RETRY_WAIT |
RetryWait |
NOT CONNECTED, Active가 T5 동안 재연결 대기 |
튜토리얼 예제의 state 명령이 STATE tcp=4를 출력하면 SELECTED 상태입니다.
연결이 끊기면 Active 끝점은 RETRY_WAIT에서 T5만큼 기다린 뒤 다시 연결합니다. Passive 끝점은 LISTENING으로 돌아가 다음 연결을 기다립니다. PDComm은 연결 시도마다 세대(generation) 번호를 올립니다. 이전 연결에서 받은 토큰을 새 연결에 쓰면 PDCOMM_STALE_TOKEN으로 거부합니다.
연결 절차
섹션 제목: “연결 절차”일반적인 세션은 다음 순서로 진행됩니다.
Active (host) Passive (equipment) | ---------- TCP connect -------------------> | | ---------- Select.req --------------------> | | <--------- Select.rsp (status 0) ---------- | both SELECTED | ---------- S1F13 W -----------------------> | | <--------- S1F14 -------------------------- | | ---------- Linktest.req ------------------> | | <--------- Linktest.rsp ------------------- | | ---------- Separate.req ------------------> | connection closesPDComm에서 Select는 Active 끝점이 자동으로 보냅니다. 링크 테스트(linktest)는 어느 쪽이든 pdcomm_tcp_linktest로 보낼 수 있고, 받은 쪽은 자동으로 응답합니다. Selected 상태에서 끝점을 멈추면 PDComm이 Separate.req를 보냅니다.
응답 코드
섹션 제목: “응답 코드”Select.rsp와 Deselect.rsp는 헤더 바이트 3에 상태 코드를 담습니다(SEMI E37, 제어 메시지).
| 메시지 | 코드 | 의미 |
|---|---|---|
| Select.rsp | 0 | 통신 수립 |
| Select.rsp | 1 | 이미 통신 중(already active) |
| Select.rsp | 2 | 연결 준비 안 됨(not ready) |
| Select.rsp | 3 | 연결 수 초과(connect exhaust) |
| Deselect.rsp | 0 | 통신 종료 |
| Deselect.rsp | 1 | 통신이 수립되지 않음 |
| Deselect.rsp | 2 | 통신 중이라 종료 불가(busy) |
Reject.req는 바이트 3에 거부 이유를 담습니다. 1은 지원하지 않는 SType, 2는 지원하지 않는 PType, 3은 열려 있지 않은 거래(transaction not open), 4는 Selected가 아닌 상태(entity not selected)입니다. PDComm에서 상대가 Select를 거부하면 요청이 실패 이벤트(PDCOMM_TCP_FAILED)로 끝나고, 상태 코드를 이벤트 정보로 알려 줍니다.
타이머
섹션 제목: “타이머”HSMS는 다섯 개의 타이머로 상대의 무응답을 감지합니다(SEMI E37, HSMS 타이머). PDComm에는 기본값이 없습니다. 모든 타이머를 밀리초(ms) 단위로 직접 정합니다.
| 타이머 | 의미 | C 필드 / .NET 속성 | PDComm의 만료 처리 |
|---|---|---|---|
| T3 | 응답 타임아웃(reply timeout). W 비트 요청이 응답을 기다리는 시간 | t3_ms / T3Milliseconds |
해당 요청만 실패합니다(PDCOMM_TCP_T3_TIMEOUT). 연결은 유지합니다. |
| T5 | 연결 분리 타임아웃(connection separation timeout). Active가 다시 연결하기 전 기다리는 시간 | t5_ms / T5Milliseconds |
RETRY_WAIT가 끝나면 다시 연결합니다. |
| T6 | 제어 거래 타임아웃(control transaction timeout). Select, Deselect, Linktest가 응답을 기다리는 시간 | t6_ms / T6Milliseconds |
요청이 실패하고(PDCOMM_TCP_T6_TIMEOUT) 연결을 끊습니다. |
| T7 | 미선택 타임아웃(not selected timeout). 연결 후 Selected가 되기까지 허용하는 시간 | t7_ms / T7Milliseconds |
연결을 끊습니다. |
| T8 | 네트워크 문자 간 타임아웃(network intercharacter timeout). 한 메시지의 바이트 사이 간격 | t8_ms / T8Milliseconds |
연결을 끊습니다. |
장비 매뉴얼에서는 T3 45초, T5 10초, T6 5초, T7 10초, T8 5초 같은 값을 흔히 봅니다. 실제 값은 연결할 장비나 호스트와 합의합니다. 튜토리얼 예제는 로컬 시험을 빨리 끝내려고 짧은 값을 씁니다. 이 값을 실제 장비에 그대로 옮기지 않습니다.
| 타이머 | 튜토리얼 C++ (tutorial_options.hpp) |
튜토리얼 C# (TutorialOptions.cs) |
|---|---|---|
| T3 | 5000 | 2000 |
| T5 | 1000 | 20 |
| T6 | 2000 | 2000 |
| T7 | 2000 | 5000 |
| T8 | 1000 | 2000 |
세션 ID
섹션 제목: “세션 ID”세션 ID는 데이터 메시지 헤더의 바이트 0–1입니다. PDComm에서는 session_id(.NET SessionId)로 정합니다. 양쪽이 같은 값을 써야 합니다. 다른 세션 ID의 데이터 메시지를 받으면 PDComm은 프로토콜 오류(PDCOMM_TCP_WRONG_SESSION)로 보고합니다. 튜토리얼 예제는 세션 ID 7을 씁니다.
그 밖의 PDComm 옵션
섹션 제목: “그 밖의 PDComm 옵션”PdcommTcpOptions(.NET HsmsOptions)에는 타이머 말고도 시간과 메모리 한도가 있습니다. PDComm은 모든 큐와 버퍼의 크기를 애플리케이션이 정하게 해 메모리 사용량에 상한을 둡니다.
| C 필드 | .NET 속성 | 의미 |
|---|---|---|
connect_timeout_ms |
ConnectTimeoutMilliseconds |
Active 연결 시도 한 번이 poll을 막을 수 있는 최대 시간 |
shutdown_timeout_ms |
ShutdownTimeoutMilliseconds |
멈출 때 연결을 정상 종료하는 데 쓰는 최대 시간 |
write_timeout_ms |
WriteTimeoutMilliseconds |
프레임 쓰기가 멈춰 있을 수 있는 최대 시간. 넘으면 연결 실패로 처리합니다. |
max_frame_bytes |
MaxFrameBytes |
보내거나 받는 프레임의 최대 크기(프레이밍 14바이트 포함) |
max_events, max_queued_bytes |
MaxEvents, MaxQueuedBytes |
내부 이벤트 큐의 개수와 바이트 한도 |
max_write_frames, max_write_bytes |
MaxWriteFrames, MaxWriteBytes |
쓰기를 기다리는 프레임의 개수와 바이트 한도 |
max_io_chunk |
MaxIoChunk |
소켓 호출 한 번에 읽거나 쓰는 최대 바이트 |
max_application_events, max_application_bytes |
MaxApplicationEvents, MaxApplicationBytes |
애플리케이션이 꺼내 가기를 기다리는 이벤트의 개수와 바이트 한도 |
poll_budget |
PollBudget |
poll 한 번이 수행하는 내부 작업 수 |
item_limits |
ItemLimits |
SECS-II 본문의 크기 한도. SECS-II 메시지를 참고합니다. |
끝점은 C에서 pdcomm_tcp_create, C++에서 TcpEndpoint로 만듭니다. 한도를 넘는 입력은 PDCOMM_LIMIT_EXCEEDED나 PDCOMM_BACKPRESSURE로 거부되며 조용히 버려지지 않습니다. 끝점을 만들거나 정지된 끝점을 다시 시작하려면 secs-gem 모듈 라이선스가 필요하고, 없으면 PDCOMM_LICENSE_REQUIRED가 돌아옵니다. 이미 연결된 끝점은 영향을 받지 않습니다. 라이선스 설치와 확인을 참고합니다.