> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# EtherNet/IP Route

> Connect to CIP-compatible devices over EtherNet/IP with TAG polling, bit extraction, ARRAY/STRUCT decoding, and bidirectional writes.

## Connect to CIP Devices over EtherNet/IP

The `ETHERNETIP` route connects Coreflux to CIP-compatible devices using EtherNet/IP **explicit messaging** (TCP port 44818). Define an `ETHERNETIP_CONFIG` block, add TAGs with CIP addresses, and publish each data point to its own MQTT topic — with optional scaling, bit extraction, joined attribute reads, and bidirectional writes.

> [!TIP]
>
> **Like reading labeled drawers in a filing cabinet.** Each CIP address (`Class.Instance.Attribute.Index`) points to a specific property on the device. The route opens the drawer, reads the value, and places it on an MQTT topic — on a schedule or when you send a command.

### When to use this

Use the EtherNet/IP route when you need explicit messaging to CIP objects on Allen-Bradley PLCs, remote I/O modules, VFDs, or other ODVA-compatible devices. Addresses use the numeric CIP path format `ClassID.InstanceID.AttributeID.AttributeIndex`.

> [!NOTE]
>
> For **symbolic tag names** (for example `Program:MainProgram.Temperature`), use the dedicated [Allen-Bradley route](./allen-bradley) instead. The EtherNet/IP route reads and writes via CIP class/instance/attribute paths.

### Key capabilities

* **Explicit messaging** — direct read/write to CIP attributes over TCP (port 44818)
* **TAG-based polling** — per-point MQTT topics with configurable intervals
* **Bit extraction** — `BIT_ADDRESS` to read a single bit from an integer word
* **Joined attribute reads** — combine bytes from two CIP attributes (legacy asset parity)
* **ARRAY and STRUCT decoding** — packed assembly buffers published as JSON arrays or objects
* **Bidirectional writes** — `WRITABLE` scalar tags with `DESTINATION_TOPIC`
* **Engineering units** — `SCALING` / `OFFSET` / `DEADBAND` per tag

***

## Quick Start

**Poll a CIP attribute**

Read an analog input point every 500 ms and publish to MQTT:

```lot wrap focus={9-12} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AnalogIO WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
        WITH POLLING_MS 500
    ADD MAPPING AnalogInputs
        WITH EVERY 500 MILLISECONDS
        ADD TAG AnalogIn1
            WITH ADDRESS "102.1.3.0"
            WITH DATA_TYPE "FLOAT32"
            WITH SOURCE_TOPIC "io/analog/in/1"
```

**Read and write**

Poll an assembly output word and accept MQTT writes to update it:

```lot wrap focus={9-15} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AssemblyIO WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
    ADD MAPPING Outputs
        WITH EVERY 500 MILLISECONDS
        ADD TAG OutputWord0
            WITH ADDRESS "4.104.3.0"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "eip/output/word0"
            WITH WRITABLE true
            WITH DESTINATION_TOPIC "eip/output/word0/set"
```

**Bit from a status word**

Extract alarm bit 5 from a UINT16 status attribute, joining a second CIP attribute for legacy device parity:

```lot wrap focus={12-22} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Kaeser WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "10.16.7.99"
        WITH PORT 44818
        WITH POLLING_MS 2000
    ADD MAPPING KaeserData
        WITH EVERY 1000 MILLISECONDS
        ADD TAG C1_Alarm
            WITH ADDRESS "4.100.3.1"
            WITH DATA_TYPE "BOOL"
            WITH BIT_ADDRESS 5
            WITH LENGTH 1
            WITH ENABLE_JOIN_ATTRIBUTE "true"
            WITH JOIN_CLASS_ID 120
            WITH JOIN_INSTANCE_ID 3711
            WITH JOIN_ATTRIBUTE_ID 1
            WITH JOIN_INDEX 0
            WITH JOIN_LENGTH 1
            WITH SOURCE_TOPIC "Air/Kaeser/Compressor/C1/Alerts/Alarm/Status"
```

**ARRAY from assembly**

Decode a packed INT32 array from an assembly buffer:

```lot wrap focus={9-14} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AssemblyArray WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
    ADD MAPPING PackedData
        WITH EVERY 1 SECOND
        ADD TAG CounterArray
            WITH ADDRESS "4.104.1.0"
            WITH DATA_TYPE "ARRAY"
            WITH ARRAY_ELEMENT_TYPE "INT32"
            WITH ARRAY_SIZE 5
            WITH SOURCE_TOPIC "eip/assembly/counters"
```

***

## Connection Configuration

### ETHERNETIP\_CONFIG Parameters

| Parameter | Required | Default | Description |
| - | - | - | - |
| `IP` | Yes | — | Device IP address |
| `PORT` | No | `44818` | EtherNet/IP explicit messaging port (TCP) |
| `POLLING_MS` | No | `750` | Default polling interval in milliseconds |
| `CONNECTION_TIMEOUT` | No | `5000` | Connection timeout in milliseconds |
| `READ_WRITE_TIMEOUT` | No | `3000` | Read/write operation timeout in milliseconds |
| `RETRY_ATTEMPTS` | No | `3` | Retry count on failure (0–10) |
| `RETRY_TIME_SECONDS` | No | `5` | Delay between retries in seconds |

> [!NOTE]
>
> The config block may be declared as `ADD ETHERNETIP_CONFIG` or `ADD EIP_CONFIG` — both are equivalent.

### Connection Example

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD ETHERNETIP_CONFIG
    WITH IP "192.168.1.100"
    WITH PORT 44818
    WITH POLLING_MS 500
    WITH CONNECTION_TIMEOUT 5000
    WITH READ_WRITE_TIMEOUT 3000
    WITH RETRY_ATTEMPTS 3
    WITH RETRY_TIME_SECONDS 5
```

### Implicit I/O Mode (Forward Open)

> [!WARNING]
>
> **Not implemented in release 2.2.** The route parser accepts `IO_MODE "IMPLICIT"` and related assembly/RPI settings for forward compatibility, but the broker does **not** establish Forward Open / cyclic UDP connections yet. Use explicit CIP assembly attributes (`4.x.y.z` paths) for I/O adapter devices until implicit mode ships.

Planned parameters (validated when present, not yet wired to the adapter):

| Parameter | Default | Description |
| - | - | - |
| `IO_MODE` | `EXPLICIT` | `EXPLICIT` (implemented) or `IMPLICIT` (planned) |
| `INPUT_ASSEMBLY` | `103` | T→O assembly instance (implicit mode) |
| `OUTPUT_ASSEMBLY` | `104` | O→T assembly instance (implicit mode) |
| `CONFIG_ASSEMBLY` | `1` | Configuration assembly for the connection path |
| `INPUT_SIZE` | `20` | Input assembly size in bytes |
| `OUTPUT_SIZE` | `12` | Output assembly size in bytes |
| `RPI` | `100000` | Requested Packet Interval in **microseconds** (e.g. `100000` = 100 ms) |

***

## CIP Addressing

EtherNet/IP uses CIP (Common Industrial Protocol) addressing. The adapter issues CIP `Get_Attribute_Single` / `Set_Attribute_Single` against numeric paths.

**Required format:** `ClassID.InstanceID.AttributeID.AttributeIndex` — four numeric segments, dot-separated.

| Component | Description | Example |
| - | - | - |
| ClassID | CIP object class | `4` (Assembly), `1` (Identity) |
| InstanceID | Object instance | `100`, `1` |
| AttributeID | Attribute number | `3` (Data on Assembly objects) |
| AttributeIndex | **Byte offset** within the attribute buffer where this tag's slice starts | `0` |

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Assembly attribute, full buffer from byte 0
WITH ADDRESS "4.100.3.0"

# Slice starting at byte 2 inside attribute 3 (packed bits)
WITH ADDRESS "4.110.1.2"

# Writable INT32 at instance 100, attribute 4
WITH ADDRESS "4.100.4.0"
```

### Common CIP Classes

| Class ID | Name | Description |
| - | - | - |
| 1 | Identity | Device identification |
| 2 | Message Router | Message routing |
| 4 | Assembly | I/O data assemblies |
| 6 | Connection Manager | Connection handling |
| 100 | Program Tags | Program-scoped tags (Allen-Bradley) |
| 107 | Controller Tags | Controller-scoped tags (Allen-Bradley) |
| 102 | Analog Input Point | Analog inputs |
| 103 | Analog Output Point | Analog outputs |
| 104 | Discrete Input Point | Digital inputs |
| 105 | Discrete Output Point | Digital outputs |

### EtherNet/IP Communication Types

| Type | Protocol | Port | Use case |
| - | - | - | - |
| Explicit messaging | TCP | 44818 | CIP read/write — **used by this route** |
| Implicit messaging | UDP | 2222 | Cyclic real-time I/O — **not implemented in 2.2** |

***

## Data Types

| Data Type | Aliases | Size / format |
| - | - | - |
| `BOOLEAN` | `BOOL` | 1 bit |
| `BYTE` | `USINT` | 8 bits |
| `SINT` | — | 8 bits (signed) |
| `INT16` | `INT` | 16 bits |
| `UINT16` | `UINT`, `WORD` | 16 bits |
| `INT32` | `DINT` | 32 bits |
| `UINT32` | `UDINT`, `DWORD` | 32 bits |
| `INT64` | `LINT` | 64 bits |
| `UINT64` | `ULINT`, `LWORD` | 64 bits |
| `FLOAT32` | `REAL`, `FLOAT` | 32 bits |
| `FLOAT64` | `LREAL`, `DOUBLE` | 64 bits — MQTT values round to `DECIMAL_PLACES` (default `2`) |
| `CHAR` | — | Single ASCII character |
| `ASCII` | — | Alias for single-byte ASCII (same as `CHAR` on read) |
| `STRING` | — | CIP string: 2-byte `UINT16` length prefix + ASCII payload |
| `TIME` | — | 32-bit signed duration in milliseconds |
| `TOD` | `TIME_OF_DAY` | 32-bit ms since midnight → `hh:mm:ss.fff` |
| `DATE` | — | 32-bit Unix-style date |
| `WCHAR` | — | 16-bit wide character |
| `ARRAY` | — | Packed elements — requires `ARRAY_ELEMENT_TYPE` + `ARRAY_SIZE` |
| `STRUCT` | — | Packed fields — requires `LENGTH` + `STRUCT_FIELD_DEFINITIONS` |

### String sizing

For `STRING` tags, `WITH LENGTH` sets the maximum string size in bytes. `STRING_SIZE` is accepted only as a parser alias for `STRING` sizing.

***

### Allen-Bradley Type Mapping

| Allen-Bradley | Coreflux `DATA_TYPE` |
| - | - |
| BOOL | `BOOL` |
| SINT / USINT | `BYTE` |
| INT | `INT16` |
| DINT | `INT32` |
| LINT | `INT64` |
| UINT | `UINT16` |
| UDINT | `UINT32` |
| ULINT | `UINT64` |
| REAL | `FLOAT32` |
| LREAL | `FLOAT64` |
| STRING | `STRING` |

***

## TAG Configuration

Every polled data point is an `ADD TAG` inside an `ADD MAPPING` block. Tags without their own `SOURCE_TOPIC` inherit `{mapping SOURCE_TOPIC}/{tagName}` when the mapping sets a base topic.

### Complete TAG Example

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD TAG ProcessTemperature
    WITH ADDRESS "102.1.3.0"
    WITH DATA_TYPE "FLOAT32"
    WITH SOURCE_TOPIC "eip/process/temperature"
    WITH SCALING_FACTOR 0.1
    WITH OFFSET 0
    WITH DEADBAND 0.5
    WITH PUBLISH_MODE "JSON"
    WITH LENGTH 1
```

### TAG Parameters

> **Address and data type**
>
> **ADDRESS** `string` *required*
>
> CIP path: `ClassID.InstanceID.AttributeID.AttributeIndex`
>
> **DATA\_TYPE** `string` *required*
>
> CIP data type (see table above).
>
> **LENGTH** `integer`
>
> Bytes to read from the primary CIP attribute starting at `AttributeIndex` (the fourth segment of `ADDRESS`). Default: `1`. For `STRING` tags, this is the maximum string size in bytes; `STRING_SIZE` is accepted only as a parser alias for that `STRING` sizing (not for join, `STRUCT`, or generic attribute-width uses). For numeric tags in join mode, `LENGTH + JOIN_LENGTH` must equal the byte width of `DATA_TYPE` (for example `1 + 1 = 2` for `INT16`).
>
> **BIT\_ADDRESS** `integer`
>
> Extract a single bit (0-based) from the underlying integer word. Alias: `BIT`. Use with integer types (`UINT16`, `INT32`, etc.) or with `BOOL` when the device returns a packed word. For `BOOL` + `BIT_ADDRESS`, read width auto-widens: bits 0–7 read 1 byte, 8–15 read 2 bytes, 16–31 read 4 bytes, 32–63 read 8 bytes.

> **Joined attribute reads**
>
> **ENABLE\_JOIN\_ATTRIBUTE** `string`
>
> When `"true"`, perform a second CIP `Get_Attribute_Single` against the join target and concatenate those bytes onto the primary read before parsing, scaling, or bit extraction. Default: `false`.
>
> **JOIN\_CLASS\_ID** `integer`
>
> CIP Class ID of the joined attribute. Required when `ENABLE_JOIN_ATTRIBUTE` is `true` (must be > 0).
>
> **JOIN\_INSTANCE\_ID** `integer`
>
> CIP Instance ID of the joined attribute. Required when join is enabled (must be >= 0; instance `0` is valid on some devices).
>
> **JOIN\_ATTRIBUTE\_ID** `integer`
>
> CIP Attribute ID of the joined attribute. Required when join is enabled (must be > 0).
>
> **JOIN\_INDEX** `integer`
>
> Byte offset within the joined CIP response where the slice starts. Default: `0`.
>
> **JOIN\_LENGTH** `integer`
>
> Bytes to take from the joined attribute starting at `JOIN_INDEX`. Required when join is enabled (must be > 0). **Not** related to `SCALING_FACTOR`.

> **Value transformation**
>
> **SCALING** `decimal`
>
> Multiply the parsed numeric value after byte parsing. Alias: `SCALING_FACTOR`. Default: `1.0`. **Unrelated to byte layout** — does not replace `JOIN_LENGTH`.
>
> **OFFSET** `decimal`
>
> Add after scaling. Default: `0.0`.
>
> **DEADBAND** `decimal`
>
> Suppress MQTT publish when the change is below this threshold. Default: `0.0`.
>
> **DECIMAL\_PLACES** `integer`
>
> Decimal places for `FLOAT32` / `FLOAT64` MQTT payloads. Default: `2`.

> **Publishing and writes**
>
> **SOURCE\_TOPIC** `string`
>
> MQTT topic where polled values are published.
>
> **PUBLISH\_MODE** `string`
>
> `VALUE_ONLY` (default — publish just the value) or `JSON` (value, quality, and timestamp). Aliases: `VALUE`, `SIMPLE` for value-only; `FULL`, `METADATA` for JSON.
>
> **WRITABLE** `boolean`
>
> When `true`, subscribe to `DESTINATION_TOPIC` and write incoming MQTT values to the device.
>
> **DESTINATION\_TOPIC** `string`
>
> MQTT topic to receive write commands. Required when `WRITABLE` is `true`.

### Publish Modes

**VALUE\_ONLY** (default) — publishes just the scaled scalar value:

```
75.5
```

**JSON** — publishes a metadata wrapper when explicitly set:

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "tag_name": "AnalogIn1",
  "value": 75.5,
  "quality": "GOOD",
  "timestamp": "2025-12-17T10:30:45.123Z"
}
```

### ARRAY and STRUCT Tags

CIP assembly buffers often contain packed arrays or structured records. Use `DATA_TYPE "ARRAY"` or `DATA_TYPE "STRUCT"` to decode them into JSON on MQTT.

#### ARRAY

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD TAG ArrInt32
    WITH ADDRESS "4.104.1.0"
    WITH DATA_TYPE "ARRAY"
    WITH ARRAY_ELEMENT_TYPE "INT32"
    WITH ARRAY_SIZE 5
    WITH SOURCE_TOPIC "eip/complex/arr_int32"
```

| Parameter | Required | Description |
| - | - | - |
| `ARRAY_ELEMENT_TYPE` | Yes | Element type (`INT32`, `FLOAT`, `BOOLEAN`, etc.) |
| `ARRAY_SIZE` | Yes | Number of elements |
| `ARRAY_ELEMENT_LENGTH` | No | Byte width per element (for `STRING` / nested `STRUCT` elements) |
| `ARRAY_START_OFFSET` | No | Skip N bytes before the first element |
| `ARRAY_ELEMENT_HEADER_SIZE` | No | Skip N bytes at the start of each element |

**MQTT payload:** JSON array, e.g. `[10, 20, 30, 40, 50]`.

#### STRUCT

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD TAG StructTag
    WITH ADDRESS "4.105.1.0"
    WITH DATA_TYPE "STRUCT"
    WITH LENGTH 12
    WITH STRUCT_FIELD_DEFINITIONS "temp:0:FLOAT:4,pressure:4:FLOAT:4,status:8:INT32:4"
    WITH SOURCE_TOPIC "eip/complex/struct"
```

Field spec format: `name:byte_offset:type:size` (comma-separated). Types follow the same aliases as scalar `DATA_TYPE`.

**MQTT payload:** JSON object, e.g. `{"temp":25.5,"pressure":101.325,"status":1}`.

> [!WARNING]
>
> ARRAY and STRUCT tags are **read-focused** in release 2.2. Writable scalar tags use normal CIP encoding; complex-type writes from JSON arrays/objects are not yet supported.

### Joined Attribute Reads

Some legacy CIP devices split a logical value across two attributes (for example a status word plus an extension attribute). When `ENABLE_JOIN_ATTRIBUTE` is `"true"`, the broker performs two `Get_Attribute_Single` reads per cycle, concatenates the byte buffers (primary first, joined second), then parses, scales, and applies `BIT_ADDRESS`.

If the join read fails, a warning is logged and the primary buffer is parsed alone — the polling loop continues.

> [!WARNING]
>
> For numeric `DATA_TYPE` values in join mode, `LENGTH + JOIN_LENGTH` must equal the type byte width (`2` for 16-bit, `4` for 32-bit, `8` for 64-bit). `BOOL` tags with `BIT_ADDRESS` are exempt (Kaeser-style alarm bit pattern).

> [!WARNING]
>
> `SCALING` / `SCALING_FACTOR` multiplies the parsed value — it does **not** replace `JOIN_LENGTH`. Using scaling to compensate for a missing join length produces silently wrong values.

***

## Migrating from a Legacy EtherNet/IP Asset

If you are converting an EtherNet/IP **Asset** (Coreflux HUB Configure / Tags panel) into a Route, map each asset field to the LoT parameter below.

| Asset field (HUB) | Route LoT parameter | Notes |
| - | - | - |
| Class Id / Instance Id / Attribute Id / Attribute Index | `WITH ADDRESS "C.I.A.Index"` | Four numeric segments separated by `.` |
| Data Type | `WITH DATA_TYPE "<type>"` | See aliases above |
| Bit | `WITH BIT_ADDRESS <n>` (alias `WITH BIT <n>`) | For boolean bits packed in an integer word |
| Length | `WITH LENGTH <n>` | Bytes from the primary attribute |
| Enable Join Attribute | `WITH ENABLE_JOIN_ATTRIBUTE "true"` | Quoted `"true"` / `"false"` |
| Join Class / Instance / Attribute Id | `JOIN_CLASS_ID`, `JOIN_INSTANCE_ID`, `JOIN_ATTRIBUTE_ID` | |
| Join Index | `WITH JOIN_INDEX <n>` | Byte offset in joined response |
| Join Length | `WITH JOIN_LENGTH <n>` | Bytes from joined attribute — **not** `SCALING` |
| Topic (Source) | `WITH SOURCE_TOPIC "<topic>"` | Where polled values publish |
| Write Direction | `WITH WRITABLE "true"` + `WITH DESTINATION_TOPIC "<topic>"` | For MQTT-to-device writes |

***

## Examples

**Device identity**

Read identity object attributes on a slow interval:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE DeviceIdentity WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
    ADD MAPPING IdentityData
        WITH EVERY 30 SECONDS
        ADD TAG VendorID
            WITH ADDRESS "1.1.1.0"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "device/identity/vendor"
        ADD TAG SerialNumber
            WITH ADDRESS "1.1.6.0"
            WITH DATA_TYPE "UINT32"
            WITH SOURCE_TOPIC "device/identity/serial"
        ADD TAG ProductName
            WITH ADDRESS "1.1.7.0"
            WITH DATA_TYPE "STRING"
            WITH LENGTH 32
            WITH SOURCE_TOPIC "device/identity/name"
```

**Scaled analog input**

Read a 4–20 mA value with engineering-unit scaling and deadband:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AnalogInputs WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.40"
    ADD MAPPING AnalogData
        WITH EVERY 500 MILLISECONDS
        ADD TAG TankLevel
            WITH ADDRESS "102.1.3.0"
            WITH DATA_TYPE "INT16"
            WITH SCALING_FACTOR 0.00625
            WITH OFFSET -25.0
            WITH DEADBAND 1.0
            WITH SOURCE_TOPIC "tank/level"
```

**Kaeser compressor status**

Three boolean status bits from one INT16 word with joined attributes (legacy asset parity):

```lot wrap focus={12-28} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Kaeser WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "10.16.7.99"
        WITH PORT 44818
        WITH POLLING_MS 2000
    ADD MAPPING KaeserData
        WITH EVERY 1000 MILLISECONDS
        ADD TAG C1_On
            WITH ADDRESS "4.100.3.1"
            WITH DATA_TYPE "BOOL"
            WITH SOURCE_TOPIC "Air/Kaeser/Compressor/C1/On"
        ADD TAG C1_Alarm
            WITH ADDRESS "4.100.3.1"
            WITH DATA_TYPE "BOOL"
            WITH BIT 5
            WITH LENGTH 1
            WITH ENABLE_JOIN_ATTRIBUTE "true"
            WITH JOIN_CLASS_ID 120
            WITH JOIN_INSTANCE_ID 3711
            WITH JOIN_ATTRIBUTE_ID 1
            WITH JOIN_INDEX 0
            WITH JOIN_LENGTH 1
            WITH SOURCE_TOPIC "Air/Kaeser/Compressor/C1/Alerts/Alarm/Status"
        ADD TAG C1_Alert
            WITH ADDRESS "4.100.3.1"
            WITH DATA_TYPE "BOOL"
            WITH BIT 6
            WITH LENGTH 1
            WITH ENABLE_JOIN_ATTRIBUTE "true"
            WITH JOIN_CLASS_ID 120
            WITH JOIN_INSTANCE_ID 3711
            WITH JOIN_ATTRIBUTE_ID 1
            WITH JOIN_INDEX 0
            WITH JOIN_LENGTH 1
            WITH SOURCE_TOPIC "Air/Kaeser/Compressor/C1/Alerts/Alert/Status"
```

**Multi-rate polling**

Fast critical data and slow diagnostics in one route:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE MultiRateEIP WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
    ADD MAPPING FastData
        WITH EVERY 100 MILLISECONDS
        ADD TAG CriticalStatus
            WITH ADDRESS "104.1.3.0"
            WITH DATA_TYPE "BOOLEAN"
            WITH SOURCE_TOPIC "fast/status"
    ADD MAPPING SlowData
        WITH EVERY 5 SECONDS
        ADD TAG DeviceStatus
            WITH ADDRESS "1.1.5.0"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "slow/device_status"
```

**ARRAY and STRUCT**

Decode packed assembly data as JSON:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE ComplexAssembly WITH TYPE ETHERNETIP
    ADD ETHERNETIP_CONFIG
        WITH IP "192.168.1.100"
        WITH PORT 44818
    ADD MAPPING PackedData
        WITH EVERY 1 SECOND
        ADD TAG TempArray
            WITH ADDRESS "4.104.1.0"
            WITH DATA_TYPE "ARRAY"
            WITH ARRAY_ELEMENT_TYPE "INT32"
            WITH ARRAY_SIZE 5
            WITH SOURCE_TOPIC "eip/array/counters"
        ADD TAG ProcessStruct
            WITH ADDRESS "4.105.1.0"
            WITH DATA_TYPE "STRUCT"
            WITH LENGTH 12
            WITH STRUCT_FIELD_DEFINITIONS "temp:0:FLOAT:4,pressure:4:FLOAT:4,status:8:INT32:4"
            WITH SOURCE_TOPIC "eip/struct/process"
```

***

## CIP Object Reference

> **Identity Object (Class 1)**
>
> | Attribute | Description | Data Type |
> | - | - | - |
> | 1 | Vendor ID | UINT16 |
> | 2 | Device Type | UINT16 |
> | 3 | Product Code | UINT16 |
> | 4 | Revision | UINT16\[2] |
> | 5 | Status | UINT16 |
> | 6 | Serial Number | UINT32 |
> | 7 | Product Name | STRING |

> **Analog Input Point (Class 102)**
>
> | Attribute | Description | Data Type |
> | - | - | - |
> | 3 | Value | FLOAT32 / INT16 |
> | 4 | Status | UINT16 |
> | 5 | Scaling Minimum | FLOAT32 |
> | 6 | Scaling Maximum | FLOAT32 |

> **Discrete Input Point (Class 104)**
>
> | Attribute | Description | Data Type |
> | - | - | - |
> | 3 | Value | BOOLEAN |
> | 4 | Status | UINT16 |

> **Assembly Object (Class 4)**
>
> | Attribute | Description | Data Type |
> | - | - | - |
> | 3 | Data | Variable |
> | 4 | Size | UINT16 |

***

## Best Practices

> **Polling intervals**
>
> Match the interval to how fast the value changes: 100 ms for safety-critical data, 1 s for process variables, 10 s or more for slow-changing status.

> **Deadbands**
>
> Use `DEADBAND` on noisy analog values to reduce MQTT traffic. Set `0.0` on digital status tags so every change publishes.

> **Timeouts on slow networks**
>
> Increase `CONNECTION_TIMEOUT` and `READ_WRITE_TIMEOUT` on congested or remote networks. Raise `RETRY_ATTEMPTS` when connections are intermittent.

> **Join attribute layout**
>
> Always set `JOIN_LENGTH` explicitly when `ENABLE_JOIN_ATTRIBUTE` is `"true"`. Verify `LENGTH + JOIN_LENGTH` matches the numeric type width before deploying to production.

> **Symbolic vs CIP paths**
>
> Use numeric CIP paths on this route. For Rockwell tag-name access (`Program:MainProgram.Counter`), use the [Allen-Bradley route](./allen-bradley).

***

## Troubleshooting

> **Connection failed**
>
> * Verify IP with `ping`
> * Confirm port 44818 (TCP) is open through firewalls
> * Ensure the device is powered and allows external connections
> * Increase `CONNECTION_TIMEOUT` and `RETRY_ATTEMPTS`

> **CIP path error**
>
> * Confirm four-segment format: `ClassID.InstanceID.AttributeID.AttributeIndex`
> * Check the device manual for valid class/instance/attribute numbers
> * Instance numbering varies by device (often starts at 1, not 0)

> **Wrong or stale values**
>
> * Verify `DATA_TYPE` matches the device attribute
> * Check `DEADBAND` is not suppressing small changes
> * For join tags, confirm `JOIN_LENGTH` and `LENGTH` byte layout
> * For bit tags, confirm `BIT_ADDRESS` is within the read width

> **Join attribute rejected at startup**
>
> * `JOIN_CLASS_ID` and `JOIN_ATTRIBUTE_ID` must be > 0
> * `JOIN_INSTANCE_ID` must be present and >= 0
> * `JOIN_LENGTH` must be > 0
> * For numeric types, `LENGTH + JOIN_LENGTH` must equal the type byte width (unless `BOOL` + `BIT_ADDRESS`)

***

## Next Steps

> **[Allen-Bradley Route](./allen-bradley)**
>
> Symbolic tag-name access for ControlLogix, CompactLogix, and MicroLogix PLCs.

> **[Industrial Routes Overview](./overview)**
>
> Compare OT routes and choose the right protocol for your devices.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.