서비스 오류와 시간 초과
서비스 오류는 명령 자체가 실패한 경우입니다. Modbus 예외 응답과 다르며, 결과가 없습니다. 같은 오류가 실행 환경마다 다음처럼 보입니다.
| 환경 | 모습 |
|---|---|
| C | PDCOMM_MODBUS_SERVICE_ERROR(5)와 오류 버퍼 {"error":"<메시지>","ok":false} |
| C++, Qt, MFC | std::runtime_error. what()은 오류 JSON 전체 |
| .NET | ModbusCommandException(InvalidOperationException 파생). Message는 메시지만, Code는 라이선스 오류의 code |
| 헤드리스 | {"error":"<메시지>","ok":false} 줄, 종료 코드 1 |
| MCP | isError가 true, content[0].text가 메시지 |
| GUI | 상태 줄의 <명령> failed: <메시지> |
아래 표의 메시지는 실제 출력 그대로입니다. Io(...)의 괄호 안 OS 오류 문구는 운영체제마다 다릅니다.
명령 형식
섹션 제목: “명령 형식”| 메시지 | 원인과 해결 |
|---|---|
Action is required |
action 필드가 없습니다. |
Unknown service action |
action 이름이 틀렸습니다. JSON 명령 레퍼런스를 확인합니다. |
key must be a string at line 1 column 2, EOF while parsing an object at line 2 column 0 등 |
JSON 문법 오류입니다. 헤드리스와 MCP는 한 줄에 객체 하나를 씁니다. |
Input exceeds one MiB |
헤드리스·MCP의 한 줄이 1MiB를 넘었습니다. |
missing field `port` , unknown field `extra`, expected one of ... |
구성이나 모델에 필드가 빠졌거나 모르는 필드가 있습니다. |
PDU is required |
request, execute에 pdu가 없습니다. |
Invalid hex length, Invalid hex |
16진수 문자열의 글자 수가 홀수이거나 16진수가 아닌 글자가 있습니다. |
Expected a byte array or hex string |
pdu나 bytes가 문자열도 0~255 배열도 아닙니다. |
Invalid unit |
unit이 0~255 밖입니다. |
| 메시지 | 원인과 해결 |
|---|---|
Invalid endpoint configuration |
다음 중 하나입니다. transport·role·parity 값이 틀림, unit이 1~247 밖, timeout_ms·ascii_timeout_ms가 0이거나 60000 초과, turnaround_ms가 60000 초과, baud가 0, stop_bits가 1·2가 아님, parity가 none인데 stop_bits가 1 |
Gateway requires a serial bus transport |
게이트웨이의 transport를 rtu나 ascii로 합니다. |
Invalid gateway route |
routes의 키가 0이거나, 값이 0이거나 247을 넘습니다. |
Invalid Security configuration |
security를 직렬 구성에 썼거나, mode가 틀렸거나, chain·private_key·trust 중 빈 값이 있습니다. |
invalid type: string "Invalid JSON", expected struct Security |
GUI Security 탭의 JSON 문법이 틀렸습니다. |
Protocol is excluded from this build |
SDK에 없는 전송이나 Security를 구성했습니다. capabilities로 확인합니다. |
Disconnect before changing configuration |
연결 중에는 구성을 바꿀 수 없습니다. disconnect 뒤 다시 합니다. |
Endpoint is already connected |
이미 연결했습니다. 다시 열려면 disconnect부터 합니다. |
| 메시지 | 원인과 해결 |
|---|---|
Invalid TCP address |
address가 IP:포트 형식이 아닙니다. localhost:1502 같은 호스트 이름은 받지 않습니다. 127.0.0.1:1502처럼 씁니다. |
Io(ConnectionRefused, "Connection refused (os error 61)") |
그 주소에서 기다리는 서버가 없습니다. 서버가 먼저 connect했는지, 포트가 같은지 봅니다. |
Io(TimedOut, "connection timed out") |
timeout_ms 안에 TCP 연결이 되지 않았습니다. 경로, 방화벽, 장비 전원을 확인합니다. |
Io(AddrInUse, "Address already in use (os error 48)") |
서버 포트를 다른 프로그램이 씁니다. 다른 포트를 쓰거나 그 프로그램을 끝냅니다. |
Io(AddrNotAvailable, "Can't assign requested address (os error 49)") |
서버 address의 IP가 이 PC의 주소가 아닙니다. 0.0.0.0이나 PC의 실제 주소를 씁니다. |
InvalidConfig |
직렬 구성의 port가 비어 있습니다. |
Io(Other, "No such file or directory") |
직렬 port 경로가 없습니다. |
Io(Other, "Unable to acquire exclusive lock on serial port") |
다른 프로그램이 직렬 포트를 열고 있습니다. |
Security(...), Config(...), Crypto(...) |
Modbus/TCP Security 오류입니다. 보안 연결 문제를 봅니다. |
라이선스
섹션 제목: “라이선스”SDK 라이브러리(C, C++, .NET, Qt, MFC)는 connect할 때 라이선스를 확인합니다. 헤드리스·MCP 시뮬레이터는 확인하지 않고, 시뮬레이터 GUI는 내장된 도구용 라이선스를 씁니다. 라이선스 오류 응답에는 error 외에 code가 있어 프로그램에서 구분할 수 있습니다.
code |
메시지 | 원인과 해결 |
|---|---|---|
license_required |
PDComm license required for module 'modbus' (<상태>): … |
connect에 필요한 라이선스가 없습니다. modbus 모듈이 필요하고, 구성에 security가 있으면 modbus-security도 필요합니다. 아무것도 열지 않았습니다. 응답의 license에 module, state, license_id가 있습니다. state를 라이선스 문제 해결에서 찾습니다. 이미 연결된 끝점은 계속 동작합니다. |
license_invalid |
License text is required, license text contains no PDCOMM LICENSE block, license text contains no valid license (<이유>), license text is larger than 1048576 bytes |
license_install의 text가 없거나, 서명이 맞는 라이선스 블록이 없거나, 1MiB를 넘습니다. 이전에 설치한 라이선스는 그대로입니다. 받은 파일 전체를 그대로 넘깁니다. |
host_id_unavailable |
Host id unavailable: <이유> |
host_id가 컴퓨터 고유 ID를 읽지 못했습니다. 컨테이너라면 /etc/machine-id를 만듭니다. 호스트 ID를 봅니다. |
거부된 connect의 응답은 다음과 같습니다.
{"code":"license_required","error":"PDComm license required for module 'modbus' (missing): no valid license for this module was found. Existing connections keep running. Install a license with the license API, PDCOMM_LICENSE_FILE or a pdcomm.license file.","license":{"license_id":null,"module":"modbus","state":"missing"},"ok":false}| 메시지 | 원인과 해결 |
|---|---|
Connect a client before sending a request |
클라이언트로 연결하지 않았습니다. 서버 역할에서 request를 보내도 이 오류입니다. |
Timeout |
timeout_ms 안에 응답이 오지 않았습니다. 아래 시간 초과를 봅니다. |
Closed |
TCP 연결이 이미 닫혔습니다. 시간 초과 뒤, 상대가 연결을 끊은 뒤, 입출력 오류 뒤에 나옵니다. state의 connected는 false입니다. 다시 connect합니다. |
Io(ConnectionReset, "Connection reset by peer (os error 54)") |
상대가 연결을 강제로 끊었습니다. 평문 클라이언트가 Security 서버에 붙은 경우에도 나옵니다. |
Unsupported |
TCP 클라이언트로 직렬 전용 기능(07, 08, 11, 12, 17)을 보내려 했습니다. |
InvalidConfig |
직렬 클라이언트로 유닛 0에 쓰기가 아닌 요청을 보냈거나, 유닛이 247을 넘습니다. |
Protocol(Correlation) |
응답이 요청과 맞지 않습니다(다른 유닛, 다른 기능 코드, 다른 트랜잭션 식별자). 같은 주소에 여러 장비가 응답하는지, 게이트웨이 설정을 확인합니다. |
Protocol(Length), Protocol(Value) |
응답의 길이나 값이 기능 정의와 맞지 않습니다. 예외 응답은 코드가 명세에 없어도(예: 07) 오류가 아니라 "status":"exception" 결과입니다. |
Protocol(Checksum), Protocol(Encoding), Protocol(Address) |
직렬 응답의 CRC·LRC, ASCII 문자, 주소가 틀렸습니다. 직렬 통신 문제를 봅니다. |
요청 PDU 검사
섹션 제목: “요청 PDU 검사”PDComm 클라이언트는 요청 PDU를 보내기 전에 검사하고, 틀리면 보내지 않습니다. 이때 메시지는 한 단어입니다.
| 메시지 | 예 |
|---|---|
Length |
길이가 기능에 맞지 않음. 0700, 빈 PDU |
Value |
수량이 0이거나 최대 수량을 넘음, 기능 05의 값이 틀림. 0300000000 |
Address |
주소와 수량이 65536을 넘음, 파일 참조가 틀림 |
Function |
지원하지 않는 기능이나 진단 하위 기능 |
같은 PDU를 execute로 실행하면 서버 쪽 동작인 예외 응답(01, 02, 03)이 나옵니다.
시간 초과
섹션 제목: “시간 초과”Timeout은 요청은 보냈지만 timeout_ms 안에 응답을 받지 못했다는 뜻입니다. 쓰기 요청이라면 장비가 이미 처리했을 수 있으므로, 다시 쓰기 전에 값을 읽습니다.
TCP에서 확인할 것
- 상대가 응답하는 Modbus 서버인지 봅니다. 포트는 열려 있지만 다른 프로토콜의 서비스일 수 있습니다.
- 유닛 식별자가 맞는지 봅니다. 일부 장비와 게이트웨이는 모르는 유닛에 응답하지 않습니다.
- 상대가 게이트웨이라면 게이트웨이의 직렬 대기 시간보다
timeout_ms를 길게 합니다. - 상대가 SDK로 만든 서버라면 그 애플리케이션이
poll을 계속 호출하는지 봅니다.poll이 멈추면 연결은 받지만 응답하지 않습니다. - 진단 로그의
failed기록에서tx_bytes(보낸 바이트),rx_partial_bytes(받다 만 바이트),max_poll_gap_us(SDK 처리 지연)를 봅니다.
시간 초과가 나면 TCP 클라이언트는 연결을 닫습니다. 늦게 온 응답이 다음 요청의 응답으로 잘못 짝지어지지 않게 하기 위해서입니다. 이후 요청은 Closed이므로 connect를 다시 합니다.
직렬 회선의 시간 초과는 직렬 통신 문제를 봅니다.
잘못된 주소
섹션 제목: “잘못된 주소”주소를 잘못 잡으면 오류 없이 다른 레지스터의 값이 돌아오거나, 예외 02가 납니다.
| 증상 | 흔한 원인 |
|---|---|
| 값이 기대한 것의 옆 레지스터 값 | 매뉴얼의 1 기반 번호(40001)에서 1을 빼지 않았거나, 이미 0 기반인 주소에서 1을 또 뺐습니다. |
| 예외 02 | 테이블 접두 숫자를 주소에 넣었습니다. 40001은 주소 40000이 아니라 0입니다. |
| 값이 늘 0이거나 엉뚱함 | 테이블을 잘못 골랐습니다. 측정값은 입력 레지스터(04)에, 설정값은 보유 레지스터(03)에 있는 경우가 많습니다. |
| 32비트 값이 크게 틀림 | 두 레지스터의 순서가 장비와 반대입니다(ABCD, CDAB). |
| 음수가 65000대로 보임 | 부호 있는 16비트 값을 부호 없이 읽었습니다. |
확인 방법:
- 매뉴얼에서 값이 고정된 레지스터(장비 ID, 펌웨어 버전 등)를 찾아 먼저 읽습니다. 기대한 값이 나오면 주소 기준이 맞습니다.
- 한 칸씩 앞뒤 주소를 읽어 기대한 값의 위치를 찾습니다.
- 변환 규칙은 데이터 모델과 주소에 있습니다.
데이터 모델과 프로젝트
섹션 제목: “데이터 모델과 프로젝트”| 메시지 | 원인과 해결 |
|---|---|
Table is required, Address is required, Values are required |
data_set의 필드가 빠졌습니다. |
Unknown data table |
table은 coils, discrete_inputs, holding_registers, input_registers 중 하나입니다. |
Invalid data values |
values가 비었거나, 비트 테이블에 0·1이 아닌 값이 있거나, 주소가 65535를 넘습니다. |
Address outside configured table |
주소가 모델의 테이블 범위 밖입니다. data_get으로 start와 크기를 확인합니다. |
Model is required |
model_set에 model이 없습니다. |
Bit table values must be zero or one |
코일이나 이산 입력에 0·1이 아닌 값이 있습니다. |
Invalid table range |
테이블의 시작 주소와 개수가 16비트 주소 범위를 넘습니다. |
Invalid file, FIFO or device identification model |
파일 번호 0이나 레코드 10000개 초과, FIFO 값 31개 초과, 객체 0·1·2 누락, 예약 객체 7server_id 길이(2 |
Invalid diagnostic register |
diagnostics_set의 register가 65535를 넘습니다. |
Project is required |
project_import에 project가 없습니다. |
Disconnect before loading a project |
연결을 끊은 뒤 불러옵니다. |
Unsupported Modbus project kind or version |
kind가 pdcomm.modbus.project, version이 1이어야 합니다. {"ok":true,...} 전체를 저장했다면 result만 저장합니다. |
Fault bytes are required, Fault input exceeds limit |
fault에 bytes가 없거나 4096바이트를 넘었습니다. |
Transport is excluded |
fault의 transport가 빌드에 없습니다. |
Serial transports are excluded |
직렬 전송이 없는 빌드에서 serial_ports를 불렀습니다. |
진단 로그
섹션 제목: “진단 로그”| 메시지 | 원인과 해결 |
|---|---|
Logging configuration is required |
logging_configure에 config가 없습니다. |
Invalid logging level |
level은 error, warn, info, debug, trace 중 하나입니다. |
Unknown logging option |
모르는 설정 필드가 있습니다. |
Logging limits are out of range |
max_bytes(8192files(1 |
Forensics requires debug or trace logging |
forensics는 debug, trace 수준에서만 켭니다. |
Cannot open logging sink |
로그 파일을 열 수 없습니다. 폴더가 있는지, 쓰기 권한이 있는지 봅니다. |
Logging flush failed |
2초 안에 기록을 마치지 못했거나 쓰기가 실패했습니다. logging_status의 write_errors를 봅니다. |
헤드리스와 시나리오
섹션 제목: “헤드리스와 시나리오”| 메시지 | 원인과 해결 |
|---|---|
Usage: pdcomm-modbus-headless [--scenario path] |
인수가 틀렸습니다(종료 코드 2). |
Scenario step N field <필드> did not match |
시나리오의 기대값과 결과가 다릅니다(종료 코드 1). |
Expected fields must be an object |
expect가 객체가 아닙니다. |
Scenario exceeds one MiB |
시나리오 파일이 1MiB를 넘습니다. |
Endpoint poll failed: <오류> |
서버나 게이트웨이의 poll이 실패해 연결을 끊었습니다. 오류 내용을 보고 다시 connect합니다. |