PLC Relay

Configure PLC Relay to read and write PLC tags over Allen-Bradley, Modbus TCP, or Siemens S7.

PLC Relay is an edge container service that provides an HTTP API for reading and writing PLC tags. You configure it through the Deployment Manager UI by selecting a protocol, entering connection details, and defining tags.

PLC Relay is available exclusively for Enterprise customers. Contact the Roboflow sales team to learn more.

Supported Protocols

When adding or editing a PLC Relay service, you select one of three protocols. Each protocol has its own connection settings and tag format.

ProtocolPLC_DRIVERPLCsDefault Port
Allen-Bradley (EtherNet/IP)allen_bradleyCompactLogix, ControlLogix, Micro80044818
Modbus TCPmodbusAny Modbus TCP device502
Siemens S7siemens_s7S7-300, S7-400, S7-1200, S7-1500102

Switching protocols clears all configured tags because tag address formats are not interchangeable between protocols. The UI will prompt for confirmation before applying the change.

Connection Settings

PLC Address

The address format depends on the selected protocol:

  • Allen-Bradley: IP or hostname, optionally followed by /slot (e.g. 192.168.1.100/0) or a full CIP routing path.
  • Modbus TCP: IP or hostname, with optional :port (e.g. 192.168.1.100:502). Also requires a Unit ID (0-255) and Word Order (big or little) for 32-bit values.
  • Siemens S7: IP or hostname, with optional :port (e.g. 192.168.1.100:102). Also requires Rack (0-7) and Slot (0-31).

Simulation Mode

When enabled, PLC Relay uses an in-memory simulator instead of connecting to a real PLC. All API operations work normally, but values are stored in memory. This is useful for testing without hardware.

Tag Configuration

Tags define the PLC data points accessible through the API. Each tag has a name, data type, writable flag, and optional description.

Data Types

TypeDescriptionRange
BOOLBooleantrue / false
INT16-bit signed integer-32,768 to 32,767
DINT32-bit signed integer-2,147,483,648 to 2,147,483,647
REAL32-bit floating pointIEEE 754

Tag Name Formats

Tag names match the PLC program and are case-sensitive.

StyleExample
SimpleTagName
Program-scopedProgram:MainProgram.TagName
Array elementTagName[0]
UDT memberMyUDT.Member

Web Dashboard

PLC Relay includes a built-in web dashboard for monitoring tag values in real time. Once the service is running, access it at http://<device-ip>:8007.

The dashboard also hosts interactive Swagger documentation at /docs and a visual config builder at /static/config-builder.html.

HTTP API

The API reads and writes the configured tags over HTTP, so a pipeline can exchange PLC data without speaking Allen-Bradley EtherNet/IP, Modbus TCP, or Siemens S7 directly. Base URL is http://<device-ip>:8007, and the API is unauthenticated. See Services for the rules shared by all on-device service APIs.

Tags come from the PLC_TAGS environment variable on the service and cannot be created or changed through the API. Only values can be written, and only for tags configured as writable.

Read Before You Trust the Status Code

A request that reaches the service but fails at the PLC returns 200. Check the body:

  • /read returns value: null with error populated.
  • /write returns success: false with error populated.
  • /healthz returns plc_connected: false.

Genuine 4xx responses mean the request itself was wrong: 404 for a tag that is not configured, 403 for a tag configured as read-only, 400 for an empty batch or a duplicate tag name in a write batch, 422 for a body or query parameter that fails validation.

Health and Validation

/healthz also reports the active driver, whether the relay is in live or simulation mode, and the latest tag validation summary. Validation compares each configured tag against the PLC and reports it as ok, not_found, type_mismatch, or not_validated.

Health check

gethttp://device-ip:8007/healthz

Returns service status, PLC connection state, the active driver and mode, and the latest tag validation summary. Returns 200 with plc_connected: false when the PLC is unreachable, so check the field rather than the status code.

Responses
200Service statusapplication/json
statusstringRequired

Service status, healthy or unhealthy

plc_connectedbooleanRequired

Whether the PLC is connected

tag_countintegerRequired

Number of configured tags

plc_driverstring · enumRequired

Active PLC driver

Possible values:allen_bradleymodbussiemens_s7
modestring · enumRequired

Whether the relay is talking to a real PLC or the in-memory simulator

Possible values:livesimulation
plc_ipstring · nullableOptional

Configured PLC address

tag_validationobject · nullableOptional

Latest validation results, null when validation has not run

Show properties
validatedbooleanRequired

Whether validation has been performed

totalintegerRequired

Total number of configured tags

okintegerRequired

Tags that passed validation

not_foundintegerRequired

Tags not found on the PLC

type_mismatchintegerRequired

Tags whose PLC type does not match the configuration

not_validatedintegerRequired

Tags not yet validated

tagsobjectRequired

Per-tag validation details, keyed by tag name

get/healthz
GET /healthz HTTP/1.1
Host: device-ip:8007
Accept: application/json
Response
{
  "status": "healthy",
  "plc_connected": true,
  "tag_count": 3,
  "plc_driver": "allen_bradley",
  "mode": "live",
  "plc_ip": "192.168.1.100/0",
  "tag_validation": {
    "validated": true,
    "total": 3,
    "ok": 3,
    "not_found": 0,
    "type_mismatch": 0,
    "not_validated": 0,
    "tags": {}
  }
}

Re-run validation after a PLC program change or a reconnection:

Re-run tag validation

posthttp://device-ip:8007/validate

Validates every configured tag against the PLC, checking that it exists and that its data type matches the configuration. Useful after a PLC program change or a reconnection. Returns a summary with validated: false and zeroed counts when no validation results are available.

Responses
200Validation summaryapplication/json
validatedbooleanRequired

Whether validation has been performed

totalintegerRequired

Total number of configured tags

okintegerRequired

Tags that passed validation

not_foundintegerRequired

Tags not found on the PLC

type_mismatchintegerRequired

Tags whose PLC type does not match the configuration

not_validatedintegerRequired

Tags not yet validated

tagsobjectRequired

Per-tag validation details, keyed by tag name

503Service not initializedapplication/json
detailstringRequired

Error message

post/validate
POST /validate HTTP/1.1
Host: device-ip:8007
Accept: application/json
Response
{
  "validated": true,
  "total": 1,
  "ok": 1,
  "not_found": 1,
  "type_mismatch": 1,
  "not_validated": 1,
  "tags": {
    "ANY_ADDITIONAL_PROPERTY": "anything"
  }
}

Tag Definitions

Tag names are PLC addresses in the format for the active driver, described in Tag Name Formats.

Get tag definitions

gethttp://device-ip:8007/schema

Returns the tag schema as defined by the PLC_TAGS environment variable.

Responses
200Tag definitionsapplication/json
tagsobject · TagDefinition[]Required
Show properties
namestringRequired

PLC tag address. The format is driver-specific: Allen-Bradley "Program:MainProgram.Tag1", Modbus "holding:100" or "coil:0", Siemens S7 "DB1.DBD0" or "M0.0".

typestring · TagDataType · enumRequired

Tag data type. INT is a 16-bit signed integer (-32,768 to 32,767), DINT a 32-bit signed integer (-2,147,483,648 to 2,147,483,647), and REAL a 32-bit float.

Possible values:BOOLINTDINTREAL
descriptionstringOptional

Human-readable description

Default:
writablebooleanOptional

Whether the tag can be written to

Default: true
countintegerRequired

Number of tags

get/schema
GET /schema HTTP/1.1
Host: device-ip:8007
Accept: application/json
Response
{
  "tags": [
    {
      "name": "text",
      "type": "BOOL",
      "description": "",
      "writable": true
    }
  ],
  "count": 1
}

Read and Write Values

curl "http://<device-ip>:8007/read?tag=Station1.CycleCount"

curl -X POST http://<device-ip>:8007/write \
  -H "Content-Type: application/json" \
  -d '{"name": "Station1.CycleCount", "value": 42}'

Read all tag values

gethttp://device-ip:8007/all_tags

Returns the current value of every configured tag, along with connection state.

Responses
200Current values for all tagsapplication/json
tagsobject · TagValueResponse[]Required
Show properties
namestringRequired
typestring · TagDataType · enumRequired

Tag data type. INT is a 16-bit signed integer (-32,768 to 32,767), DINT a 32-bit signed integer (-2,147,483,648 to 2,147,483,647), and REAL a 32-bit float.

Possible values:BOOLINTDINTREAL
descriptionstringRequired
writablebooleanRequired
valueone ofOptional

Current value, null when the read failed

Show properties
booleanOptional
numberOptional
last_updatedstring · nullableOptional
errorstring · nullableOptional

Read error, null on success

validation_statusstring · enum · nullableOptional
Possible values:oknot_foundtype_mismatchnot_validated
validation_detailstring · nullableOptional
countintegerRequired
plc_connectedbooleanRequired
503Service not initializedapplication/json
detailstringRequired

Error message

get/all_tags
GET /all_tags HTTP/1.1
Host: device-ip:8007
Accept: application/json
Response
{
  "tags": [
    {
      "name": "text",
      "type": "BOOL",
      "description": "text",
      "writable": true,
      "value": true,
      "last_updated": "text",
      "error": "text",
      "validation_status": "ok",
      "validation_detail": "text"
    }
  ],
  "count": 1,
  "plc_connected": true
}

Read a single tag

gethttp://device-ip:8007/read

Reads one tag by name. A read that reaches the service but fails at the PLC returns 200 with error populated and value null, so check error as well as the status code.

Query parameters
tagstringRequired

Name of the tag to read, as configured in PLC_TAGS.

Responses
200Tag valueapplication/json
namestringRequired
typestring · TagDataType · enumRequired

Tag data type. INT is a 16-bit signed integer (-32,768 to 32,767), DINT a 32-bit signed integer (-2,147,483,648 to 2,147,483,647), and REAL a 32-bit float.

Possible values:BOOLINTDINTREAL
descriptionstringRequired
writablebooleanRequired
valueone ofOptional

Current value, null when the read failed

Show properties
booleanOptional
numberOptional
last_updatedstring · nullableOptional
errorstring · nullableOptional

Read error, null on success

validation_statusstring · enum · nullableOptional
Possible values:oknot_foundtype_mismatchnot_validated
validation_detailstring · nullableOptional
404Unknown tagapplication/json
detailstringRequired

Error message

422Request failed validationapplication/json
detailobject · ValidationError[]Optional

One entry in a request-validation failure.

Show properties
locany[]Required

Path to the offending field, for example ["body", "value"].

msgstringRequired
typestringRequired
503Service not initializedapplication/json
detailstringRequired

Error message

get/read
GET /read?tag=text HTTP/1.1
Host: device-ip:8007
Accept: application/json
Response
{
  "name": "Station1.CycleCount",
  "type": "DINT",
  "description": "Total cycle count",
  "writable": true,
  "value": 42,
  "last_updated": "2025-01-15T12:00:00+00:00",
  "error": null,
  "validation_status": "ok",
  "validation_detail": null
}

Write a single tag

posthttp://device-ip:8007/write

Writes one value to a configured writable tag. A write that reaches the PLC and fails there returns 200 with success: false and error populated.

Bodyapplication/json
namestringRequired

Tag name to write

valueone ofRequired

Value to write

Show properties
booleanOptional
numberOptional
Responses
200Write resultapplication/json
namestringRequired
valueone ofOptional

Value written, null when the write failed

Show properties
booleanOptional
numberOptional
successbooleanRequired
errorstring · nullableOptional
403Tag is configured as not writableapplication/json
detailstringRequired

Error message

404Unknown tagapplication/json
detailstringRequired

Error message

422Request failed validationapplication/json
detailobject · ValidationError[]Optional

One entry in a request-validation failure.

Show properties
locany[]Required

Path to the offending field, for example ["body", "value"].

msgstringRequired
typestringRequired
503Service not initializedapplication/json
detailstringRequired

Error message

post/write
POST /write HTTP/1.1
Host: device-ip:8007
Content-Type: application/json
Accept: application/json

{
  "name": "text",
  "value": true
}
Response
{
  "name": "text",
  "value": true,
  "success": true,
  "error": "text"
}

Batch Operations

Both batch endpoints validate as a unit before anything runs, so an unknown tag rejects the whole request rather than returning partial results.

/write_batch additionally rejects a batch that names the same tag twice, since keeping only the last value for a repeated name would silently drop the earlier writes. /read_batch accepts duplicates and returns one result per entry, in the order you sent them.

PLC failures that happen after validation are reported per entry in write_batch's results, with success_count and error_count summarizing the batch.

curl -X POST http://<device-ip>:8007/read_batch \
  -H "Content-Type: application/json" \
  -d '{"tags": ["Station1.PartPresent", "Station1.CycleCount"]}'

Read several tags

posthttp://device-ip:8007/read_batch

Reads several tags in one request. Every name is checked before any read runs, so an unknown tag fails the whole request with 404 rather than returning partial results.

Bodyapplication/json
tagsstring[]Required

Tag names to read

Responses
200Tag valuesapplication/json
tagsobject · TagValueResponse[]Required
Show properties
namestringRequired
typestring · TagDataType · enumRequired

Tag data type. INT is a 16-bit signed integer (-32,768 to 32,767), DINT a 32-bit signed integer (-2,147,483,648 to 2,147,483,647), and REAL a 32-bit float.

Possible values:BOOLINTDINTREAL
descriptionstringRequired
writablebooleanRequired
valueone ofOptional

Current value, null when the read failed

Show properties
booleanOptional
numberOptional
last_updatedstring · nullableOptional
errorstring · nullableOptional

Read error, null on success

validation_statusstring · enum · nullableOptional
Possible values:oknot_foundtype_mismatchnot_validated
validation_detailstring · nullableOptional
countintegerRequired

Number of tags read

400Empty tag listapplication/json
detailstringRequired

Error message

404Unknown tag in the listapplication/json
detailstringRequired

Error message

422Request failed validationapplication/json
detailobject · ValidationError[]Optional

One entry in a request-validation failure.

Show properties
locany[]Required

Path to the offending field, for example ["body", "value"].

msgstringRequired
typestringRequired
503Service not initializedapplication/json
detailstringRequired

Error message

post/read_batch
POST /read_batch HTTP/1.1
Host: device-ip:8007
Content-Type: application/json
Accept: application/json

{
  "tags": [
    "text"
  ]
}
Response
{
  "tags": [
    {
      "name": "text",
      "type": "BOOL",
      "description": "text",
      "writable": true,
      "value": true,
      "last_updated": "text",
      "error": "text",
      "validation_status": "ok",
      "validation_detail": "text"
    }
  ],
  "count": 1
}

Write several tags

posthttp://device-ip:8007/write_batch

Writes several tags in one request. The whole batch is validated first: an empty list, a duplicate tag name, an unknown tag, or a tag that is not writable rejects the request before any write happens. Duplicates are rejected rather than collapsed, since silently keeping the last value for a name would drop the earlier writes.

Individual PLC failures after validation are reported per entry in results, with success_count and error_count summarizing the batch.

Bodyapplication/json
writesobject · WriteBatchItem[]Required
Show properties
namestringRequired
valueone ofRequired
Show properties
booleanOptional
numberOptional
Responses
200Per-write resultsapplication/json
resultsobject · WriteTagResponse[]Required
Show properties
namestringRequired
valueone ofOptional

Value written, null when the write failed

Show properties
booleanOptional
numberOptional
successbooleanRequired
errorstring · nullableOptional
success_countintegerRequired
error_countintegerRequired
400Empty writes list or duplicate tag name in the batchapplication/json
detailstringRequired

Error message

403A tag in the batch is configured as not writableapplication/json
detailstringRequired

Error message

404Unknown tag in the batchapplication/json
detailstringRequired

Error message

422Request failed validationapplication/json
detailobject · ValidationError[]Optional

One entry in a request-validation failure.

Show properties
locany[]Required

Path to the offending field, for example ["body", "value"].

msgstringRequired
typestringRequired
503Service not initializedapplication/json
detailstringRequired

Error message

post/write_batch
POST /write_batch HTTP/1.1
Host: device-ip:8007
Content-Type: application/json
Accept: application/json

{
  "writes": [
    {
      "name": "text",
      "value": true
    }
  ]
}
Response
{
  "results": [
    {
      "name": "text",
      "value": true,
      "success": true,
      "error": "text"
    }
  ],
  "success_count": 1,
  "error_count": 1
}

CLI

The plc-cli tool gives an interactive terminal interface for reading and writing tags. Use it for debugging and quick operations when the web dashboard is not reachable. It is a local client running inside the container against the same HTTP API the dashboard uses.

docker exec -it plc-relay plc-cli
KeyAction
RRead all tag values
TRead a single tag, selected from a list
WWrite a tag, selected from the writable tags
SShow the tag schema
HShow detailed health status
VRun tag validation against the PLC
EnterRefresh the display
QQuit

Environment Variables

The Configure modal writes these for you. Edit them directly only when a deployment is managed by hand. See Update Device Configuration.

VariableDefaultDescription
PLC_DRIVERnoneallen_bradley, modbus, or siemens_s7
PLC_IPnonePLC address in the format for the selected driver
PLC_TAGSnoneTag definitions as JSON. The config builder at /static/config-builder.html generates this
SIMULATION_MODEoffUse the in-memory simulator instead of a real PLC
LOG_LEVELINFOLogging verbosity: DEBUG, INFO, WARNING, or ERROR

Driver-specific settings:

DriverVariableRangeDefault
ModbusMODBUS_UNIT_ID0 to 2551
ModbusMODBUS_WORD_ORDERbig or littlebig
Siemens S7S7_RACK0 to 70
Siemens S7S7_SLOT0 to 311

Connection Monitoring

The device page shows a live PLC Relay status card with the current connection state, the active protocol, and the latest tag values. When the relay cannot reach the PLC, the card displays an unreachable banner.

To be notified when a relay loses connectivity, add a "PLC Disconnected" alert from the device's "Device Alerts" tab. See Set up Device Alerts.

Troubleshooting

SymptomFix
"PLC not connected" (Allen-Bradley)Verify PLC address format (IP/Slot), check port 44818 is reachable
"PLC not connected" (Modbus)Check IP/port (default 502) and verify Unit ID matches the device
"PLC not connected" (Siemens S7)Check IP/port (default 102), rack, and slot values; for S7-1200/1500 enable PUT/GET and disable optimized block access
"Function refused" (Siemens S7)PUT/GET disabled in TIA Portal, or optimized block access enabled on the target DB
REAL value reads as garbage (Modbus)Try the opposite Word Order (big vs. little)
Validation shows NOT_FOUNDCheck the PLC program for the exact tag name (case-sensitive)