Discovering Drivers and their Configuration
This page is for people integrating PLC4X into another tool: a connection dialog, a config form, a validation step in a pipeline, a node in a flow-based editor.
You do not have to hard-code any of it. Every driver ships a description of itself, and PLC4X can hand you that description at runtime: which drivers are on the classpath, which transports each one speaks, and every configuration parameter each combination accepts - with its type, whether it is mandatory, and its default.
This is the same metadata the protocol pages on this site are generated from, so what your tool shows and what the documentation says can never drift apart.
This API is part of PLC4J (Java). PLC4Go, PLC4Py and PLC4C do not expose an equivalent
yet - PLC4Go’s GetMetadata() describes a connection, not a driver.
|
Where it starts
Everything hangs off PlcDriverManager:
import org.apache.plc4x.java.api.PlcDriverManager;
PlcDriverManager driverManager = PlcDriverManager.getDefault();
Drivers are found with Java’s ServiceLoader, so "which drivers do I have" literally means
"which driver jars are on the classpath". If your tool loads plugins into their own classloader,
hand it over explicitly:
import org.apache.plc4x.java.DefaultPlcDriverManager;
PlcDriverManager driverManager = new DefaultPlcDriverManager(myPluginClassLoader);
Which drivers do I have?
Set<String> protocolCodes = driverManager.getProtocolCodes(); (1)
PlcDriver driver = driverManager.getDriver("s7"); (2)
String humanReadable = driver.getProtocolName(); (3)
| 1 | The short codes - the s7 in s7://10.0.0.1. |
| 2 | Throws PlcConnectionException if no driver with that code is on the classpath. |
| 3 | A display name, e.g. Siemens S7 (Basic). Use the code for wiring, the name for humans. |
getProtocolCodes() returns an unordered Set. Sort it before showing it to anyone,
or your dropdown will shuffle itself between runs.
|
If you already have a connection string and just want the driver behind it, use
driverManager.getDriverForUrl(url) instead of parsing the scheme yourself.
With the plc4j-driver-all dependency, the above yields 18 drivers:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Which transports does a driver support?
Everything else lives on PlcDriverMetadata:
import org.apache.plc4x.java.api.metadata.PlcDriverMetadata;
PlcDriverMetadata metadata = driver.getMetadata();
List<String> transports = metadata.getSupportedTransportCodes(); (1)
Optional<String> preferred = metadata.getDefaultTransportCode(); (2)
boolean canDiscover = metadata.isDiscoverySupported(); (3)
| 1 | e.g. [tcp, tls, tls-psk, udp, test] for modbus-tcp. |
| 2 | What the driver uses when the connection string names no transport. Pre-select this. |
| 3 | Whether the driver can search the network for devices - use it to enable a "Scan" button. |
Two things to handle:
-
Filter out
test. It is an in-memory transport used by PLC4X’s own unit tests. It shows up in the list, but it is not something a user can pick. The protocol pages on this site filter it out for exactly this reason. -
The list can be empty.
simulatedreports no transports at all, because it never talks to anything. Do not assume at least one.
List<String> selectable = metadata.getSupportedTransportCodes().stream()
.filter(code -> !"test".equals(code))
.toList();
What is the configuration for a driver?
import org.apache.plc4x.java.api.metadata.OptionMetadata;
Optional<OptionMetadata> protocolOptions =
metadata.getProtocolConfigurationOptionMetadata();
It is an Optional because a driver may declare no options of its own - simulated has none.
What is the configuration for a transport of that driver?
Ask for the pair, never for the transport alone:
Optional<OptionMetadata> transportOptions =
metadata.getTransportConfigurationOptionMetadata("tcp");
A driver’s transport configuration is exposed per driver-and-transport combination, so always query it through the driver you are configuring rather than caching one table per transport code.
What parameters does a configuration define?
OptionMetadata gives you the list, and Option describes each entry:
import org.apache.plc4x.java.api.metadata.Option;
List<Option> all = protocolOptions.get().getOptions();
List<Option> mandatory = protocolOptions.get().getRequiredOptions(); // convenience filter
| Method | Returns | What to do with it |
|---|---|---|
|
|
The parameter name as it appears in a connection string. |
|
|
Pick the right widget and validate input. See below. |
|
|
Mark the field mandatory and block submission while it is empty. |
|
|
Pre-fill the field. Empty means there is no default. |
|
|
Tooltip or help text. |
|
|
Render as a password field and keep it out of logs. |
|
|
PLC4X version that introduced the option, e.g. |
OptionType is a small enum - BOOLEAN, INT, LONG, FLOAT, DOUBLE, STRING, FILE,
STRUCT:
-
FILEis a path - offer a file picker (used for keystores and certificates). -
STRUCTis a composite value the driver parses from its string form, such as the ADStarget-ams-net-id. Treat it as free text and let the driver validate it; the syntax is documented on the driver’s own protocol page.
Most options are optional and defaulted. ads is the one driver in the default set that really
demands input - four mandatory parameters, two of them STRUCT:
target-ams-net-id STRUCT required
target-ams-port INT required
source-ams-net-id STRUCT required
source-ams-port INT required
Turning options back into a connection string
The keys are used verbatim as query parameters, with one rule: transport options are namespaced with their transport code, protocol options are not.
s7://10.0.0.1?pdu-size=2048&cotp.local-rack=1&cotp.remote-slot=2
^ ^ ^
| | └── transport option, prefixed with "cotp."
| └── transport option
└── protocol option, no prefix
So when you build the string, prefix exactly the values that came from
getTransportConfigurationOptionMetadata(…):
String key = (transportCode == null) ? option.getKey()
: transportCode + "." + option.getKey();
Anything marked isSecret() ends up in that same string. PLC4X redacts those parameters in
its own log output - do the same before you log, display or persist a connection string.
|
A complete example
This program prints the full catalogue - every driver, its transports, and every option with its type, whether it is mandatory, and its default:
import org.apache.plc4x.java.api.PlcDriver;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.metadata.Option;
import org.apache.plc4x.java.api.metadata.OptionMetadata;
import org.apache.plc4x.java.api.metadata.PlcDriverMetadata;
import java.util.List;
import java.util.TreeSet;
public class DriverCatalog {
public static void main(String[] args) throws Exception {
PlcDriverManager driverManager = PlcDriverManager.getDefault();
for (String protocolCode : new TreeSet<>(driverManager.getProtocolCodes())) {
PlcDriver driver = driverManager.getDriver(protocolCode);
PlcDriverMetadata metadata = driver.getMetadata();
System.out.println("== " + protocolCode + " (" + driver.getProtocolName() + ")");
System.out.println(" discovery supported : " + metadata.isDiscoverySupported());
System.out.println(" default transport : "
+ metadata.getDefaultTransportCode().orElse("<none>"));
// "test" is an in-memory transport used by PLC4X's own unit tests.
List<String> transports = metadata.getSupportedTransportCodes().stream()
.filter(code -> !"test".equals(code))
.toList();
System.out.println(" transports : " + transports);
metadata.getProtocolConfigurationOptionMetadata()
.ifPresent(options -> print(" protocol options", options, null));
for (String transportCode : transports) {
metadata.getTransportConfigurationOptionMetadata(transportCode)
.ifPresent(options ->
print(" transport options (" + transportCode + ")", options, transportCode));
}
System.out.println();
}
}
private static void print(String heading, OptionMetadata metadata, String prefix) {
System.out.println(heading + ":");
for (Option option : metadata.getOptions()) {
// In a connection string, transport options are namespaced with the transport code.
String key = (prefix == null) ? option.getKey() : prefix + "." + option.getKey();
System.out.printf(" %-34s %-8s %-9s %-7s %s%n",
key,
option.getType(),
option.isRequired() ? "required" : "optional",
option.isSecret() ? "secret" : "",
option.getDefaultValue().map(v -> "default=" + v).orElse(""));
}
}
}
Its output for the ADS driver:
== ads (Beckhoff TwinCat ADS)
discovery supported : true
default transport : tcp
transports : [tcp]
protocol options:
target-ams-net-id STRUCT required
target-ams-port INT required
source-ams-net-id STRUCT required
source-ams-port INT required
request-timeout-ms INT optional default=4000
max-data-type-table-depth INT optional default=20
load-symbol-and-data-type-tables BOOLEAN optional default=true
transport options (tcp):
tcp.connect-timeout-ms INT optional default=5000
tcp.read-timeout-ms INT optional default=0
tcp.write-timeout-ms INT optional default=0
tcp.no-delay BOOLEAN optional default=true
tcp.keep-alive BOOLEAN optional default=false
tcp.send-buffer-size INT optional default=81920
tcp.receive-buffer-size INT optional default=81920
tcp.local-address STRING optional
tcp.local-port INT optional default=0
To run it, you need a driver on the classpath. plc4j-driver-all gives you every one of them:
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-all</artifactId>
<version>1.0.0</version>
</dependency>
Things to watch out for
-
getProtocolCodes()is an unorderedSet- sort before displaying. -
Filter the
testtransport out of anything a user sees. -
A driver may report no transports (
simulated) and no options (simulatedagain). Every metadata accessor that can be absent returns anOptionalor an empty list - none of them returnnull, but none of them promise content either. -
Ask for transport options per driver-and-transport pair, not per transport code.
-
Treat
isSecret()options as credentials everywhere: masked in the UI, redacted in logs.