Rec-BMS PHP auslesen

Hallo
Ich benötige Hilfe mit einer Datenseite aus meinem REC-BMS.
Ich würde gerne die Werte in eine Variable speichern.
Mir fehlen aber die Fähigkeiten das umzusetzen.
Kann mir jemand helfen?

<!doctype html>

<html lang="en">

    <head>
        <meta name='viewport' charset="UTF-8" content='width=device-width, initial-scale=1' />
        <script src='/src/jquery.min.js'></script>
        <script src='/src/bootstrap.bundle.min.js'></script>
        <link rel='stylesheet' type='text/css' href='/src/bootstrap.min.css'>
        <link rel='stylesheet' type='text/css' href='/src/my_css.css'>
        <link rel="icon" href="src/icon.png">
        <script src='/src/custom_script.js'></script>
        <title>REC_BMS</title>
    </head>

    <body>
        <div id="main">
            <div class='container-fluid'>
                <div class='row'>
                    <div id="mySidenav" class="sidenav"></div>
                    <div class='col-1'>
                        <span class="openicon sticky-top" onclick="openNav()">&#9776;</span>
                    </div>
                    <div class="col-10 text-center">
                        <div class='row'>
                            <a class="col-12" href="http://rec-bms.com/">
                                <img src="/src/logoREC.png" alt="REC logo" width="250" height="80"></a>
                        </div>
                        <div class="row">
                            <div class="col">
                                <h4 id="bms-name"></h4><br>
                            </div>
                        </div>
                        <div id="VoltTable" class='row'></div>
                    </div>
                    <div class='col-1'></div>
                </div>
            </div>
        </div>
        <script>
            var reconnect_counter = 0;
            var connected = 1;
            var deviceInfo = new Object();

            function fillInfoData(data) {
                deviceInfo = data;
                const lockStatus = data.login.loggedin && data.login.enabled;
                createMenu("mySidenav", lockStatus);
            }
            $(document).ready(function () {
                getInfo(fillInfoData);
                var parsed_data;
                if (!!window.EventSource) {
                    var source = new EventSource('/ws');
                    source.addEventListener('status', function (e) {
                        response = parseJSON(e.data);
                        createTable(
                            function () {
                                CreateVoltTable(response);
                            });
                        if (parsed_data.err.p && (parsed_data.err.num == 1 || parsed_data.err.num == 2))
                            if (parsed_data.vnc.bms != null || parsed_data.nnc.bms != null) {
                                document.getElementById("b_" + parsed_data.nnc.bms + "c_" +
                                    parsed_data.nnc.cell).style.backgroundColor = "#F00";
                                document.getElementById("b_" + parsed_data.vnc.bms + "c_" +
                                    parsed_data.vnc.cell).style.backgroundColor = "#F00";
                            }
                    }, false);
                    source.addEventListener('settings', function (e) {
                        parsed_data = parseJSON(e.data);
                        if (parsed_data.bms_name != "") {
                            if (!connected) {
                                $("#bms-name").html(
                                    "<div style='color:green'>Connected!</div>");
                                $("#VoltTable").fadeIn();
                            } else
                                $("#bms-name").html("");
                            connected = 1;
                        } else {
                            connected = 0;
                            let dots = "&nbsp;";
                            for (let i = 0; i < reconnect_counter % 4; i++)
                                dots += ". ";
                            reconnect_counter++;

                            $("#bms-name").html(
                                "<div style='color:red'>Connection lost<br>Reconnecting<br> " +
                                dots +
                                "</div>");
                            $("#VoltTable").fadeOut();
                        }
                    }, false);
                }
            });
        </script>
    </body>

</html>

Es gibt ein Logging das einmal am Tag heruntergeladen werden kann, was für eine aktuelle Beobachtung sinnlos ist.
Dort sehen die Zeilen so aus, nach „nap“ sind die zellspannungen die ich gerne loggen würde.

{"bms_array":{"master":{"time_remaining":"Full in:<br> 0h 00 min","st_naprav":1,"time":"","date":"","mincell":4.055347,"maxcell":4.084739,"ibat":29.74018,"tmax":22,"vbat":56.96735,"soc":1,"soh":0.998128,"erro":{"present":0,"addr":0,"st":0,"con_st":0},"error":""},"slave":{"0":{"address":2,"st_temp":4,"temp_bms":21.59611,"st_celic":14,"temp":{"0":22,"1":21,"2":21.5,"3":22},"res":{"0":0.000584,"1":0.000352,"2":0.0005,"3":0.000737,"4":0.000329,"5":0.00055,"6":0.00078,"7":0.00044,"8":0.000493,"9":0.000815,"10":0.000361,"11":0.000442,"12":0.000518,"13":0.000307},"nap":{"0":4.063704,"1":4.071898,"2":4.071171,"3":4.070842,"4":4.067813,"5":4.063611,"6":4.075356,"7":4.084739,"8":4.070627,"9":4.076136,"10":4.073825,"11":4.065398,"12":4.056897,"13":4.055347}}}}},

Wenn du das Logging zum Beispiel alle 10 min über einen json-decoder abholst hast du die gewünschten Variablen regelmässig aktualisiert.

Wenn ich das könnte würde ich es machen.
Aber leider bin ich da abhängig von anderen.

der json-decoder ist eine Instanz in IP-Symcon.
Installiere diese einmal und versuch es…

Okay, Instanz angelegt, Gateway konfigurieren ausgewählt,
den Http Client einmal auf die Seite in der nur die Voltzahlen stehen gestellt und Seite aktualisieren ausgewählt.
Keine Daten unter der Instanz
Auch ausstellen von "Die Optionen ist nur für SSL Verbindungen anwendbar.
Im Debug steht wenn manuell ausgelöst

21.07.2025, 14:44:59 | http://192.168.6.16/cell_volt | <!doctype html><CR><LF><CR><LF><html lang="en"><CR><LF><CR><LF>    <head><CR><LF>        <meta name='viewport' charset="UTF-8" content='width=device-width, initial-scale=1' /><CR><LF>        <script src='/src/jquery.min.js'></script><CR><LF>        <script src='/src/bootstrap.bundle.min.js'></script><CR><LF>        <link rel='stylesheet' type='text/css' href='/src/bootstrap.min.css'><CR><LF>        <link rel='stylesheet' type='text/css' href='/src/my_css.css'><CR><LF>        <link rel="icon" href="src/icon.png"><CR><LF>        <script src='/src/custom_script.js'></script><CR><LF>        <title>REC_BMS</title><CR><LF>    </head><CR><LF><CR><LF>    <body><CR><LF>        <div id="main"><CR><LF>            <div class='container-fluid'><CR><LF>                <div class='row'><CR><LF>                    <div id="mySidenav" class="sidenav"></div><CR><LF>                    <div class='col-1'><CR><LF>                        <span class="openicon sticky-top" onclick="openNav()">&#9776;</span><CR><LF>                    </div><CR><LF>                    <div class="col-10 text-center"><CR><LF>                        <div class='row'><CR><LF>                            <a class="col-12" href="http://rec-bms.com/"><CR><LF>                                <img src="/src/logoREC.png" alt="REC logo" width="250" height="80"></a><CR><LF>                        </div><CR><LF>                        <div class="row"><CR><LF>                            <div class="col"><CR><LF>                                <h4 id="bms-name"></h4><br><CR><LF>                            </div><CR><LF>                        </div><CR><LF>                        <div id="VoltTable" class='row'></div><CR><LF>                    </div><CR><LF>                    <div class='col-1'></div><CR><LF>                </div><CR><LF>            </div><CR><LF>        </div><CR><LF>        <script><CR><LF>            var reconnect_counter = 0;<CR><LF>            var connected = 1;<CR><LF>            var deviceInfo = new Object();<CR><LF><CR><LF>            function fillInfoData(data) {<CR><LF>                deviceInfo = data;<CR><LF>                const lockStatus = data.login.loggedin && data.login.enabled;<CR><LF>                createMenu("mySidenav", lockStatus);<CR><LF>            }<CR><LF>            $(document).ready(function () {<CR><LF>                getInfo(fillInfoData);<CR><LF>                var parsed_data;<CR><LF>                if (!!window.EventSource) {<CR><LF>                    var source = new EventSource('/ws');<CR><LF>                    source.addEventListener('status', function (e) {<CR><LF>                        response = parseJSON(e.data);<CR><LF>                        createTable(<CR><LF>                            function () {<CR><LF>                                CreateVoltTable(response);<CR><LF>                            });<CR><LF>                        if (parsed_data.err.p && (parsed_data.err.num == 1 || parsed_data.err.num == 2))<CR><LF>                            if (parsed_data.vnc.bms != null || parsed_data.nnc.bms != null) {<CR><LF>                                document.getElementById("b_" + parsed_data.nnc.bms + "c_" +<CR><LF>                                    parsed_data.nnc.cell).style.backgroundColor = "#F00";<CR><LF>                                document.getElementById("b_" + parsed_data.vnc.bms + "c_" +<CR><LF>                                    parsed_data.vnc.cell).style.backgroundColor = "#F00";<CR><LF>                            }<CR><LF>                    }, false);<CR><LF>                    source.addEventListener('settings', function (e) {<CR><LF>                        parsed_data = parseJSON(e.data);<CR><LF>                        if (parsed_data.bms_name != "") {<CR><LF>                            if (!connected) {<CR><LF>                                $("#bms-name").html(<CR><LF>                                    "<div style='color:green'>Connected!</div>");<CR><LF>                                $("#VoltTable").fadeIn();<CR><LF>                            } else<CR><LF>                                $("#bms-name").html("");<CR><LF>                            connected = 1;<CR><LF>                        } else {<CR><LF>                            connected = 0;<CR><LF>                            let dots = "&nbsp;";<CR><LF>                            for (let i = 0; i < reconnect_counter % 4; i++)<CR><LF>                                dots += ". ";<CR><LF>                            reconnect_counter++;<CR><LF><CR><LF>                            $("#bms-name").html(<CR><LF>                                "<div style='color:red'>Connection lost<br>Reconnecting<br> " +<CR><LF>                                dots +<CR><LF>                                "</div>");<CR><LF>                            $("#VoltTable").fadeOut();<CR><LF>                        }<CR><LF>                    }, false);<CR><LF>                }<CR><LF>            });<CR><LF>        </script><CR><LF>    </body><CR><LF><CR><LF></html>

und einmal auf die Loggingseite des BMS verwiesen, wobei sich der Link jeden Tag eine Zahl hochzählt.
Dort folgt nach einiger Zeit
Timeout was reached: Operation timed out after 10001 milliseconds with 2547300 out of 3496117 bytes received in /- on line 3

Das BMS hat nur http kein https.

Bernd

Du musst auf die URL stellen, wo du dieses Ergebnis in json erhälst

{"bms_array":{"master":{"time_remaining":"Full in:<br> 0h 00 min","st_naprav":1,"time":"","date":"","mincell":4.055347,"maxcell":4.084739,"ibat":29.74018,"tmax":22,"vbat":56.96735,"soc":1,"soh":0.998128,"erro":{"present":0,"addr":0,"st":0,"con_st":0},"error":""},"slave":{"0":{"address":2,"st_temp":4,"temp_bms":21.59611,"st_celic":14,"temp":{"0":22,"1":21,"2":21.5,"3":22},"res":{"0":0.000584,"1":0.000352,"2":0.0005,"3":0.000737,"4":0.000329,"5":0.00055,"6":0.00078,"7":0.00044,"8":0.000493,"9":0.000815,"10":0.000361,"11":0.000442,"12":0.000518,"13":0.000307},"nap":{"0":4.063704,"1":4.071898,"2":4.071171,"3":4.070842,"4":4.067813,"5":4.063611,"6":4.075356,"7":4.084739,"8":4.070627,"9":4.076136,"10":4.073825,"11":4.065398,"12":4.056897,"13":4.055347}}}}},

Mit html kann der json-decoder nichts anfangen.

Die Abfrage URL sollte in etw so aussehen

http://<IP_des_WiFi_Moduls>/cell

1 „Gefällt mir“

Ja super.
Klappt!
Vielen Dank!

Hallo, wie bist du auf den Link /cell gekommen?
Ich versuche mich jetzt im Auslesen der Seite eines SMA Tripower,
ich finde aber nichts wie ich den Pfad einstellen muss um auf ein Json zu kommen.
Egal welche Verkürzung des Names ich eingeben ich lande immer auf einer HTML Seite

Bei diesen Geräten scheint es keinen direkten Pfad zu geben, wo du JSON abholen kannst.
Das geht offenbar nur über ein Script…
Allenfalls kann das Gerät Modbus, schau mal in der Doku, ob das Register aufgeführte sind
Wäre die empfohlene Möglichkeit.
Ich habe die Geräte nicht, wohlverstanden, ich habe für dich bei ChatGPT nachgefragt.
Als Alternative gibt ChatGPT folgendes Skript für Symcon aus, welches du allenfalls anpassen kannst.
Skript einmal manuell ausführen → es legt Kategorie & Variablen an und erstellt einen zyklischen Timer

<?php
/*************************************************************
 * SMA Tripower → Symcon (Sunny WebBox / Data Manager JSON-RPC)
 * - Liest GetPlantOverview und schreibt Variablen.
 * - Erstellt bei erstem Lauf automatisch Kategorie + Variablen.
 * - Optional: Basic-Auth, HTTPS (Self-Signed: verify off).
 *
 * Konfiguration
 *************************************************************/
$HOST        = 'http://192.168.1.50';   // WebBox/Data Manager Basis-URL (ohne Slash am Ende)
$USE_HTTPS   = false;                   // true, wenn https verwendet wird
$USERNAME    = '';                      // Basic-Auth Benutzer (leer, wenn nicht benötigt)
$PASSWORD    = '';                      // Basic-Auth Passwort
$POLL_SEC    = 30;                      // Abfrageintervall Sekunden
$CATEGORY    = 'SMA Tripower';          // Kategorie-Name
$CAT_IDENT   = 'SMA_TRIPOWER_CAT';      // Eindeutige Ident in Symcon

/*************************************************************
 * Ab hier nichts mehr anpassen
 *************************************************************/
$baseUrl = rtrim($HOST, '/');
if ($USE_HTTPS && stripos($baseUrl, 'https://') !== 0) {
    $baseUrl = 'https://' . preg_replace('#^https?://#i', '', $baseUrl);
} elseif (!$USE_HTTPS && stripos($baseUrl, 'http://') !== 0) {
    $baseUrl = 'http://' . preg_replace('#^https?://#i', '', $baseUrl);
}

$rpcUrl = $baseUrl . '/rpc';

/** Hilfsfunktionen **/
function ensureCategory(string $ident, string $name, int $parent = 0): int {
    $objID = @IPS_GetObjectIDByIdent($ident, $parent);
    if ($objID === false) {
        $objID = IPS_CreateCategory();
        IPS_SetParent($objID, $parent);
        IPS_SetName($objID, $name);
        IPS_SetIdent($objID, $ident);
    }
    return $objID;
}
function ensureVar(int $parent, string $ident, string $name, int $type, string $profile = ''): int {
    $varID = @IPS_GetObjectIDByIdent($ident, $parent);
    if ($varID === false) {
        $varID = IPS_CreateVariable($type);
        IPS_SetParent($varID, $parent);
        IPS_SetName($varID, $name);
        IPS_SetIdent($varID, $ident);
        if ($profile !== '') {
            IPS_SetVariableCustomProfile($varID, $profile);
        }
    }
    return $varID;
}
function ensureProfileIfMissing(string $name, int $type, string $suffix = '', int $digits = 0) {
    if (!IPS_VariableProfileExists($name)) {
        IPS_CreateVariableProfile($name, $type);
        IPS_SetVariableProfileText($name, '', $suffix);
        IPS_SetVariableProfileDigits($name, $digits);
    }
}

/**
 * JSON-RPC POST
 */
function rpcCall(string $url, array $payload, string $user = '', string $pass = ''): array {
    $opts = [
        'http' => [
            'method'  => 'POST',
            'header'  => "Content-Type: application/json\r\n",
            'content' => json_encode($payload),
            'timeout' => 10
        ],
        'ssl' => [
            'verify_peer'      => false,
            'verify_peer_name' => false
        ]
    ];
    if ($user !== '') {
        $auth = base64_encode($user . ':' . $pass);
        $opts['http']['header'] .= "Authorization: Basic {$auth}\r\n";
    }
    $ctx = stream_context_create($opts);
    $res = @file_get_contents($url, false, $ctx);
    if ($res === false) {
        throw new Exception("RPC-Request fehlgeschlagen: {$url}");
    }
    $json = json_decode($res, true);
    if (!is_array($json)) {
        throw new Exception("Ungültige JSON-Antwort vom RPC-Endpunkt.");
    }
    return $json;
}

/**
 * Werte aus GetPlantOverview extrahieren
 * Wir suchen nach Meta-Codes, die häufig verwendet werden:
 *  - GriPwr     (aktuelle AC-Leistung, W)
 *  - GriEgyTdy  (Tagesenergie, kWh)
 *  - GriEgyTot  (Gesamtenergie, kWh)
 */
function extractOverviewValues(array $rpcResult): array {
    $out = [
        'power_w'   => null,
        'day_kwh'   => null,
        'total_kwh' => null
    ];
    if (!isset($rpcResult['result']['overview']) || !is_array($rpcResult['result']['overview'])) {
        return $out;
    }
    foreach ($rpcResult['result']['overview'] as $item) {
        $meta  = $item['meta']  ?? '';
        $value = $item['value'] ?? null;
        switch ($meta) {
            case 'GriPwr':
                $out['power_w'] = is_numeric($value) ? floatval($value) : null;
                break;
            case 'GriEgyTdy':
                $out['day_kwh'] = is_numeric($value) ? floatval($value) : null;
                break;
            case 'GriEgyTot':
                $out['total_kwh'] = is_numeric($value) ? floatval($value) : null;
                break;
        }
    }
    return $out;
}

/** Kategorie & Profile anlegen **/
$catID = ensureCategory($CAT_IDENT, $CATEGORY, 0);
ensureProfileIfMissing('~Watt', VARIABLETYPE_FLOAT, ' W', 0);
ensureProfileIfMissing('~Electricity.KilowattHour', VARIABLETYPE_FLOAT, ' kWh', 3);

/** Variablen anlegen **/
$varPower   = ensureVar($catID, 'Power_W',   'Leistung (W)',        VARIABLETYPE_FLOAT, '~Watt');
$varDay     = ensureVar($catID, 'Day_kWh',   'Tagesenergie (kWh)',  VARIABLETYPE_FLOAT, '~Electricity.KilowattHour');
$varTotal   = ensureVar($catID, 'Total_kWh', 'Gesamtenergie (kWh)', VARIABLETYPE_FLOAT, '~Electricity.KilowattHour');
$varStatus  = ensureVar($catID, 'LastStatus','Status',              VARIABLETYPE_STRING, '');

/** RPC-Payload vorbereiten **/
$payload = [
    'proc'    => 'GetPlantOverview',
    'format'  => 'JSON',
    'version' => '1.0',
    'id'      => (string)time()
];

try {
    $resp = rpcCall($rpcUrl, $payload, $USERNAME, $PASSWORD);
    IPS_LogMessage('SMA RPC', 'Antwort erhalten');

    if (isset($resp['result'])) {
        $vals = extractOverviewValues($resp);
        if ($vals['power_w']   !== null) SetValueFloat($varPower, $vals['power_w']);
        if ($vals['day_kwh']   !== null) SetValueFloat($varDay,   $vals['day_kwh']);
        if ($vals['total_kwh'] !== null) SetValueFloat($varTotal, $vals['total_kwh']);
        SetValueString($varStatus, 'OK: GetPlantOverview');
    } else {
        $err = $resp['error']['message'] ?? 'Unbekannter Fehler';
        SetValueString($varStatus, 'Fehler: ' . $err);
        throw new Exception('RPC Fehler: ' . $err);
    }
} catch (Exception $e) {
    IPS_LogMessage('SMA RPC', 'Fehler: ' . $e->getMessage());
    SetValueString($varStatus, 'Fehler: ' . $e->getMessage());
}

/** Timer/Neustart-sicheres Intervall setzen (wenn als Ereignis ausgeführt) **/
$scriptID = $_IPS['SELF'] ?? 0;
if ($scriptID) {
    // Auto: Zyklisches Ereignis „SMA_Tripower_Timer“ unter dem Skript anlegen/aktualisieren
    $eventIdent = 'SMA_Tripower_Timer';
    $eventID = @IPS_GetObjectIDByIdent($eventIdent, $scriptID);
    if ($eventID === false) {
        $eventID = IPS_CreateEvent(1); // zyklisch
        IPS_SetParent($eventID, $scriptID);
        IPS_SetIdent($eventID, $eventIdent);
        IPS_SetName($eventID, 'SMA Tripower Poll');
        IPS_SetEventCyclic($eventID, 0, 0, 0, 0, 1, $POLL_SEC); // Sekündlich
        IPS_SetEventActive($eventID, true);
        IPS_SetEventScript($eventID, "IPS_RunScript({$scriptID});");
    } else {
        // Intervall aktualisieren
        IPS_SetEventCyclic($eventID, 0, 0, 0, 0, 1, $POLL_SEC);
        IPS_SetEventActive($eventID, true);
    }
}

Moin
Es kommt eine Fehlermeldung

Da ich „doof“ bin habe ich jetzt nachgesehen welche ModbusTabelle ich bisher durchgesehen habe.
Jetzt mit der richtigen Tabelle kann ich auch die Werte richtig auslesen die ich haben wollte.

Sorry für die Umstände