MQTT Anbindung an Bayrol PH/C oder SALT Dosieranlagen

Hallo zusammen,

Ich habe heraus gefunden, dass die Dosieranlagen von Bayrol PH/C oder Salt als MQTT Client arbeiten und alle Daten an das www.bayrol-poolaccess.de Portal sendet.
Von hier aus kann man alle Daten dann wiederum abgreifen. Dazu muss man aber folgende Login Regel programmieren, die nach meiner Info nicht mit der Instanz MQTT Client funktioniert.

Login Scenario
Session starten → Einloggen → Detailseite parsen → Token und Serial via API abfragen → mqtt via Websocket Protokoll starten

mqtt Server ist der hier : wss://www.bayrol-poolaccess.de:8083
Username : Token von der API
Password : *

Wer kann hier unterstützen und sagen ob man das via Script hinbekommt ?

Ich würde mich auch über einen Script- Vorschlag, oder andere Einbindungsmöglichkeiten freuen.
Wir haben die Bayrou Automatic Salt.

Herzliche Grüße
Lieschen

Hallo,
auch ich habe Interesse an einer funktionierende Verbindung.
Mit z.B. MQTT Explorer klappt das ja einwandfrei.
Bedeutet das, dass der MQTT Explorer das Login anders umsetzt als der MQTT Client von IPS?

Ich habe Symcon nun auf 7.1 hochgezogen.
Nun bekomme ich im Debug folgende Meldung:

07.07.2024, 10:05:39 | QoS | We do not properly support QoS > 0 (PubAck)

Wer kann mir da weiterhelfen?

Hallo,

da ich nix für IPS gefunden habe was geht, habe ich (naja. okay. Claude) mal eben gebastelt.
Hier die Beta Version.
Bitte das Skript starten und dann erststellt es ein paar Childs wo man die Daten rein kloppen muss.

Falls Intersse daran ist, bitte Bescheid sagen, dann Poste ich Updates

<?php

$debug = true;
$parentID = $_IPS['SELF'];

// ==========================================
// Hilfsfunktionen
// ==========================================
function extractFloat($text) {
    if (preg_match('/[+-]?\d+(?:[\.,]\d+)?/', $text, $m)) {
        return (float)str_replace(',', '.', $m[0]);
    }
    return null;
}

function CreateChildVariable($ident, $name, $type, $profile = "", $icon = "", $position = 0) {
    global $parentID;
    $varID = @IPS_GetObjectIDByIdent($ident, $parentID);
    if ($varID === false) {
        $varID = IPS_CreateVariable($type);
        IPS_SetName($varID, $name);
        IPS_SetIdent($varID, $ident);
        IPS_SetParent($varID, $parentID);
        IPS_SetPosition($varID, $position);
        if ($profile != "") IPS_SetVariableCustomProfile($varID, $profile);
        if ($icon != "")    IPS_SetIcon($varID, $icon);
    }
    return $varID;
}

// Wie oben, setzt aber EINMALIG beim Anlegen einen Default-Wert
function CreateChildVariableWithDefault($ident, $name, $type, $default, $profile = "", $icon = "", $position = 0) {
    global $parentID;
    $existing = @IPS_GetObjectIDByIdent($ident, $parentID);
    $isNew = ($existing === false);
    $varID = CreateChildVariable($ident, $name, $type, $profile, $icon, $position);
    if ($isNew) {
        SetValue($varID, $default);
    }
    return $varID;
}

// ==========================================
// Variablen anlegen
// ==========================================
$varUsernameID   = CreateChildVariable("ConfigUsername", "1. Username / E-Mail", 3, "", "Mail", 10);
$varPasswordID   = CreateChildVariable("ConfigPassword", "2. Passwort", 3, "~Password", "Key", 11);
$varWebAccessID  = CreateChildVariable("ConfigWebAccessID", "3. Web Access ID (cid)", 3, "", "Network", 12);
$varOnlyActiveID = CreateChildVariable("AbrufNurBeiBetrieb", "4. Abruf nur bei Betrieb", 0, "~Switch", "Power", 13);

// Child mit der Objekt-ID der externen Pumpenstatus-Quelle, Default 22120
$varPumpSourceID = CreateChildVariableWithDefault("PumpStatusSourceID", "5. ID Pumpenstatus (Quelle)", 1, 22120, "", "Gauge", 14);

// Messwerte
$varPhID    = CreateChildVariable("PoolPHWert", "Pool PH-Wert", 2, "~pH", "Drops", 20);
$varRedoxID = CreateChildVariable("Redoxwert", "Redoxwert", 2, "", "Electricity", 21);
$varTempID  = CreateChildVariable("Pooltemperatur", "Pooltemperatur", 2, "~Temperature", "Temperature", 22);

// Alarme
$varPhAlarmID    = CreateChildVariable("PoolPHAlarm", "Pool pH Alarm", 0, "~Alert", "Alert", 30);
$varRedoxAlarmID = CreateChildVariable("RedoxAlarm", "Redoxwert Alarm", 0, "~Alert", "Alert", 31);
$varTempAlarmID  = CreateChildVariable("PooltempAlarm", "Pooltemperatur Warnung", 0, "~Alert", "Alert", 32);

// Kanister-Alarme (wieder da!)
$varPhCanisterEmptyID    = CreateChildVariable("PhCanisterEmpty", "pH Kanister leer", 0, "~Alert", "Container", 40);
$varChlorCanisterEmptyID = CreateChildVariable("ChlorCanisterEmpty", "Chlor Kanister leer", 0, "~Alert", "Container", 41);

// ==========================================
// Timer
// ==========================================
$timerName = "Bayrol Abfrage-Timer";
$eventID = @IPS_GetEventIDByName($timerName, $parentID);
if ($eventID === false) {
    $eventID = IPS_CreateEvent(1);
    IPS_SetParent($eventID, $parentID);
    IPS_SetName($eventID, $timerName);
    IPS_SetEventCyclic($eventID, 0, 0, 0, 0, 1, 5);
    IPS_SetEventActive($eventID, true);
}

// ==========================================
// Konfiguration laden
// ==========================================
$username   = GetValue($varUsernameID);
$password   = GetValue($varPasswordID);
$cid        = trim(GetValue($varWebAccessID));
$onlyActive = GetValue($varOnlyActiveID);

// >>> PUMPENSTATUS: aus der externen Objekt-ID lesen <<<
$pumpSourceObjID = (int)GetValue($varPumpSourceID);
$filterPumpOn = false;
$pumpSourceOk = false;
if ($pumpSourceObjID > 0 && @IPS_ObjectExists($pumpSourceObjID)) {
    $filterPumpOn = (bool)GetValue($pumpSourceObjID);
    $pumpSourceOk = true;
}

if (empty($username) || empty($password) || empty($cid)) {
    echo "Bitte Zugangsdaten in den Variablen unter dem Skript eintragen.\n";
    return;
}

// ==========================================
// cURL-Abruf (läuft IMMER, damit im Debug-Log alle Werte erscheinen)
// ==========================================
$cookieFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'bayrol_cookie_' . preg_replace('/[^a-zA-Z0-9]/', '', $cid) . '.txt';

if ($debug) {
    echo "=== DEBUG START ===\n";
    echo "Windows Cookie-Pfad: $cookieFile\n";
    echo "Pumpenstatus-Quelle: Objekt-ID $pumpSourceObjID" . ($pumpSourceOk ? "" : " (EXISTIERT NICHT -> Pumpe = AUS gewertet)") . "\n";
}

$ch = curl_init();
$commonOpts = [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_COOKIEJAR      => $cookieFile,
    CURLOPT_COOKIEFILE     => $cookieFile,
    CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    CURLOPT_TIMEOUT        => 20,
];

// --- SCHRITT 1: Login-Seite initial laden ---
$loginPageUrl = "https://www.bayrol-poolaccess.de/webview/m/login.php";
if ($debug) echo "1. Initialisiere Session auf: $loginPageUrl\n";
curl_setopt_array($ch, $commonOpts);
curl_setopt($ch, CURLOPT_URL, $loginPageUrl);
curl_exec($ch);

// --- SCHRITT 2: Login absenden ---
$loginTargetUrl = "https://www.bayrol-poolaccess.de/webview/m/login.php?r=reg";
if ($debug) echo "2. Sende POST-Login an: $loginTargetUrl\n";
curl_setopt_array($ch, $commonOpts);
curl_setopt($ch, CURLOPT_URL, $loginTargetUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'username' => $username, 'password' => $password, 'autologin' => 'None', 'login' => 'Anmelden'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/x-www-form-urlencoded',
    'Referer: https://www.bayrol-poolaccess.de/webview/m/login.php'
]);
curl_exec($ch);
$loginHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$effectiveUrl  = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
if ($debug) {
    echo "Login HTTP Code: $loginHttpCode\n";
    echo "Effektive URL nach Login: $effectiveUrl\n";
}

// --- SCHRITT 3: getdata.php abrufen ---
$dataUrl = "https://www.bayrol-poolaccess.de/webview/getdata.php?cid=" . urlencode($cid);
if ($debug) echo "3. Rufe Pool-Daten ab von: $dataUrl\n";
curl_setopt_array($ch, $commonOpts);
curl_setopt($ch, CURLOPT_URL, $dataUrl);
curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Referer: https://www.bayrol-poolaccess.de/webview/m/plants.php']);
$dataResponse = curl_exec($ch);
$dataHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($debug) echo "Datenabruf HTTP Code: $dataHttpCode\n";

if (empty($dataResponse)) {
    echo "CRITICAL ERROR: Keine HTML-Daten empfangen (Antwort ist komplett leer).\n";
    return;
}

// --- VOLLSTÄNDIGER HTML-AUSZUG IM LOG (zum Analysieren) ---
if ($debug) {
    echo "HTML-Antwort erhalten (Länge: " . strlen($dataResponse) . " Zeichen).\n";
    echo "--- HTML AUSZUG START ---\n";
    $cleanHtml = preg_replace('/\s+/', ' ', $dataResponse);
    echo htmlspecialchars(substr($cleanHtml, 0, 1500)) . "...\n";
    echo "--- HTML AUSZUG ENDE ---\n";
}

// ==========================================
// PARSEN
// ==========================================
$dom = new DOMDocument();
@$dom->loadHTML('<?xml encoding="UTF-8">' . $dataResponse);
$xpath = new DOMXPath($dom);

$phValue = $redoxValue = $tempValue = null;
$phCanisterEmpty = false;
$chlorCanisterEmpty = false;

// Werte über h1-Reihenfolge (pH, Redox, Temp)
$h1s = $xpath->query('//h1');
if ($h1s->length >= 3) {
    $phValue    = extractFloat($h1s->item(0)->nodeValue);
    $redoxValue = extractFloat($h1s->item(1)->nodeValue);
    $tempValue  = extractFloat($h1s->item(2)->nodeValue);
}

// Fallback über Regex
if ($phValue === null && preg_match('/pH[^\d]*([\d\.,]+)/i', $dataResponse, $m)) $phValue = extractFloat($m[1]);
if ($redoxValue === null && preg_match('/mV[^\d]*([\d\.,]+)/i', $dataResponse, $m)) $redoxValue = extractFloat($m[1]);
if ($tempValue === null && preg_match('/°C[^\d]*([\d\.,]+)/i', $dataResponse, $m)) $tempValue = extractFloat($m[1]);

// Kanister-Status: aus den tab_box-Klassen der jeweiligen Box ableiten.
// Bayrol markiert leere Kanister mit den CSS-Klassen "stat_empty" bzw. "canister_empty".
$boxes = $xpath->query('//div[contains(@class, "tab_box")]');
foreach ($boxes as $box) {
    $html = $dom->saveHTML($box);
    $isEmptyBox = (stripos($html, 'canister_empty') !== false || stripos($html, 'stat_empty') !== false);
    if (!$isEmptyBox) continue;
    if (stripos($html, 'pH') !== false)                                     $phCanisterEmpty = true;
    if (stripos($html, 'Redox') !== false || stripos($html, 'mV') !== false) $chlorCanisterEmpty = true;
}
// Zusätzlicher Fallback über den Roh-HTML-Text
if (stripos($dataResponse, 'canister_empty') !== false || stripos($dataResponse, 'stat_empty') !== false) {
    if ($debug) echo "HINWEIS: Kanister-Leer-Markierung im HTML gefunden.\n";
}

// Alarmgrenzen
$phAlarm    = ($phValue    !== null) ? ($phValue < 6.8 || $phValue > 7.6) : false;
$redoxAlarm = ($redoxValue !== null) ? ($redoxValue < 650.0)             : false;
$tempAlarm  = ($tempValue  !== null) ? ($tempValue < 10.0 || $tempValue > 35.0) : false;

// ==========================================
// AUSGABE IM DEBUG-LOG (IMMER, komplett)
// ==========================================
echo "=== DATEN-ABRUF-BEREIT ===\n";
echo "Filterpumpe (aus Quelle-ID $pumpSourceObjID): " . ($filterPumpOn ? 'AN' : 'AUS') . "\n";
if ($phValue    !== null) echo "pH-Wert: $phValue (Alarm: " . ($phAlarm ? 'JA' : 'NEIN') . " | Kanister leer: " . ($phCanisterEmpty ? 'JA' : 'NEIN') . ")\n";
if ($redoxValue !== null) echo "Redoxwert: $redoxValue mV (Alarm: " . ($redoxAlarm ? 'JA' : 'NEIN') . " | Kanister leer: " . ($chlorCanisterEmpty ? 'JA' : 'NEIN') . ")\n";
if ($tempValue  !== null) echo "Pooltemperatur: $tempValue °C (Warnung: " . ($tempAlarm ? 'JA' : 'NEIN') . ")\n";

// ==========================================
// ABBRUCH-LOGIK: Pumpe aus -> NICHTS schreiben (Werte stehen oben im Log)
// ==========================================
if ($onlyActive && !$filterPumpOn) {
    echo "=== ABBRUCH ===\n";
    echo "Die Filterpumpe ist AUS. Alle Live-Werte wurden oben angezeigt, werden aber NICHT in die Variablen geschrieben.\n";
    return;
}

// ==========================================
// SCHREIBEN (alles, wie am Anfang)
// ==========================================
echo "=== DATEN-UPDATE IN VARIABLE ===\n";
if ($phValue !== null) {
    SetValue($varPhID, $phValue);
    SetValue($varPhAlarmID, $phAlarm);
    SetValue($varPhCanisterEmptyID, $phCanisterEmpty);
}
if ($redoxValue !== null) {
    SetValue($varRedoxID, $redoxValue);
    SetValue($varRedoxAlarmID, $redoxAlarm);
    SetValue($varChlorCanisterEmptyID, $chlorCanisterEmpty);
}
if ($tempValue !== null) {
    SetValue($varTempID, $tempValue);
    SetValue($varTempAlarmID, $tempAlarm);
}
echo "Variablen erfolgreich in IP-Symcon aktualisiert.\n";