PLC Bridge User Guide
What the bridge does, how it is deployed, and how to tell that it is healthy
PLC Bridge lets a machine's own controller read live Vorne XL production data and act on it, and lets it tell the board what is happening, without anyone standing at a terminal.
It is one service on the plant network. Your PLC talks only to the bridge; the bridge talks to every board it is configured for. It speaks six protocols on the controller side (EtherNet/IP, OPC UA, Modbus TCP, MQTT Sparkplug B, Mitsubishi SLMP/MC Protocol, and Beckhoff ADS), and it hides the difference between V1 and V2 boards, so a controller sees the same names and numbers whichever generation is on the line.
This guide is for the plant, and for whoever runs the service. It does not cover the controller side; that is the PLC Bridge Integrator Guide, which is what your PLC programmer or system integrator needs.
What the bridge does, and what it does not
What it does.
- Reads every board it is given, continuously. Once a second by default, it collects the running job and part, counts, targets, OEE and its three components for both the job and the shift, the machine's production state and its reason, and the board's own identity and clock.
- Publishes those values to your controller in the shape that controller expects: named tags on a Logix controller, browsable nodes for an OPC UA client, a register map for a Modbus master or slave, or Sparkplug metrics on your MQTT broker.
- Accepts commands from the controller and carries them out on the board. Start a job, tag a job, inject good or reject counts, submit rejects with a reason, set a down reason, start and end changeovers, start and end shifts, set the team, and post a comment.
- Marks every value with its quality, so a controller can always tell a fresh reading from a held one, and a real zero from a value the board never provided.
What it does not do.
- It has no screens. There is nothing to log into and nothing to click. It is a service with a status endpoint, configured by a file and read by machines.
- It does not replace Sync Bridge. Sync Bridge is still what brings Vorne data into the suite for Pulse, Explorer, and the rest. The PLC bridge is a sibling service that runs beside it and serves the controller instead.
- It does not reach outside the plant. It talks to boards and to controllers on the local network.
- It never invents a number. A value the board did not provide is not written as a zero, and a command it cannot carry out is reported as failed rather than quietly dropped.
One service, one or many boards
A single bridge can serve several boards and several controllers at once. Each controller connection is configured with the board it serves, which points that controller sees, what it calls them, and which commands it is allowed to send.
How it is deployed
The bridge is deployed through Client Manager, the same control plane that deploys the rest of the suite, as one more service in the app suite beside Sync Bridge. It runs as a container on the plant server.
Two things are set per deployment:
- Its port. The status and health endpoints listen on port 5510 by default, and the published port follows whatever the environment's app suite entry says.
- Its configuration. There are three ways to deliver the document, and none of them is a default. Every deployment picks one.
The three ways to deliver the configuration.
- Put the file on the service's own configuration volume, before the first deployment. The document never enters the deployment pipeline at all, which is the route for a site whose policy keeps credentials out of deployment configuration. A volume an earlier deployment already filled counts too, so redeploying a configured site needs nothing further.
- Hand the deployment the whole document, base64 encoded, and let it write the file onto that volume before the container starts. The document lives on the volume without anyone touching the server, and it is never echoed: it reaches the writer through the environment rather than a command line, so it stays out of the job log and the deployment records. The write goes to a temporary name and is moved into place, so an interrupted deployment leaves the previous file intact rather than half a document.
- Hand the deployment the whole document as an environment variable, base64 encoded. It needs no volume edit and wins over the file when both are present.
It is base64 rather than plain text because deployments hand environment variables to the container line by line, and a multi-line value would be split into broken lines. One encoded line survives that intact. A value that is not valid base64, or that does not decode to valid text, is reported as a configuration error naming the variable rather than being quietly truncated. Where the configuration does travel with the deployment, it is redacted out of the deployment records the pipeline keeps.
Three storage volumes outlive the container:
- the configuration volume, where the configuration file lives under the first two delivery routes above;
- the buffer volume, which holds the command buffer. That one matters: it is what lets commands sent while a board was down be replayed afterwards, so it has to survive a container restart.
- the state volume, which holds the OPC UA server's certificate, so a client that trusted the bridge once does not have to trust it again after every deployment.
The container's own health check polls the health endpoint, so the platform restarts a bridge that has stopped serving.
Two different "no configuration" cases, and they behave differently
A deployment that used none of the three delivery routes never starts. The deployment checks before it starts the container, finds no document and an empty configuration volume, and stops there, naming all three routes in the failure. That is deliberate: a bridge with no document reports itself unhealthy, so starting it would only burn the deployment's whole health-check budget before failing anyway.
A bridge that has a document it cannot use stays up. Where the document was delivered but is missing from the path, will not parse, or fails validation, the service starts anyway and stays up: the status endpoint answers, the configuration check reports the error and the path it looked for, the health endpoint reports unhealthy, and no board or controller connection is started.
It does not exit and it does not invent a configuration, so whoever is commissioning it can reach the service, read exactly what it wants, deliver a corrected document, and restart.
What the configuration declares
One document declares everything. It has three parts: how often boards are read and where the command buffer lives, which boards to read, and which controller connections to serve.
poll:
intervalMs: 1000
timeoutMs: 30000
buffer:
path: /var/lib/plc-bridge/commands.sqlite
maxQueuedPerBoard: 10000
retainTerminalPerBoard: 10000
status:
host: 127.0.0.1
port: 5510
boards:
fromDevicesTable: false
list:
- name: line-1
host: 10.0.0.5
family: V2
pollIntervalMs: 500
inputs: { good: 1, reject: 2 }
adapters:
- name: plc-1
type: ethernet-ip
settings:
host: 10.10.20.5
mapping:
board: line-1
points:
board.online: PLC_ONLINE
job.good_count: PLC_GOOD_COUNT
state.process_state: PLC_STATE
commands:
job.set: PLC_SET_JOB
state.set_reason: PLC_SET_FAULT
reasonMap:
PLC_FAULT_MAINT: maintenance
PLC_FAULT_MEETING: meeting
Boards come from one of two places, and both are explicit. There is no guessing.
- From the suite's own device list. Turn
fromDevicesTableon and the bridge reads the active, non-simulated Vorne devices the suite already knows about, with their generation and their login details. This is the normal choice on a plant that already runs the suite. - From the file. List boards by hand with their address, board generation, and, where counts are injected, the digital input pins to use. This is what a bench or a site running without the suite database uses.
Both can be used together. Turning the device list off means the bridge starts nothing from it, whether or not a database is reachable.
Adapters are the controller connections. Each one names its protocol, its own settings (the controller address, the port the bridge listens on, the broker URL, and so on), and its mapping:
board- which board this controller sees.points- which values it sees, and what it calls them. An empty list means every point under its own name. A non-empty list is a whitelist as well as a rename: those points and nothing else.commands- the same for commands, and keyed the same way round: the bridge's own command name on the left, the name the controller uses on the right. Withjob.set: PLC_SET_JOBthe controller writesPLC_SET_JOBand the bridge runsjob.set.reasonMap- the controller's own fault codes, translated into the reason keys the board is configured with. The bridge applies this, and with a non-empty map a code that is not in it is refused rather than passed to the board as it stands.
Check a configuration before you deploy it
The service can validate a configuration document without running it. Every error names the source and the exact field, and all of them are reported at once, so a bad document is fixed in one pass rather than one error per attempt.
The deployment does it for you as well, whichever route delivered the document. It runs that same check against the document the service will actually get, before it touches the running bridge, so a typo refuses the deployment and leaves the healthy service running instead of replacing it with one that cannot start. A protocol name that is not a real one is caught there too, and so is a controller connection pointed at a board the site does not have.
Status, health, and what healthy means
The bridge serves two endpoints on port 5510, in the same shape Sync Bridge uses, so a monitoring system that already watches one can watch the other.
/healthis the blunt one, and it is what the container's own health check polls. It answers 200 when the service is serving and 503 when it is not./statusis the detailed one, and it answers 200 even when something is wrong, because a status page that stops answering when there is a problem is a status page that tells you nothing.
/status carries the service name, its overall state, whether it is
ready, when it started, when this answer was produced, which build it
is running, the last error it recorded, its individual checks, and
then the three things specific to this service:
- boards - each configured board, whether it is online, what the bridge last read from it, and the last error it saw on it.
- adapters - each controller connection, its state, a one-line summary of what it is doing, and when its controller last proved it was alive.
- buffer - where the buffer lives, how many commands are waiting and how many failed, when the oldest waiting one arrived, and the newest failure with its reason. The same figures are given per board, with the count of finished and failed records still kept.
Healthy means all of this, not just that the process is up:
- A configuration was found and it parsed.
- Every configured board is answering, so its points carry good quality rather than stale.
- Every controller connection is serving: the socket or the broker session is up, and no tag, register, or node it needs is failing.
- Nothing is stuck in the command buffer.
Anything short of that is visible in /status before it is visible on
the line. A connection with one bad tag reports as degraded and keeps
serving the other fifty; a connection with no usable link reports as
an error.
Health is what is true now, not what went wrong once
The state a connection reports is derived from the faults happening right now. A fault that somebody fixes stops being reported on the very exchange that proves it fixed, with no restart and no acknowledgement step. The last error text is kept separately, as history, so a healthy connection can still show the text of the last thing that went wrong.
What happens to a command while a board is down
Commands are not fired and forgotten, and they are not lost when a board is unreachable.
- Every command is written down before it is acknowledged. The controller gets its receipt only after the command is persisted, so a command the controller believes was accepted is one the bridge has on disk.
- They are carried out strictly in order, per board. A command cannot overtake the one before it.
- A board that is unreachable is retried. The same command is tried again with a backoff that starts at one second and grows to thirty, for as long as the board is down. When the board comes back, the queue drains in the order it was submitted.
- A restart replays what was in flight. A command that was being sent when the service stopped is put back in the queue and sent again, with a warning in the log naming it.
At-least-once, and what that means for counts
Point 4 is a deliberate trade. If the service dies between sending a command and recording that it succeeded, the two cases are indistinguishable, so the bridge sends it again rather than risk losing it.
For most commands that is harmless. For count injection it is not: a count injected twice is counted twice. This is documented rather than hidden, and it is the reason the buffer volume outlives the container. Where exact counts matter, have the controller compare what it asked for against what came back before it asks again.
The queue has a ceiling, ten thousand commands per board by default, and it counts only the commands still waiting or in flight. A controller that fills it gets a refused receipt, immediately and by name. Nothing is ever silently dropped to make room.
Finished and failed records are kept apart from that queue, ten thousand per board by default, and the oldest go once there are more, so a line injecting counts all day cannot fill the volume. Nothing waiting is ever deleted. A failure old enough to age out is gone from the status counts but not from the log, which is the durable record of it.
What a failed command looks like, and why
A failed command is recorded as failed, counted in /status, and kept
for audit for as long as the buffer retains it. It never blocks the
queue: the commands behind it carry on. Every failure is logged when
it happens as well, and that line is the record that outlives the
buffer.
There are five reasons a command fails, and they behave differently.
1. Unauthorized. The board refused the write because its own Access Control settings do not permit it. This one is never retried: retrying a write the board is configured to refuse would fail identically forever. The log names the endpoint and the setting that refused it.
This is the failure to prevent before commissioning rather than diagnose afterwards. Which board settings have to be right, and for which commands, is in the PLC Bridge Integrator Guide under the Vorne Access Control requirement; get them set before the integrator starts.
2. Rejected. The board understood the command and said no: a reject reason that is not configured on that board, a part id it does not know, a value it will not take. Not retried, and the board's own reason is recorded.
3. Bad response. The board answered, but with something the bridge
could not use. Not retried, and logged as bad_response with what
came back. It is the rarest of the five, and it means the board or its
firmware is behaving unexpectedly rather than that the command was
wrong.
4. Unreachable. The board did not answer, or answered with a server error. This one is retried, with the backoff above, for as long as the board is down. It is the only failure that resolves itself when the line comes back.
5. Capability missing. The board cannot do it: firmware older than the command needs, or a setting the command depends on that the site has not configured, such as the digital input pins for count injection. Not retried, and refused with the reason named.
A sixth case never reaches the board at all. A command the controller is not allowed to send, a payload the model does not accept, or a fault code the site did not map is refused at the door, with a reason on the receipt the controller reads back. Nothing is buffered, and the bridge stays healthy, because a controller asking for something it is not allowed to ask for is ordinary traffic rather than a fault in the service.
Accepted is not the same as done
When a controller gets an accepted receipt, the command is in the
bridge's buffer and will be sent. It does not yet mean the board acted
on it. The buffer counts in /status are where you see the difference
between commands still waiting and commands that failed.
Where the logs are
The bridge logs the way Sync Bridge does, so the same places already being watched cover both.
- Container output. Everything the service writes goes to the container's own log, which is the first place to look.
- A log file, when the deployment sets a log path. It sits in the service's own directory on the plant server.
- The observability stack. Logs are exported through the suite's standard telemetry configuration where the environment has it enabled, so bridge lines land next to everything else.
What you will and will not find there:
- Nothing is swallowed. Anything the service cannot do is an error line that names the board, the command or the value, the endpoint, and the status.
- State changes are logged once per change, not once per poll. A board that has been down for an hour produced one line when it went down and one when it came back, not thirty-six hundred.
- Credentials are never logged. Board logins, broker passwords, and OPC UA account passwords are never written to a log, a status answer, or an error message. A problem with one is reported by the name of the setting, never by its value.