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

SECS-I 가상 쌍

SECS-I 가상 쌍(virtual pair)은 시뮬레이터 프로세스 안에서 SECS-I 끝점(endpoint) 두 개를 메모리 버퍼로 맞물린 것입니다. 직렬 포트, 케이블, 드라이버가 필요 없습니다. Windows, Linux, macOS에서 같은 방식으로 SECS-I 블록 전송과 오류 처리를 시험할 수 있습니다.

시작하기 전에 다음 문서를 읽어 두면 좋습니다.

simulator process
+--------------------------------------------------------------+
| Host endpoint (Slave) Equipment endpoint (Master)
| sends ToEquipment <== in-memory byte FIFOs ==> sends ToHost
| ^ ^
| +-------- Poll: one bounded I/O cycle for both ----+
+--------------------------------------------------------------+
  • 쌍 하나는 PDComm SDK의 SECS-I 끝점 두 개입니다. 가상 쌍은 SEMI E4의 배치대로 장비 쪽을 마스터(master), 호스트 쪽을 슬레이브(slave)로 만듭니다. 두 끝점이 동시에 보내려 하면 경합(contention) 규칙에 따라 슬레이브인 호스트가 양보합니다. 호스트는 장비의 메시지를 먼저 받은 뒤 자기 메시지를 보냅니다.
  • 끝점의 읽기·쓰기 콜백(callback)은 직렬 포트 대신 메모리 FIFO에 연결됩니다. SDK로 만든 내 프로그램이 실제 포트에 쓰는 구조와 같습니다.
  • 쌍은 Poll을 할 때만 진행합니다. Poll 한 번이 양쪽 끝점의 I/O를 한 번씩 제한된 양만큼 처리합니다. 시뮬레이터는 Poll마다 쌍의 시계를 1틱(tick)씩 올립니다.
  • 시뮬레이터는 쌍을 최대 8개까지 만듭니다. 쌍마다 대기 이벤트는 최대 32개, 메시지 본문은 최대 1,952바이트입니다. 본문이 244바이트를 넘으면 여러 블록으로 나뉩니다.
  • 이 작업공간은 Overview의 역할이나 HSMS 세션과 독립적입니다. 어떤 역할로 실행한 시뮬레이터에서도 쓸 수 있습니다.

탐색 목록에서 SECS-I를 엽니다. 패널 제목은 SECS-I Virtual Pair입니다.

SECS-I Virtual Pair 패널. Pair lifecycle 버튼, Traffic 입력란, 이벤트 표가 표시됩니다.SECS-I Virtual Pair 패널. Pair lifecycle 버튼, Traffic 입력란, 이벤트 표가 표시됩니다.

화면은 세 그룹으로 나뉩니다.

그룹 컨트롤
Pair lifecycle Create, Start, Stop, Reset, Inspect, Destroy와 양쪽 끝점의 상태 표시
Traffic Endpoint(Host/Equipment), Device(0–32767), Stream(0–127), Function(0–255), System bytes(0–4294967295), Reply expected, Body hex, Submit
Bounded polling and events Poll, Drain, 결함 종류(Closed, Connection reset, Broken pipe, Timed out), Inject fault, 이벤트 표

이벤트 표의 열은 Revision, Side, Kind, Token, S/F, System, Body, Fault입니다. Kind는 Inbound(받은 메시지), Outbound complete(보내기 완료), Outbound failed(보내기 실패), Fault(끝점 결함) 중 하나입니다. 표는 최근 128행을 보관합니다.

  1. Create를 누릅니다. 상태 표시가 Host · Slave · Created · endpoint 1 · generation 1 · revision 1과 Equipment · Master · Created · ...로 바뀝니다.
  2. Start를 누릅니다. 양쪽 상태가 Running이 됩니다.

Start는 호스트 쪽이 Created 또는 Stopped일 때 켜집니다. Submit, Poll, Stop, Inject fault는 양쪽이 모두 Running일 때만 켜집니다.

  1. Traffic에서 Endpoint를 Host로 고릅니다. Device는 1, Stream은 1, Function은 1, System bytes는 1로 둡니다.
  2. Reply expected를 켜고 Body hex는 비웁니다. 그리고 Submit을 누릅니다. 아래 안내 문구 끝에 · token 1처럼 전송 토큰이 표시됩니다.
  3. Poll을 몇 번 누릅니다. 본문이 없는 단일 블록은 보통 네다섯 번이면 전달됩니다. Poll 한 번에 ENQ, EOT, 블록, ACK 교환이 조금씩 진행되기 때문입니다.
  4. Drain을 누릅니다. 이벤트 표에 다음 두 행이 추가됩니다.
Side Kind S/F System Body
Equipment Inbound S1F1 1
Host Outbound complete

Drain을 누르기 전까지 이벤트는 쌍 안의 대기열에 남아 있습니다. 대기열이 32개를 넘지 않도록 자주 비웁니다.

  1. Endpoint를 Equipment로 바꿉니다. Function은 2, System bytes는 요청과 같은 1로 둡니다.
  2. Reply expected를 끄고 Body hex에 0100(빈 리스트 L[0])을 넣은 뒤 Submit을 누릅니다.
  3. Poll을 몇 번 누른 뒤 Drain을 누릅니다. 호스트 쪽에 Inbound S1F2(본문 0100)가, 장비 쪽에 Outbound complete가 나타납니다.

가상 쌍은 블록 계층만 다룹니다. 응답을 자동으로 만들거나 요청과 짝짓지 않습니다. 응답 메시지는 위처럼 직접 보냅니다.

  1. Endpoint를 Equipment로 두고 결함 종류에서 Closed를 고른 뒤 Inject fault를 누릅니다.
  2. Poll을 한 번 누르고 Drain을 누릅니다. 장비 쪽에 Fault 행이 생기고 Fault 열에 3/0이 표시됩니다. 3은 연결이 닫혔다는 결함 종류(PDCOMM_SECS1_FAULT_CLOSED)입니다. 장비 상태는 Faulted가 됩니다.
  3. 결함 상태에서는 Submit과 Poll이 꺼집니다. Reset을 누르면 쌍의 세대(generation)가 올라가고 양쪽이 Created로 돌아갑니다.
  4. Start를 다시 눌러 통신을 이어 갑니다.

Connection reset, Broken pipe, Timed out도 같은 방법으로 주입합니다. 각 결함은 다음 Poll에서 해당 끝점의 읽기 콜백 결과로 나타납니다.

Stop으로 양쪽 끝점을 멈춘 뒤 Destroy를 누릅니다. 안내 문구가 Pair destroyed; capacity released.로 바뀌면 쌍이 차지하던 자원이 풀린 것입니다. 쌍은 프로젝트 파일에 저장되지 않습니다.

헤드리스 시뮬레이터의 secs1-virtual-pair 시나리오는 위 절차를 자동으로 수행합니다. 단일·다중 블록 양방향 전송, 동시 송신 경합, 결함 주입과 리셋을 차례로 확인합니다.

터미널 창
& "$Root\simulator\pdcomm-simulator-headless.exe" --scenario secs1-virtual-pair
$LASTEXITCODE

한 줄에 JSON 기록 하나씩, 모두 17줄을 출력하고 종료 코드 0으로 끝납니다. 중요한 기록은 다음과 같습니다.

detail 확인하는 내용
host-to-equipment-single-block-complete 호스트에서 장비로 32바이트 본문 단일 블록 전송
host-to-equipment-multi-block-complete 245바이트 본문을 두 블록으로 나눠 전송
equipment-to-host-single-block-complete, equipment-to-host-multi-block-complete 반대 방향 전송
simultaneous-contention-complete 양쪽이 동시에 보낼 때 경합 해결 후 두 메시지 모두 전달. 마스터인 장비의 메시지가 먼저 완료됩니다.
fault-observed, fault-reset-complete 결함 주입 뒤 관찰하고 리셋으로 복구
post-reset-bidirectional-complete 리셋 뒤 양방향 전송

마지막 줄은 다음과 같은 요약입니다.

{"schema":1,"sequence":17,"scenario":"secs1-virtual-pair","scope":"pair","kind":"summary","outcome":"passed","exit_code":0,"elapsed_ms":0,"records_emitted":17,"max_records":128,"truncated":false,"status_names":0,"status_values":0}

outcome이 passed가 아니거나 종료 코드가 0이 아니면 실패입니다. 다른 시나리오와 출력 형식은 헤드리스 시뮬레이터를 참고합니다.

시뮬레이터 MCP 서버는 같은 기능을 도구(tool)로 제공합니다.

도구 GUI 대응
simulator.secs1.pair.create Create
simulator.secs1.pair.get Inspect
simulator.secs1.pair.start, simulator.secs1.pair.stop, simulator.secs1.pair.reset, simulator.secs1.pair.destroy Start, Stop, Reset, Destroy
simulator.secs1.pair.poll Poll. 쌍의 시계 값 now를 직접 넘깁니다.
simulator.secs1.endpoint.submit Submit
simulator.secs1.events.drain Drain
simulator.secs1.endpoint.inject_fault Inject fault. fault는 closed, connection_reset, broken_pipe, timed_out 중 하나입니다.

쌍을 바꾸는 도구는 pair_id, generation, expected_revision, operation_id를 받습니다. 응답에 들어 있는 최신 generation과 revision을 다음 요청에 그대로 씁니다. operation_id는 요청마다 새 값을 씁니다. 같은 값을 같은 입력으로 다시 보내면 저장된 결과가 replayed로 돌아옵니다.

호스트 쪽에서 S1F1을 보내는 요청은 다음과 같습니다.

{"jsonrpc":"2.0","method":"tools/call","params":{"name":"simulator.secs1.endpoint.submit","arguments":{"pair_id":1,"generation":1,"expected_revision":2,"operation_id":"op-3","side":"host","device_id":1,"stream":1,"function":1,"reply_expected":true,"system_bytes":1,"body_hex":""}},"id":5}

simulator.secs1.pair.poll을 now 값을 1씩 올리며 네 번 부른 뒤 simulator.secs1.events.drain을 부르면 장비 쪽 수신 이벤트가 돌아옵니다.

{"discarded_partial":false,"fault_detail":0,"fault_kind":0,"generation":1,"kind":1,"message":{"body_hex":"","device_id":1,"function":1,"reply_expected":true,"stream":1,"system_bytes":1},"pair_id":1,"revision":7,"side":"equipment","token":{"endpoint_id":0,"generation":0,"value":0}}

kind의 1–4는 Inbound, Outbound complete, Outbound failed, Fault입니다. 결함 상태에서 simulator.secs1.endpoint.submit을 부르면 INVALID_STATE 오류가 돌아옵니다. MCP 서버 설정과 공통 규칙은 시뮬레이터 MCP 서버를 참고합니다.

SDK로 실제 직렬 포트에 연결하기

섹션 제목: “SDK로 실제 직렬 포트에 연결하기”

가상 쌍에서 확인한 흐름은 SDK의 SECS-I 끝점에서 그대로 쓸 수 있습니다. 다른 점은 메모리 FIFO 대신 내 프로그램이 연 직렬 포트를 읽기·쓰기 콜백으로 넘긴다는 것뿐입니다. SDK는 직렬 포트를 열거나 닫지 않습니다. 포트의 설정과 수명은 애플리케이션이 관리합니다.

언어 주요 API
C pdcomm_secs1_create에 PdcommSecs1Io(콜백 두 개)와 PdcommSecs1Options를 넘깁니다. pdcomm_secs1_start, pdcomm_secs1_poll, pdcomm_secs1_submit, pdcomm_secs1_next_event로 진행합니다. 만들기와 시작에는 secs-gem 라이선스가 필요합니다.
C++ 포트 클래스를 Secs1IoAdapter로 감싸 Secs1Endpoint를 만듭니다. 어댑터가 C++ 예외를 C ABI 밖으로 내보내지 않도록 바꿔 줍니다.
C# Secs1Worker.StartAsync에 Secs1Read와 Secs1Write 대리자를 넘깁니다. SubmitAsync와 ReadEventsAsync로 메시지를 주고받습니다.

콜백과 스레드에는 다음 규칙이 있습니다.

  • 콜백은 막히지(blocking) 않아야 합니다. 지금 읽거나 쓸 바이트가 없으면 PDCOMM_SECS1_IO_WOULD_BLOCK(C#은 Secs1IoStatus.WouldBlock)을 바로 돌려줍니다. 처리한 바이트 수는 capacity를 넘으면 안 됩니다.
  • 콜백은 poll 안에서만 호출됩니다. 콜백 안에서 같은 끝점의 함수를 다시 부르면 PDCOMM_INVALID_STATE가 됩니다.
  • 끝점의 생성, 호출, 파괴는 모두 끝점을 만든 스레드에서 해야 합니다. C#의 Secs1Worker는 전용 스레드를 만들어 이 규칙을 대신 지킵니다.
  • 끝점은 마스터와 슬레이브 중 하나로 만듭니다(priority). 한쪽이 마스터면 상대는 슬레이브여야 경합이 풀립니다. SEMI E4에서는 장비가 마스터(PDCOMM_SECS1_MASTER), 호스트가 슬레이브(PDCOMM_SECS1_SLAVE)입니다. 가상 쌍도 같은 배치를 씁니다. 상대 장치가 다른 배치를 요구하면 그 설정에 맞춥니다.
  • 본문이 max_outbound_body_bytes나 max_body_bytes를 넘거나 블록 수가 max_outbound_blocks나 max_blocks를 넘으면 pdcomm_secs1_submit는 PDCOMM_LIMIT_EXCEEDED를 돌려줍니다. C#의 SubmitAsync는 PdcommStatus.LimitExceeded로 실패합니다. PDCOMM_INVALID_ARGUMENT는 NULL 인수나 잘못된 메시지 필드에만 씁니다.
  • 타이머 옵션(grant_wait_ticks, ack_wait_ticks, inbound_progress_ticks, inter_block_ticks)은 poll에 넘기는 now와 같은 단위입니다. C#의 Secs1Worker는 밀리초를 넘깁니다. 옵션과 SEMI E4 타이머의 대응은 SECS-I 배경 지식을 참고합니다.