Skip to content

Controller Communication

The Consystence edge runtime reads and writes field data through a single driver abstraction, IPlcDriver. Two paths sit behind it: the PLCnext border gateway (the primary model for Consystence-managed equipment) and the direct native drivers — EtherNet/IP CIP and Modbus TCP — the interop path for third-party and legacy controllers. This page describes both, how a driver is selected, and where the control boundary sits.

Throughout, we use the vendor-neutral term controller rather than “PLC”. Consystence treats ControlLogix and PLCnext controllers uniformly behind the same engineering model: controller → module → channel → tag/register. Modbus is modelled in the same terms: a native Modbus TCP driver (modbus-tcp, default port 502) ships in the shared driver layer, reading and writing coils, discrete inputs, and holding/input registers. Register addresses are specified in the tag path with an explicit data type and word order. The connection-level configuration wiring for Modbus is still landing, so confirm connection setup against the current release before commissioning a Modbus controller. OPC UA and S7 drivers are future additions. For the edge runtime in general, see the edge overview.

Every controller connection is a IPlcDriver. The interface is deliberately small and symbolic — callers address tags by path, not by raw register offset:

public interface IPlcDriver : IAsyncDisposable
{
PlcConnectionState State { get; }
event EventHandler<PlcConnectionStateChangedEventArgs>? StateChanged;
Task ConnectAsync(CancellationToken ct = default);
Task DisconnectAsync(CancellationToken ct = default);
Task<TagValue> ReadTagAsync(string tagPath, CancellationToken ct = default);
Task<IReadOnlyList<TagValue>> ReadTagsAsync(
IReadOnlyList<string> tagPaths, CancellationToken ct = default);
Task WriteTagAsync(string tagPath, object value, CancellationToken ct = default);
Task WriteTagsAsync(IReadOnlyList<TagWrite> writes, CancellationToken ct = default);
}

The batch ReadTagsAsync / WriteTagsAsync calls exist so a driver can coalesce many tags into one wire request (a single batched request, or a single GDS subscription read) rather than issuing one round-trip per tag.

Tag values carry the platform tag model: a dataType of Bool, Int, DInt, Real or String, an access mode of Read, Write or ReadWrite, and a physicalRole. The driver is responsible only for moving typed values; the meaning of each tag is defined by the device type and instance mapping (see Concepts).

Inside the Orleans silo, a connection grain wraps each IPlcDriver and owns reconnect logic, so callers never touch a raw socket. Poll groups drive reads on independent timers. Changed values (by value or quality) stream upstream as they occur, with a periodic full snapshot so static values stay attested — or buffer to SQLite when the link to the server is down (store-and-forward) and replay on reconnect.

Drivers are constructed by a factory from a small config record. The Driver field is the selector:

public sealed class PlcDriverConfig
{
public required string PlcId { get; init; }
public required string Driver { get; init; } // "plcnext-grpc" | "ethernet-ip" | "modbus-tcp" | "stub"
public Dictionary<string, string> Settings { get; init; } = [];
}
Driver valueUse
plcnext-grpcPrimary — read/write GDS variables on a PLCnext border gateway over gRPC
ethernet-ipDirect EtherNet/IP CIP to a ControlLogix/CompactLogix — third-party / legacy-controller interop
modbus-tcpDirect Modbus TCP (default port 502) to a Modbus device — third-party interop
stubSimulated IO for tests and edge simulation mode (safe-off / IO injection)

Settings carries transport-specific values — a socketPath for PLCnext, or an address/port and a CIP route for EtherNet/IP — so the rest of the edge stays transport-agnostic. The CIP route is a comma-separated series of port,link hops (for example 1,0 for the local backplane slot 0) and supports multi-hop routes through an EtherNet/IP bridge to a CPU in a remote chassis; a malformed route is rejected outright rather than falling back to a default slot. Note that the cloud-side simulator is a separate mechanism — the server’s Sim Runtime, selected by sim:// endpoint prefixes — not this driver.

flowchart LR
  EDGE["Consystence Edge<br/>(.NET 10 Orleans silo)"]
  subgraph PLCnext["PLCnext border gateway"]
    GDS["GDS / PORT variables"]
  end
  FIELD["Field devices<br/>(VFDs, relays, IO)"]
  LEGACY["Third-party / legacy controller<br/>(direct)"]

  EDGE -- "gRPC-over-UDS (primary)" --> GDS
  GDS -- "CIP / EtherNet-IP" --> FIELD
  EDGE -. "EtherNet/IP CIP / Modbus TCP<br/>(direct interop)" .-> LEGACY

In the primary topology the edge does not talk to field devices directly. A PLCnext controller acts as the border gateway between an isolated internal field network and the wider OT network. The PLCnext runs the deterministic control logic and speaks CIP / EtherNet-IP to the field devices (VFDs, protection relays, remote IO) on its internal port. The edge connects only to the PLCnext, and only on the external port.

This gives a clean security boundary: the field network is invisible to anything outside the PLCnext, the controller is the single point of control for field devices, and control continues to run on the controller even if the edge disconnects. The edge sees abstracted device and process variables, not raw IO.

The plcnext-grpc driver reads and writes PLCnext GDS (Global Data Space) PORT variables through the controller’s local gRPC API. The default transport is a Unix Domain Socket at /run/plcnext/grpc.sock — lowest latency, no network stack — which suits the native deployment where the edge runtime runs co-located on the PLCnext device itself. A TCP fallback (GrpcHost / port 41100) is used when the socket cannot be shared — typically the containerised vPLCnext deployment, where the edge and the vPLCnext runtime do not share a filesystem.

The client splits reads from writes for correctness:

  • Reads are designed around the PLCnext subscription service, which delivers task-consistent, buffered samples at the controller scan-cycle rate rather than polling each variable independently.
  • Writes use the data-access service for immediate, direct updates.

GDS variables are addressed by a full component path of the shape Arp.Plc.Eclr/{Component}.{Program}.{Port} — dot-joined after the first slash. The driver builds it from a configured ComponentPath prefix plus the PORT name. For example (illustrative only — names vary per project):

ComponentPath : Arp.Plc.Eclr/ExamplePumpComp.PumpStation1
PORT name : PP01_Running
Full GDS path : Arp.Plc.Eclr/ExamplePumpComp.PumpStation1.PP01_Running

The ethernet-ip driver is a native EtherNet/IP driver for ControlLogix / CompactLogix controllers, speaking EtherNet/IP CIP over TCP (port 44818) and batching reads where the protocol allows. It needs no native library dependency. It connects the edge straight to a controller’s symbolic tag space, for example Program:Main.Pump1.SpeedSP (illustrative).

The modbus-tcp driver speaks Modbus TCP (default port 502) to Modbus devices, addressing registers through the tag path with an explicit data type and word order — see the connection-configuration caveat in the introduction.

This direct path is the supported interop route for third-party and legacy controllers without a PLCnext gateway, and it is actively expanding. Prefer the border-gateway model for Consystence-managed equipment, for its network segmentation and independence properties.

ConcernPLCnext border gateway (plcnext-grpc)Direct native drivers (ethernet-ip, modbus-tcp)
Edge → controller transportgRPC over UDS (TCP fallback)EtherNet/IP CIP over TCP 44818, or Modbus TCP over TCP 502
Controller → fieldCIP / EtherNet-IP on isolated internal networkn/a (edge talks to the controller directly)
Field network isolationYes — invisible behind the gatewayNo
Control if edge disconnectsContinues on the controllerContinues on the controller
StatusPrimary — preferred for Consystence-managed equipmentSupported interop path for third-party / legacy controllers (expanding)

For where the edge tier sits in the wider system, see cell topology. Tag reads and writes surfaced to clients are documented in the API reference.