헤드리스와 시나리오
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 줄 입출력
섹션 제목: “JSON 줄 입출력”- 한 줄에 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)와 비교할 필드. 적은 필드만 정확히 같은지 비교합니다. |
예제 시나리오는 빌드 기능을 확인하고, 쓰기와 읽기와 예외를 확인합니다.
[ {"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의 서버에 쓰고 읽어 결과를 확인합니다.
[ {"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.jsonlecho '{"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에서 쓰려면 경로를 고칩니다. 키 내용은 프로젝트에 넣지 않습니다.
다음 단계
섹션 제목: “다음 단계”- 시뮬레이터로 시험 흐름 만들기에서 GUI 프로젝트를 헤드리스로 재생합니다.
- 같은 명령을 AI 도구에서 쓰려면 MCP 서버를 봅니다.