e-plc 웹 에디터(OpenPLC + EtherCAT + CM5 기반, 미쯔비시 MELSEC 스타일)의 래더 프로그램을 자연어 요구사항으로부터 생성한다. 웹 AI 채팅 모드에서는 LLM이 7종 래더 편집 tool(insert_rung / add_element / add_connection / set_rung_comment / set_device_comment / delete_rung / delete_element)을 직접 호출해 래더를 구성하고, CLI(Claude Code) 모드에서는 writer→simulator→reviewer 3-agent 루프로 검증까지 수행한다. 두 환경 모두에서 MELSEC GX Works 스타일 니모닉을 이해하되, 실행은 e-plc-runtime이 지원하는 12 opcode(LD/LDI/AND/ANI/OR/ORI/ORB/ANB/OUT/SET/RST/END + TMR/CNT)로 한정한다. "래더 작성", "PLC 래더", "ladder program", "자기유지 회로", "타이머 지연", "모터 제어", "시퀀스 제어", "PLC 시뮬레이션", "래더 테스트" 등의 요청에 반드시 자동 적용한다.
OpenPLC 기반 e-plc 웹 에디터 및 CLI 환경 양쪽에서 동작하는 래더 프로그램 작성 스킬이다. 실행 환경에 따라 동작이 근본적으로 달라지므로 가장 먼저 환경을 판정한다.
이 스킬은 두 환경에서 로드될 수 있고, 각 환경에서 해야 할 일이 서로 다르다.
현재 가용한 도구(tools) 목록에 insert_rung 이 있는지를 기준으로 판정한다.
| 조건 | 환경 | 동작 모드 |
|---|---|---|
insert_rung 도구가 있음 | 웹 AI 채팅 모드 (e-plc Next.js /ladder 페이지의 AIChatPanel) | §1 웹 모드 섹션만 따른다 |
Task / Bash 도구가 있고 insert_rung 이 없음 | CLI (Claude Code) 모드 | §2 CLI 모드 섹션을 따른다 |
| 둘 다 없음 | 알 수 없음 | 사용자에게 환경을 물어본다 |
Task(...)node scripts/pipeline.mjsbasheplc_runtimeTask / pipeline.mjs전제: 현재 사용 가능한 도구에
insert_rung,add_element,add_connection,set_rung_comment,set_device_comment,delete_rung,delete_element7종이 포함돼 있다. 그 외 서브에이전트(Task), 쉘(Bash), 파일시스템(Read/Write) 도구는 존재하지 않는다고 간주하고 호출 시도조차 하지 말아야 한다.
사용자 요구사항 → 이 7개 tool의 tool_use 호출 시퀀스를 생성해 같은 응답 안에서 직접 실행한다. 계획/설계 마크다운만 출력하고 끝내는 것은 실패다.
Task(subagent_type="ladder-writer", ...) 같은 서브에이전트 호출 — 웹에는 Task 도구 없음node scripts/pipeline.mjs, node scripts/apply_tools.mjs 등 bash/Node 실행eplc_runtime 바이너리 구동·재빌드| # | 도구 | 용도 | 필수 입력 |
|---|---|---|---|
| 1 | insert_rung | 새 Rung(래더 라인) 삽입 | (선택: comment, insertAfterIndex) |
| 2 | add_element | 접점·코일·FUNCTION 1개 추가 | rungIndex, elementType, device, col |
| 3 | add_connection | 병렬 회로 수직 연결선 | rungIndex, fromRow, fromCol, toRow, toCol |
| 4 | set_rung_comment | Rung 코멘트 설정 | rungIndex, comment |
| 5 | set_device_comment | 디바이스 심볼 주석 | device, comment |
| 6 | delete_rung | Rung 삭제 | rungIndex |
| 7 | delete_element | 요소 1개 삭제 | rungIndex, row, col |
자세한 스키마는 references/ladder-tools-api.md.
LD, LDI, AND, ANI, OR, ORI, ORB, ANB, OUT, SET, RST, ENDOUT T<n> K<pv> / OUT C<n> K<pv>로 컴파일)
미지원 기능 요청 시 → 생성 중단 후 사용자에게 "런타임 미지원. 자기유지+TMR 조합으로 대체 가능한지 확인 부탁드립니다"라고 안내.
NO_CONTACT / NC_CONTACT → col ∈ [0, 7]FUNCTION (TMR/CNT) → col = 8 고정 (2칸 점유: 8~9). 별도 COIL 불필요(FUNCTION이 출력 역할).COIL / SET_COIL / RST_COIL → col = 10 고정row ≥ 1에 요소 추가 후 오른쪽 합류 지점에 add_connection 1회. fromCol은 병렬 접점들의 col보다 커야 함(path-tracer가 mergeCol로 사용).반드시 이 순서로 작업한다. 단계를 건너뛰면 래더가 엉키거나 Rung {i}가 존재하지 않습니다 오류가 발생한다.
특히 [5] Pseudo 시뮬레이션과 [6] Pseudo 리뷰는 건너뛰지 말 것 — 2026-04-17 현장 재현: "편집만 되고 시뮬/리뷰가 빠졌다"는 사용자 불만의 근본 원인이 이 두 단계 누락이다.
[1] 요구사항 재진술 (1~3줄, 한국어) — 애매하면 질문 후 진행
[2] I/O 할당표 — X/Y/M/T/C/D 매핑 (짧은 테이블)
[3] Rung 설계 — 각 Rung이 무엇을 하는지 한 줄씩
[4] ★ 도구 호출 시퀀스 실행 ★ — 이 단계를 반드시 같은 응답에서 수행
Rung 당 순서:
insert_rung
→ add_element(접점들, row=0, col 0→7)
→ add_element(병렬 접점들, row=1+, col 동일)
→ add_connection(오른쪽 합류점)
→ add_element(COIL col=10 또는 FUNCTION col=8)
→ set_rung_comment
→ set_device_comment(해당 Rung에 처음 등장한 디바이스들)
[5] Pseudo 시뮬레이션 (LLM in-context trace) ★필수★ — §1.11 참조
[6] Pseudo 리뷰 (정적 체크리스트) ★필수★ — §1.12 참조
[7] 완료 요약 + 실제 시뮬 안내 — §1.13 참조 (F4 / 시뮬레이션 탭)
주의: 실제 eplc_runtime 바이너리는 웹 AI 채팅 런타임에서 실행 불가. 따라서 [5]/[6]은 실제 실행이 아니라 LLM이 생성한 IL을 머리로 트레이스하는 "Pseudo" 검증이다. 라벨에 반드시 "Pseudo 시뮬", "Pseudo 리뷰" 로 표기해 사용자가 실제 실행과 혼동하지 않도록 한다.
col=10인가?col=8인가?col 0~7 범위인가?add_connection이 하나씩 있고 fromCol > 접점 col인가?호출할 tool_use (순서대로):
insert_rung — { comment: "모터 기동/정지 자기유지", insertAfterIndex: -1 }add_element — { rungIndex:0, elementType:"NO_CONTACT", device:"X0", row:0, col:0 }add_element — { rungIndex:0, elementType:"NO_CONTACT", device:"Y0", row:1, col:0 }add_connection — { rungIndex:0, fromRow:0, fromCol:1, toRow:1, toCol:1 }add_element — { rungIndex:0, elementType:"NC_CONTACT", device:"X1", row:0, col:2 }add_element — { rungIndex:0, elementType:"COIL", device:"Y0", row:0, col:10 }set_rung_comment — { rungIndex:0, comment:"X0=기동, X1=정지 자기유지" }set_device_comment — { device:"X0", comment:"기동 버튼" }set_device_comment — { device:"X1", comment:"정지 버튼" }set_device_comment — { device:"Y0", comment:"모터 출력" }예상 IL: LD X0 / LD Y0 / ORB / ANI X1 / OUT Y0 / END
insert_rung — { comment:"X0 ON 후 3초 지연 타이머 T0" }add_element — { rungIndex:0, elementType:"NO_CONTACT", device:"X0", row:0, col:0 }add_element — { rungIndex:0, elementType:"FUNCTION", device:"TMR T0 K30", row:0, col:8 }set_rung_comment — { rungIndex:0, comment:"X0 ON → 3초 카운트" }insert_rung — { comment:"T0 만료 시 Y0 ON" }add_element — { rungIndex:1, elementType:"NO_CONTACT", device:"T0", row:0, col:0 }add_element — { rungIndex:1, elementType:"COIL", device:"Y0", row:0, col:10 }set_rung_comment — { rungIndex:1, comment:"타이머 만료 → 출력 Y0" }set_device_comment — { device:"X0", comment:"기동 입력" }set_device_comment — { device:"T0", comment:"3초 지연 타이머" }set_device_comment — { device:"Y0", comment:"지연 출력" }예상 IL: LD X0 / OUT T0 K30 / LD T0 / OUT Y0 / END
T0/T1 상호 리셋으로 0.5s/0.5s 플립플롭 구성 후 T0으로 Y0 구동:
insert_rung — { comment:"T1 OFF 구간에서 T0 카운트 (0.5s)" }add_element — { rungIndex:0, elementType:"NO_CONTACT", device:"X0", row:0, col:0 }add_element — { rungIndex:0, elementType:"NC_CONTACT", device:"T1", row:0, col:1 }add_element — { rungIndex:0, elementType:"FUNCTION", device:"TMR T0 K5", row:0, col:8 }insert_rung — { comment:"T0 ON되면 T1 카운트 (0.5s)" }add_element — { rungIndex:1, elementType:"NO_CONTACT", device:"T0", row:0, col:0 }add_element — { rungIndex:1, elementType:"FUNCTION", device:"TMR T1 K5", row:0, col:8 }insert_rung — { comment:"T0 ON 동안 Y0 ON → 1Hz 점멸" }add_element — { rungIndex:2, elementType:"NO_CONTACT", device:"T0", row:0, col:0 }add_element — { rungIndex:2, elementType:"COIL", device:"Y0", row:0, col:10 }set_rung_comment — { rungIndex:0, comment:"T1 OFF 구간 동안 0.5s 측정" }set_rung_comment — { rungIndex:1, comment:"T0 만료 시 T1 0.5s 측정 시작" }set_rung_comment — { rungIndex:2, comment:"T0 상태 → Y0 (1초 주기)" }set_device_comment — { device:"X0", comment:"점멸 기동" }set_device_comment — { device:"T0", comment:"0.5s 타이머 (OFF구간)" }set_device_comment — { device:"T1", comment:"0.5s 타이머 (ON구간 리셋용)" }set_device_comment — { device:"Y0", comment:"1Hz 점멸 출력" }예상 IL: LD X0 / ANI T1 / OUT T0 K5 / LD T0 / OUT T1 K5 / LD T0 / OUT Y0 / END
§1.13 완료 요약 표준 문구 참조. 간단 요약: "N개 Rung 편집 / tool_use M회 호출 / Pseudo 시뮬 passed·warn / 실제 검증은 F4 + 시뮬레이션 탭".
insertAfterIndex를 적절히 쓰거나 delete_rung으로 정리 후 추가. 덮어쓰기 실수를 피하기 위해 "현재 프로젝트 상태" 섹션(projectContext)을 먼저 읽는다.add_element(rungIndex, "NO_CONTACT", "Y0", row:1, col:0) + add_connection으로 피드백해야 자기유지가 성립한다.목적: tool_use로 래더를 편집한 직후, 실제 eplc_runtime 실행 없이 LLM이 in-context로 IL을 트레이스해 동작을 사용자에게 미리 보여준다. 실제 실행 아님을 "Pseudo" 라벨로 명시.
references/supported-opcodes.md 규칙대로 방금 만든 Rung들을 MELSEC IL로 변환. 각 Rung은 한 줄씩 LD / LDI / AND / ANI / OR / ORI / ORB / ANB / OUT / SET / RST 로 기술하고 마지막에 END.OUT Tn Km: 입력 조건 참이면 Tn 카운트 +1. 카운트가 m 이상이면 Tn 접점 ON. 입력 거짓이면 Tn 카운트 0 + 접점 OFF(비적산 타이머).OUT Cn Km: 입력 상승 에지(직전 OFF → 현재 ON)일 때만 카운트 +1. 카운트가 m 이상이면 Cn 접점 ON. RST 신호로 0 복원.최소 3행 필수: (a) 초기 / (b) 첫 전이 / (c) 정상 동작 반복. 상태가 복잡하면 ≤ 8행으로 축약.
### [5] Pseudo 시뮬레이션 (in-context trace, 실제 실행 아님)
**시나리오**: X4 ON 유지 → 1Hz 점멸 관찰 (cycleMs=100, settleMs=200)
| 사이클 | t(ms) | X4 | T0 cnt | T1 cnt | T0 | T1 | Y1 | 비고 |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | - | - | 0 | 0 | 0 | 초기 |
| 1 | 100 | 1 | 1 | - | 0 | 0 | 0 | X4 ON → T0 카운트 시작 |
| 5 | 500 | 1 | 5 | - | 1 | 0 | 1 | T0 만료(K5) → T0 접점 ON → Y1 ON, T1 카운트 시작 |
| 10| 1000 | 1 | 0 | 5 | 0 | 1 | 0 | T1 만료 → T0 입력측 NC(T1)이 OFF → T0 리셋 → Y1 OFF |
| 11| 1100 | 1 | 1 | 0 | 0 | 0 | 0 | T1 OFF되며 다음 주기 시작 |
**판정**: Y1이 500ms ON / 500ms OFF로 반복 → 1Hz 점멸 요구사항과 일치. **Pseudo verdict = passed**.
verdict 값:
passed: 모든 기대 동작 재현 확인warn: 동작은 재현되지만 §1.12 리뷰 체크리스트에서 경고 항목 존재uncertain: 요구사항 모호 또는 트레이스 불확정 → 사용자 질문delete_element / add_element 덮어쓰기 등)로 수정을 시도할 수 있다. 수정 후 Pseudo 시뮬 표를 다시 출력. 최대 2회까지만 시도하고, 초과 시 사용자에게 개입 요청.[5] 트레이스 직후 아래 6개 항목 각각 ✓ / ✗ / N/A 로 출력. ✗ 하나라도 있으면 원인·수정안 명시.
### [6] Pseudo 리뷰 체크리스트
1. **opcode 지원 범위** (✓/✗) — LD/LDI/AND/ANI/OR/ORI/ORB/ANB/OUT/SET/RST + TMR/CNT 내에서만 사용했는가?
- ✗일 때 예: "MOV 사용 발견 → 런타임 미지원. 자기유지+타이머 조합으로 대체 필요"
2. **디바이스 중복 OUT** (✓/✗) — 동일 Y/M에 OUT이 2회 이상 나오지 않는가? (DOUBLE_COIL 방지)
- ✗일 때 예: "Rung 2·Rung 4 둘 다 OUT Y0 → 뒤의 OUT이 이김. Rung 4 OUT을 SET/RST로 변경 고려"
3. **타이머/카운터 단위·값** (✓/✗) — K 값이 의도한 시간과 일치? (K1=100ms, 1초=K10, 3초=K30)
- ✗일 때 예: "요구는 3초인데 K3(=300ms) 사용 → K30으로 수정"
4. **엣지/자기유지 구조** (✓/✗) — SET/RST 쌍 맞음, 자기유지 시 OR Y 피드백 접점과 add_connection 존재?
- ✗일 때 예: "Rung 0 자기유지에 Y0 피드백 접점 누락 → X0 놓는 순간 Y0 OFF. `add_element(row=1, Y0)` + `add_connection` 필요"
5. **인터록/안전** (✓/✗/N/A) — 위험 기동 조건에 비상정지 NC 접점 혹은 인터록 포함? (없으면 warn, 사용자에게 고지)
- N/A: 단순 데모/점멸 회로
- ✗ 예: "모터 구동 Rung에 비상정지 미반영. 실제 적용 전 하드와이어 비상정지 이중화 필수 (ISO 13849, IEC 62061)"
6. **명명/코멘트** (✓/✗) — 모든 Rung에 한국어 코멘트, 새 디바이스에 `set_device_comment` 부여?
- ✗일 때 예: "T1에 device 코멘트 없음 → set_device_comment 추가 권장"
**리뷰 verdict**: passed(모두 ✓) / warn(✗ ≥1 있지만 동작 가능) / failed(opcode 미지원 또는 재편집 필수)
리뷰 verdict가 failed이면 §1.11.4대로 동일 응답 내 tool_use 재호출 루프 (최대 2회) 수행.
아래 템플릿을 그대로 채워 넣어 출력. 사용자가 실제 e-plc UI에서 검증하도록 F4 컴파일 + 시뮬레이션 탭을 반드시 안내.
### [7] 완료 요약
- 편집된 Rung: N개 (rungIndex 0~N-1)
- 호출된 tool_use: 총 M회 (insert_rung a / add_element b / add_connection c / set_rung_comment d / set_device_comment e)
- Pseudo 시뮬 verdict: passed | warn | uncertain
- Pseudo 리뷰 verdict: passed | warn | failed
- 경고 항목 요약 (있는 경우): [3] 타이머 K값 재확인 권장, [5] 비상정지 미반영 등
### 실제 동작 검증 (사용자 수행)
1. **F4** 키를 눌러 컴파일하고 IL 패널에서 MELSEC 니모닉을 확인하세요.
2. 화면 **하단 "시뮬레이션" 탭**에서 입력 X를 토글하며 실제 동작을 검증하세요.
3. 좌측 "디바이스" 패널에서 Y/T/C 상태와 코멘트를 확인하세요.
4. 예상과 다른 동작이면 구체적 불일치(어느 사이클에 어느 디바이스가 기대와 다름)를 알려주시면 재편집 + Pseudo 시뮬 다시 수행하겠습니다.
전제: 이 모드는
Task서브에이전트 도구,Bash,Read/Write가 사용 가능한 Claude Code 터미널 환경이다. 웹에서는 이 섹션의 어떤 절차도 실행하지 말 것.
/home/pjy4617/Repos/raspberrypi-ec/Program/e-plc-runtime/build/eplc_runtime/home/pjy4617/Repos/raspberrypi-ec/Program/e-plc//home/pjy4617/Repos/plc-skill/ 또는 temp/plc-skill/plc-skill/
├── SKILL.md ← 본 파일
├── USAGE.md ← 사용자용 상세 가이드
├── agents/
│ ├── ladder-writer.md ← (CLI) 요구사항 → tool_call JSON
│ ├── ladder-simulator.md ← (CLI) tool_call → IL → 런타임 시뮬
│ └── ladder-reviewer.md ← (CLI) 실패 진단 + 수정 지시
├── scripts/
│ ├── apply_tools.mjs ← tool_call → LadderProject
│ ├── compile_il.mjs ← LadderProject → MELSEC IL + 정적검증
│ ├── run_sim.mjs ← IL + 시나리오 → WS 검증
│ ├── pipeline.mjs ← 오케스트레이터 CLI
│ ├── run_evals.mjs ← Layer 1 회귀
│ └── run_agent_evals.mjs ← Layer 2 회귀
├── references/
│ ├── ladder-tools-api.md
│ ├── supported-opcodes.md
│ ├── ws-protocol.md
│ └── ladder-grid-rules.md
└── evals/
├── evals.json
└── fixtures/
┌─────────────┐ tool_calls ┌──────────────┐
│ USER 요구 │─────────────────▶│ ladder-writer │
└─────────────┘ └───────┬──────┘
│ JSON
▼
┌──────────────────┐
│ ladder-simulator │
│ - apply_tools │
│ - validate │
│ - compile IL │
│ - run_sim (WS) │
└───────┬──────────┘
│ report.json
┌───────────┴──────────┐
▼ ▼
passed failed
│ │
▼ ▼
USER에게 제출 ladder-reviewer
│ 수정 지시
▼
ladder-writer
Claude Code의 Task 도구로 서브에이전트를 호출:
Task(subagent_type="ladder-writer", prompt="...")
Task(subagent_type="ladder-simulator", prompt="tool_calls=... scenario=...")
Task(subagent_type="ladder-reviewer", prompt="report=...")
cd /home/pjy4617/Repos/plc-skill
node scripts/pipeline.mjs \
--tools /tmp/tool_calls.json \
--scenario /tmp/scenario.json \
--runtime /home/pjy4617/Repos/raspberrypi-ec/Program/e-plc-runtime/build/eplc_runtime \
--out /tmp/report.json
echo "exit=$?"
IL만 (시뮬 없이):
node scripts/pipeline.mjs --tools /tmp/tool_calls.json --project-only
런타임 재빌드:
cd /home/pjy4617/Repos/raspberrypi-ec/Program/e-plc-runtime
cd build && cmake .. && make -j4 eplc_runtime
node scripts/apply_tools.mjs tool_calls.json > project.json
| 증상 | 조치 |
|---|---|
eplc_runtime 바이너리 없음 | Program/e-plc-runtime 빌드 |
| 포트 8765 충돌 | pkill -f eplc_runtime; sleep 1 |
| WebSocket 미지원 | Node 22+ 확인 |
upload 응답 error | 생성된 IL을 확인해 파싱 오류와 대조 |
observed.Y0=undefined | 디바이스가 IL에 등장 안 함 → tool_calls 재확인 |
plc-simulation-plan.md 참조상세 규칙이 필요할 때 아래 파일을 읽는다 (progressive disclosure).
references/ladder-tools-api.mdreferences/supported-opcodes.mdreferences/ws-protocol.mdreferences/ladder-grid-rules.mdCLI 모드에만 해당:
scripts/run_evals.mjs) — 무료·10초. 골든 tool_calls → pipeline → assertion. CI 권장scripts/run_agent_evals.mjs) — Claude API 과금. writer 프롬프트/모델 변경 시 수동 1회apply_tools.mjs로 project.json 만들어 e-plc 웹 UI import 후 육안 검토cd /home/pjy4617/Repos/plc-skill
node scripts/run_evals.mjs
이 스킬이 활성화됐는데도 래더가 편집되지 않는다면:
insert_rung이 보이면 §1 모드를 따라야 하는데 LLM이 §2로 오인한 것이다. 이 SKILL.md의 §0을 다시 확인.pgrep eplc_runtime·which node 먼저 검증.