상태 코드와 오류
PDComm SECS/GEM SDK는 실패를 두 가지 방식으로 알립니다.
- 호출 결과: 함수를 호출한 순간 바로 판단할 수 있는 실패입니다. C는 상태 코드(status code)를 반환하고, C++는
pdcomm::Error를, .NET은PdcommException을 던집니다. - 비동기 결과: 요청을 보낸 뒤 나중에 알게 되는 실패입니다. HSMS 이벤트(event)와 GEM 결과(result)로 전달됩니다. 예를 들어 T3 응답 시간 초과는 요청 호출이 성공한 뒤 이벤트로 옵니다.
요청 호출이 성공했다는 것은 요청을 큐에 받았다는 뜻입니다. 상대가 응답했다는 뜻은 아닙니다. 응답이나 실패는 항상 이벤트로 확인합니다.
C 상태 코드
섹션 제목: “C 상태 코드”C 함수는 PdcommStatus(int32_t)를 반환합니다. .NET에서는 같은 값을 PdcommStatus로 씁니다.
| 값 | C 이름 | .NET 이름 | 뜻 | 대응 |
|---|---|---|---|---|
| 0 | PDCOMM_OK |
Ok |
성공입니다. | 없습니다. |
| 1 | PDCOMM_INVALID_ARGUMENT |
InvalidArgument |
인수가 잘못되었습니다. NULL 포인터, 잘못된 struct_size나 version, 숫자가 아닌 주소, 0인 타이머, 응답에 홀수 펑션을 쓴 경우 등입니다. |
옵션 구조체를 0으로 초기화한 뒤 struct_size와 version을 채웠는지 확인합니다. 주소는 127.0.0.1:5000처럼 숫자로 씁니다. |
| 2 | PDCOMM_LIMIT_EXCEEDED |
LimitExceeded |
애플리케이션이 정한 한도를 넘었습니다. 메시지 크기, 항목 수, 중첩 깊이, ID 수 등입니다. | 한도는 표준 값이 아니라 애플리케이션 정책입니다. 필요한 크기에 맞게 PdcommSecsLimits나 옵션의 한도를 늘립니다. |
| 3 | PDCOMM_BUFFER_TOO_SMALL |
BufferTooSmall |
출력 버퍼가 작습니다. required에 필요한 크기가 들어 있고 버퍼는 바뀌지 않습니다. |
required 크기로 버퍼를 다시 잡고 호출합니다. |
| 4 | PDCOMM_ALLOCATION_FAILED |
AllocationFailed |
메모리 할당에 실패했습니다. | 이벤트를 꺼내다 실패했다면 그 이벤트는 보존됩니다. 메모리를 확보한 뒤 같은 호출을 다시 합니다. |
| 5 | PDCOMM_INTERNAL_ERROR |
InternalError |
내부 오류가 잡혔거나 시계가 거꾸로 갔습니다. | 재현 절차와 진단 로그를 함께 문의합니다. |
| 6 | PDCOMM_INVALID_DATA |
InvalidData |
SECS-II 인코딩이나 데이터 형식이 잘못되었습니다. | .NET에서는 ErrorOffset이 문제가 된 바이트 위치를 알려 줍니다. |
| 7 | PDCOMM_TRUNCATED |
Truncated |
SECS-II 입력이 중간에 끝났습니다. | 받은 바이트가 모두 있는지 확인합니다. |
| 8 | PDCOMM_OUT_OF_RANGE |
OutOfRange |
없는 ID나 범위를 벗어난 인덱스입니다. | 등록한 ID 목록과 인덱스 범위를 확인합니다. |
| 9 | PDCOMM_BACKPRESSURE |
Backpressure |
지금은 더 받을 수 없습니다. 열린 거래(transaction)가 32개 찼거나, 같은 제어 요청이 이미 진행 중이거나, 이벤트 큐가 찼습니다. | 이벤트를 계속 꺼내 처리하고, 응답이나 실패가 온 뒤 다시 보냅니다. 재시도 횟수와 간격은 애플리케이션이 정합니다. |
| 10 | PDCOMM_INVALID_STATE |
InvalidState |
현재 상태에서 할 수 없는 호출입니다. 예를 들어 Selected가 아닐 때 요청하거나, 통신 수립 전에 상태 값을 요청하거나, 시작 후에 시작 전 설정을 호출한 경우입니다. | 상태를 확인하고 순서를 맞춥니다. |
| 11 | PDCOMM_STALE_TOKEN |
StaleToken |
다른 끝점이나 이전 세대(generation)의 토큰 또는 이벤트입니다. 이미 GEM이 처리한 이벤트를 다시 넘긴 경우도 해당합니다. | 재연결 뒤에는 새 요청으로 다시 시작합니다. 이벤트는 한 번만 처리합니다. |
| 12 | PDCOMM_IO_ERROR |
IoError |
소켓 바인드(bind), 수락(accept), 로컬 주소 조회, 쓰기 시간 초과에 실패했습니다. pdcomm_license_host_id는 머신 ID를 읽을 수 없을 때 이 값을 반환합니다. |
Passive 쪽에서 끝점을 만들 때 나면 포트가 이미 쓰이는 경우가 많습니다. 포트가 이미 사용 중을 참고합니다. 호스트 ID 조회에서 나면 호스트 ID를 참고합니다. |
| 13 | PDCOMM_POISONED |
Poisoned |
끝점 호출 안에서 예기치 않은 오류가 잡혔습니다. 이후 그 끝점의 모든 호출이 이 값을 반환합니다. | 끝점을 해제하고 새로 만듭니다. 재현 절차와 로그를 문의합니다. |
| 14 | PDCOMM_PROGRAM_UNCONFIRMED |
ProgramUnconfirmed |
공정 프로그램(process program)을 설치했지만 영구 저장이 확인되지 않았습니다. | 저장 결과를 확인하고 정책에 따라 다시 확정합니다. |
| 15 | PDCOMM_SPOOL_UNCONFIRMED |
SpoolUnconfirmed |
스풀(spool) 변경의 영구 저장이 확인되지 않았습니다. | 큐와 저장 결과를 보존하고 정책에 따라 다시 시도합니다. |
| 16 | PDCOMM_LICENSE_REQUIRED |
LicenseRequired |
새 통신에 필요한 라이선스가 없습니다. 끝점 생성·시작(pdcomm_tcp_create, pdcomm_tcp_start, pdcomm_secs1_create, pdcomm_secs1_start)은 secs-gem 모듈을, GEM300 기능 목록 생성·교체는 gem300 모듈을 요구합니다. 라이선스가 없거나 만료되었거나 이 컴퓨터의 호스트 ID가 없는 경우입니다. 아무것도 만들거나 시작하지 않았습니다. |
pdcomm_license_status로 모듈의 state를 확인하고 라이선스 설치와 확인을 따릅니다. 이미 실행 중인 끝점은 계속 동작합니다. |
PDComm SECS/GEM에는 마지막 오류 메시지를 읽는 함수가 없습니다. 상태 코드와 이벤트의 코드, 진단 로그로 원인을 판단합니다. PDCOMM_LICENSE_REQUIRED의 원인은 pdcomm_license_status가 돌려주는 상태 JSON의 modules에 있습니다. 진단 로그를 켰다면 거부할 때마다 license_required 기록도 남습니다.
C++ 예외
섹션 제목: “C++ 예외”C++ 래퍼는 실패하면 Error(std::runtime_error 파생)를 던집니다. what()은 PDCOMM_LICENSE_REQUIRED일 때(PDComm license required (status 16)로 시작)를 빼면 항상 PDComm native operation failed이므로 원인을 알려 주지 않습니다. catch (const pdcomm::Error &error)로 받은 뒤 Error::status로 상태 코드를 읽어 기록합니다. 튜토리얼 C++ 예제도 F0 응답이 실패하면 REPLY ERROR status= 뒤에 이 값을 출력합니다.
튜토리얼 C++ 예제는 what()만 출력합니다. 그래서 통신 수립 전에 values를 입력하면 다음처럼만 나옵니다.
COMMAND ERROR PDComm native operation failed같은 상황에서 C# 예제는 상태 이름까지 보여 줍니다(InvalidState). 진단 로그용 래퍼(DiagnosticLogging)는 std::runtime_error를 던지며 메시지는 Cannot configure PDComm logging, Cannot query PDComm logging, Cannot flush PDComm logging 중 하나입니다. 라이선스 래퍼 pdcomm::License(pdcomm_license.hpp)도 std::runtime_error를 던지며 메시지는 Cannot install the PDComm license, Cannot query the PDComm license status, Cannot read the PDComm host id 중 하나입니다.
.NET 예외
섹션 제목: “.NET 예외”| 예외 | 언제 나는가 | 대응 |
|---|---|---|
PdcommException |
네이티브 호출이 실패했습니다. Status에 상태 코드, ErrorOffset에 디코딩 실패 위치가 있습니다. 메시지는 PDComm native operation failed with status InvalidState. 형식입니다. Status가 LicenseRequired이면 메시지 끝에 License state: secs-gem missing, gem300 missing.처럼 모듈 상태가 붙습니다. |
위 상태 코드 표를 따릅니다. 라이선스는 PDComm.License.Status()로 자세히 봅니다. |
System.DllNotFoundException |
PdcommRuntime.Initialize에 넘긴 경로에서 네이티브 라이브러리를 읽지 못했습니다. |
절대 경로와 파일 이름, 프로세스 아키텍처(x64, arm64)를 확인합니다. |
ArgumentException |
PdcommRuntime.Initialize에 상대 경로를 넘겼습니다. |
절대 경로를 넘깁니다. |
NotSupportedException |
네이티브 라이브러리의 ABI가 0.17–0.30이 아니거나 필요한 함수가 없습니다. | 같은 SDK 묶음의 PDComm.dll과 네이티브 라이브러리를 함께 씁니다. |
InvalidOperationException |
이미 다른 경로로 초기화했거나, DiagnosticLogging 설정·조회·플러시가 실패했습니다. |
프로세스마다 한 라이브러리만 씁니다. 로그 설정 JSON과 파일 경로를 확인합니다. |
TimeoutException |
워커(worker)가 종료 기한 안에 정리하지 못했습니다. | 이벤트를 계속 읽고 있는지, 종료 시간 옵션이 충분한지 확인합니다. |
워커가 이벤트 처리 중 실패하면 예외를 던지지 않고 GemEvent.ProcessingStatus에 상태를 남깁니다. 워커의 명령 큐가 차면 PdcommException(Backpressure)이 납니다.
GEM 결과
섹션 제목: “GEM 결과”TCP 이벤트를 GEM에 넘기면(pdcomm_gem_handle_event) 결과 종류(result kind)가 나옵니다. .NET 이름은 GemResultKind입니다.
| 값 | C 이름 | .NET 이름 | 뜻 |
|---|---|---|---|
| 0 | PDCOMM_GEM_IGNORED |
Ignored |
GEM과 관계없는 이벤트입니다. 상태 변화, 프로토콜 오류, 전송 오류가 여기에 해당합니다. |
| 1 | PDCOMM_GEM_PRIMARY |
Primary |
상대가 보낸 요청(primary)입니다. GEM이 자동으로 응답했을 수 있습니다. |
| 2 | PDCOMM_GEM_RESPONSE |
Response |
내 요청에 대한 정상 응답입니다. |
| 3 | PDCOMM_GEM_FAILED |
Failed |
내 요청이 응답 없이 끝났습니다. code가 이유입니다. |
| 4 | PDCOMM_GEM_INVALID_RESPONSE |
InvalidResponse |
응답이 왔지만 검증에 실패했습니다. code가 GEM 오류입니다. |
| 5 | PDCOMM_GEM_UNHANDLED |
Unhandled |
받은 요청을 GEM이 처리하지 않았고 응답도 보내지 않았습니다. code가 GEM 오류입니다. |
Failed의 code는 HSMS 실패 이유(아래 표의 1–8)입니다. 상대가 F0으로 거래를 중단하면 code는 12(PDCOMM_GEM_ERROR_ABORTED)입니다. InvalidResponse와 Unhandled의 code는 GEM 오류(GemError)입니다.
| 값 | GEM 오류 | 뜻 |
|---|---|---|
| 1 | Disconnected |
Selected 세션이 없습니다. |
| 2 | State |
현재 GEM 통신 상태나 제어 상태에서 허용되지 않습니다. |
| 3 | Unsupported |
이 역할이나 설정에서 지원하지 않는 스트림과 펑션입니다. 받은 S9Fx도 여기에 해당합니다. |
| 4 | Wait |
W 비트가 기대와 다릅니다. |
| 5 | MissingBody |
본문이 있어야 하는데 없습니다. |
| 6 | UnexpectedBody |
본문이 없어야 하는데 있습니다. |
| 7 | Body |
본문 구조가 잘못되었습니다. |
| 8 | Ack |
확인 코드(acknowledge code) 값이 잘못되었습니다. |
| 9 | Response |
예상과 다른 응답입니다. |
| 10 | Codec |
SECS-II 디코딩에 실패했습니다. |
| 11 | Correlation |
요청과 응답의 대응이 맞지 않습니다. |
| 12 | Aborted |
상대가 F0으로 거래를 중단했습니다. |
튜토리얼 출력 읽기
섹션 제목: “튜토리얼 출력 읽기”튜토리얼 예제는 GEM 결과를 RESULT kind=종류 request=요청 outcome=결과 한 줄로 출력합니다. 받은 요청(kind=1)에는 request=0이 붙습니다. 실제 실행에서 나온 줄의 뜻은 다음과 같습니다.
| 출력 | 출력한 쪽 | 뜻 |
|---|---|---|
RESULT kind=2 request=1 outcome=2 |
Host | S1F13 통신 수립에 성공했습니다. |
RESULT kind=2 request=3 outcome=5 |
Host | S1F3 상태 값을 받았습니다. |
RESULT kind=1 request=0 outcome=13 |
Equipment | S1F3을 받고 자동으로 응답했습니다. |
RESULT kind=3 request=3 outcome=0 |
Host | S1F3이 응답 없이 실패했습니다(예: T3). |
요청 종류(request)의 1–7은 다음과 같습니다. 8 이상은 보고서, 알람, 원격 명령, 공정 프로그램, 스풀링 요청입니다. 전체 목록은 GemRequestKind를 참고합니다.
| 값 | 요청 | 메시지 |
|---|---|---|
| 1 | Establish |
S1F13 |
| 2 | AreYouOnline |
S1F1 |
| 3 | StatusValues |
S1F3 |
| 4 | StatusNames |
S1F11 |
| 5 | Offline |
S1F15 |
| 6 | Online |
S1F17 |
| 7 | OperatorOnline |
장비 운영자의 온라인 전환 |
결과(outcome)의 0–13은 다음과 같습니다. 14 이상은 GemOutcome를 참고합니다.
| 값 | 결과 | 값 | 결과 |
|---|---|---|---|
| 0 | None |
7 | OfflineAccepted |
| 1 | Replied |
8 | OnlineAccepted |
| 2 | CommunicationEstablished |
9 | AlreadyOnline |
| 3 | CommunicationRejected |
10 | OnlineRejected |
| 4 | EquipmentIdentity |
11 | OfflineAck |
| 5 | ValuesReceived |
12 | OnlineAck |
| 6 | NamesReceived |
13 | StatusReplied |
튜토리얼 예제는 Failed 결과의 code를 출력하지 않습니다. 이유를 알아야 하면 진단 로그의 transaction_failed 레코드를 봅니다.
HSMS 이벤트
섹션 제목: “HSMS 이벤트”TCP 끝점에서 꺼낸 이벤트(pdcomm_tcp_next_event)의 종류는 다음과 같습니다. .NET 이름은 HsmsEventKind입니다.
| 값 | 이벤트 | 뜻 |
|---|---|---|
| 1 | STATE_CHANGED |
연결 상태가 바뀌었습니다. from_state, to_state를 봅니다. |
| 2 | RECEIVED |
메시지를 받았습니다. is_reply가 1이면 응답, aborted가 1이면 F0 응답입니다. |
| 3 | FAILED |
요청이 응답 없이 끝났습니다. code가 아래 실패 이유입니다. |
| 4 | PROTOCOL_ERROR |
받은 데이터가 프로토콜에 맞지 않습니다. code가 아래 프로토콜 오류입니다. |
| 5 | TRANSPORT_ERROR |
소켓 작업이 실패했습니다. code는 작업, detail은 I/O 오류 종류입니다. |
| 6 | W0_WRITTEN |
W0 메시지를 끝까지 썼습니다. 상대가 받았다는 뜻은 아닙니다. |
실패 이유(FAILED)
섹션 제목: “실패 이유(FAILED)”.NET 이름은 HsmsFailureReason입니다.
| 값 | 이유 | 뜻 |
|---|---|---|
| 1 | Cancelled |
애플리케이션이 취소했거나 끝점을 멈췄습니다. |
| 2 | T3Timeout |
데이터 메시지 응답이 T3 안에 오지 않았습니다. 연결은 유지됩니다. |
| 3 | T6Timeout |
Select, Deselect, Linktest 응답이 T6 안에 오지 않았습니다. 이어서 연결이 끊깁니다. |
| 4 | Disconnected |
응답을 기다리던 중 연결이 끊겼습니다. |
| 5 | Deselected |
상대가 Deselect로 통신을 끝냈습니다. |
| 6 | Rejected |
상대가 Reject.req로 거부했습니다. value1은 거부된 유형, value2는 이유 코드입니다. |
| 7 | SelectStatus |
Select.rsp의 상태가 0이 아닙니다. value1이 상태 값입니다. |
| 8 | DeselectStatus |
Deselect.rsp의 상태가 0이 아닙니다. value1이 상태 값입니다. |
프로토콜 오류(PROTOCOL_ERROR)
섹션 제목: “프로토콜 오류(PROTOCOL_ERROR)”.NET 이름은 HsmsProtocolError입니다.
| 값 | 오류 | 뜻 |
|---|---|---|
| 1 | InvalidFrame |
받은 프레임의 헤더나 본문이 잘못되었습니다. detail에 7–13의 세부 이유(PType, SType, 세션 ID, 헤더 필드, 제어 메시지 본문, SECS-II 본문 등)가 있습니다. 길이 필드처럼 바이트 스트림의 구분 자체가 깨지면 다시 맞출 방법이 없으므로, 이 이벤트 대신 연결을 닫고 전송 오류(Read, InvalidData)를 냅니다. |
| 2 | FrameLimit |
예약된 값입니다. 이 버전은 내지 않습니다. max_frame_bytes보다 큰 프레임을 받으면 연결을 닫고 전송 오류(Read, InvalidData)를 냅니다. 보낼 본문이 프레임에 들어가지 않으면 요청 호출이 PDCOMM_LIMIT_EXCEEDED를 반환합니다. |
| 3 | WrongSession |
데이터 메시지의 세션 ID가 다릅니다. 메시지는 버려집니다. value1은 기대 값, value2는 받은 값입니다. |
| 4 | DuplicatePrimary |
같은 시스템 바이트(system bytes)의 요청이 아직 열려 있습니다. |
| 5 | IncomingLimit |
받기 슬롯 32개가 모두 응답하지 않은 W1 요청으로 찬 상태에서 새 W1 요청이 왔습니다. SDK가 새 요청에 자동으로 F0을 보냅니다. |
| 6 | UnexpectedReply |
열린 거래가 없는 응답입니다. T3가 지난 뒤 도착한 늦은 응답이 대표적입니다. |
| 7 | UnexpectedControl |
예약된 값입니다. 이 버전은 내지 않습니다. 짝이 없는 제어 응답에는 이벤트 대신 Reject.req(이유 3)로 답합니다. |
| 8 | InvalidReject |
대응하는 거래가 없는 Reject.req입니다. |
| 9 | InvalidReply |
예약된 값입니다. 이 버전은 내지 않습니다. |
전송 오류(TRANSPORT_ERROR)
섹션 제목: “전송 오류(TRANSPORT_ERROR)”code는 실패한 작업(HsmsIoOperation)입니다. 1 Bind, 2 LocalAddress, 3 Connect, 4 Configure, 5 Accept, 6 Read, 7 Write입니다.
detail은 I/O 오류 종류(HsmsIoErrorKind)입니다. 자주 보는 값은 다음과 같습니다.
| 값 | 종류 | 흔한 상황 |
|---|---|---|
| 3 | Refused |
Active 쪽 연결 대상 포트에서 아무도 기다리지 않습니다. |
| 4 | Reset |
상대가 연결을 강제로 닫았습니다. |
| 7 | AddressInUse |
같은 주소와 포트를 이미 다른 프로그램이 씁니다. |
| 8 | AddressUnavailable |
이 컴퓨터에 없는 IP 주소입니다. |
| 11 | TimedOut |
연결이나 쓰기가 시간 안에 끝나지 않았습니다. |
| 13 | UnexpectedEof |
상대가 연결을 닫았습니다. |
| 15 | InvalidData |
프레임 중간에 연결이 끊겼거나, 받은 프레임의 길이 필드가 잘못되었거나 max_frame_bytes를 넘어 연결을 닫았습니다. |
전체 값은 0 Other, 1 NotFound, 2 Permission, 3 Refused, 4 Reset, 5 Aborted, 6 NotConnected, 7 AddressInUse, 8 AddressUnavailable, 9 BrokenPipe, 10 WouldBlock, 11 TimedOut, 12 Interrupted, 13 UnexpectedEof, 14 InvalidInput, 15 InvalidData, 16 WriteZero입니다. WouldBlock은 SDK 안에서 처리되므로 애플리케이션에 오지 않습니다.
처리되지 않은 메시지와 S9
섹션 제목: “처리되지 않은 메시지와 S9”PDComm SDK는 S9Fx 오류 메시지를 자동으로 보내지 않습니다. PDComm 시뮬레이터는 다르게 동작합니다. 이 절 끝의 시뮬레이터의 응답을 참고합니다. 상대가 보낸 S9Fx는 Unhandled 결과(code 3, Unsupported)로 애플리케이션에 전달됩니다.
받은 W1 요청이 Unhandled이면 SDK는 아무 응답도 보내지 않습니다. 이때 애플리케이션이 직접 응답해야 합니다.
- 원래 이벤트로
pdcomm_tcp_reply(.NETGemEndpoint.ReplyRaw)를 호출해 응답하거나, 펑션 0(SxF0)으로 거래를 중단합니다. - 응답할 수 없으면 끝점을 닫습니다.
응답하지 않으면 두 가지 문제가 생깁니다.
- 상대는 T3 시간이 지날 때까지 기다린 뒤 실패합니다.
- 응답하지 않은 W1 요청이 받기 슬롯(incoming slot)을 계속 차지합니다. 32개가 차면 SDK는 이후 모든 W1 요청에 자동으로 F0을 보내고
IncomingLimit프로토콜 오류를 냅니다. 이때부터는 S1F3 같은 정상 요청도 실패합니다.
튜토리얼 예제는 처리하지 않은 W1 요청을 F0으로 중단합니다. 알람 카탈로그가 없는 C++ 튜토리얼 Equipment에 C# Host가 alarms(S5F5)를 보내면 Equipment 쪽에 다음이 출력됩니다.
RESULT kind=5 request=0 outcome=0SECS-II 01 00ABORT S5F0 for S5F5Host 쪽에는 다음이 출력됩니다.
RESULT kind=3 request=13 outcome=0 status=NoneHost 쪽 결과는 Failed이고 code는 12(Aborted)입니다. T3를 기다리지 않고 바로 끝납니다.
시뮬레이터의 응답
섹션 제목: “시뮬레이터의 응답”PDComm 시뮬레이터는 처리하지 않은 1차 메시지에 SEMI E5 스트림 9(Stream 9)에 따라 직접 응답합니다. 장비 역할 세션이 통신 수립(COMMUNICATING) 상태이면 다음과 같이 답합니다.
| 받은 1차 메시지 | 시뮬레이터의 응답 |
|---|---|
| 장비가 모르는 스트림 | S9F3(Unrecognized Stream Type) |
| 아는 스트림의 모르는 펑션 | S9F5(Unrecognized Function Type) |
| 본문 형식이 잘못된 메시지 | S9F7(Illegal Data) |
알지만 지금 처리할 수 없는 메시지(예: 알람 카탈로그 없이 받은 S5F5) |
SxF0만 보냅니다. |
S9Fx에는 받은 메시지의 10바이트 헤더(MHEAD)가 들어 있고 W 비트가 없습니다. 받은 메시지에 W 비트가 있으면 시뮬레이터는 같은 스트림의 F0(SxF0)도 보내 거래를 닫습니다. 그래서 상대는 T3를 기다리지 않고, 받기 슬롯도 쌓이지 않습니다. 호스트 역할 세션이나 통신 수립 전의 세션은 S9Fx를 보내지 않고 W 비트 요청에 SxF0만 보냅니다.