Über Modbus-TCP Register blockweise lesen
Dieser Artikel behandelt das synchrone/blockweise Lesen von Registern über Modbus-TCP in der Loxone Config. Es können beliebig viele Register mit unterschiedlichsten Datentypen gleichzeitig gelesen und angezeigt werden. Dieser Artikel führt zum einen die Informationen der mehrteiligen Reihe zusammen, zum anderen enthält er Verbesserungen, Beispiele und bietet die Sourcen (v23) zum Download an.
Teil 1 dieser Reihe hatte sich noch damit beschäftigt Register mit bis zu 64 Bit zusammenhängend/gleichzeitig zu Lesen, mittels Loxone-Bordmitteln zu verarbeiten und daraus entweder 2 oder 4 Werte herauszurechnen und anzuzeigen. Ab Teil 2 war der Ansatz dann vollständig picoC basiert und ging weit über 64 Bit hinaus: Auslesen, Auswerten, Anzeigen.
Die Kurzfassung der vorliegenden Lösung lautet: Mit dem Einsatz dieser Implementierung kann man größere Mengen aufeinanderfolgender Register(maximal 252 Byte) synchron/blockweise lesen und beliebige Werte daraus anzeigen, unabhängig von deren Datentypen. Die aktuellsten Sourcen hierzu finden sich am Ende dieses Artikels.
Nun zur Langfassung… 🙂
Wozu das Ganze, was sind die Anwendungsmöglichkeiten?
- Am offensichtlichsten und meistdiskutiertesten ist mit Sicherheit das Thema mit den Skalierungsfaktoren. Werden Werte asynchron zu deren Skalierungsfaktoren gelesen, kann es zu temporären Ausreißern führen. Datenerfassung, Zählerbausteine, Grafiken, Statistiken – allesamt betroffen.
- Auch manche fachliche Werte müssen zeitgleich gelesen werden, um vernünftige Berechnungen oder Auswertungen machen zu können. Sie ergeben nur mit den korrekten Pendants Sinn. Ich gehe jetzt nicht darauf ein, aber einige Begriffe hierzu wären: Verlustleistungen, Wirkungsgrade, Phasenleistungen, Volt/Amp/Watt, Umwandlungswerte AC/DC etc..
- Viele Einzelabfragen belasten das Device zudem mehr als einige wenige Abfragen, im Idealfall vielleicht sogar nur eine einzige.
Voraussetzungen
Die hier beschriebene Lösung kann über Modbus-TCP Adressbereiche am Stück/blockweise/synchron auslesen. Hat man also eine Startadresse, dann werden davon ausgehend eine Menge an definierten Bytes gelesen. Unterstützt werden die Modbus Funktionen
- Read Holding Registers (03)
- Read Coils (01)
Also: Der gelesene Adressbereich muss zusammenhängend sein, darf keine reservierten Bereiche enthalten(führt zu Auslesefehlern) und darf in Summe maximal 252 Bytes groß sein(Modbusspezifikation).
Und was kann die Lösung nun?
Mit Blick auf das nachstehende Bild mit einer beispielhaften Darstellung für das Lesen und Anzeigen von Registern und Coils zunächst eine allgemeine Erklärung. Details finden sich dann in den Beispielen weiter unten. (Mit einem Klick auf das Bild wird es lesbar. 🙂 )

Von links nach rechts:
- Zunächst sieht man oben links im Bild zwei Textgenerator-Bausteine, welche die Konfiguration zum Lesen von Registern enthalten. Darauf wird gleich eingegangen.
- Die Konfiguration wird an den ersten Programm-Baustein an 5 Eingangskanäle (T1-T3,I1 und I2) angeschlossen. Dieser liest dann über Modbus-TCP Daten aus dem Device und zeigt diese an den Ausgabekanälen O1-O13 an. Reichen diese nicht aus, so wird ein Hex-String generiert, welcher an den nächsten Baustein weitergereicht und dort angezeigt wird. Diese Weitergabe kann (theoretisch) beliebig oft gemacht werden. Einfach einen weiteren Programm-Baustein samt picoC Code einfügen, Aus-/Eingang verbinden, fertig.
- Ein ähnliches Vorgehen findet sich im unteren Teil des Bildes. Dort werden Coils synchron gelesen und angezeigt. Details dazu in Beispiel 3.
Noch ein paar Worte zum Textgenerator-Bausteins(Tr-Eingang aktivieren! 🙂 ):

- IP, Port und Modbus Id
- Wird nach dem Port nichts mehr angegeben, so wird DeviceId 1 angenommen. Eine andere ModbusId kann kommasepariert angegeben werden. Mit einer Id von 3 also z.B.
192.168.1.10:502,3
- Wird nach dem Port nichts mehr angegeben, so wird DeviceId 1 angenommen. Eine andere ModbusId kann kommasepariert angegeben werden. Mit einer Id von 3 also z.B.
- Startadresse in Hex-Format: Hier gibts im Internet diverse Rechner zum Umrechnen von Dezimal nach Hex.
- Datentyp-Angaben(darauf wird in den Beispielen eingegangen)
- Per Default wartet das Programm 1000ms auf die Antwort des Devices, bevor die Verarbeitung der Antwort beginnt. Kann das eigene Gerät schneller oder eben langsamer antworten, so kann man den Datentypangaben noch in ms die Wartezeit auf eine Antwort voranstellen. Bei zum Beispiel 500ms würde die Datentypangabe dann hier so aussehen:
500:5x2,4x0,5x2,2x0,15x2
- Per Default wartet das Programm 1000ms auf die Antwort des Devices, bevor die Verarbeitung der Antwort beginnt. Kann das eigene Gerät schneller oder eben langsamer antworten, so kann man den Datentypangaben noch in ms die Wartezeit auf eine Antwort voranstellen. Bei zum Beispiel 500ms würde die Datentypangabe dann hier so aussehen:
- Datenformat der Antwort: Angabe, in welchem Format das Device antwortet: Big-Endian(=1) oder Little-Endian(=0)
- Wartezeit zwischen zwei Anfragen in ms. Wird nichts angegeben, so liegt der Default bei 10 Sekunden, also 10000ms.
Die Ausgabe der Registerwerte muss nicht zwangsweise dem komplett gelesenen Registerblock entsprechen. Man kann z.B. 40 Werte lesen und nur den ersten und den letzten Wert ausgeben. Wie das?
Die Daten müssen zwar zusammenhängend ausgelesen werden, aber in der Anzeige kann man selektiv sein. Dafür gibt es da noch einen eingeführten Pseudodatentypen IGNORE16. Man kann also z.B. einen Bereich mit 40 Werten auslesen, aber gezielt Werte ausblenden bzw. anzeigen. Dann kann man auf unwichtige Werte verzichten. Eine Erläuterung hierzu findet sich im ersten Beispiel.
Generell werden drei verschiedene Arten des Auslesens unterstützt: Numerische Datentypen, Strings und Coils (also Bit-Register). Zu jede dieser Arten findet sich dann gleich ein Beispiel. Die unterstützten Datentypen sind im Detail:
0 = IGNORE16
1 = UINT16
2 = INT16
3 = UINT32
4 = INT32
5 = FLOAT16
6 = FLOAT32
7 = STRING16
8 = STRING32
9 = COIL
Die folgenden ersten beiden Beispiele sind mit einem SolarEdge Wechelrichter umgesetzt, das letzte Coil-Beispiel mit einer Wärmepumpe.
Beispiel 1 – 25 numerische Werte synchron auslesen und anzeigen
Auf die allgemeine Konfiguration im Textgenerator-Baustein wurde bereits eingegangen. Deshalb die Konzentration auf die Datentyp-Konfiguration. Diese Variante ist anwendbar für die Datentypen 1 bis 6 aus der Liste:

Der interessante Punkt des Bildes ist nun der folgende:

Der obere Baustein enthält die allgemeine Konfiguration und die Startadresse. Im unteren Baustein habe ich ein paar unterschiedliche Auslesenkonfigurationen erstellt, die man testweise jeweils anschließen kann. Ich erkläre mal die unterste:
Startend bei der Hex-Adresse 9CFB, dezimal 40187, werden in Summe 47 Registerwerte ausgelesen, aber nur 39 angezeigt. Das passt hier im Beispiel genau auf die 3×13 Ausgabekanäle. Muss aber natürlich nicht passgenau sein. Auf das Ergebnis wird 800ms gewartet. Die Erläuterung der Werte im Detail.
- 3 Werte vom Datentyp „1“(UINT16)
- 5 Werte mit Pseudodatentyp „0“(IGNORE16)
- 15 Werte vom Datentyp „2“(INT16)
- 3 Werte mit Pseudodatentyp „0“(IGNORE16)
- 13 Werte vom Datentyp „2“(INT16)
- 8 Werte vom Datentyp „3“(UINT32)
Die numerischen Datentypen 1 bis 6 aus der Liste können beliebig vermischt werden. Man kann also FLOAT, INT mit 16 oder 32 Bit in beliebiger Reihenfolge gleichzeitig abgreifen, sofern diese in einem zusammenhängenden Adressblock auf dem Device verfügbar sind.
IGNORE16 sorgt dafür, dass zwar 2 Byte gelesen, aber nicht angezeigt werden. Würde man also z.B. einen FLOAT32 Wert „überspringen“ wollen, so würde man 2×0 angeben. Entsprechend beim Ignorieren von z.B. zwei FLOAT32 und einem UINT16 Wert dann 5×0.
Das Sizing ist derzeit so, dass max. 74 16-Bit oder 49 32-Bit Datentypen angezeigt werden können(gelesen werden können mehr). Wer mehr braucht, muss einfach den Parameter TRANSFER_SIZE in den beiden Codesourcen hochsetzen.
Sinnvollerweise hat man natürlich eine passende Dokumentation der Register neben sich…
Wird das Ganze dann auf den Miniserver übertragen und das Programm beginnt zu laufen, werden je nach Stand folgende Informationen im linken Hauptbaustein angezeigt.
1)
Lesen der Bytes über Modbus-TCP
Aktives schicken der Anfrage und Lesen der Ergebnisbytes. Frühere Versionen hatten in dieser Phase Instabilitäten, sofern man gleichzeitig neue Konfigurationen einspielen wollte. Dies ist behoben.
2)
Wartezustand und Statusinformationen
Nach der Verarbeitung wird „Reading ok“ ausgegeben und die Werte werden auf den Ausgabekanälen angezeigt, samt deren passenden Registeradressen wie im Bild dargestellt. Gerade beim Überspringen von Adressen zur Kontrolle hilfreich.
Zur besseren Übersicht wird zudem die Anzahl an empfangenen Bytes vom Device ausgegeben. Zieht man davon 9 Bytes an Metadaten ab, dann erhält man die Anzahl an Bytes, die der eigenen Angabe an Datentypen entsprechen sollte.
Beispiel 2 – Auslesen und Anzeigen von 4 Werten vom Datentyp String
Liest man String-Datentypen aus, so werden diese am Ausgang „Txt2“ des linken Programm-Bausteins gesammelt ausgegeben. Sie dienen ohnehin nur zur Info und so viele Textausgänge gäbe es auch gar nicht.
Hierfür wird ein Textgenerator-Baustein mit einer anderen Startadresse(hier 9CBB) erstellt und angeschlossen. Hier im Beispiel ist es eine Mischung aus STRING16 und STRING32 Datentypen. Und ein UINT16 wird ebenfalls noch angezeigt.

Beispiel 3 – 13 Coils synchron auslesen und anzeigen
Coils sind Bit-Register und von daher anders zu behandeln. Die Anzeige sollte deshalb nicht mit „normalen“ Registern vermischt werden. Wenn z.B. 9 Coils gelesen werden, dann sind hierfür 2 Byte an Daten notwendig, wobei vom zweiten Byte dann nur das erste Bit relevant ist.
Mit diesem Wissen nun zum Beispiel mit 13 Coils. Die Ausgabe an den Kanälen erfolgt so, dass jeweils ein Byte an einen Ausgang gelegt und ein Loxone Binärdekoder angeschlossen wird. Sieht im Ergebnis dann folgendermaßen aus:

Ich lese hier startend bei der Registeradresse 80 (in Hex 0050) also 13 Coils(Datentypwert 9) synchron aus und lasse diese anzeigen. Die Ergebnisse sind im Screenshot von oben nach unten zu lesen. Das erste Bit entspricht also dem Wert von Register 80, das nächste 81 usw..
Wer mag – die Sourcen
Für den linken Programm-Baustein mit der Modbus-Connection nachstehend der picoC-Code.
Für die weiteren Programm-Bausteine wird ein Decodier- und Anzeigecode benötigt. Nachstehend zum Download.
Feedback ist natürlich jederzeit willkommen.

