Edge (PLCnext / Orin)
The Consystence edge runtime (consystence-edge) is a self-contained .NET 10, single-silo Orleans application that sits between your controllers and the site server. It reads controller tags, carries an operator’s command to a controller only where the site’s policy admits Consystence commands (see Alarms & commands), buffers data through upstream outages, and sits on the boundary between the IT and OT networks. This page covers what an edge host needs, how the edge connects to its site server, and how to configure its controller drivers.
For the conceptual model — what the edge does and how it talks to controllers — start at Edge runtime and PLC communication.
Topology
Section titled “Topology”flowchart LR
SS["Site server"]
subgraph EDGE["Edge host"]
ER["consystence-edge<br/>.NET 10 single-silo Orleans<br/>SQLite store-and-forward"]
end
CLX["ControlLogix"]
PNX["PLCnext"]
IO["Field IO"]
SS <-->|"gRPC (HTTP/2 TLS)<br/>edge listener port"| ER
ER -->|"EtherNet/IP CIP (TCP 44818)"| CLX
ER -->|"gRPC (GDS)<br/>local socket or TCP"| PNX
PNX -.->|"CIP / EtherNet-IP<br/>(border-gateway topology)"| IO
The edge reads controllers directly, on one shared driver layer: a Rockwell ControlLogix over EtherNet/IP CIP, and a Phoenix Contact PLCnext over gRPC (GDS), through the local socket when the edge runs beside the PLCnext runtime or over TCP to a separate or virtual PLCnext. One edge can connect to both. A site server can also read a ControlLogix directly, read-only, without an edge. This is in early access: our engineers enable it per controller, and it has run at a site trial since 28 October 2026. Off by default. Production control stays on your existing control system.
Where Consystence manages the control equipment, the PLCnext border gateway is an accepted target topology, demonstrated in our workshop on a virtual PLCnext: the PLCnext runs the control logic and speaks CIP / EtherNet/IP to the field IO, and the edge reads the PLCnext’s process variables over gRPC and writes its command ports for operator commands the site admits. See PLC communication and PLCnext border gateway for the protocol detail.
Edge hosts
Section titled “Edge hosts”| PLCnext VL3 | Nvidia Jetson Orin | |
|---|---|---|
| Status | Target platform | Workshop host |
| Role | Edge runtime beside the PLCnext control runtime on one device | Edge beside external controllers |
| Controller access | PLCnext GDS over gRPC (local socket) | EtherNet/IP CIP to a ControlLogix; gRPC over TCP to a separate PLCnext |
The VL3 is a target platform for the edge runtime; we are proving the edge runtime for it on a Linux container in our workshop (see VL3 edge setup). The edge runs today on an Nvidia Jetson Orin in our workshop. It first ran there on 10 September 2026, when one runtime connected to both a physical ControlLogix and a virtual PLCnext (see Nvidia Orin edge).
Packages
Section titled “Packages”There is no public download of the edge runtime yet. During the beta, edge deployments are arranged with our team (contact us); the published packages will be announced on consystence.com/download.
However it is packaged, the edge keeps its state under /var/lib/consystence: the store-and-forward buffer, and the write-once install identity and offline-licence state described under Licensing. That directory must persist across restarts and redeploys; an edge that loses it mints a new install identity. The edge also needs a network path to its site server’s edge listener and to its controllers and, where it reads a PLCnext through the local socket, access to /run/plcnext/grpc.sock.
Bootstrap and registration
Section titled “Bootstrap and registration”All edge configuration is local: the runtime reads standard .NET configuration (appsettings.json plus environment variables such as Edge__Id and Upstream__ServerUrl) on every boot. Server-pushed configuration is a roadmap item; today the full driver configuration below is maintained in appsettings.json on the device.
The minimal configuration is the mandatory edge identity plus the upstream URL:
{ "Edge": { "Id": "edge-001" }, "Upstream": { "ServerUrl": "https://site-server.example:5001", "ForceHttp3": false, "BufferPath": "/var/lib/consystence/buffer.db" }}Edge:Id is required — startup throws Edge:Id is required in configuration without it; nothing auto-detects a serial or MAC. The local diagnostics API listens on 127.0.0.1:8080 by default (LocalApi:Address, LocalApi:Port), so it is reachable from this host only. Binding it to a network address also requires LocalApi:TokenFile, a file holding an access token of at least 32 characters; every request from another host, except GET /health, must then present it as Authorization: Bearer <token>. LocalApi is the only binding source: a Kestrel endpoint section stops the edge at startup, and ASPNETCORE_URLS is ignored.
The site server accepts edge connections only on a dedicated edge listener port, and accepts none until that port is set. With HTTPS configured on the site server (SiteTls__Enabled=true plus the certificate settings described in /etc/consystence/site-server.env.example; see On-prem deployment), add EdgeListener__Port=5001 to /etc/consystence/site-server.env and restart the service. The server then serves edge connections over HTTPS on that port only; allow only your edge hosts to reach it. Point each edge at it with Upstream:ServerUrl = https://site-server.example:5001; the edge must trust the site server’s certificate.
The edge registers with the site server on every boot and reconnect, presenting its configured Edge:Id as the hardware_id (plus hostname, version, and capabilities). The site server answers APPROVED, PENDING or REJECTED, and the edge retries every five seconds until it is approved. On approval the response echoes the edge_id and carries the tenant slug as tenant_code.
Driver configuration
Section titled “Driver configuration”The full configuration is plain JSON (appsettings.json on the device). The deployment-relevant sections are the controller list, the poll groups, the device instances, and the command mappings.
Controllers (Plcs)
Section titled “Controllers (Plcs)”Each entry selects a driver from the shared driver layer with its Driver value. Valid Driver values are plcnext-grpc, ethernet-ip, and stub.
"Plcs": [ { "PlcId": "plcnext-001", "Name": "PLCnext VL3 (local)", "Driver": "plcnext-grpc", "Address": "localhost", "Port": 0, "Slot": 0, "TimeoutMs": 5000, "ReconnectIntervalMs": 10000 }]Driver | Transport | Use |
|---|---|---|
plcnext-grpc | PLCnext GDS over gRPC: the local socket /run/plcnext/grpc.sock by default, or TCP | Read and write PLCnext GDS/PORT variables on a co-located, separate or virtual PLCnext |
ethernet-ip | Pure-.NET EtherNet/IP CIP stack, TCP 44818 | Direct to a Rockwell ControlLogix |
stub | In-process simulator | Development and CI only |
For the plcnext-grpc driver, the PLCnext connection itself is configured under a dedicated PlcNext block. The default is the local Unix domain socket. To reach a separate or virtual PLCnext over TCP instead, set GrpcHost (and GrpcPort, default 41100); a host, when set, takes precedence over the socket:
"PlcNext": { "SocketPath": "/run/plcnext/grpc.sock", "ComponentPath": "Arp.Plc.Eclr/ExampleComponent.ExampleProgram", "SubscriptionSampleRateUs": 0}Poll groups (PollGroups)
Section titled “Poll groups (PollGroups)”Tags are grouped by polling interval; each group runs its own timer and reads its tags directly from the controller on each tick, timestamped at the edge. Subscription-based, task-consistent acquisition with controller timestamps is planned. A tag maps a device instance’s logical tag name to the physical PLC tag path on a specific controller.
"PollGroups": [ { "GroupId": "plcnext-fast", "IntervalMs": 250, "Tags": [ { "InstanceId": "dev_k7m2p9qx", "TagName": "Running", "PlcId": "plcnext-001", "PlcTagPath": "PP01_Running" }, { "InstanceId": "dev_k7m2p9qx", "TagName": "Speed", "PlcId": "plcnext-001", "PlcTagPath": "PP01_Speed" } ] }]Instances (Instances)
Section titled “Instances (Instances)”Each instance binds a per-site device to a reusable device type (dotted TypeId, e.g. consystence.pump.centrifugal) and maps its type-defined tag names to physical PLC tag paths.
"Instances": [ { "InstanceId": "dev_k7m2p9qx", "Name": "Dam Transfer Pump 1", "TypeId": "consystence.pump.centrifugal", "TypeVersion": "1.0.0", "TagMappings": { "Running": "PP01_Running", "Speed": "PP01_Speed", "Current": "PP01_Current" } }]Command mappings (CommandMappings)
Section titled “Command mappings (CommandMappings)”Commands are keyed by {instanceId}:{commandName} and resolve to one or more tag writes on a target controller. A write value can reference a command parameter as {paramName}, but commands that take parameters are not yet offered.
"CommandMappings": { "dev_k7m2p9qx:start": { "PlcId": "plcnext-001", "Writes": [ { "TagPath": "PP01_Cmd_Start", "Value": "true" } ] }, "dev_k7m2p9qx:stop": { "PlcId": "plcnext-001", "Writes": [ { "TagPath": "PP01_Cmd_Stop", "Value": "true" } ] }}Store-and-forward and upstream
Section titled “Store-and-forward and upstream”The edge streams tag batches to the site server over multiplexed gRPC over HTTP/2. Upstream:ForceHttp3 defaults to false (HTTP/2); leave it there. When the upstream link drops, polling continues and batches are persisted to a WAL-mode SQLite buffer at /var/lib/consystence/buffer.db. On reconnect the buffer flushes in monotonic sequence order; the server acknowledges sequences and acknowledged messages are deleted. The buffer survives restarts and power cycles.
The buffer is disk-capped via Upstream:BufferMaxBytes (default 536870912 — 512 MiB). On overflow the oldest data is dropped first; drops are counted and reported via the heartbeat, and the edge reports degraded health while overflowing. Size the cap for the longest upstream outage the site must ride out.
"Upstream": { "ServerUrl": "https://site-server.example:5001", "ForceHttp3": false, "BufferPath": "/var/lib/consystence/buffer.db", "BufferMaxBytes": 536870912, "HeartbeatIntervalMs": 30000, "MaxBatchSize": 200, "FlushIntervalMs": 500}IT/OT network policy
Section titled “IT/OT network policy”Keep the edge host’s IT (upstream) and OT (field) interfaces on separate network segments with no routing between them, and restrict the OT segment to your controllers. Your plant network design, including a PLCnext gateway’s own port separation where one is used, remains the field-network boundary.
For a dual-NIC edge host, the edge generates a reference nftables policy from its Firewall section; Network security sets out the full reference policy. A host installer that applies it is planned.
"Firewall": { "Enabled": true, "ItInterface": "eth0", "OtInterface": "eth1", "BlockForwarding": true, "ItRules": { "InboundAllow": [ { "Port": 22, "Protocol": "tcp", "Description": "SSH" } ], "OutboundAllow": [ { "Port": 5001, "Protocol": "tcp", "Description": "gRPC upstream (TLS)" }, { "Port": 53, "Protocol": "tcp+udp", "Description": "DNS" }, { "Port": 123, "Protocol": "udp", "Description": "NTP" } ] }, "OtRules": { "InboundAllow": [ { "Port": 44818, "Protocol": "tcp+udp", "Description": "EtherNet/IP CIP", "AllowedSources": ["192.0.2.10"] } ], "OutboundAllow": [ { "Port": 44818, "Protocol": "tcp+udp", "Description": "EtherNet/IP CIP", "AllowedSources": ["192.0.2.10"] } ] }}The policy is default-drop: only established/related and loopback traffic pass, plus the allow rules declared under ItRules/OtRules. Each rule takes Port, Protocol (tcp, udp or tcp+udp), a Description and an optional AllowedSources IP list. With BlockForwarding, the policy drops all forwarding between the two interfaces. In the example above, the OT interface permits EtherNet/IP CIP (TCP and UDP 44818) only, to and from the declared controller address, and the IT interface permits SSH in and the upstream, DNS and NTP out. The generated policy replaces the host’s entire nftables ruleset, so declare every flow the host needs, including the upstream to the site server’s edge listener port and your management access.
GET /api/firewall reports the policy status: whether it is enabled and applied, when it was last applied, the last error and the rule counts. The OT traffic check is configured under NetworkMonitor: GET /api/network reads the interface counters and established connections on request, and flags unexpected sources, unexpected ports and traffic spikes against those settings.
Licensing
Section titled “Licensing”Edge licensing follows the platform licensing model (see Licensing):
- Provisioned identity, not hardware ID. The licence binds to a write-once install ID minted on first boot and kept under
/var/lib/consystence— not a motherboard/MAC fingerprint. It survives snapshot, clone and migrate operations. - Account-issued, expiring, per-device. Each edge licence is issued by the account against the organisation’s Edge SKU pool, carries an expiry, and is refreshed online through the site server or from an offline bundle file.
- Cell-mediated. Issuance authority lives in the account control plane; the cell mediates and the edge enforces.
- Offline = account-signed. The offline bundle is verified against an account ECDSA key published as JWKS — no live call needed to validate a held entitlement.
- Degrade, never kill. After a 30-day grace period, a lapsed licence can only trim the edge to its licensed number of device instances, never to zero. Poll groups, command mappings and controller connections are untouched; a licence problem never gates operator control writes (Start/Stop/Reset/ack) and never stops the poll loop.
"Licence": { "BundlePath": "/var/lib/consystence/edge-licence.bundle.json"}Local diagnostics API
Section titled “Local diagnostics API”The edge exposes a minimal, read-only HTTP API, on 127.0.0.1:8080 by default, for on-device diagnostics:
| Method | Path | Description |
|---|---|---|
GET | /health | ASP.NET health check (used by the container health check) |
GET | /api/status | Controller, upstream, and buffer status |
GET | /api/diagnostics/read-tags?plcId=&paths= | One-shot live read of named tags (at most 64 paths a request) |
GET | /api/firewall | Firewall policy status: enabled, applied, rule counts, last error |
GET | /api/network | Interface counters and established connections, read on request, with unexpected sources, ports and traffic spikes flagged |
See also
Section titled “See also”- Edge runtime — the conceptual overview
- PLC communication — GDS-over-gRPC and CIP detail
- Licensing — the full entitlement model
- Deployment — Azure and on-prem server deployment
- Cell topology — where account-issued licences originate