> ## 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.
</Tip>

### 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.
</Note>

### 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

<Tabs>
  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>
</Tabs>

***

## 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.
</Note>

### 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.
</Warning>

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

<AccordionGroup>
  <Accordion title="Address and data type">
    <ParamField path="ADDRESS" type="string" required>
      CIP path: `ClassID.InstanceID.AttributeID.AttributeIndex`
    </ParamField>

    <ParamField path="DATA_TYPE" type="string" required>
      CIP data type (see table above).
    </ParamField>

    <ParamField path="LENGTH" type="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`).
    </ParamField>

    <ParamField path="BIT_ADDRESS" type="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.
    </ParamField>
  </Accordion>

  <Accordion title="Joined attribute reads">
    <ParamField path="ENABLE_JOIN_ATTRIBUTE" type="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`.
    </ParamField>

    <ParamField path="JOIN_CLASS_ID" type="integer">
      CIP Class ID of the joined attribute. Required when `ENABLE_JOIN_ATTRIBUTE` is `true` (must be > 0).
    </ParamField>

    <ParamField path="JOIN_INSTANCE_ID" type="integer">
      CIP Instance ID of the joined attribute. Required when join is enabled (must be >= 0; instance `0` is valid on some devices).
    </ParamField>

    <ParamField path="JOIN_ATTRIBUTE_ID" type="integer">
      CIP Attribute ID of the joined attribute. Required when join is enabled (must be > 0).
    </ParamField>

    <ParamField path="JOIN_INDEX" type="integer">
      Byte offset within the joined CIP response where the slice starts. Default: `0`.
    </ParamField>

    <ParamField path="JOIN_LENGTH" type="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`.
    </ParamField>
  </Accordion>

  <Accordion title="Value transformation">
    <ParamField path="SCALING" type="decimal">
      Multiply the parsed numeric value after byte parsing. Alias: `SCALING_FACTOR`. Default: `1.0`. **Unrelated to byte layout** — does not replace `JOIN_LENGTH`.
    </ParamField>

    <ParamField path="OFFSET" type="decimal">
      Add after scaling. Default: `0.0`.
    </ParamField>

    <ParamField path="DEADBAND" type="decimal">
      Suppress MQTT publish when the change is below this threshold. Default: `0.0`.
    </ParamField>

    <ParamField path="DECIMAL_PLACES" type="integer">
      Decimal places for `FLOAT32` / `FLOAT64` MQTT payloads. Default: `2`.
    </ParamField>
  </Accordion>

  <Accordion title="Publishing and writes">
    <ParamField path="SOURCE_TOPIC" type="string">
      MQTT topic where polled values are published.
    </ParamField>

    <ParamField path="PUBLISH_MODE" type="string">
      `VALUE_ONLY` (default — publish just the value) or `JSON` (value, quality, and timestamp). Aliases: `VALUE`, `SIMPLE` for value-only; `FULL`, `METADATA` for JSON.
    </ParamField>

    <ParamField path="WRITABLE" type="boolean">
      When `true`, subscribe to `DESTINATION_TOPIC` and write incoming MQTT values to the device.
    </ParamField>

    <ParamField path="DESTINATION_TOPIC" type="string">
      MQTT topic to receive write commands. Required when `WRITABLE` is `true`.
    </ParamField>
  </Accordion>
</AccordionGroup>

### 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.
</Warning>

### 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>

<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.
</Warning>

***

## 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

<Tabs>
  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>

  <Tab title="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"
    ```
  </Tab>
</Tabs>

***

## CIP Object Reference

<AccordionGroup>
  <Accordion title="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 |
  </Accordion>

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

  <Accordion title="Discrete Input Point (Class 104)">
    | Attribute | Description | Data Type |
    | - | - | - |
    | 3 | Value | BOOLEAN |
    | 4 | Status | UINT16 |
  </Accordion>

  <Accordion title="Assembly Object (Class 4)">
    | Attribute | Description | Data Type |
    | - | - | - |
    | 3 | Data | Variable |
    | 4 | Size | UINT16 |
  </Accordion>
</AccordionGroup>

***

## Best Practices

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="Deadbands">
    Use `DEADBAND` on noisy analog values to reduce MQTT traffic. Set `0.0` on digital status tags so every change publishes.
  </Accordion>

  <Accordion title="Timeouts on slow networks">
    Increase `CONNECTION_TIMEOUT` and `READ_WRITE_TIMEOUT` on congested or remote networks. Raise `RETRY_ATTEMPTS` when connections are intermittent.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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).
  </Accordion>
</AccordionGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="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`
  </Accordion>

  <Accordion title="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)
  </Accordion>

  <Accordion title="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
  </Accordion>

  <Accordion title="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`)
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Allen-Bradley Route" icon="industry" href="./allen-bradley">
    Symbolic tag-name access for ControlLogix, CompactLogix, and MicroLogix PLCs.
  </Card>

  <Card title="Industrial Routes Overview" icon="network-wired" href="./overview">
    Compare OT routes and choose the right protocol for your devices.
  </Card>
</CardGroup>


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