DDF-Handbuch / Teil A – Protokoll-DDF

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

  1. Registerliste des Herstellers beschaffen: Adresse, Funktionscode, Datentyp, Skalierung, Wort-/Bytereihenfolge.
  2. *GENERAL anlegen: Metadaten, PROTOCOL;MODBUS TCP (DDF), PORT, TIMEOUT, WORDORDER, BYTEORDER.
  3. Zusammenhängende Register zu Lese-Blöcken zusammenfassen (≤ 200 Register je Block, Lücken sind erlaubt).
  4. Je Block eine Zeile in *READ mit einer Formel, die IF <A>.Q THEN … ENDIF; prüft und Items befüllt.
  5. Schreibbare Werte: eine *WRITE-Funktion mit POLLING 0 anlegen und vom Item per WFORMULA setzen (…:=x; <A>.F:=1;).
  6. *ITEM mit Alias, Einheit und Typ ergänzen.
  7. 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:

Fallstricke

Checkliste