Skip to content

VL3 Edge Setup

The Phoenix Contact VL3 UPC 2440 EDGE is the primary hardware host for the Consystence edge runtime. A single DIN-rail device runs both the deterministic control runtime and the Consystence intelligence layer, sitting between the field and the site server. This page covers how to provision one: the two-process model, the systemd unit that runs the edge runtime, the local configuration and registration flow, and the local gRPC link to the PLCnext controller.

For the broader edge picture, start at the edge overview. For how the PLCnext acts as the field gateway, see the PLCnext border gateway.

The VL3 hosts two cooperating processes, with a clean separation of concerns:

ProcessRoleRuntime
vPLCnext (Virtual PLCnext Control)Deterministic real-time control — state machines, interlocks, alarms, IO mapping. Speaks EtherNet/IP CIP to the field and exposes tags over a local gRPC server.Phoenix Contact PLCnext runtime
consystence-edgePlatform functions — tag collection, store-and-forward, store buffering, upstream streaming, local diagnostics..NET 10, single-silo Orleans

The control logic stays in the PLCnext runtime where it belongs; consystence-edge never issues control commands directly. It reads and writes tags through the PLCnext over a local socket, buffers them, and streams them upstream.

flowchart TB
  subgraph VL3["VL3 UPC 2440 EDGE"]
    EDGE["consystence-edge<br/>.NET 10 / Orleans<br/>store-and-forward (SQLite)"]
    PLC["vPLCnext<br/>Virtual PLCnext Control<br/>CSY control program (C# eCLR)"]
    EDGE -->|"gRPC over UDS<br/>/run/plcnext/grpc.sock"| PLC
  end
  EDGE -->|"gRPC (HTTP/2 TLS)<br/>store-and-forward"| SS["Site server"]
  PLC -->|"EtherNet/IP CIP<br/>TCP 44818"| FIELD["Field IO / ControlLogix rack"]

The unit ships as consystence-edge.service and runs the published Consystence.Edge binary under a dedicated unprivileged consystence user with filesystem hardening:

[Unit]
Description=Consystence Edge Runtime
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/opt/consystence/edge/Consystence.Edge
WorkingDirectory=/opt/consystence/edge
Environment=ASPNETCORE_URLS=http://+:8080
Environment=DOTNET_ENVIRONMENT=Production
Restart=always
RestartSec=10
TimeoutStartSec=30
TimeoutStopSec=30
User=consystence
Group=consystence
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/consystence /var/log/consystence
StateDirectory=consystence
PrivateTmp=true
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
[Install]
WantedBy=multi-user.target

Points worth understanding before you deploy:

  • StateDirectory=consystence — systemd creates and owns /var/lib/consystence and, critically, keeps it across a unit replacement or redeploy. This is where the write-once install identity and the offline-licence state persist. Do not delete it between upgrades. Offline licensing degrades gracefully and never gates control writes — see licensing.
  • ProtectSystem=strict + ReadWritePaths — the root filesystem is read-only to the service; only /var/lib/consystence (state, store-and-forward buffer) and /var/log/consystence (logs) are writable.
  • ASPNETCORE_URLS=http://+:8080 — the local diagnostics API binds port 8080. The systemd unit sets the binding through this environment variable; the shipped appsettings.json binds the same port through its Kestrel:Endpoints section (which is how a containerised deploy binds it, without ASPNETCORE_URLS). See the caution below on securing that management surface.
  • Restart=always / RestartSec=10 — the edge self-heals after crashes or power events; store-and-forward replays any buffered data on reconnect.

Install and enable:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now consystence-edge.service
sudo systemctl status consystence-edge.service
journalctl -u consystence-edge.service -f

The target design is a minimal bootstrap: an unconfigured edge carries only enough configuration to find and register with the site server, and everything else — the controller/module/channel/tag model, the PLC driver wiring, alarm definitions — is pushed from the server. Server-side push is a target capability, not yet shipping. Today the edge builds its entire runtime model — PLC drivers, poll groups, device instances, command mappings — from its local appsettings.json, with environment-variable overrides (for example Upstream__ServerUrl to point the edge at its site server).

The keys the runtime actually reads, in appsettings.json form:

{
"Edge": {
"Id": "edge-01",
"Site": "your-site",
"TenantCode": "your-org"
},
"Upstream": {
"ServerUrl": "https://site-server.example.internal:5001",
"BufferPath": "/var/lib/consystence/buffer.db",
"BufferMaxBytes": 536870912,
"ForceHttp3": false
},
"Plcs": [
{
"PlcId": "plcnext-001",
"Name": "PLCnext VL3 (local)",
"Driver": "plcnext-grpc",
"Address": "localhost"
}
]
}
KeyMeaning
Edge:IdThe only required setting — the edge’s identity. Bootstrap fails (Edge:Id is required in configuration) if it is missing, and the value is what the edge sends the server as its hardware ID at registration. The target design is an activation flow that enrols the device by its write-once install identity, with Edge:Id remaining a display label — not yet shipped. (Licensing identity is provisioned by the account, not derived from hardware — see licensing.)
Edge:Site, Edge:TenantCodeThe site code and org slug the edge reports itself under.
Upstream:ServerUrlWhere the edge phones home — set this explicitly to the site server’s hostname or address.
Upstream:BufferPathSQLite store-and-forward buffer, under the writable /var/lib/consystence state directory. Buffers tag data when the upstream link drops and replays on reconnect.
Upstream:BufferMaxBytesDisk cap on the store-and-forward buffer (default 536870912 — 512 MiB). On overflow the oldest data is dropped first; drops are counted and reported on the heartbeat, and edge health degrades while overflowing. Size it for the longest outage the site must ride out.
Upstream:ForceHttp3Attempts HTTP/3 (QUIC) upstream. The site server currently serves HTTP/1.1 and HTTP/2 only, so set this to false (as the workshop config does) — gRPC then runs over HTTP/2 TLS.
Plcs, PollGroups, Instances, CommandMappingsThe full PLC driver wiring, poll schedule, device instances, and command tag writes — all local today. See PLC communication.
Edge:ConfigVersionThe command-catalogue version this edge’s local config corresponds to. Reported to the site server on the command channel; commands are rejected fail-closed if it does not match the version the site activated for this edge.

The local diagnostics API binds wherever ASPNETCORE_URLS points (port 8080 in the unit above); there is no localApi config section.

  1. The VL3 powers on and gets an IP (DHCP in the workshop).
  2. consystence-edge reads its local configuration, takes its identity from Edge:Id, and registers with the site server at Upstream:ServerUrl.
  3. For a single-site org — the normal site-server topology — the server registers the edge into that site on first contact and returns Approved; the device then appears in the site’s edge directory.
  4. For a multi-site org where the edge is not yet known, the server returns Pending and the edge retries until it is admitted to a site.
  5. Nothing is pushed after registration — the edge runs on the configuration it already holds locally.

The site server runs the same codebase in ServerMode.Site; see the server model for how sites sit under an org.

consystence-edge reads and writes field data through the co-located PLCnext, not by talking to the field directly. The link is gRPC over a Unix Domain Socket at /run/plcnext/grpc.sock — the PLCnext gRPC server (RSC/Data Access services) also listens on TCP 41100, but the local UDS path is preferred for lowest latency and no network stack.

This is implemented behind the edge’s IPlcDriver abstraction. The relevant drivers:

DriverUse
plcnext-grpcPrimary. Reads/writes PLCnext GDS/PORT variables over gRPC-over-UDS.
ethernet-ipDirect EtherNet/IP CIP from the edge — interop path for third-party/legacy controllers; on the VL3 the PLCnext owns the field link.
modbus-tcpDirect Modbus TCP (port 502) from the edge — interop path for Modbus devices.
stubIn-memory driver for testing and simulation.

The PLCnext, in turn, speaks EtherNet/IP CIP (TCP 44818) to the ControlLogix rack or field IO. So the data path is: field → PLCnext (CIP) → consystence-edge (gRPC/UDS) → site server (gRPC over HTTP/2 TLS). The edge never speaks CIP itself in this topology.

PLC driver configuration lives in the edge’s local appsettings.json — the Plcs, PollGroups, and Instances sections shown above — so today you provision the link by editing the edge’s own config. Server-pushed driver config after registration is the target design, not current behaviour.

The VL3 is a dual-homed IT/OT boundary device. The runtime expects two interfaces — eth0 on the IT side (upstream to the site server) and eth1 on the OT side (the field network) — with nftables enforcing the segmentation. Get this right before going live: the OT side should reach only the PLCnext and field devices, and the IT side only the site server. See network security for the firewall ruleset and the full topology.

The VL3 is the primary hardware, but the Nvidia Orin is a supported, GPU-capable alternative that runs the same .NET 10 edge runtime — the same Consystence.Edge binary, the same systemd unit, the same appsettings.json configuration model. The Orin is not a standalone ML appliance; it is simply an edge host with a GPU. See Orin setup for the differences.