Teil 3 – Daten austauschen: UDP zu Loxone & REST zurück zu ioBroker

Ziel dieses Teils

In Teil 2 sendet der Hyper 2000 seine Daten lokal an ioBroker.
Jetzt bauen wir die Kommunikation zwischen ioBroker und dem Loxone Miniserver auf – in beide Richtungen.

Am Ende dieses Teils:

  • sendet ioBroker alle relevanten Werte per UDP an Loxone
  • empfängt Loxone die Daten über einen Virtuellen UDP Eingang
  • kann Loxone Befehle per HTTP zurück an ioBroker senden
  • läuft die Kommunikation stabil und entkoppelt

Das ist bei mir tatsächlich der einfachste Teil des Projekts.


Warum nicht der Loxone-Adapter?

Eigentlich könnte man die Werte direkt über den Loxone-Adapter austauschen.

Bei mir trat jedoch das Problem auf, dass sich der Adapter regelmäßig aufgehängt hat.
Das Verhalten wurde auch von anderen gemeldet.

Ich habe mich deshalb bewusst für eine einfache und robuste Lösung entschieden:

  • UDP für Monitoring (ioBroker → Loxone)
  • HTTP/REST für Steuerung (Loxone → ioBroker)

Keine Dauerverbindung, keine blockierenden Zustände, leicht zu debuggen.


🔄 Kommunikationsstruktur

ioBroker → Loxone

  • Protokoll: UDP
  • Intervall: 1 Sekunde
  • Format: JSON
  • Zweck: Monitoring

Loxone → ioBroker

  • Protokoll: HTTP (Virtueller Ausgang)
  • Adapter: Simple API
  • Zweck: Steuerbefehle

Diese Trennung läuft bei mir absolut stabil.


📌 Voraussetzungen

  • Hyper liefert Werte im ioBroker (Teil 2)
  • JavaScript-Adapter in ioBroker installiert
  • Simple API Adapter installiert
  • IP-Adresse vom Miniserver bekannt
  • Freier UDP-Port definiert (z. B. 6555)

Schritt 1 – UDP-Skript im ioBroker anlegen

Pfad:

ioBroker → Skripte → + → neues JavaScript

Wichtig:

  • TARGET_HOST = IP des Miniserver
  • TARGET_PORT = UDP-Port
  • SEND_INTERVAL_MS = 1000 (1 Sekunde)

Zusätzlich werden die Temperaturen der Batterien übertragen.
Dafür brauchst du die Seriennummern der Packs.

Diese findest du unter:

Objekte → zendure-solarflow → 0 → gDa3tb → SN Wechselrichter → packData

Batterien anpassen

Wenn du nur eine Batterie hast:

`zendure-solarflow.0.gDa3tb.${SN}.packData.${SN_PACK2}.maxTemp`,

einfach entfernen.

Wenn du drei Batterien hast:

const SN_PACK3 = "XXXXXXXX";

und ergänzen:

`zendure-solarflow.0.gDa3tb.${SN}.packData.${SN_PACK3}.maxTemp`,

Vollständiges Script

/****************************************************
* UDP-Sender (SN-Variable, Seriennummer nicht in UDP-Keys)
****************************************************/

// === Konfiguration ===
const TARGET_HOST = "192.168.178.XXX"; // Ziel-IP/Hostname
const TARGET_PORT = 6555; // Ziel-Port
const SEND_INTERVAL_MS = 1000; // Intervall in ms (1000 = 1s)

// Seriennummer (Mittelteil aller States)
const SN = "XXXXXXXX"; // nur hier ändern

// PackData Seriennummern (Batteriemodule)
const SN_PACK1 = "XXXXXXXX";
const SN_PACK2 = "XXXXXXXX";

// Direkte State-IDs mit Seriennummer
const STATE_IDS = [
`zendure-solarflow.0.gDa3tb.${SN}.electricLevel`,
`zendure-solarflow.0.gDa3tb.${SN}.gridInputPower`,
`zendure-solarflow.0.gDa3tb.${SN}.hyperTmp`,
`zendure-solarflow.0.gDa3tb.${SN}.outputHomePower`,
`zendure-solarflow.0.gDa3tb.${SN}.packData.${SN_PACK1}.maxTemp`,
`zendure-solarflow.0.gDa3tb.${SN}.packData.${SN_PACK2}.maxTemp`,
`zendure-solarflow.0.gDa3tb.${SN}.packPower`,
`zendure-solarflow.0.gDa3tb.${SN}.pvPower1`,
`zendure-solarflow.0.gDa3tb.${SN}.pvPower2`,
`zendure-solarflow.0.gDa3tb.${SN}.acMode`,
`zendure-solarflow.0.gDa3tb.${SN}.packNum`,
`zendure-solarflow.0.gDa3tb.${SN}.autoModel`,
`zendure-solarflow.0.gDa3tb.${SN}.control.autoModel`,
`zendure-solarflow.0.gDa3tb.${SN}.mqttstate`,
`zendure-solarflow.0.gDa3tb.${SN}.control.passMode`
];

const INCLUDE_TS = false; // Zeitstempel mitsenden?
const SKIP_NULLS = false; // null-Werte überspringen
const DEBUG_LOG = true; // Diagnose-Logs
const SCRIPT_NAME = "udp-send-noSNinKeys";
// === Ende Konfiguration ===

const dgram = require("dgram");
const client = dgram.createSocket("udp4");

// Hilfsfunktion: Schlüssel ohne Seriennummer erzeugen
function keyWithoutSN(id) {
const parts = id.split(".");
const snIndex = parts.indexOf(SN);
if (snIndex >= 0) {
return parts.slice(snIndex + 1).join("."); // alles nach der SN
}
return parts.slice(-2).join("."); // Fallback: letzte 2 Teile
}

function readStateSafe(id) {
try {
const s = getState(id);
return s || null;
} catch (e) {
if (DEBUG_LOG) log(`[READ-ERR] ${id}: ${e}`, "warn");
return null;
}
}

function collectStates() {
const payload = {};
for (const id of STATE_IDS) {
const s = readStateSafe(id);
const key = keyWithoutSN(id);

if (!s || s.val === null || s.val === undefined) {
if (DEBUG_LOG) log(`[NULL] ${id} (${key}) -> kein Wert`);
if (!SKIP_NULLS) payload[key] = null;
continue;
}

payload[key] = INCLUDE_TS
? { val: s.val, ts: s.ts, lc: s.lc, ack: s.ack }
: s.val;
}
payload._meta = { source: SCRIPT_NAME, sentAt: Date.now() };
return payload;
}

function sendUdp(obj) {
try {
const msg = Buffer.from(JSON.stringify(obj), "utf8");
client.send(msg, 0, msg.length, TARGET_PORT, TARGET_HOST, err => {
if (err) log(`UDP-Fehler: ${err}`, "error");
});
} catch (e) {
log(`Sende-/JSON-Fehler: ${e}`, "error");
}
}

function tick() {
const data = collectStates();
if (DEBUG_LOG) log(`Sende ${Object.keys(data).length - 1} Keys an ${TARGET_HOST}:${TARGET_PORT}`);
sendUdp(data);
}

// Start
const intervalTimer = setInterval(tick, SEND_INTERVAL_MS);
tick();

log(`UDP-Sender gestartet: SN=${SN}, Intervall=${SEND_INTERVAL_MS}ms, Ziel=${TARGET_HOST}:${TARGET_PORT}`);

onStop(() => {
clearInterval(intervalTimer);
try { client.close(); } catch {}
}, 1000);

Schritt 2 – Virtuellen UDP Eingang in Loxone anlegen

Da ich bereits eine Vorlage angelegt habe für die Virtuellen Ein/Ausgänge könnt ihr euch einfach diese Datei herunterladen und einfügen.

In der Loxone Config: Peripherie → Virtuelle Eingänge → Virtueller UDP Eingang → Vorlage importieren

Wichtig:

  • Port = derselbe wie im Script falls dieser geändert wurde

📥 Verfügbare Eingänge

Nach dem Import stehen dir folgende Virtuellen Eingänge zur Verfügung:

🔵 Hyper 2000

  • AC Ausgangsleistung
  • AC Eingangsleistung
  • AC Modus
  • Anzahl Batterien erkannt
  • Bypass Mode
  • Energieplan-Einstellungen
  • Lade-/Entladeleistung der Batterie
  • Modus Batteriespeicher
  • MQTT State
  • PV String 1
  • PV String 2
  • SOC Gesamtsystem
  • Temp Batterie 1
  • Temp Batterie 2
  • Temp Hyper 2000

Alle Werte werden jede Sekunde aktualisiert.


Schritt 3 – Steuerung zurück zu ioBroker (HTTP)

Für die Steuerung nutzt du Virtuelle Ausgänge.

Peripherie → Virtuelle Ausgänge → Vordefinierte Geräte → Vorlage importieren

Diese senden HTTP-Befehle an ioBroker über den Simple API Adapter.

Wichtig:
In jedem Befehl muss die Seriennummer des Wechselrichters korrekt eingetragen sein.

Ich habe hier bewusst auf eine Authentifizierung verzichtet das der iOBroker nur lokal erreichbar ist, ihr dürft aber gerne auch die API mit Benutzername und Kennwort absichern!


📤 Steuerbare Ausgänge

🔵 Hyper 2000 / IO Broker

  • AC Übergabe
  • autoModel
  • Bypass Modus
  • MQTT – Restart
  • Setzen des Entlade-Limits

Diese Befehle schreiben direkt in die entsprechenden States im ioBroker.


✅ Validierung (kurz, aber wichtig)

  1. ioBroker Log zeigt jede Sekunde „Sende X Keys …“
  2. Loxone UDP Monitor zeigt eingehende Pakete (wenn aktiviert)
  3. Virtuelle Eingänge in Loxone bekommen Live-Werte
  4. Ein virtueller Ausgang löst in ioBroker eine State-Änderung aus (Simple API)
  5. Loxone HTPP Monitor zeigt ausgehende Pakete

Wenn 1–3 klappt, ist der Datenweg ioBroker → Loxone fertig.
Wenn 4-5 klappt, ist die Steuerung Loxone → ioBroker fertig.


✅ Ergebnis von Teil 3

  • Live-Monitoring in Loxone
  • Steuerung des Hyper aus Loxone
  • saubere Trennung von Datenfluss und Befehlen
  • stabile Grundlage für Automationen

Im nächsten Teil bauen wir darauf echte Logiken:
Entlade-Limits, Moduswechsel und Energiesteuerung.

Schreibe einen Kommentar