Beckhoff TwinCAT/ADS 드라이버
개요
Beckhoff TwinCAT 의 AMS/ADS over TCP. JNI / 외부 라이브러리 없이 Beckhoff 가 공개한 infosys AMS/ADS wire spec 만으로 단일 JVM 안에서 socket 직접 통신합니다.
| 항목 | 값 |
|---|---|
opc_type | ADS |
| 구현 클래스 | plantpulse.driver.protocol.ads.ADSDriver |
| 라이브러리 | (없음 — 자체 구현. Beckhoff infosys 공개 spec) |
| read | 양호 (indexGroup/Offset 또는 심볼 핸들) |
| write | 양호 (isWriteSupported() = true) |
| 디폴트 포트 | 48898 (ADS over TCP) |
| 디폴트 AMS 포트 | 851 (PLC Runtime 1) |
| 보안 | 없음 (TCP 직결, ADS Secure 미지원) |
ADS 통신은 PC/PLC 의 AMS Router 에 등록된 정적 라우트가 필요합니다. PLC 측 TwinCAT System Manager 에서 엣지의 AmsNetId 를 라우트로 등록한 뒤 본 드라이버가 해당 NetId 로 요청합니다.
클래스 / 구조
ADSDriver (BaseProtocolDriver)
├── AmsTcpHeader 6 byte reserved + length(LE)
├── AmsHeader 32 byte target/source NetId+Port + cmd + state + dataLen + err + invokeId
├── AdsRead.buildRequest(indexGroup, indexOffset, length)
├── AdsWrite.buildRequest(indexGroup, indexOffset, data)
├── AdsResponse.parse(payload) — errorCode + data
├── AdsCommand — READ / WRITE / READ_STATE / READ_WRITE 상수, IGRP_*
└── AdsCodec — putUInt16/32LE, getUInt16/32LE, getInt32LE
요청-응답 매칭은 AtomicInteger invokeIdCounter 가 발급하는 32-bit invokeId 로 이루어지며 단일 socket 에서
synchronized sendCommand(...) 로 직렬화됩니다. 심볼 이름 → handle 변환 결과는 symbolHandleCache (HashMap) 에 캐시.
wire 포맷 요약
AMS/TCP Header (6) | AMS Header (32) | ADS payload (가변)
└ reserved(2) len(4)│└ tgtNetId(6) tgtPort(2) srcNetId(6) srcPort(2) cmd(2) state(2) dataLen(4) err(4) invokeId(4)
- 모든 정수 필드 little-endian.
AmsNetId는 6 byte (예5.40.40.116.1.1).cmd:READ=2,WRITE=3,READ_STATE=4,READ_WRITE=9.stateFlags:STATEFLAG_REQ_RESP = 0x0004(응답 요구).
OPC 등록 옵션 (options)
| 키 | 의미 | 디폴트 |
|---|---|---|
target-netid | PLC AmsNetId, 예 5.40.40.116.1.1 | host + .1.1 |
target-port | AMS 대상 포트 (PLC Runtime) | 851 |
source-netid | 로컬 AmsNetId | 127.0.0.1.1.1 |
source-port | 로컬 AMS 포트 | 32905 |
connect-timeout | 연결 타임아웃 (ms) | 3000 |
read-timeout | 응답 대기 타임아웃 (ms) | 3000 |
connect() 직후 READ_STATE 호출로 핸드셰이크 검증합니다 — 실패 시 connected=false.
태그 주소 형식
| 표기 | 의미 | indexGroup |
|---|---|---|
M0:4 | %M offset 0, 4 byte | IGRP_PLC_RW_MB (0x4020) |
I0:2 | %I offset 0, 2 byte | IGRP_PLC_RW_IB (0x4000) |
Q0:1 | %Q offset 0 | IGRP_PLC_RW_QB (0x4030) |
DB10:4 | DB10 offset 0 | IGRP_PLC_RW_DB (0x4040) |
0x4020:0x10:4 | 직접 indexGroup:indexOffset:length | (그대로) |
MAIN.fCounter:REAL | 심볼릭 (ReadWrite + IGRP_GET_SYMHANDLE_BYNAME → 캐시) | (handle 기반) |
: 뒤에는 byte 길이 또는 BOOL / INT / DINT / REAL / LREAL / STRING 등 TwinCAT 타입 키워드를 쓸 수 있고
sizeOfTypeKeyword(...) 가 byte 길이를 결정합니다.
데이터 인코딩 / 디코딩
decode(byte[], data_type) / encode(value, data_type, hintLen) 가 little-endian 으로 처리:
data_type | byte | 비고 |
|---|---|---|
| Boolean / Bool | 1 | data[0] != 0 |
| Byte | 1 | unsigned |
| Short / Int / Integer / Word | 2 | signed 16 |
| UInt16 | 2 | unsigned |
| Int32 / DWord | 4 | signed 32 |
| UInt32 | 4 | unsigned |
| Float / REAL | 4 | IEEE 754 |
| Long / Int64 / LWord | 8 | signed |
| Double / LREAL | 8 | IEEE 754 |
| String | N | UTF-8, NUL-terminated 처리 |
지원 / 미지원
- ✅ Memory area read/write (M / I / Q / DB)
- ✅ 심볼 이름 → handle (
IGRP_GET_SYMHANDLE_BYNAME=0xF003) →IGRP_RW_SYMVAL_BYHANDLE(0xF005) - ✅
READ_STATE(ADS state + Device state) - ❌ ADS Notification (구독)
- ❌ SUMUP (다중 read 1회 wrapping)
- ❌ ADS Secure / TLS
테스트 커버리지
test/java/plantpulse/driver/protocol/ads/
| 클래스 | 테스트 수 |
|---|---|
AdsAddressParseTest | 10 |
AdsCodecTest | 11 |
AdsReadTest | 5 |
AdsResponseTest | 5 |
AdsSpecComplianceTest | 23 |
AdsWriteTest | 5 |
AmsHeaderTest | 6 |
AmsNetIdTest | 7 |
AmsTcpHeaderTest | 7 |
총 79 테스트.
참고
- Beckhoff infosys: TwinCAT ADS / AMS over TCP wire format
- 코드:
src/plantpulse/driver/protocol/ads/