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

# Industrial Routes Overview

> Connect your Coreflux broker to PLCs, industrial controllers, and automation systems using industrial protocol routes

## Why Industrial Routes?

Bridge the gap between your PLCs and MQTT without custom middleware. Read sensor data, write setpoints, and integrate industrial equipment into your IoT pipeline—using a single, consistent configuration pattern across all supported protocols.

## Industrial Protocol Integration

Industrial routes enable direct communication between your Coreflux MQTT broker and industrial automation equipment. Read data from PLCs, write setpoints, and bridge the gap between IT and OT systems - all through standardized LoT (Language of Things) syntax.

> [!TIP]
>
> **Like a translator at an international meeting.** Industrial routes speak both PLC (Modbus, S7, OPC UA) and MQTT—so your broker can read sensor data and send commands without custom glue code.

## Supported Protocols

> **[Modbus TCP Client](./modbus-tcp-client)**
>
> Poll PLCs and industrial devices over Modbus TCP.

> **[Modbus Serial Client](./modbus-serial-client)**
>
> RS-232/RS-485 serial communication with Modbus RTU devices.

> **[Modbus TCP Server](./modbus-tcp-server)**
>
> Expose MQTT topics as Modbus registers and coils for SCADA and HMIs.

> **[Siemens S7](./siemens-s7)**
>
> Native S7 protocol for Siemens S7-200/300/400/1200/1500 PLCs.

> **[OPC UA Client](./opcua-client)**
>
> Connect to remote OPC UA servers and poll device data into MQTT.

> **[OPC UA Server](./opcua-server)**
>
> Expose MQTT topics as OPC UA nodes for SCADA and OT clients.

> **[ADS (Beckhoff)](./ads)**
>
> TwinCAT ADS protocol for Beckhoff PLCs with AutoDiscovery support.

> **[Allen-Bradley](./allen-bradley)**
>
> ControlLogix, CompactLogix, and other Rockwell Automation PLCs.

> **[EtherNet/IP](./ethernetip)**
>
> CIP-based communication for Allen-Bradley and compatible devices.

> **[FINS (Omron)](./fins)**
>
> Omron FINS protocol for CJ/CS/CP series PLCs.

> **[BACnet](./bacnet)**
>
> BACnet/IP for building automation controllers (ASHRAE 135) — HVAC, lighting, and metering.

***

## Protocol Comparison

| Protocol | Transport | Use Case | Vendor |
| - | - | - | - |
| Modbus TCP Client | TCP/IP | Poll remote Modbus devices | Multi-vendor |
| Modbus Serial Client | RS-232/485 | Legacy devices, long distances | Multi-vendor |
| Modbus TCP Server | TCP/IP | Expose MQTT to Modbus masters | Multi-vendor |
| Siemens S7 | TCP/IP | Siemens automation systems | Siemens |
| OPC UA Client | TCP/IP | Read from remote OPC UA servers | Multi-vendor |
| OPC UA Server | TCP/IP | Expose MQTT to SCADA/HMI clients | Multi-vendor |
| ADS | TCP/IP | Beckhoff TwinCAT systems | Beckhoff |
| Allen-Bradley | TCP/IP | Rockwell Automation | Rockwell |
| EtherNet/IP | TCP/IP | CIP devices, explicit messaging | Multi-vendor |
| FINS | TCP/UDP | Omron PLCs | Omron |
| BACnet | UDP (BACnet/IP) | Building automation (HVAC, lighting, metering) | Multi-vendor |

***

## Common Architecture

All industrial routes share a common architecture with three key components:

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
graph LR
    subgraph broker [Coreflux Broker]
        Route[Industrial Route]
        Tags[TAG Definitions]
        Mapping[MAPPING Config]
    end
    
    subgraph plc [PLC / Controller]
        Registers[Registers/Variables]
    end
    
    subgraph mqtt [MQTT Topics]
        Pub[Published Data]
        Sub[Write Commands]
    end
    
    Route --> Tags
    Tags --> Mapping
    Mapping -->|Poll| Registers
    Registers -->|Read| Pub
    Sub -->|Write| Registers
```

### 1. Connection Configuration

Each protocol has specific connection parameters:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD <PROTOCOL>_CONFIG
    WITH HOST "192.168.1.100"
    WITH PORT 502
    // Protocol-specific options...
```

### 2. MAPPING with Polling

**MAPPING Definition**

A **MAPPING** is a scheduled, continuous polling operation that automatically reads data from industrial devices (PLCs, Modbus devices, etc.) at regular intervals and publishes the values to MQTT topics.

* **Polls:** industrial device at regular intervals (`EVERY`)
* **Reads:** multiple data points (TAGs) in a single operation
* **Publishes:** each TAG value to its own MQTT topic

In LoT, you define a MAPPING like this:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MAPPING SensorGroup
    WITH EVERY 500 MILLISECONDS
    ADD TAG Temperature
        // TAG config...
    ADD TAG Pressure
        // TAG config...
```

#### Common MAPPING-level fields

These fields are available at the MAPPING level across all industrial protocols:

> **SOURCE\_TOPIC**
>
> **SOURCE\_TOPIC** `string`
>
> Base MQTT topic for publishing tag data. Each TAG publishes to `{SOURCE_TOPIC}/{TAG_NAME}`.

> **EVERY**
>
> **EVERY** `string` *required*
>
> Polling interval (e.g. `500 MILLISECONDS`, `1 SECOND`).

***

### 3. TAG Definitions

**TAG Definition**

A **TAG** is an individual data point within a MAPPING that represents a specific variable or register in an industrial device.

* **Identifies:** a specific memory address in the device
* **Transforms:** raw value using scaling, offset, and engineering units
* **Publishes:** to a dedicated MQTT topic
* **Monitors:** changes using deadband filtering

> **TAG capabilities**
>
> * **Data transformation:** Scaling, offset, min/max validation
> * **Change detection:** Deadband to filter noise
> * **Formatting:** Publish as JSON or raw value

In LoT, you define a TAG like this:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD TAG Temperature
    WITH ADDRESS "100"
    WITH DATA_TYPE "FLOAT"
    WITH SOURCE_TOPIC "plc/temperature"
    WITH SCALING 0.1
    WITH UNIT "°C"
```

#### Common TAG Parameters

These parameters are available across all industrial protocols:

> **Addressing**
>
> **ADDRESS** `string` *required*
>
> Protocol-specific address (register number, variable path, NodeId, etc.).
>
> **ADDRESS\_TYPE** `string`
>
> Address type varies by protocol (e.g., HOLDING\_REGISTER, DATABLOCK, SYMBOL).

> **Data Types**
>
> **DATA\_TYPE** `string` *required*
>
> Data type for the value. Common types: `BOOL`, `INT16`, `UINT16`, `INT32`, `UINT32`, `FLOAT`, `DOUBLE`, `STRING`.
>
> **LENGTH** `integer`
>
> Maximum string size when `DATA_TYPE` is `STRING` on routes that support configurable sizing. Units and defaults vary—see [String sizing by Route](#string-sizing-by-route). Preferred keyword; `STRING_SIZE` is accepted as an alias where sizing is supported.

> **Topics**
>
> **SOURCE\_TOPIC** `string`
>
> Topic where PLC values are published. Subscribe here to receive sensor data.
>
> **DESTINATION\_TOPIC** `string`
>
> Topic to send write commands. Publish a value here to write it to the PLC (requires WRITABLE true).

> **Value Transformation**
>
> **SCALING** `double`
>
> Multiplier applied to raw value. Formula: `published_value = (raw_value * SCALING) + OFFSET`. Default: 1.0.
>
> **OFFSET** `double`
>
> Value added after scaling. Default: 0.0.
>
> **DECIMAL\_PLACES** `integer`
>
> Number of decimal places in published value. Default: 2.

> **Filtering**
>
> **MIN\_VALUE** `double`
>
> Minimum allowed value. Values below this are filtered out.
>
> **MAX\_VALUE** `double`
>
> Maximum allowed value. Values above this are filtered out.
>
> **DEADBAND** `double`
>
> Minimum change required to publish a new value. Reduces network traffic for slowly changing values. Default: 0.0.

> **Publishing Options**
>
> **PUBLISH\_MODE** `string`
>
> Output format: `VALUE_ONLY` (just the value) or `JSON` (structured object with metadata). Default: VALUE\_ONLY.
>
> **UNIT** `string`
>
> Engineering unit for documentation and JSON output (e.g., °C, bar, RPM).
>
> **DESCRIPTION** `string`
>
> Human-readable description of the TAG.

> **Write Control**
>
> **WRITABLE** `boolean`
>
> Allow writing to this TAG via DESTINATION\_TOPIC. Default: false.

> **Byte Ordering**
>
> **BYTE\_ORDER** `string`
>
> Byte order for multi-byte values: `BIGENDIAN` or `LITTLEENDIAN`. Default: BIGENDIAN.
>
> **WORD\_ORDER** `string`
>
> Word order for 32-bit values: `BIGENDIAN` or `LITTLEENDIAN`. Default: BIGENDIAN.

### String sizing by Route

When `DATA_TYPE` is `STRING`, routes that support configurable sizing accept `WITH LENGTH` to declare how many bytes the tag occupies on the wire. Use `LENGTH` as the preferred keyword; `STRING_SIZE` is accepted as an alias where sizing is supported. OPC UA Server strings are natively variable-length (no sizing parameter); Allen-Bradley uses a fixed 88-byte CIP layout—`LENGTH`/`STRING_SIZE` do not resize it.

| Route | Unit | Default | Constraint |
| - | - | - | - |
| Modbus Server | bytes, 2 bytes/register | 40 bytes | at most 246 bytes/123 registers |
| Modbus clients | bytes | 256 | consecutive registers |
| Siemens S7 | bytes including 2-byte header | 256 | — |
| EtherNet/IP | bytes | type-driven; 1 in join mode | separate join/array semantics |
| OPC UA client | bytes | 256 | — |
| OPC UA Server | natively variable | unbounded | no sizing parameter |
| ADS | bytes including length byte | read 256; write 80 | — |
| Allen-Bradley | fixed layout | 88-byte CIP STRING | sizing keywords do not resize |
| FINS | payload bytes plus 2-byte header | 256 | — |
| BACnet | bytes | 256 | `MAX_APDU` is unrelated |

See the protocol-specific route guide for register math, validation limits, and complete examples.

***

### 4. SOURCE\_TOPIC Configuration

There are two ways to configure where TAG values are published. Use group-level when you want consistent topic structure across all TAGs in a MAPPING; use individual TAG when you need custom paths per sensor (e.g., different namespaces for temperature vs pressure).

**Group-level (Recommended)**

Define `SOURCE_TOPIC` at the MAPPING level. Each TAG automatically publishes to `{SOURCE_TOPIC}/{TAG_NAME}`.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MAPPING ProductionLine
    WITH SOURCE_TOPIC "factory/line1"
    WITH EVERY 1 SECOND
    ADD TAG Temperature
        WITH ADDRESS "100"
        WITH DATA_TYPE "FLOAT"
    ADD TAG Pressure
        WITH ADDRESS "102"
        WITH DATA_TYPE "FLOAT"
```

**Result:** Temperature publishes to `factory/line1/Temperature`; Pressure publishes to `factory/line1/Pressure`.

**Individual TAG**

Define `SOURCE_TOPIC` at each TAG level for custom topic structure.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MAPPING ProductionLine
    WITH EVERY 1 SECOND
    ADD TAG Temperature
        WITH SOURCE_TOPIC "sensors/temp/line1"
        WITH ADDRESS "100"
        WITH DATA_TYPE "FLOAT"
    ADD TAG Pressure
        WITH SOURCE_TOPIC "sensors/pressure/line1"
        WITH ADDRESS "102"
        WITH DATA_TYPE "FLOAT"
```

**Result:** Temperature publishes to `sensors/temp/line1`; Pressure publishes to `sensors/pressure/line1`.

***

### 5. EVENT (On-Demand Operations)

Understanding on-demand operations in routes.

**EVENT Definition**

An **EVENT** is an on-demand operation that waits for messages on an MQTT topic, executes a specific action when triggered, and publishes the results back to another MQTT topic.

Use EVENT when you need on-demand reads or writes (triggered by a message); use MAPPING for continuous polling. Syntax varies by protocol—see the protocol-specific docs (e.g., [Modbus TCP Client](./modbus-tcp-client#event-based-operations)) for details.

In LoT, you define an EVENT like this:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD EVENT ReadOnDemand
    WITH SOURCE_TOPIC "plc/commands/read"
    WITH DESTINATION_TOPIC "plc/responses/read"
    WITH QUERY "<protocol_query>"
```

#### Common EVENT Parameters

These parameters are available across all industrial protocols for EVENT definitions:

> **SOURCE\_TOPIC**
>
> **SOURCE\_TOPIC** `string` *required*
>
> MQTT topic where the EVENT listens for trigger messages. Publish a message here to execute the operation.

> **DESTINATION\_TOPIC**
>
> **DESTINATION\_TOPIC** `string` *required*
>
> MQTT topic where the EVENT publishes the result of the operation.

> **QUERY**
>
> **QUERY** `string` *required*
>
> Protocol-specific specification of the operation (read or write) and its parameters. Uses LoT object syntax. See each protocol's documentation for supported operations.

### When to Use MAPPING vs EVENT

Use the right acquisition pattern for the job:

| | MAPPING (Cyclic) | EVENT (On-Demand) |
| - | - | - |
| **Trigger** | Automatic, on a timer (`WITH EVERY`) | Manual, via an MQTT message to SOURCE\_TOPIC |
| **Direction** | Reads from device, publishes to MQTT | Reads or writes on command, publishes result |
| **Best for** | Dashboards, alarms, continuous telemetry | Operator commands, diagnostics, recipe downloads |
| **Data volume** | Constant stream at defined interval | Only when requested |

> [!NOTE]
>
> Most real-world routes combine both patterns: MAPPING for continuous monitoring and EVENT for on-demand operations within the same `DEFINE ROUTE`.

***

## Basic Example Pattern

### Cyclic Only

This minimal pattern works across all industrial protocols for continuous data acquisition:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE MyPLCConnection WITH TYPE <PROTOCOL_TYPE>
    ADD <PROTOCOL>_CONFIG
        WITH HOST "192.168.1.100"
        // Connection parameters...
    
    ADD MAPPING CriticalSensors
        WITH EVERY 100 MILLISECONDS
        ADD TAG Temperature
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/temperature"
        ADD TAG Pressure
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/pressure"
    
    ADD MAPPING SlowChangingData
        WITH EVERY 5 SECONDS
        ADD TAG ProductCount
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "INT32"
            WITH SOURCE_TOPIC "plc/production/count"
```

### Combined (Cyclic + On-Demand)

A production route typically combines MAPPING for continuous monitoring with EVENT for on-demand operations. Publish a message to the EVENT's SOURCE\_TOPIC to trigger a one-shot read or write, and receive the result on the DESTINATION\_TOPIC:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE MyPLCConnection WITH TYPE <PROTOCOL_TYPE>
    ADD <PROTOCOL>_CONFIG
        WITH HOST "192.168.1.100"
        // Connection parameters...
    
    // Continuous monitoring — polls every 500ms
    ADD MAPPING ProcessSensors
        WITH EVERY 500 MILLISECONDS
        ADD TAG Temperature
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/temperature"
        ADD TAG Pressure
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/pressure"
    
    // On-demand read — triggered by publishing to SOURCE_TOPIC
    ADD EVENT ReadRegister
        WITH SOURCE_TOPIC "plc/commands/read"
        WITH DESTINATION_TOPIC "plc/responses/read"
        WITH QUERY "{operation: READ, address: <protocol_specific_address>, data_type: FLOAT}"
    
    // On-demand write — triggered by publishing to SOURCE_TOPIC
    ADD EVENT WriteSetpoint
        WITH SOURCE_TOPIC "plc/commands/write"
        WITH DESTINATION_TOPIC "plc/responses/write"
        WITH QUERY "{operation: WRITE, address: <protocol_specific_address>, value: 0}"
```

***

## Advanced Example Pattern

When you need engineering-unit conversion, noise filtering, range validation, or bidirectional control, add transformation and publishing parameters to your TAGs. This pattern builds on the basic structure above to unlock the full power of industrial routes.

### Scaled and Filtered Monitoring

Apply scaling and offset to convert raw PLC register values into meaningful engineering units, and use deadband to suppress noise:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AdvancedPLCConnection WITH TYPE <PROTOCOL_TYPE>
    ADD <PROTOCOL>_CONFIG
        WITH HOST "192.168.1.100"
        // Connection parameters...
    
    ADD MAPPING ProcessMonitoring
        WITH EVERY 500 MILLISECONDS
        ADD TAG Temperature
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/temperature"
            // Value transformation — raw 245 becomes (245 * 0.1) + (-40) = -15.5
            WITH SCALING 0.1
            WITH OFFSET -40
            WITH UNIT "°C"
            // Noise filtering — only publish when change exceeds 0.5
            WITH DEADBAND 0.5
            // Range validation — discard values outside physical limits
            WITH MIN_VALUE -20
            WITH MAX_VALUE 150
            // Precision — publish with 1 decimal place
            WITH DECIMAL_PLACES 1
            // Structured output — includes unit, description, and timestamp
            WITH PUBLISH_MODE "JSON"
        ADD TAG Pressure
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/sensors/pressure"
            WITH SCALING 0.01
            WITH UNIT "bar"
            WITH DEADBAND 0.1
```

### Writable Setpoints with Validation

Enable bidirectional control by marking TAGs as writable. Published values on the DESTINATION\_TOPIC are written to the PLC, with MIN/MAX enforcing safe limits:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    ADD MAPPING Setpoints
        WITH EVERY 1 SECOND
        ADD TAG TemperatureSetpoint
            WITH ADDRESS "<protocol_specific_address>"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "plc/setpoints/temperature"
            // Bidirectional — publish to DESTINATION_TOPIC to write a value
            WITH WRITABLE "true"
            WITH DESTINATION_TOPIC "plc/setpoints/temperature/write"
            WITH UNIT "°C"
            // Safety limits — reject values outside the safe operating range
            WITH MIN_VALUE 0
            WITH MAX_VALUE 100
```

***

## Best Practices

> **Optimize Polling Intervals**
>
> Group TAGs by update frequency:
>
> * **Fast (50-100ms)**: Safety signals, real-time control
> * **Medium (500ms-1s)**: Process values, sensor readings
> * **Slow (5-30s)**: Configuration, counters, status
>
> ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> ADD MAPPING FastLoop
>     WITH EVERY 100 MILLISECONDS
>     // Critical TAGs only
>
> ADD MAPPING SlowLoop
>     WITH EVERY 5 SECONDS
>     // Non-critical TAGs
> ```

> **Use Deadband for Analog Values**
>
> Reduce network traffic for slowly changing values:
>
> ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> ADD TAG Temperature
>     WITH DEADBAND 0.5  // Only publish if change > 0.5
> ```

> **Apply Scaling at the Edge**
>
> Transform raw PLC values to engineering units in the route:
>
> ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> ADD TAG Temperature
>     WITH SCALING 0.1   // Raw 245 becomes 24.5
>     WITH OFFSET -40    // Apply offset if needed
>     WITH UNIT "°C"
> ```

> **Use JSON Mode for Rich Data**
>
> Include metadata in published messages:
>
> ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> ADD TAG Temperature
>     WITH PUBLISH_MODE "JSON"
>     WITH UNIT "°C"
>     WITH DESCRIPTION "Main reactor temperature"
> ```
>
> Output:
>
> ```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> {
>   "value": 24.5,
>   "unit": "°C",
>   "description": "Main reactor temperature",
>   "timestamp": "2024-01-15T10:30:00Z"
> }
> ```

> **Secure Write Operations**
>
> Only enable WRITABLE for TAGs that need it, and use specific topics:
>
> ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> ADD TAG Setpoint
>     WITH WRITABLE "true"
>     WITH DESTINATION_TOPIC "plc/setpoints/temperature/write"
>     WITH MIN_VALUE 0
>     WITH MAX_VALUE 100
> ```

***

## Next Steps

Choose the protocol that matches your equipment:

> **[Modbus TCP Client](./modbus-tcp-client)**
>
> Start with the most common industrial protocol.

> **[Siemens S7](./siemens-s7)**
>
> Connect to Siemens PLCs directly.


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