> For the complete documentation index, see [llms.txt](https://docs.cybus.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cybus.io/connectors/shop-floor-connectors/ads.md).

# ADS

Connect Connectware to Beckhoff TwinCAT controllers and other ADS devices, and subscribe to, read, and write PLC symbols.

ADS (Automation Device Specification) is the device-independent and fieldbus-independent interface of Beckhoff Automation. The ADS connector reads, writes, and subscribes to the PLC symbols of TwinCAT 2 and TwinCAT 3 controllers, and of other ADS devices such as bus couplers and custom ADS servers.

The connector addresses data by symbol name, such as `MAIN.counter`. It takes the data type and size of every value from the symbol table of the PLC, so you do not declare data types in the service commissioning file.

* [Connection Properties](/connectors/shop-floor-connectors/ads/adsconnection.md)
* [Endpoint Properties](/connectors/shop-floor-connectors/ads/adsendpoint.md)

## Preparing the Connection

Connectware connects to the ADS router of the target at `host` and `routerTcpPort`, and addresses the target by its AMS Net ID (`netId`) and AMS port (`port`). For itself, Connectware uses the AMS Net ID that you set in `localAddress` and the local ADS port that you set in `localPort`.

Before you connect, check the following:

* **The target has an AMS route back to Connectware.** The route pairs the AMS Net ID from `localAddress` with the IP address of the host that runs the Protocol Mapper or agent. Without the route, the target does not answer, and the log shows no ADS error code.
* **Each connection to the same target has its own `localAddress`.** TwinCAT routes one AMS Net ID to one connection per target. If two connections use the same `localAddress` for the same target, both lose their connection repeatedly, and Connectware logs a warning that the local AMS identity is used by more than one connection. Put the endpoints on one connection, or give each connection its own `localAddress` and its own route on the target.
* **Connections that share a `localAddress` have different `localPort` values.** This applies to connections to different targets. Two connections with the same `localAddress` and `localPort` receive each other's notifications.
* **`localPort` is set if the target cannot assign one.** If you omit `localPort` or set it to `0`, the AMS router of the target assigns a free port. TwinCAT 3 routers do this. TwinCAT 2 and [ADS devices without TwinCAT](#ads-devices-without-twincat) do not, so set `localPort` for them.

### TwinCAT 2

TwinCAT 2 differs from TwinCAT 3 in the following settings:

* The PLC runtime uses AMS port 801 (`port: 801`). TwinCAT 3 uses 851.
* Symbol names are in uppercase, such as `MAIN.COUNTER`.
* Global variables start with a dot, such as `.MACHINESTATE`.
* `localPort` is required.

TwinCAT 2 reports an enumeration as its `INT` base type without member names, so you read and write enumerations as numbers. A `VAR_IN_OUT` member of a struct reads as its pointer address, a `UDINT`.

### ADS Devices Without TwinCAT

Set `rawClient: true` for ADS devices that have no TwinCAT system manager, such as remote I/O, bus couplers, and custom ADS servers. Such a device cannot assign a local ADS port, so also set `localPort`.

If you omit `rawClient` and set `connectionCheckEnabled: false`, Connectware handles the target as if `rawClient` were `true`, but does not monitor the connection.

## Monitoring the Connection

Connectware checks the target every `connectionCheckInterval` milliseconds. If the target does not answer for `connectionDownDelay` milliseconds, Connectware marks the connection as lost and reconnects. For the defaults, see [Connection Properties](/connectors/shop-floor-connectors/ads/adsconnection.md).

* **TwinCAT targets:** Connectware checks the TwinCAT system state.
* **Targets with `rawClient: true`:** Connectware sends an ADS ReadState request. An answer with an ADS error code also counts as an answer. When it connects, Connectware reports the connection as connected only after the target answers the first request.

`connectionCheckEnabled: false` turns this check off. Connectware then logs a warning when it connects, because it notices a target that stopped answering only when a read or write fails.

After a lost connection, Connectware reconnects with an increasing delay, as set in `connectionStrategy`, and keeps trying until you disable the service. When the connection is back, Connectware subscribes all endpoints again.

## Subscribing to Symbols

A `subscribe` endpoint registers an ADS device notification on the target, and the target sends the values. With `onChange: false`, the target sends the value every `interval` milliseconds. With `onChange: true`, the target checks the value every `interval` milliseconds and sends it only when it has changed. With `maxDelay`, the target collects changes for up to `maxDelay` milliseconds and sends them in one packet. This reduces the number of packets at small intervals. For details, see [Endpoint Properties](/connectors/shop-floor-connectors/ads/adsendpoint.md).

Each value is published as a flat object:

```json
{ "timestamp": 1641819737462, "value": 123 }
```

In all messages of the ADS connector, `timestamp` is the time when Connectware received the value, not the time of the PLC. 64-bit integers (`LINT`, `ULINT`, `LWORD`, and `LTIME`) are published as decimal strings, such as `"9007199254740993"`, also inside structs and arrays, because a JSON number cannot hold every 64-bit value exactly.

If two `subscribe` endpoints of one connection use the same `symbolName`, `interval`, `onChange`, and `maxDelay`, they share one notification, and only the endpoint that subscribed last receives the values. Use one `subscribe` endpoint for each symbol, and forward its values to several topics with [mappings](/data-flows/service-commissioning-files/resources/cybus-mapping.md).

A PLC has a limited pool of notification handles, which it shares with all its ADS clients, such as HMIs. A subscription fails if the symbol does not exist or the pool has no free handle. The endpoint then does not reach the enabled state, the service shows **Deviated**, and the Protocol Mapper or agent log names the ADS error and the symbol. See [Deviations](/data-flows/services/deviations.md).

Connectware subscribes again each time the connection reconnects. After you fix the cause, disable and enable the service to subscribe again right away.

## Reading Symbols

To read a symbol, send a message to the `/req` topic of a `read` endpoint. The optional `id` correlates the request with the response:

{% code lineNumbers="true" %}

```json
{ "id": 1234 }
```

{% endcode %}

The response on the `/res` topic nests the result in `value`:

```json
{
  "id": 1234,
  "timestamp": 1641819737462,
  "value": {
    "value": 123,
    "type": { "name": "", "type": "INT", "size": 2 },
    "symbol": { "name": "MAIN.counter", "type": "INT", "size": 2 }
  }
}
```

The response contains the following fields:

* `value.value`: The value of the symbol.
* `value.type`: The description of the data type, such as `type` for the name of the data type, `size`, and `subItems` for the members of a struct. For a basic data type such as `INT`, `name` is empty. The example shows a shortened object.
* `value.symbol`: The description of the symbol, such as `name`, `indexGroup`, `indexOffset`, `size`, `type`, `comment`, and `flags`. The example shows a shortened object.
* `value.rawValue`: The raw bytes of the value, as an object with `"type": "Buffer"` and the bytes in `data`. Only present if the connection sets `includeRawValue: true`.

If a read fails, the response is an empty message, and the Protocol Mapper or agent log names the cause. To receive an `error` object with `code` and `message` instead, set [publishError](/data-flows/service-commissioning-files/resources/cybus-endpoint.md#publisherror) on the endpoint.

## Writing Symbols

To write a symbol, send a message with the value to the `/set` topic of a `write` endpoint. The optional `id` correlates the request with the response:

{% code lineNumbers="true" %}

```json
{ "id": 1234, "value": 5658473 }
```

{% endcode %}

If the write succeeds, the response on the `/res` topic contains `value: true`:

```json
{ "id": 1234, "timestamp": 1641819737463, "value": true }
```

If the write fails, the response contains an `error` object instead of `value`, whatever `publishError` is set to. `code` is the ADS error code, or `-1` if the failure is not an ADS error, such as a value that does not fit the data type:

```json
{
  "id": 1234,
  "timestamp": 1641819737463,
  "error": { "code": 1808, "message": "Symbol not found (ADS error 1808)" }
}
```

### Write Values

Before Connectware writes a value, it checks the value against the data type of the symbol. Connectware refuses a value of the wrong type or outside the range of the data type, answers with an `error` object, and does not change the variable on the PLC. The message names the symbol, the data type, and the value, such as `MAIN.counter: INT takes a whole number, got "abc"`.

Each data type accepts the following values:

| PLC Data Type                                                                           | Accepted Values                                                                                                                                                                    |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BOOL`                                                                                  | `true`, `false`, `0`, or `1`. Strings such as `"false"` or `"1"` are refused.                                                                                                      |
| `BYTE`, `WORD`, `DWORD`, `SINT`, `USINT`, `INT`, `UINT`, `DINT`, `UDINT`, `TIME`, `TOD` | A whole number within the range of the data type, or a decimal string such as `"42"`.                                                                                              |
| `LINT`, `ULINT`, `LWORD`, `LTIME`                                                       | A whole number within the range of the data type, or a decimal string. Send values beyond 9007199254740991 as a decimal string, because a JSON number loses precision beyond that. |
| `REAL`, `LREAL`                                                                         | A finite number, or a numeric string. A `REAL` value must be within the range of a 32-bit floating-point number.                                                                   |
| `DATE`, `DT`                                                                            | Seconds since January 1, 1970 as a whole number, or an ISO 8601 string. A read returns an ISO 8601 string.                                                                         |
| `STRING(n)`, `WSTRING(n)`                                                               | A string, or a number, which is written as its decimal text. Connectware writes the first `n` characters of a longer string, and the write succeeds.                               |
| Enumeration (TwinCAT 3)                                                                 | A member name or a whole number.                                                                                                                                                   |
| `ARRAY`                                                                                 | An array with exactly the declared number of elements, each one accepted by the element type.                                                                                      |
| `STRUCT`                                                                                | An object with every member of the struct, each one accepted by its data type. Member names are not case-sensitive.                                                                |

Connectware does not check values for other data types before it writes them.

## Troubleshooting

You can read the Protocol Mapper log in the [Container Logs View](/monitoring/system-status/system-container-status.md#container-logs-view).

| Symptom                                                                                                                            | Cause and Fix                                                                                                                                                                                                                                                                        |
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The connection does not connect, and the log shows no ADS error code.                                                              | The target has no AMS route to Connectware. Add a route for the AMS Net ID in `localAddress` and the IP address of the host that runs the Protocol Mapper or agent. See [Preparing the Connection](#preparing-the-connection).                                                       |
| The connection fails, and the log says that the router did not assign a local ADS port.                                            | The target cannot assign a local ADS port. Set `localPort`.                                                                                                                                                                                                                          |
| Two connections lose their connection repeatedly, and the log warns that a local AMS identity is used by more than one connection. | Both connections use the same `localAddress` for the same target. Give each connection its own `localAddress` and route, or put the endpoints on one connection.                                                                                                                     |
| The service shows **Deviated**, and the log shows `Symbol not found (ADS error 1808)`.                                             | The symbol does not exist on the PLC. Check the name. On TwinCAT 2, use uppercase names and a leading dot for global variables. If you correct `symbolName`, update the service. If you correct the PLC program instead, disable and enable the service.                             |
| The service shows **Deviated**, and the log shows `No more notification handles (ADS error 1814)`.                                 | The PLC has no free notification handle. Reduce the number of `subscribe` endpoints, or the notifications that other ADS clients register on the PLC. Then disable and enable the service.                                                                                           |
| Small symbols read fine, but large structs and arrays time out after `timeoutDelay`.                                               | A network path with a smaller MTU, such as a VPN tunnel, drops the large responses, because the container network of the Protocol Mapper or agent uses a larger MTU. Set the MTU of the container network to the MTU of the path, or clamp the TCP maximum segment size on the host. |
| A write is answered with an `error` object that names the values that the data type takes.                                         | The value does not fit the data type of the symbol. See [Write Values](#write-values).                                                                                                                                                                                               |

## Service Commissioning File Example

{% file src="/files/loZDKHtr4Mt7te5ThnR8" %}

{% code title="ads-example.yml" lineNumbers="true" expandable="true" %}

```yaml
description: >
  Sample ADS service commissioning file

metadata:
  name: Sample ADS service
  provider: cybus
  homepage: https://www.cybus.io
  version: 1.0.0

parameters:
  # The local AMS Net ID of Connectware. Use a separate one for every connection to the same target.
  adsLocalNetId:
    type: string
    description: The local AMS Net ID of Connectware
    default: 192.168.7.12.1.1
  adsLocalPort:
    type: integer
    description: The local ADS port of Connectware
    default: 32000
  adsHost:
    type: string
    description: The device address (IP address or hostname) of the ADS router to connect to
    default: 192.168.2.170
  adsRouterTcpPort:
    type: integer
    description: The TCP port of the ADS router of the device
    default: 48898

  # The AMS port of the ADS device, usually known as PortNr.
  # This is not a TCP port. The TCP port of the ADS router is adsRouterTcpPort.
  adsPort:
    type: integer
    description: The AMS port of the ADS device (PortNr), not a TCP port
    default: 851
  adsNetId:
    type: string
    description: The AMS Net ID of the ADS device
    default: 192.168.2.170.1.1

resources:
  adsConnection:
    type: Cybus::Connection
    properties:
      protocol: Ads
      connection:
        localAddress: !ref adsLocalNetId # The local AMS Net ID of Connectware
        localPort: !ref adsLocalPort
        host: !ref adsHost
        routerTcpPort: !ref adsRouterTcpPort
        port: !ref adsPort # The AMS port of the device, not a TCP port
        netId: !ref adsNetId # The AMS Net ID of the device

  adsQuery1:
    type: Cybus::Endpoint
    properties:
      protocol: Ads
      connection: !ref adsConnection
      subscribe:
        symbolName: 'MAIN.byByte[4]'
        interval: 1000

  adsQuery2:
    type: Cybus::Endpoint
    properties:
      protocol: Ads
      connection: !ref adsConnection
      write:
        symbolName: 'MAIN.byByte[4]'

  mapping:
    type: Cybus::Mapping
    properties:
      mappings:
        - subscribe:
            endpoint: !ref adsQuery1
          publish:
            topic: 'output-topic'
        - subscribe:
            topic: 'input-topic'
          publish:
            endpoint: !ref adsQuery2
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.cybus.io/connectors/shop-floor-connectors/ads.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
