Skip to main content

Beckhoff TwinCAT/ADS Driver

Overview

AMS/ADS over TCP for Beckhoff TwinCAT. Communicates directly over sockets within a single JVM, using only the AMS/ADS wire spec published by Beckhoff in infosys — no JNI or external libraries.

ItemValue
opc_typeADS
Implementation classplantpulse.driver.protocol.ads.ADSDriver
Library(none — in-house implementation. Beckhoff infosys public spec)
readGood (indexGroup/Offset or symbol handle)
writeGood (isWriteSupported() = true)
Default port48898 (ADS over TCP)
Default AMS port851 (PLC Runtime 1)
SecurityNone (direct TCP, ADS Secure not supported)
TwinCAT Router Access

ADS communication requires a static route registered in the AMS Router of the PC/PLC. Register the Edge's AmsNetId as a route in the TwinCAT System Manager on the PLC side; this driver then issues requests to that NetId.


Classes / Structure

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

Request-response matching uses the 32-bit invokeId issued by AtomicInteger invokeIdCounter, and requests are serialized on a single socket via synchronized sendCommand(...). Symbol name → handle conversion results are cached in symbolHandleCache (HashMap).


Wire Format Summary

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)
  • All integer fields are little-endian.
  • AmsNetId is 6 bytes (e.g. 5.40.40.116.1.1).
  • cmd: READ=2, WRITE=3, READ_STATE=4, READ_WRITE=9.
  • stateFlags: STATEFLAG_REQ_RESP = 0x0004 (response required).

OPC Registration Options (options)

KeyMeaningDefault
target-netidPLC AmsNetId, e.g. 5.40.40.116.1.1host + .1.1
target-portAMS target port (PLC Runtime)851
source-netidLocal AmsNetId127.0.0.1.1.1
source-portLocal AMS port32905
connect-timeoutConnection timeout (ms)3000
read-timeoutResponse wait timeout (ms)3000

Immediately after connect(), the handshake is verified by calling READ_STATE — on failure, connected=false.


Tag Address Formats

NotationMeaningindexGroup
M0:4%M offset 0, 4 bytesIGRP_PLC_RW_MB (0x4020)
I0:2%I offset 0, 2 bytesIGRP_PLC_RW_IB (0x4000)
Q0:1%Q offset 0IGRP_PLC_RW_QB (0x4030)
DB10:4DB10 offset 0IGRP_PLC_RW_DB (0x4040)
0x4020:0x10:4Direct indexGroup:indexOffset:length(as-is)
MAIN.fCounter:REALSymbolic (ReadWrite + IGRP_GET_SYMHANDLE_BYNAME → cached)(handle-based)

After : you may specify a byte length or a TwinCAT type keyword such as BOOL / INT / DINT / REAL / LREAL / STRING, and sizeOfTypeKeyword(...) determines the byte length.


Data Encoding / Decoding

decode(byte[], data_type) / encode(value, data_type, hintLen) handle little-endian:

data_typebytesNotes
Boolean / Bool1data[0] != 0
Byte1unsigned
Short / Int / Integer / Word2signed 16
UInt162unsigned
Int32 / DWord4signed 32
UInt324unsigned
Float / REAL4IEEE 754
Long / Int64 / LWord8signed
Double / LREAL8IEEE 754
StringNUTF-8, NUL-terminated handling

Supported / Not Supported

  • ✅ Memory area read/write (M / I / Q / DB)
  • ✅ Symbol name → handle (IGRP_GET_SYMHANDLE_BYNAME = 0xF003) → IGRP_RW_SYMVAL_BYHANDLE (0xF005)
  • READ_STATE (ADS state + Device state)
  • ❌ ADS Notification (subscription)
  • ❌ SUMUP (wrapping multiple reads into one)
  • ❌ ADS Secure / TLS

Test Coverage

test/java/plantpulse/driver/protocol/ads/

ClassTest count
AdsAddressParseTest10
AdsCodecTest11
AdsReadTest5
AdsResponseTest5
AdsSpecComplianceTest23
AdsWriteTest5
AmsHeaderTest6
AmsNetIdTest7
AmsTcpHeaderTest7

79 tests in total.


References

  • Beckhoff infosys: TwinCAT ADS / AMS over TCP wire format
  • Code: src/plantpulse/driver/protocol/ads/