Modbus TCP / UDP Treiber
Überblick
PLC-Kommunikation auf Basis des Modbus-Industriestandards (Modbus-IDA / IEC 61158-6-12).
| Punkt | Wert |
|---|---|
opc_type | MODBUS (TCP) / MODBUSUDP (UDP) |
| Implementierungsklasse | plantpulse.driver.protocol.modbus.ModbusTCPDriver / ModbusUDPDriver |
| Bibliothek | Eigene native Implementierung (plantpulse-plc-protocol mit ModbusTcp / ModbusUdp) |
| read | ✅ |
| write | ✅ (coil / holding-register) |
| Sicherheit | keine (durch den Modbus-Standard bedingt) |
Sowohl Modbus TCP als auch UDP wurden von der alten PLC4j-Delegation befreit und als native ModbusTcp / ModbusUdp (gemeinsame Basis AbstractNativeModbusDriver) in plantpulse-plc-protocol neu geschrieben — nach demselben Muster wie FINS / MELSEC / AB-ETH. UDP arbeitet als MBAP-over-UDP (ein Datagramm = eine ADU); Adressierung, Format und Write-Layer sind vollständig identisch mit TCP. Die Adresssyntax (holding-register:N[:TYPE]) bleibt unverändert in der PLC4X-modbus4x-Notation erhalten.
OPC-Registrierungsformular
| Feld | Bedeutung | Beispiel |
|---|---|---|
opc_agent_ip | IP des Modbus-Slaves | 192.168.0.50 |
opc_agent_port | Modbus-Port | 502 (TCP-Standard) |
timecycle | Abfrageintervall (ms) | 1000 |
options.request-timeout | Anforderungs-Timeout (ms) | 5000 |
Bei Modbus TCP werden Slaves üblicherweise über IP+Port unterschieden, daher ist unit-identifier (Alias unit-id) standardmäßig 1.
Befinden sich hinter einem Seriell-TCP-Gateway mehrere Slaves, wird ein Wert von 1–247 angegeben.
Tag-plc_address-Format
Adressnotation (<area>:<address> + :<datatype> + [<count>]) — die PLC4X-modbus4x-Syntax bleibt unverändert erhalten.
Adressen sind 1-basiert (Umrechnung auf 0-basiert auf dem Wire, holding-register:5 → Wire-Register 4).
| Notation | Bedeutung |
|---|---|
holding-register:1 | Holding Register 1 (16 Bit) |
holding-register:1:UINT | 16 Bit unsigned |
holding-register:1:DINT | 32 Bit signed (2 Register) |
holding-register:1:REAL | 32 Bit float (2 Register) |
holding-register:100:STRING[10] | ASCII-Zeichenkette aus 100–109 |
coil:1 | Coil, 1 Bit |
discrete-input:1 | Discrete Input, 1 Bit |
input-register:1 | Input Register, 16 Bit |
Detaillierte Notationsregeln siehe PLC4j-Modbus-Dokumentation.
Zuordnung data_type / format
Der format des Edge wird direkt auf den Type-Suffix von PLC4j abgebildet.
edge data_type | edge format | PLC4j-addr-Zuordnung | Hinweis |
|---|---|---|---|
| Boolean | (empty) / BOOL | coil:N oder :BOOL | 1 Bit |
| Integer / Short | (empty) | :INT | 16 Bit signed |
| UInteger | UI / UWORD | :UINT | 16 Bit unsigned |
| Integer (32) | DW | :DINT | 32 Bit signed (2 Reg.) |
| UInteger (32) | UDW | :UDINT | 32 Bit unsigned |
| Float | REAL | :REAL | 32 Bit IEEE 754 |
| Double | LREAL | :LREAL | 64 Bit IEEE 754 |
| String | STR[N] | :STRING[N] | N Zeichen |
Beginnt format mit STR[N], REAL[N] oder UDINT[N], wird der Wert als Array behandelt (readWordValues).
Write (nativ — gemeinsam für TCP/UDP)
ModbusTCPDriver / ModbusUDPDriver unterstützen Schreibvorgänge (isWriteSupported()=true):
coil:N→ Single Coil Write (Boolean).holding-register:N[:TYPE]→ Register-Write je nach Datentyp.REAL/DINTverwenden 2 Register in MSWord-first-Reihenfolge, alle anderen ein einzelnes 16-Bit-Register. Der Wert wird vom Aufrufer alsProtocolAddress.valueübergeben.discrete-input/input-registersind protokollbedingt read-only — Schreiben nicht möglich.
Häufige Fehler + Abhilfe
| Meldung / Symptom | Ursache | Abhilfe |
|---|---|---|
PLC_READ_TIMEOUT_EXCEPTION | Verzögerte oder fehlende Antwort des Slaves | Kabel/Firewall/Port (502) prüfen. request-timeout erhöhen |
PLC_READ_RUNTIME_EXCEPTION: Invalid PLC4j address | Tippfehler in der Adressnotation | holding-register:N-Format prüfen, auf fehlendes : achten |
| Es wird nur 0 zurückgegeben | Falscher Registertyp (Input vs. Holding) | Im Slave-Handbuch prüfen, ob FC 03/04/01/02 zutrifft |
| 32-Bit-Wert vertauscht | Byte-/Word-Reihenfolge | Ist der Slave Little-Endian, format z. B. auf :UDINT_LSWORD_FIRST ändern (PLC4j-Option) |
curl-Registrierungsbeispiel (Modbus TCP)
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_MB_HVAC",
"opc_type": "MODBUS",
"opc_name": "HVAC Slave",
"opc_agent_ip": "192.168.0.50",
"opc_agent_port": "502",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "request-timeout": "5000" },
"tag_list": [
{
"tag_id": "OPC_MB_HVAC_TAG_00001",
"tag_name": "Temperature",
"plc_address": "holding-register:1:REAL",
"data_type": "Float",
"format": "REAL"
},
{
"tag_id": "OPC_MB_HVAC_TAG_00002",
"tag_name": "Status",
"plc_address": "coil:1",
"data_type": "Boolean"
}
]
}'
Bei UDP auf opc_type: "MODBUSUDP" ändern; der Port ist üblicherweise 502 oder richtet sich nach der Slave-Konfiguration.
Wert lesen:
curl -s http://<edge-host>/api/v1/tag/OPC_MB_HVAC_TAG_00001/value | jq
Beispielsammlung (nach Datentyp)
Coil / Discrete (Boolean)
data_type | format | plc_address Beispiel | Hinweis |
|---|---|---|---|
Boolean | (empty) | coil:1 | FC 01 (Read Coils) |
Boolean | (empty) | discrete-input:1 | FC 02 (Read Discrete Inputs) |
Boolean | BIN[3] | holding-register:1 | Word lesen, dann Bit 3 extrahieren |
16-Bit-Ganzzahl
data_type | format | plc_address Beispiel | Hinweis |
|---|---|---|---|
Integer | (empty) | holding-register:1 | signed INT 16 |
Integer | (empty) | holding-register:1:INT | identisch (PLC4j-Notation) |
Integer | UI | holding-register:1:UINT | unsigned 16 |
Integer | (empty) | input-register:1 | Input Register (FC 04) |
32-Bit-Ganzzahl
data_type | format | plc_address Beispiel | Hinweis |
|---|---|---|---|
Integer | DW | holding-register:1:DINT | signed 32 (2 Register) |
Integer | UDW | holding-register:1:UDINT | unsigned 32 |
Integer | UDINT | holding-register:1 | 32 Bit durch explizites Format |
Gleitkommazahlen
data_type | format | plc_address Beispiel | Hinweis |
|---|---|---|---|
Float | REAL | holding-register:1 | 32 Bit float (2 Register) |
Float | REAL | holding-register:1:REAL | identische Notation |
Double | LREAL | holding-register:1:LREAL | 64 Bit double (4 Register) |
Zeichenketten
data_type | format | plc_address Beispiel | Hinweis |
|---|---|---|---|
String | STR[5] | holding-register:10 | 5 Words = 10 Byte ASCII |
String | STR[16] | holding-register:100:STRING[16] | PLC4j-Notation |
Einsatz von Formeln
| Zweck | data_type | fomula | Hinweis |
|---|---|---|---|
| Ganzzahl-Rohwert → Dezimalwert | Float | ${VALUE}*0.1 | Skalierung von Druck-/Temperatur-Rohwerten |
| Word-Swap-Korrektur | Float | ${VALUE}*1.0 | Für Slaves mit abweichender Byte-Reihenfolge bei 32-Bit-float. Alternative: :REAL_LSWORD_FIRST |
| RH/Temp-Kombination (zwei Werte in einem Word) | Float | ${VALUE}/256 | nur die oberen 8 Bit |
Umfassendes curl-Beispiel
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_MB_FULL",
"opc_type": "MODBUS",
"opc_name": "Modbus Full",
"opc_agent_ip": "192.168.0.50",
"opc_agent_port": "502",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "request-timeout": "5000" },
"tag_list": [
{"tag_id":"OPC_MB_FULL_T01", "tag_name":"Coil1", "plc_address":"coil:1", "data_type":"Boolean"},
{"tag_id":"OPC_MB_FULL_T02", "tag_name":"DI1", "plc_address":"discrete-input:1", "data_type":"Boolean"},
{"tag_id":"OPC_MB_FULL_T03", "tag_name":"HRSigned", "plc_address":"holding-register:1", "data_type":"Integer"},
{"tag_id":"OPC_MB_FULL_T04", "tag_name":"HRUnsigned", "plc_address":"holding-register:2:UINT", "data_type":"Integer", "format":"UI"},
{"tag_id":"OPC_MB_FULL_T05", "tag_name":"DInt", "plc_address":"holding-register:3:DINT", "data_type":"Integer", "format":"DW"},
{"tag_id":"OPC_MB_FULL_T06", "tag_name":"Temp", "plc_address":"holding-register:5:REAL", "data_type":"Float", "format":"REAL"},
{"tag_id":"OPC_MB_FULL_T07", "tag_name":"BatchName", "plc_address":"holding-register:10:STRING[10]", "data_type":"String", "format":"STR[10]"},
{"tag_id":"OPC_MB_FULL_T08", "tag_name":"PressScale", "plc_address":"holding-register:7:REAL", "data_type":"Float", "format":"REAL", "fomula":"${VALUE}*0.1"}
]
}'