MODBUS TCP
Konzept
Der MODBUS-Treiber liest und schreibt Register- und Bitbereiche. Eine Funktion in *READ bzw. *WRITE beschreibt einen zusammenhängenden Block (Funktionscode, Startadresse, Anzahl). Die Formel der Funktion interpretiert den Block über Registerindex + Datentyp.
PROTOCOL beginnt mit MODBUS TCP (z. B. MODBUS TCP (DDF)).
Parameter (*GENERAL)
| Key | Default | Beschreibung |
|---|---|---|
CONNECTION |
– | IPADDRESS |
IPADDRESS |
– | wird vom Benutzer gesetzt |
PORT |
502 | TCP-Port |
SLAVE |
– | Unit-ID bzw. Liste |
SLAVESMAX |
0 | > 0 für mehrere Unit-IDs über eine Verbindung |
TIMEOUT |
10000 ms | Antwort-Timeout |
WAIT_AFTER_CONNECT |
2 s | Wartezeit nach dem Verbindungsaufbau |
MIN_PAUSE |
50 ms | Pause zwischen Telegrammen |
WORDORDER |
– | BIG_ENDIAN / LITTLE_ENDIAN (Reihenfolge der 16-Bit-Wörter bei 32/64-Bit-Werten) |
BYTEORDER |
– | BIG_ENDIAN / LITTLE_ENDIAN (Bytes innerhalb eines Worts) |
ERROR_HANDLING |
– | FLUSH_OR_RECONNECT_ON_ERROR, NOP_ON_ERROR, FLUSH_OR_CLOSE_ON_ERROR |
Spalten *READ / *WRITE
| Spalte | Beschreibung |
|---|---|
ALIAS |
Name des Blocks |
FUNCTION |
FC1 Coils lesen, FC2 Discrete Inputs, FC3 Holding Register, FC4 Input Register, FC5 Single Coil schreiben, FC6 Single Register schreiben, FC15 Multiple Coils, FC16 Multiple Register. Ohne FCx gilt FC3. |
ADDRESS |
Startadresse (dezimal oder 0x…) |
COUNT |
Anzahl Register/Bits, max. 200 |
POLLING |
Intervall in ms (0 = nur bei .F) |
SLAVE |
optional: abweichende Unit-ID für diese Funktion |
FORMULA |
Formel |
Zugriff in Formeln
<ALIAS>.<regindex>.<TYP>
regindex ist der Registerindex relativ zu ADDRESS, beginnend bei 0.
| Typ | Breite | Beschreibung |
|---|---|---|
| (ohne) | 16 Bit | Rohwert des Registers |
WORD / UINT |
16 Bit | vorzeichenlos |
INT |
16 Bit | vorzeichenbehaftet |
DWORD / DUINT |
32 Bit | vorzeichenlos (2 Register) |
DINT |
32 Bit | vorzeichenbehaftet |
LWORD / LUINT |
64 Bit | vorzeichenlos (4 Register) |
LINT |
64 Bit | vorzeichenbehaftet |
FLOAT |
32 Bit | IEEE754 |
DOUBLE |
64 Bit | IEEE754 |
ASCII.<len> |
len ≤ 20 | Zeichenkette |
HEX.<len> |
– | Hex-Darstellung |
.ISMAX |
Zusatz | prüft, ob der Wert dem Maximalwert des Typs entspricht (typisch „nicht verfügbar“) |
Weitere Zugriffe: <ALIAS>.ADDRESS und <ALIAS>.COUNT (r/w, damit lassen sich Adressen dynamisch ändern), dazu die allgemeinen Suffixe .Q, .F, .EC usw.
Schreiben: <ALIAS>.<idx>.<TYP> := wert; füllt den Sendepuffer, <ALIAS>.F := 1; löst das Senden aus.
Schritt für Schritt
- Registerliste des Herstellers beschaffen: Adresse, Funktionscode, Datentyp, Skalierung, Wort-/Bytereihenfolge.
*GENERALanlegen: Metadaten,PROTOCOL;MODBUS TCP (DDF),PORT,TIMEOUT,WORDORDER,BYTEORDER.- Zusammenhängende Register zu Lese-Blöcken zusammenfassen (≤ 200 Register je Block, Lücken sind erlaubt).
- Je Block eine Zeile in
*READmit einer Formel, dieIF <A>.Q THEN … ENDIF;prüft und Items befüllt. - Schreibbare Werte: eine
*WRITE-Funktion mitPOLLING0 anlegen und vom Item perWFORMULAsetzen (…:=x; <A>.F:=1;). *ITEMmit Alias, Einheit und Typ ergänzen.- Test:
DEBUG(<A>.Q, <A>.EC);und die Rohwerte prüfen. Danach die Debugausgaben entfernen.
Realer Auszug: Keba KeContact P30 c-series
Datei Keba.Wallbox.MODBUS TCP (DDF).KeContact P30 c-series.1(0x0A00003300010100).csv (gekürzt, leere Füllspalten am Zeilenende entfernt):
*GENERAL;
;DEVICE;KeContact P30 c-series;
;MANUFACTURER;Keba;
;TYPE;Wallbox;
;PROTOCOL;MODBUS TCP (DDF);
;VERSION_NR;1;
;MODEL_NR;1;
;ID;0x0A00003300010100;
;CONNECTION;IPADDRESS;0:SERIALPORT,1:SERIALPORT+,2:IPADDRESS,3:DOMAIN;
;PORT;502;
;SLAVE;1;
;SLAVESMAX;1;
;TIMEOUT;5000;ms;
;WAIT_AFTER_CONNECT;2;s;
;MIN_PAUSE;50;ms;
;WORDORDER;BIG_ENDIAN;
;BYTEORDER;BIG_ENDIAN;
;ERROR_HANDLING;FLUSH_OR_RECONNECT_ON_ERROR;FLUSH_OR_RECONNECT_ON_ERROR;NOP_ON_ERROR; FLUSH_OR_CLOSE_ON_ERROR;
*READ;ALIAS;FUNCTION;ADDRESS;COUNT;POLLING;FORMULA;
;GetCps;FC3;1000;2;1000;"""
IF GetCps.Q THEN
IF GetCps.0.DWORD == 3 THEN
ITEM.CPS:= 67;
ELSE IF GetCps.0.DWORD == 4 THEN
ITEM.CPS:= 69;
ELSE IF (ITEM.CABLE>=5) && (GetCps.0.DWORD>0) && (GetCps.0.DWORD!=4) THEN
ITEM.CPS:=66;
ELSE IF GetCps.0.DWORD == 1 THEN
ITEM.CPS:= 65;
ELSE
ITEM.CPS:= 70;
ENDIF;
ENDIF;
ITEM.QUALITY:= GetCps.Q;
ITEM.TIMESTAMP:= GetCps.T;
""";
;GetI1;FC3;1008;2;1000;"""
IF GetI1.Q THEN
ITEM.CURRENT_L1:= GetI1.0.DWORD/1000;
ENDIF;
""";
;GetPower;FC3;1020;2;1000;"""
IF GetPower.Q THEN
ITEM.POWER:= GetPower.0.DWORD/1000000;
ENDIF;
""";
;GetRfid;FC3;1500;2;1000;"""
IF GetRfid.Q THEN
IF ISEQUAL(GetRfid.0.HEX.4,'00000000')>0 THEN
ITEM.RFID:= '';
ELSE
ITEM.RFID:= GetRfid.0.HEX.4;
ENDIF;
ENDIF;
""";
*WRITE;ALIAS;FUNCTION;ADDRESS;COUNT;FORMULA;
;SetCurrent;FC6;5004;1;"""
SetCurrent.F:=0;
""";
;EnCharging;FC6;5014;1;"""
EnCharging.F:=0;
""";
*GROUP;ID;ALIAS;NAME;
;0;EMOBIL;EMOBIL;
*ITEM;ALIAS;NAME;VISIBILITY;ID;UNIT;TYPE;DIR;DEFAULT;WFORMULA;RFORMULA;POLLING;COMMENT;
;CPS;Car state;;0;65= A (no car), 66= B (car connected), 67= C (charging), …;int;IN;
;CURRENT_L1;Ampere L1;;4;A;real;IN;
;POWER;Power Total;;10;kW;real;IN;
;POWER_UNIT;Power unit;HIDDEN;12;kW;string;IN;kW;
;CURRENT_SET;Current Set;;14;A;real;OUT;;"""
SetCurrent.0.WORD:= ITEM.CURRENT_SET*1000;
EnCharging.0.WORD:= 1;
SetCurrent.F:=1;
EnCharging.F:= 1;
""";
;QUALITY;Quality;;200;;int;IN;
;TIMESTAMP;Timestamp;;201;;datetime;IN;
*OBJECT;GROUP;ID;ALIAS;TYPE;ENUM;ENUMTEXT;ENUMVAL;MIN;MAX;IOTYPE;DIGITS;DEFAULT;ITEMID;UNIT;ALARM;ALARMVAL;ALARMTIME;OUTTYPE;CMDITEMID;COMMAND;COMMANDENUM;COMMANDVAL;VIEWTYPE;LOG;SVGANIMATE;SVGEVENT
;0;0;CPS;1;6;No Car, Car plugged, Charging, Charging with ventilation, Error, Communication Error;65,66,67,68,69,70;;;;;;0;;;;;;;;;;0;
;0;1;Power;4;;;;;;;2;;10;kW;;;;;;;;;0;1;
Was der Auszug zeigt:
- Jede Anweisung endet mit
;, auchENDIF;. NachTHENundELSEsteht kein;. - Jeder Lesewert steht in einem eigenen kleinen Block (
COUNT2 = ein DWORD). - Schreibmuster: Die
WFORMULAvonCURRENT_SETfüllt die SendepufferSetCurrent.0.WORDundEnCharging.0.WORDund setzt beide Flags. Die Formel der Schreibfunktion setzt nach dem SendenSetCurrent.F:=0;zurück. Die*WRITE-Funktionen haben hier keinePOLLING-Spalte. - Temporäre Variablen wie
PHASES7_COUNT(im Original) dienen als Zähler über mehrere Zyklen. ENUMim*OBJECTgibt die Anzahl der Aufzählungswerte an (6 Texte, 6 Werte).- In der Originaldatei sind die Zeilen mit vielen
;auf eine einheitliche Spaltenzahl aufgefüllt. Für den Parser sind diese leeren Felder bedeutungslos.
Fallstricke
- Wort- und Bytereihenfolge: Falsche Werte bei FLOAT/DWORD (sehr große oder sinnlose Zahlen) deuten fast immer auf
WORDORDERhin. - Offset 0/1: Manche Hersteller zählen Register ab 1 (40001).
ADDRESSist die Protokolladresse (0-basiert). - COUNT > 200 wird nicht unterstützt. Teilen Sie den Bereich in mehrere Blöcke.
- Zu kurze
MIN_PAUSEüberlastet einfache Geräte. Bei Timeouts die Pause erhöhen. - Schreib-Funktion mit
POLLING> 0 sendet zyklisch, auch ohne Änderung. Für Sollwerte ist meistPOLLING0 plus.Frichtig.
Checkliste
- [ ]
PROTOCOLbeginnt mitMODBUS TCP - [ ]
PORT,TIMEOUT,WORDORDER,BYTEORDERgesetzt - [ ] jeder Block ≤ 200 Register, Funktionscode korrekt
- [ ] jede Formel prüft
.Q - [ ] Registerindex relativ zu
ADDRESS, 32-Bit-Werte belegen 2 Indizes - [ ] Schreibwege:
WFORMULAsetzt Werte und.F - [ ] Items haben eindeutige
IDundALIAS