| name | plc4x |
| description | Expert guidance for Apache PLC4X industrial protocols library including Siemens S7, Modbus, OPC-UA, integration with Apache NiFi, connection strings, and field addressing. Use this when working with industrial automation and PLC communication. |
| tags | plc4x, industrial, iot, scada, automation |
| color | green |
Apache PLC4X Expert Skill
You are an expert in Apache PLC4X, a universal protocol adapter for industrial automation that allows communication with PLCs using various protocols.
Core Concepts
Supported Protocols
- S7 (Siemens): S7-300, S7-400, S7-1200, S7-1500
- Modbus: TCP, RTU, ASCII
- OPC-UA: Industrial standard protocol
- Beckhoff ADS: TwinCAT PLCs
- EtherNet/IP: Allen-Bradley PLCs
- KNX: Building automation
- BACnet: Building automation
Architecture
Application Layer
↓
PLC4X API (Protocol-independent)
↓
Protocol Drivers (S7, Modbus, OPC-UA, etc.)
↓
Network Layer (TCP/IP, Serial, etc.)
↓
PLC Device
Connection Strings
Format
protocol://host:port/options?parameter1=value1¶meter2=value2
Examples by Protocol
Siemens S7
s7://192.168.1.100 # Default (rack=0, slot=0)
s7://192.168.1.100:102 # Custom port
s7://192.168.1.100?rack=0&slot=2 # S7-1200/1500 (slot 2)
s7://192.168.1.100?controller-type=S7_400 # Explicit controller type
Modbus TCP
modbus-tcp://192.168.1.200 # Default port 502
modbus-tcp://192.168.1.200:502 # Explicit port
modbus-tcp://192.168.1.200?unit-identifier=1 # Slave/unit ID
OPC-UA
opcua:tcp://192.168.1.50:4840 # Standard OPC-UA
opcua:tcp://192.168.1.50:4840?discovery=true # Enable discovery
opcua:tcp://user:pass@192.168.1.50:4840 # With authentication
EtherNet/IP
eip://192.168.1.150 # Allen-Bradley PLC
Field Addressing
Siemens S7
# Data Blocks (DB)
DB1.DBB0 # Byte at DB1, offset 0
DB1.DBW2 # Word (2 bytes) at DB1, offset 2
DB1.DBD4 # Double word (4 bytes) at DB1, offset 4
DB1.DBX0.0 # Bit 0 at DB1, byte 0
# Memory Areas
%I0.0 # Input bit 0.0
%IB0 # Input byte 0
%IW0 # Input word 0
%ID0 # Input double word 0
%Q0.0 # Output bit 0.0
%QB0 # Output byte 0
%QW0 # Output word 0
%QD0 # Output double word 0
%M0.0 # Memory bit 0.0
%MB0 # Memory byte 0
%MW0 # Memory word 0
%MD0 # Memory double word 0
# Data Types
:BOOL # Boolean
:BYTE # Byte (8 bits)
:INT # Integer (16 bits)
:DINT # Double Integer (32 bits)
:REAL # Real (32-bit float)
:STRING(10) # String with max length
Modbus
# Function Codes
holding-register:0 # Read Holding Register (FC03)
input-register:0 # Read Input Register (FC04)
coil:0 # Read Coil (FC01)
discrete-input:0 # Read Discrete Input (FC02)
# Extended addressing
holding-register:0:INT # As 16-bit integer
holding-register:0:REAL # As 32-bit float (2 registers)
holding-register:0[10] # Array of 10 registers
OPC-UA
# Node IDs
ns=2;s=MyVariable # String identifier
ns=2;i=1001 # Numeric identifier
ns=3;g=09087e75-8e5e-499b-954f-f2a9603db28a # GUID
Java API Usage
Basic Read Example
// Connect to PLC
try (PlcConnection connection = new PlcDriverManager()
.getConnection("s7://192.168.1.100")) {
// Build read request
PlcReadRequest request = connection.readRequestBuilder()
.addTagAddress("temperature", "DB1.DBD0:REAL")
.addTagAddress("pressure", "DB1.DBD4:REAL")
.addTagAddress("running", "DB1.DBX8.0:BOOL")
.build();
// Execute read
PlcReadResponse response = request.execute().get();
// Get values
float temp = response.getFloat("temperature");
float press = response.getFloat("pressure");
boolean running = response.getBoolean("running");
System.out.println("Temperature: " + temp);
System.out.println("Pressure: " + press);
System.out.println("Running: " + running);
}
Write Example
try (PlcConnection connection = new PlcDriverManager()
.getConnection("s7://192.168.1.100")) {
PlcWriteRequest request = connection.writeRequestBuilder()
.addTagAddress("setpoint", "DB1.DBD10:REAL", 75.5f)
.addTagAddress("enable", "DB1.DBX12.0:BOOL", true)
.build();
PlcWriteResponse response = request.execute().get();
if (response.getResponseCode("setpoint") == PlcResponseCode.OK) {
System.out.println("Write successful");
}
}
Subscription (Polling)
try (PlcConnection connection = new PlcDriverManager()
.getConnection("s7://192.168.1.100")) {
PlcSubscriptionRequest request = connection.subscriptionRequestBuilder()
.addCyclicTagAddress("sensor1", "DB1.DBW0:INT", Duration.ofSeconds(1))
.addCyclicTagAddress("sensor2", "DB1.DBW2:INT", Duration.ofSeconds(1))
.build();
PlcSubscriptionResponse response = request.execute().get();
response.getSubscriptionHandle("sensor1").register(event -> {
int value = event.getInteger("sensor1");
System.out.println("Sensor1: " + value);
});
}
NiFi Integration
PLC4X Processors in NiFi
GetPLC4X (Read from PLC)
Properties:
- PLC Connection String: s7://192.168.1.100
- Fields to Read: temperature:DB1.DBD0:REAL,pressure:DB1.DBD4:REAL,status:DB1.DBX8.0:BOOL
- Polling Interval: 1 sec
- Timeout: 5 sec
Output:
- FlowFile per polling cycle with attributes:
temperature=25.5
pressure=1.2
status=true
PutPLC4X (Write to PLC)
Properties:
- PLC Connection String: s7://192.168.1.100
- Fields to Write: setpoint:DB1.DBD10:REAL,enable:DB1.DBX12.0:BOOL
Input FlowFile Attributes:
setpoint=75.5
enable=true
Result: Values written to PLC
NiFi Flow Example
GetPLC4X (Read sensor data)
↓
RouteOnAttribute (Filter by status)
↓
UpdateAttribute (Add timestamp)
↓
JoltTransformJSON (Format data)
↓
PublishKafka (Send to analytics)
Expression Language in NiFi
# Read PLC value and use in flow
${plc.temperature} # Access temperature attribute
${plc.temperature:toNumber()} # Convert to number
${plc.temperature:gt(50)} # Compare value
Docker Deployment
Standalone PLC4X Application
FROM eclipse-temurin:11-jre
COPY plc4x-app.jar /app/
COPY plc4x-drivers/ /app/drivers/
CMD ["java", "-jar", "/app/plc4x-app.jar"]
With NiFi
services:
nifi:
image: apache/nifi:latest
volumes:
- ./nifi-plc4x-nar:/opt/nifi/nifi-current/lib/nifi-plc4x-nar.nar
environment:
- NIFI_WEB_HTTP_PORT=8080
networks:
- plc-network
networks:
plc-network:
driver: bridge
Data Type Mapping
S7 to Java
| S7 Type | PLC4X Type | Java Type |
|---|---|---|
| BOOL | BOOL | boolean |
| BYTE | USINT | byte |
| WORD | UINT | short |
| DWORD | UDINT | int |
| INT | INT | short |
| DINT | DINT | int |
| REAL | REAL | float |
| STRING | STRING | String |
| TIME | TIME | Duration |
| DATE | DATE | LocalDate |
Modbus to Java
| Modbus | PLC4X Type | Java Type |
|---|---|---|
| Coil | BOOL | boolean |
| Discrete Input | BOOL | boolean |
| Holding Register | UINT | short |
| Input Register | UINT | short |
| 2x Holding Reg | REAL | float |
Best Practices
Connection Management
// Use connection pooling for multiple requests
PlcConnectionManager manager = new CachedPlcConnectionManager();
try (PlcConnection connection = manager.getConnection(connectionString)) {
// Reuse connection
}
Error Handling
try {
PlcReadResponse response = request.execute().get(5, TimeUnit.SECONDS);
for (String tagName : response.getTagNames()) {
if (response.getResponseCode(tagName) == PlcResponseCode.OK) {
// Process value
} else {
// Handle error
System.err.println("Error reading " + tagName + ": " +
response.getResponseCode(tagName));
}
}
} catch (TimeoutException e) {
System.err.println("PLC read timeout");
} catch (ExecutionException e) {
System.err.println("PLC communication error: " + e.getMessage());
}
Performance Optimization
- Batch reads: Request multiple tags in one call
- Connection pooling: Reuse connections
- Appropriate polling intervals: Don't poll faster than needed
- Use subscriptions: More efficient than polling for frequent updates
Troubleshooting
Common Issues
| Issue | Solution |
|---|---|
| Connection timeout | Check IP, port, firewall, PLC rack/slot |
| Access denied | Verify PLC security settings, PUT/GET enabled |
| Invalid address | Check field addressing syntax, data block exists |
| Data type mismatch | Verify PLC data type matches field specification |
| Performance issues | Reduce polling frequency, batch requests |
Logging
<!-- logback.xml -->
<logger name="org.apache.plc4x" level="DEBUG"/>
<logger name="org.apache.plc4x.java.s7" level="TRACE"/>
Network Diagnostics
# Test TCP connection to PLC
nc -zv 192.168.1.100 102
# Monitor network traffic
tcpdump -i eth0 host 192.168.1.100 -w plc-traffic.pcap
# Analyze with Wireshark
wireshark plc-traffic.pcap
Siemens S7 Specific
Controller Types
- S7_300: Rack 0, Slot 2
- S7_400: Rack 0, Slot 3
- S7_1200: Rack 0, Slot 1
- S7_1500: Rack 0, Slot 1
Configuration Requirements
- Enable PUT/GET in PLC configuration
- Disable block protection for accessed DBs
- Configure network (IP, subnet, gateway)
- Allow external access in security settings
TIA Portal Setup
1. Open PLC Properties → Protection & Security
2. Connection mechanisms:
✓ Permit access with PUT/GET
3. Compile and download to PLC