Dokumentation
Vorlagen (Dateiformat)
Benötigt: Symcon >= 7.0
Die Instanz "ModBus Gerät" kann ihre komplette Konfiguration als Vorlage exportieren und wieder importieren. Eine Vorlage ist eine JSON-Datei, die alle Adressen, virtuellen Adressen, benötigten Variablenprofile, die Byte-Reihenfolge und die Abfrageeinstellungen enthält. So kann ein einmal eingerichtetes Gerät mit wenigen Klicks auf weiteren Systemen eingerichtet oder mit anderen Nutzern geteilt werden.
Viele fertige Vorlagen sind in unserer Community zu finden: Vorlagen anzeigen
Auf dieser Seite wird das Dateiformat vollständig beschrieben, damit Vorlagen auch direkt aus dem Datenblatt eines Herstellers erstellt oder per Skript erzeugt werden können.
Import und Export
| Aktion | Beschreibung |
|---|---|
| Vorlage exportieren | Erzeugt eine JSON-Datei aus der aktuellen Konfiguration. Alle verwendeten Profile, die nicht mit "~" beginnen, werden mit exportiert. |
| Vorlage importieren | Ersetzt Adressen, virtuelle Adressen, Byte-Reihenfolge und Abfrageeinstellungen im Konfigurationsformular. Die Änderungen werden erst mit "Übernehmen" gespeichert. Fehlende Profile werden dabei angelegt, bestehende Profile werden nicht verändert. |
Vor dem Import wird die Datei geprüft. Enthält sie nicht die drei Schlüssel "Addresses", "VirtualAddresses" und "Profiles", wird der Import mit der Meldung "This is not a valid ModBus template!" abgebrochen. Existiert ein Profil bereits mit anderen Einstellungen, wird ein Hinweis angezeigt (z.B. "2 profiles do not match!"). Das vorhandene Profil wird in diesem Fall weiterverwendet.
Beim Import wird die bestehende Konfiguration der Instanz überschrieben. Variablen, deren Ident in der neuen Konfiguration nicht mehr vorkommt, werden beim Übernehmen gelöscht - inklusive ihrer Archivdaten.
Aufbau
Eine Vorlage ist ein JSON-Objekt mit folgenden Schlüsseln:
| Schlüssel | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| Addresses | Array | Ja | Liste der Modbus-Adressen, siehe Adressen |
| VirtualAddresses | Array | Ja | Liste der virtuellen Adressen, siehe Virtuelle Adressen, ggf. leer |
| Profiles | Objekt | Ja | Variablenprofile, die beim Import angelegt werden, siehe Profile, ggf. leer |
| ByteOrder | Zahl | Nein | Byte-Reihenfolge der Instanz, siehe Byte-Reihenfolge |
| Requests | Objekt | Nein | Abfrageeinstellungen, siehe Abfrage |
Die Reihenfolge der Schlüssel ist beliebig. Unbekannte Schlüssel werden ignoriert. Eine minimale, gültige Vorlage mit einer einzigen Adresse sieht so aus:
{
"Addresses": [
{
"Active": true,
"Name": "Battery state of charge",
"Ident": "battery_soc",
"Translation": [
{ "Language": "de", "Name": "Batterieladezustand" }
],
"DataType": 2,
"ReadFunctionCode": 4,
"ReadAddress": 33139,
"WriteFunctionCode": 0,
"WriteAddress": 0,
"Factor": 0,
"Length": 0,
"ByteOrder": -1,
"Profile": "~Battery.100"
}
],
"VirtualAddresses": [],
"Profiles": {},
"ByteOrder": 0,
"Requests": {
"Type": 0,
"Interval": 5000,
"DataBlocks": []
}
}
Adressen
Jeder Eintrag in "Addresses" beschreibt einen Wert des Geräts. Für jeden aktiven Eintrag wird eine Variable unterhalb der Instanz angelegt.
| Feld | Typ | Beschreibung |
|---|---|---|
| Active | Bool | Legt fest, ob die Variable angelegt und der Wert gelesen wird. Inaktive Einträge bleiben in der Vorlage erhalten und können später vom Nutzer aktiviert werden. |
| Name | String | Name der Variable. Empfohlen wird ein englischer Name, der über "Translation" übersetzt wird. |
| Ident | String | Ident der Variable. Ist das Feld leer, wird der Ident automatisch gebildet, siehe Ident. |
| Translation | Array | Übersetzungen des Namens als Liste von Objekten mit "Language" (z.B. "de") und "Name". |
| DataType | Zahl | Datentyp des Registers, siehe Datentypen. |
| ReadFunctionCode | Zahl | Funktionscode zum Lesen: 0 (nicht lesen), 1, 2, 3 oder 4, siehe Funktionscodes. |
| ReadAddress | Zahl | Leseadresse (Startregister bzw. Coil), siehe Adressierung. |
| WriteFunctionCode | Zahl | Funktionscode zum Schreiben: 0 (nicht schreiben), 5, 6, 15 oder 16. Ist ein Funktionscode gesetzt, erhält die Variable eine Standardaktion. |
| WriteAddress | Zahl | Schreibadresse. Meist identisch mit der Leseadresse. |
| Factor | Zahl | Faktor, mit dem der gelesene Wert multipliziert wird. 0 bedeutet "kein Faktor", siehe Faktor. |
| Length | Zahl | Nur für Strings: Länge in Byte (2 Byte pro Register). Bei allen anderen Datentypen 0. |
| ByteOrder | Zahl | Byte-Reihenfolge dieser Adresse. -1 übernimmt die Einstellung der Instanz, siehe Byte-Reihenfolge. |
| Profile | String | Name des Variablenprofils oder leer. Der Typ des Profils muss zum Variablentyp passen, siehe Datentypen. |
Alle Felder sollten immer vollständig und mit dem richtigen JSON-Typ angegeben werden. Zahlen dürfen nicht als String ("3") und nicht als null angegeben werden, sonst schlägt das Übernehmen der Konfiguration fehl. Ausnahme ist "Active": fehlt es, gilt die Adresse als aktiv.
Ältere Exporte enthalten teilweise zusätzliche Felder wie "SwapBytes" oder "CustomFactor". Diese werden ignoriert und können entfallen. Der Wert eines eigenen Faktors steht immer im Feld "Factor".
Datentypen in Vorlagen
In der Vorlage wird der Datentyp als Zahl gespeichert. Die Zahlen sind aus Kompatibilitätsgründen nicht fortlaufend nach Größe sortiert.
| DataType | Anzeige in der Konsole | Register | Variablentyp (ohne Faktor) |
|---|---|---|---|
| 0 | BOOL | 1 Bit | Boolean |
| 1 | UINT8 (MSB) | 1 | Integer |
| 12 | UINT8 (LSB) | 1 | Integer |
| 2 | UINT16 | 1 | Integer |
| 3 | UINT32 | 2 | Integer |
| 11 | UINT64 | 4 | Float |
| 4 | INT8 (MSB) | 1 | Integer |
| 13 | INT8 (LSB) | 1 | Integer |
| 5 | INT16 | 1 | Integer |
| 6 | INT32 | 2 | Integer |
| 8 | INT64 | 4 | Float |
| 7 | FLOAT32 | 2 | Float |
| 9 | FLOAT64 | 4 | Float |
| 10 | STRING (PLAIN) | Length/2 | String |
| 14 | STRING (HEX) | Length/2 | String |
- MSB/LSB: UINT8/INT8 lesen ein ganzes Register und verwenden das höherwertige (MSB) bzw. das niederwertige Byte (LSB). So lassen sich zwei 8-Bit-Werte aus einem Register mit zwei Einträgen auf derselben Adresse auslesen.
- 64 Bit: INT64 und UINT64 werden als Float-Variable abgebildet, da Symcon auch 32-Bit-Systeme unterstützt.
- STRING (PLAIN): Die Bytes werden als Text interpretiert. Leerzeichen und Null-Bytes am Anfang und Ende werden entfernt.
- STRING (HEX): Die Bytes werden als Hex-Zeichenkette (Großbuchstaben) dargestellt, z.B. "0A1B". Das ist nützlich für Bitfelder, Versionsnummern oder MAC-Adressen.
- Faktor: Sobald ein Faktor ungleich 0 gesetzt ist, wird die Variable immer als Float angelegt - auch bei ganzzahligen Datentypen und auch beim Faktor 1.
Der Typ eines Profils muss zum Variablentyp passen. Ein UINT16-Register ohne Faktor ergibt eine Integer-Variable und kann daher z.B. nicht das Float-Profil "~Watt" bekommen. Soll ein ganzzahliger Wert ein Float-Profil erhalten, kann der Faktor 1 gesetzt werden.
Funktionscodes und Adressen
| Funktionscode | Richtung | Name | Erlaubte Datentypen |
|---|---|---|---|
| 1 | Lesen | Read Coils | BOOL |
| 2 | Lesen | Read Discrete Inputs | BOOL |
| 3 | Lesen | Read Holding Registers | alle außer BOOL |
| 4 | Lesen | Read Input Registers | alle außer BOOL |
| 5 | Schreiben | Write Single Coil | BOOL |
| 15 | Schreiben | Write Multiple Coils | BOOL |
| 6 | Schreiben | Write Single Register | nur 8/16-Bit-Typen |
| 16 | Schreiben | Write Multiple Registers | alle außer BOOL |
Ungültige Kombinationen werden beim Übernehmen mit einer Fehlermeldung abgelehnt, z.B. "Non-Bit values must use function Read Holding Registers/Read Input Registers" oder "Writing in only one register is not possible for multi register values like Int32/UInt32, Int64/UInt64, Float32/Float64, String".
Adressierung: "ReadAddress" und "WriteAddress" sind die Adressen, die tatsächlich im Modbus-Telegramm übertragen werden, beginnend bei 0. Viele Datenblätter verwenden stattdessen die klassische Schreibweise mit Präfix (z.B. 40001 für das erste Holding Register). In diesem Fall muss der Präfix abgezogen werden, siehe Funktionscodes. Andere Hersteller geben die Adressen bereits direkt an (z.B. Solis mit 33000 für ein Input Register). Im Zweifel hilft ein Test mit einem bekannten Wert wie der Seriennummer oder der Netzfrequenz.
Byte-Reihenfolge
Die Byte-Reihenfolge wird für die gesamte Instanz im Schlüssel "ByteOrder" auf oberster Ebene festgelegt. Einzelne Adressen können sie mit ihrem eigenen Feld "ByteOrder" überschreiben. Der Wert -1 übernimmt die Einstellung der Instanz. Die Beispiele zeigen, wie der 32-Bit-Wert 0x11223344 im Gerät übertragen wird:
| ByteOrder | Bezeichnung | Übertragene Bytes | Typische Beschreibung im Datenblatt |
|---|---|---|---|
| -1 | Von Instanz übernehmen | ----------------- | nur auf Adressebene |
| 0 | Big-Endian (Standard) | 11 22 33 44 | "High Word first", "ABCD", Modbus-Standard |
| 1 | Little-Endian | 44 33 22 11 | "DCBA" |
| 2 | Big-Endian (Byte Swap) | 22 11 44 33 | "BADC" |
| 3 | Little-Endian (Byte Swap) | 33 44 11 22 | "Low Word first", "Word Swap", "CDAB" |
Bei 16-Bit-Werten wirken sich nur 1 und 2 aus (Bytes innerhalb des Registers vertauscht). Für BOOL-Werte ist die Byte-Reihenfolge ohne Bedeutung.
Faktor
Der gelesene Rohwert wird mit "Factor" multipliziert, bevor er in die Variable geschrieben wird. Beim Schreiben wird der Wert vorher durch den Faktor geteilt. Der Faktor ist immer ein Multiplikator: Eine Division durch 10 wird als 0.1 angegeben.
| Datenblatt | Factor |
|---|---|
| Einheit 0.1 V | 0.1 |
| Einheit 0.01 Hz | 0.01 |
| Einheit 10 W | 10 |
| Wh, gewünscht in kWh | 0.001 |
| kein Faktor | 0 |
Für BOOL- und String-Adressen muss der Faktor 0 sein. Besitzt das Gerät einen eigenen Skalierungsfaktor in einem Register (z.B. SunSpec "Scale Factor"), kann dieser über eine virtuelle Adresse verrechnet werden.
Ident
Der Ident identifiziert die Variable dauerhaft. Ist "Ident" leer, wird er aus Datentyp, Funktionscode und Leseadresse gebildet: A[DataType][ReadFunctionCode]_[ReadAddress], z.B. "A_7_3100". Für virtuelle Adressen wird der Name verwendet, wobei alle Zeichen außer Buchstaben, Ziffern und Unterstrich durch "" ersetzt werden.
Ohne festen Ident ändert sich der Ident, sobald Datentyp, Funktionscode oder Adresse (bzw. bei virtuellen Adressen der Name) angepasst werden. Die alte Variable wird dann samt Archivdaten gelöscht und eine neue angelegt. Vorlagen sollten daher immer einen festen, sprechenden Ident wie "battery_soc" setzen. Jeder Ident darf nur einmal vorkommen - auch nicht zwischen Adressen und virtuellen Adressen.
Der Ident wird außerdem in den Skripten der virtuellen Adressen verwendet, um auf die Werte zuzugreifen.
Virtuelle Adressen
Virtuelle Adressen berechnen eine Variable per PHP aus den Werten der übrigen Adressen oder verteilen einen geschriebenen Wert auf eine oder mehrere Adressen.
| Feld | Typ | Beschreibung |
|---|---|---|
| Active | Bool | Legt fest, ob die Variable angelegt und die Skripte ausgeführt werden. |
| Name | String | Name der Variable |
| Ident | String | Ident der Variable. Ist das Feld leer, wird der Ident aus dem Namen gebildet. |
| Translation | Array | Übersetzungen des Namens, wie bei den Adressen |
| VariableType | Zahl | 0 = Boolean, 1 = Integer, 2 = Float, 3 = String |
| Profile | String | Name des Variablenprofils oder leer. Der Profiltyp muss dem VariableType entsprechen. |
| ReadAction | String | PHP-Code zum Berechnen des Werts, leer für keine Berechnung |
| WriteAction | String | PHP-Code zum Schreiben. Ist er gesetzt, erhält die Variable eine Standardaktion. |
Die Skripte enthalten nur den Inhalt einer Funktion, also ohne <?php. Zeilenumbrüche werden im JSON als \n (bzw. \r\n) angegeben.
ReadAction: Wird nach jeder Abfrage ausgeführt. In $VALUES stehen alle Adresswerte als Array mit dem Ident als Schlüssel zur Verfügung. Virtuelle Adressen werden der Reihe nach berechnet und ihr Ergebnis wird ebenfalls in $VALUES aufgenommen - eine virtuelle Adresse kann also auf die Ergebnisse der vorherigen zugreifen. Der Rückgabewert wird in die Variable geschrieben. Wird null zurückgegeben, bleibt die Variable unverändert. Der Rückgabewert muss zum VariableType passen.
return ($VALUES['pv_voltage_1'] ?? 0) * ($VALUES['pv_current_1'] ?? 0);
WriteAction: Wird beim Schalten der Variable ausgeführt. $VALUE enthält den neuen Wert, $VALUES die aktuellen Werte aller lesbaren Adressen. Zurückgegeben wird ein Array mit Ident als Schlüssel und dem zu schreibenden Wert. Für jeden Eintrag wird die Aktion der entsprechenden Adresse ausgeführt, also inklusive Faktor und Datentyp. Wird null zurückgegeben, wird nichts geschrieben.
// Nur die Bits 0-3 eines Bitfelds ändern, alle anderen Bits beibehalten $current = $VALUES['control_register'] ?? 0; return ['control_register' => ($current & ~0x0F) | ($VALUE & 0x0F)];
Der Operator ?? 0 verhindert Fehler, solange eine Adresse noch nicht gelesen wurde oder inaktiv ist. Gibt ein Skript Fehler aus, sind diese im Debug der Instanz unter dem Ident der virtuellen Adresse zu sehen.
Werte werden nur geschrieben, wenn sie sich ändern oder die Variable älter als 60 Sekunden ist. Das gilt sowohl für gelesene Adressen als auch für virtuelle Adressen.
Profile
"Profiles" ist ein Objekt mit dem Profilnamen als Schlüssel. Profile, deren Name mit "~" beginnt, sind Systemprofile. Sie werden nicht exportiert und müssen nicht in der Vorlage enthalten sein. Eigene Profile sollten einen eindeutigen Präfix erhalten (z.B. "Hersteller.Name"), damit sie nicht mit Profilen anderer Vorlagen kollidieren.
| Feld | Typ | Beschreibung |
|---|---|---|
| Type | Zahl | 0 = Boolean, 1 = Integer, 2 = Float, 3 = String |
| Prefix | String | Präfix |
| Suffix | String | Suffix, inkl. führendem Leerzeichen, z.B. " W" |
| MinValue | Zahl | Minimalwert |
| MaxValue | Zahl | Maximalwert |
| StepSize | Zahl | Schrittweite. Bei schaltbaren Variablen bestimmt sie die Darstellung als Slider. |
| Digits | Zahl | Anzahl der Nachkommastellen |
| Icon | String | Name des Icons oder leer |
| Associations | Array | Assoziationen, jeweils mit "Value", "Name", "Icon" und "Color" (-1 = keine Farbe) |
Alle Felder müssen angegeben werden. Der JSON-Typ von "Value" in den Assoziationen sollte zum Profiltyp passen (true/false bei Boolean, ganze Zahl bei Integer).
"Profiles": {
"Vendor.OperatingMode": {
"Type": 1,
"Prefix": "",
"Suffix": "",
"MinValue": 0.0,
"MaxValue": 0.0,
"StepSize": 0.0,
"Digits": 0,
"Icon": "Information",
"Associations": [
{ "Value": 0, "Name": "Standby", "Icon": "", "Color": -1 },
{ "Value": 1, "Name": "Running", "Icon": "", "Color": 65280 }
]
}
}
Beim Import werden nur fehlende Profile angelegt. Ein bestehendes Profil mit gleichem Namen wird nicht verändert, auch wenn es abweicht.
Abfrage
Der Schlüssel "Requests" enthält die Abfrageeinstellungen:
| Feld | Typ | Beschreibung |
|---|---|---|
| Type | Zahl | 0 = Einzelne Adressen, 1 = Datenblöcke |
| Interval | Zahl | Abfrageintervall in Millisekunden für den Typ "Einzelne Adressen". 0 deaktiviert die Abfrage. |
| DataBlocks | Array | Datenblöcke für den Typ "Datenblöcke" |
Einzelne Adressen: Im Intervall wird jede aktive Adresse mit gesetztem Lese-Funktionscode einzeln abgefragt. Das ist einfach, erzeugt bei vielen Adressen aber sehr viele Anfragen. Gerade bei Modbus RTU oder langsamen Datenloggern reicht die Zeit dann oft nicht aus.
Datenblöcke: Jeder Datenblock liest einen zusammenhängenden Bereich mit einer einzigen Anfrage und hat ein eigenes Intervall. Anschließend werden alle Adressen aktualisiert, die vollständig im Block liegen und denselben Funktionscode verwenden. So können z.B. Messwerte alle 5 Sekunden und Zählerstände nur jede Minute gelesen werden.
| Feld | Typ | Beschreibung |
|---|---|---|
| Function | Zahl | Funktionscode 1, 2, 3 oder 4 |
| Address | Zahl | Startadresse |
| Quantity | Zahl | Anzahl der Register bzw. Coils (maximal 125 Register) |
| Poller | Zahl | Intervall in Millisekunden |
"Requests": {
"Type": 1,
"Interval": 5000,
"DataBlocks": [
{ "Function": 4, "Address": 33049, "Quantity": 10, "Poller": 5000 },
{ "Function": 4, "Address": 33161, "Quantity": 20, "Poller": 60000 },
{ "Function": 3, "Address": 43110, "Quantity": 1, "Poller": 10000 }
]
}
Adressen, die in keinem Datenblock liegen, werden beim Typ "Datenblöcke" nicht aktualisiert. Ein Datenblock sollte nur Register umfassen, die das Gerät auch unterstützt - viele Geräte beantworten eine Anfrage mit einer Lücke im Registerbereich mit einem Fehler (ILLEGAL_DATA_ADDRESS) für den gesamten Block.
Checkliste
- Alle drei Pflichtschlüssel "Addresses", "VirtualAddresses" und "Profiles" sind vorhanden, auch wenn sie leer sind.
- Jede Adresse enthält alle Felder mit dem korrekten JSON-Typ (keine Strings oder null statt Zahlen).
- Jede Adresse und virtuelle Adresse hat einen festen, eindeutigen Ident.
- Namen sind englisch und haben eine deutsche Übersetzung in "Translation".
- Adressen werden so angegeben, wie sie im Telegramm übertragen werden (ohne 30001/40001-Präfix).
- Funktionscode und Datentyp passen zusammen (BOOL nur mit 1, 2, 5, 15; Mehr-Register-Werte nicht mit 6).
- Der Profiltyp passt zum Variablentyp (Faktor ungleich 0 ergibt immer Float).
- Eigene Profile haben einen eindeutigen Präfix und sind vollständig in "Profiles" enthalten.
- Selten benötigte Werte sind mit "Active": false enthalten, statt sie wegzulassen.
- Bei vielen Adressen werden Datenblöcke verwendet und jede aktive Adresse liegt in einem Block.
- Die Vorlage wurde einmal importiert, übernommen und wieder exportiert. Der Export muss dieselben Adressen enthalten.