> ## 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 explicit messaging — TAG polling, bit extraction, joined reads, 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` |

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