콘텐츠로 이동
무료 평가 시작

헤드리스와 시나리오

pdcomm-modbus-headless는 화면 없이 동작하는 Modbus 시뮬레이터입니다. GUI와 같은 서비스 명령을 표준 입력으로 받습니다. CI, 원격 서버, 스크립트에서 서버나 클라이언트를 띄울 때 씁니다.

명령 동작 종료 코드
pdcomm-modbus-headless 표준 입력에서 JSON 명령을 한 줄씩 읽고 처리합니다. 모든 명령이 성공하면 0, 하나라도 실패하면 1
pdcomm-modbus-headless --scenario <파일> 시나리오 파일의 단계를 차례로 실행하고 기대값을 비교합니다. 통과 0, 불일치나 오류 1
그 밖의 인수 사용법 Usage: pdcomm-modbus-headless [--scenario path]을 표준 오류에 씁니다. 2
  • 한 줄에 JSON 객체 하나를 씁니다. 줄의 최대 길이는 1MiB입니다. 여러 줄로 나눈 JSON은 줄마다 따로 해석되어 실패합니다.
  • 명령마다 응답 한 줄을 표준 출력에 씁니다. 성공은 {"ok":true,"result":...}, 실패는 {"ok":false,"error":"..."}입니다.
  • JSON 문법 오류도 {"ok":false,"error":"key must be a string at line 1 column 2"}처럼 응답합니다.
  • {"action":"shutdown"}은 응답 없이 끝냅니다. 입력을 닫아도(EOF) 끝납니다.
  • 입력을 기다리는 동안에도 약 2ms마다 poll을 실행합니다. 그래서 서버와 게이트웨이는 계속 응답합니다.
  • poll이 실패하면 표준 오류에 Endpoint poll failed: <오류>를 쓰고 연결을 끊습니다.
  • 표준 출력에는 응답만 씁니다. 진단 로그는 파일이나 표준 오류로 갑니다.
  • 오류가 한 번이라도 나면 이후 명령은 계속 처리하지만 종료 코드는 1입니다. shutdown이나 EOF로 끝내도 오류 기록은 지워지지 않습니다.

명령 목록과 필드는 JSON 명령 레퍼런스에 있습니다.

터미널 창
"$MODBUS_SDK/bin/pdcomm-modbus-headless"

터미널에 명령을 한 줄씩 붙여 넣습니다. 서버로 쓴다면 터미널을 열어 둡니다. 입력을 닫으면 서버도 끝납니다.

명령을 미리 파일이나 다른 프로그램 출력으로 만들어 넣을 수 있습니다. 입력이 끝나면 모든 응답을 쓰고 종료합니다.

터미널 창
printf '%s\n' '{"action":"execute","pdu":"060000002A"}' '{"action":"execute","pdu":"0300000001"}' | "$MODBUS_SDK/bin/pdcomm-modbus-headless"
{"ok":true,"result":{"response":[6,0,0,0,42],"source":"local","status":"success"}}
{"ok":true,"result":{"response":[3,2,0,42],"source":"local","status":"success"}}

서버를 정해진 시간 동안 열어 두려면 입력을 늦게 닫습니다.

터미널 창
(printf '%s\n' '{"action":"configure","config":{"transport":"tcp","role":"server","address":"127.0.0.1:1502","port":"","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000}}' '{"action":"connect"}'; sleep 4) | "$MODBUS_SDK/bin/pdcomm-modbus-headless" > /dev/null

이 서버는 4초 동안 127.0.0.1:1502에서 요청에 응답한 뒤 끝납니다. 오래 열어 둘 서버라면 예제의 peer.py처럼 입력 파이프를 유지하는 래퍼를 씁니다.

시나리오(scenario) 파일은 단계의 JSON 배열입니다. 크기는 1MiB까지입니다. 각 단계는 다음과 같습니다.

필드 필수 설명
command 예 실행할 명령 객체
expect 아니요 결과(result)와 비교할 필드. 적은 필드만 정확히 같은지 비교합니다.

예제 시나리오는 빌드 기능을 확인하고, 쓰기와 읽기와 예외를 확인합니다.

examples/modbus-tutorial/scenario.json
[
{"command":{"action":"capabilities"},"expect":{"protocol":"modbus","canopen":false}},
{"command":{"action":"execute","pdu":"060000002A"},"expect":{"response":[6,0,0,0,42],"status":"success"}},
{"command":{"action":"execute","pdu":"0300000001"},"expect":{"response":[3,2,0,42],"status":"success"}},
{"command":{"action":"execute","pdu":"0300000000"},"expect":{"response":[131,3],"status":"exception"}}
]
터미널 창
"$MODBUS_SDK/bin/pdcomm-modbus-headless" --scenario "$MODBUS_EXAMPLES/modbus-tutorial/scenario.json"
{"result":{"ascii":true,"canopen":false,...,"protocol":"modbus","rtu":true,"security":true,"tcp":true},"step":1}
{"result":{"response":[6,0,0,0,42],"source":"local","status":"success"},"step":2}
{"result":{"response":[3,2,0,42],"source":"local","status":"success"},"step":3}
{"result":{"response":[131,3],"source":"local","status":"exception"},"step":4}
  • 단계마다 {"step":N,"result":...}를 출력합니다.
  • 기대값이 다르면 표준 오류에 Scenario step N field <필드> did not match를 쓰고 바로 종료 코드 1로 끝납니다.
  • 명령이 실패하면 오류 메시지(예: Connect a client before sending a request)를 쓰고 종료 코드 1입니다.
  • expect가 객체가 아니면 Expected fields must be an object입니다.
  • 시나리오는 새 세션에서 시작하며 poll을 하지 않습니다. 서버 역할로 연결해도 요청에 응답하지 않으므로, 시나리오는 로컬 execute나 클라이언트 request 검사에 씁니다.

클라이언트 시나리오로 실행 중인 장비나 서버를 점검할 수도 있습니다. 다음은 127.0.0.1:1502의 서버에 쓰고 읽어 결과를 확인합니다.

device-check.json
[
{"command":{"action":"configure","config":{"transport":"tcp","role":"client","address":"127.0.0.1:1502","port":"","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000}}},
{"command":{"action":"connect"},"expect":{"connected":true}},
{"command":{"action":"request","pdu":"060000002A"},"expect":{"status":"success"}},
{"command":{"action":"request","pdu":"0300000001"},"expect":{"response":[3,2,0,42],"source":"wire"}}
]
터미널 창
"$MODBUS_SDK/bin/pdcomm-modbus-headless" --scenario device-check.json

실제 장비에 쓰기를 넣을 때는 매뉴얼에서 안전한 주소인지 먼저 확인합니다.

프로젝트는 구성과 데이터 모델을 담은 JSON 파일입니다. GUI의 Project 메뉴, 헤드리스, MCP, SDK가 같은 형식을 씁니다.

{
"kind": "pdcomm.modbus.project",
"version": 1,
"config": {"transport": "tcp", "role": "server", "address": "127.0.0.1:1502", ...},
"model": {"coils": {...}, "discrete_inputs": {...}, "holding_registers": {...}, "input_registers": {...},
"files": {...}, "fifos": {...}, "objects": {...}, "server_id": [1, 255],
"exception_status": 0, "individual_access": true}
}

project_export 결과의 result만 파일로 저장합니다. {"ok":true,...} 전체를 저장하지 않습니다.

터미널 창
printf '%s\n' '{"action":"data_set","table":"holding_registers","address":0,"values":[42,7]}' '{"action":"project_export"}' | "$MODBUS_SDK/bin/pdcomm-modbus-headless" | tail -n 1 | jq '.result' > plant.modbus.json

불러올 때는 파일을 project 필드에 넣습니다. 문자열 치환으로 JSON을 만들지 말고 jq 같은 JSON 도구를 씁니다.

터미널 창
jq -c '{action:"project_import",project:.}' plant.modbus.json > replay.jsonl
echo '{"action":"execute","pdu":"0300000002"}' >> replay.jsonl
"$MODBUS_SDK/bin/pdcomm-modbus-headless" < replay.jsonl
{"ok":true,"result":{"loaded":true}}
{"ok":true,"result":{"response":[3,4,0,42,0,7],"source":"local","status":"success"}}

불러오기 규칙은 다음과 같습니다.

  • 연결을 끊은 상태에서만 불러옵니다. 아니면 Disconnect before loading a project입니다.
  • kind가 pdcomm.modbus.project, version이 1이어야 합니다. 아니면 Unsupported Modbus project kind or version입니다.
  • 모르는 필드가 있거나 필드가 빠지면 실패합니다(unknown field ..., missing field ...).
  • 구성과 모델을 모두 검사한 뒤에 바꿉니다. 실패하면 현재 상태를 그대로 둡니다.
  • security의 인증서와 키는 경로만 저장합니다. 다른 PC에서 쓰려면 경로를 고칩니다. 키 내용은 프로젝트에 넣지 않습니다.