ADS Client Implementation#
This document explains how CATio implements the Beckhoff ADS (Automation Device Specification) protocol to communicate with TwinCAT systems running on Beckhoff PLCs.
What is ADS?#
ADS is Beckhoff’s proprietary protocol for communication with TwinCAT automation systems. It provides a standardized way to:
Read and write PLC variables
Query device information and state
Subscribe to value change notifications
Access EtherCAT diagnostic data
The protocol runs over TCP (port 48898) for reliable communication or UDP (port 48899) for route management. CATio implements a pure-Python ADS client using asyncio for non-blocking operations.
For the official specification, see the Beckhoff ADS Documentation.
Protocol Architecture#
ADS messages are wrapped in AMS (Automation Message Specification) frames for transport:
┌───────────────────────────────────────────┐
│ ADS Commands │
│ (Read, Write, Notification, etc.) │
├───────────────────────────────────────────┤
│ AMS Header │
│ (NetId, Port, CommandId, InvokeId) │
├───────────────────────────────────────────┤
│ TCP/IP Transport │
│ (Port 48898 for unencrypted ADS) │
└───────────────────────────────────────────┘
Key concepts:
AMS NetId: A 6-byte address identifying a TwinCAT system (often derived from IP, e.g.,
192.168.1.100.1.1)AMS Port: Identifies the target service within TwinCAT (I/O server = 300, EtherCAT Master = 65535)
Invoke ID: Correlates requests with responses for async operation
Index Group/Offset: Addresses specific data within a service
Establishing Communication#
Before sending ADS commands, the client must establish a route with the TwinCAT router.
Route Discovery and Registration#
TwinCAT maintains a routing table of authorized clients. CATio uses UDP messages to:
Discover the target’s AMS NetId: Query the TwinCAT system for its network identity
Register this client: Add an entry to the routing table with authentication credentials
The RemoteRoute class in client.py handles route management with these parameters:
Parameter |
Purpose |
|---|---|
|
IP address of the TwinCAT system |
|
Human-readable name for this client |
|
This client’s AMS NetId |
|
TwinCAT authentication user |
|
TwinCAT authentication password |
Note
Default TwinCAT credentials are typically Administrator / 1. Production systems should use proper authentication.
TCP Connection#
Once routed, CATio opens a persistent TCP connection for ADS communication. The AsyncioADSClient.connected_to() method in client.py uses Python’s asyncio.open_connection() for non-blocking I/O, returning stream reader/writer pairs for bidirectional communication.
ADS Commands#
CATio implements the core ADS command set:
Command ID |
Name |
Purpose |
|---|---|---|
0x01 |
|
Get device name and version |
0x02 |
|
Read data from index group/offset |
0x03 |
|
Write data to index group/offset |
0x04 |
|
Get ADS and device state |
0x06 |
|
Subscribe to value notifications |
0x07 |
|
Unsubscribe from notifications |
0x08 |
|
Notification data (server-initiated) |
0x09 |
|
Combined read and write operation |
Request/Response Pattern#
ADS communication is asynchronous: the client sends a request and later receives a response matched by invoke ID. CATio handles this with ResponseEvent objects:
Send request with unique invoke ID
Create
ResponseEventand store in pending mapBackground task receives response, looks up event by invoke ID
Event is set, awakening the waiting coroutine
This pattern allows multiple concurrent requests without blocking.
I/O Server Introspection#
When CATio connects, it introspects the TwinCAT I/O server to discover the hardware topology. This involves a series of ADS reads to gather:
Server Information#
Device name, version, and build number
Number of registered EtherCAT devices
Device Discovery#
For each EtherCAT device:
Device ID, type, and name
AMS NetId (for direct communication)
CANopen identity (vendor, product, revision)
Frame counters for diagnostic statistics
Slave terminal count
Terminal Discovery#
For each slave terminal:
EtherCAT address
Terminal type and name (e.g., “EL3064”)
CANopen identity
State machine and link status
CRC error counters per port
This information populates the IOServer, IODevice, and IOSlave data structures that the FastCS layer uses to create controllers.
Symbol Management#
ADS symbols provide named access to PLC variables, avoiding hard-coded index group/offset values. CATio discovers available symbols and maps them for convenient access.
The AdsSymbol Structure#
The AdsSymbol dataclass in devices.py represents ADS symbols. Symbols carry type information (dtype) allowing CATio to correctly interpret binary data. The group and offset fields are used in ADS read/write commands.
Symbol-Based Access vs Direct Access#
Approach |
Pros |
Cons |
|---|---|---|
Symbol-based |
Self-documenting, type-safe |
Requires symbol upload, slight overhead |
Direct index |
Fastest possible access |
Brittle if PLC changes, no type info |
CATio uses symbol-based access for maintainability, falling back to direct indexing only for standard EtherCAT registers that have fixed addresses.
Notification System#
For high-frequency data, polling is inefficient. ADS notifications let the server push value changes to the client.
How Notifications Work#
Subscribe: Client sends
ADDDEVICENOTEwith index group/offset, buffer size, and timing parametersReceive handle: Server returns a notification handle for this subscription
Receive updates: Server sends
DEVICENOTEmessages when values changeUnsubscribe: Client sends
DELETEDEVICENOTEwith the handle
Notification Parameters#
Parameter |
Purpose |
|---|---|
|
Maximum time (100ns units) before server sends accumulated changes |
|
Minimum interval between notifications |
|
When to send (on change, cyclic, etc.) |
CATio typically uses ADSTRANS_SERVERCYCLE mode where the server sends data at regular intervals regardless of whether values changed.
Buffering and Processing#
Notifications can arrive faster than the application processes them. CATio uses a buffering strategy:
Background task accumulates notification data in a
bytearrayPeriodically (configurable flush period), the buffer contents are queued
The FastCS layer processes queued notifications and updates attributes
This decouples network I/O from application processing, preventing backpressure.
The API Layer#
CATio’s ADS client exposes a clean API for the FastCS layer, abstracting protocol details behind method calls.
Query and Command Dispatch#
The client uses string-based dispatch to route API calls:
query("SYSTEM_TREE")→ callsget_system_tree()command("DEVICE_STATE", ...)→ callsset_device_state(...)
This pattern, while flexible, has tradeoffs discussed in API Decoupling Analysis.
Key API Methods#
Method |
Purpose |
|---|---|
|
Returns hierarchical view of I/O system |
|
Retrieves IOServer/IODevice/IOSlave by ID |
|
Frame statistics for an EtherCAT device |
|
State array for all terminals on a device |
|
State of a specific terminal |
Error Handling#
ADS operations can fail for various reasons. CATio defines error codes matching the TwinCAT specification:
Error Code |
Meaning |
|---|---|
|
General device error |
|
Service not supported |
|
Invalid index group |
|
Invalid index offset |
|
Invalid data |
|
No notification handle |
The client raises exceptions with meaningful messages when operations fail, allowing proper error recovery in the FastCS layer.
Testing with MockADSServer#
CATio includes a mock ADS server for testing without hardware. The MockADSServer class in mock_server.py provides:
Accepts TCP connections on the standard ADS port
Parses AMS headers and dispatches to command handlers
Returns configurable mock responses
Simulates notification subscriptions
This enables comprehensive testing of the client logic independent of real TwinCAT systems.
See Also#
Architecture Overview - High-level system architecture
FastCS EPICS IOC Implementation - Details of the EPICS layer
API Decoupling Analysis - API design discussion