EcoFlow API

Gibt es dazu ein Script oder Modul? Wie hast du das gelöst? Da ich auch eine Delta Pro und Powerstream betreibe wäre ich sehr an einer Steuerung der Einspeiseleitung ins Haus interessiert…

1 „Gefällt mir“

Ich habe bisher nur drei oder vier rudimentäre, unaufgeräumte Scripte die ich mir aus den Infos und Beispielen in der EcoFlow-API Facebook Gruppe abgeleitet habe.
Wollte daraus demnächst mal irgendwann was generischeres machen was zumindest einige Abfragen vereint.

Kann mal die die ich habe zur Verfügung stellen falls du schon spielen willst.
Eines zum Setzen der Einspeiseleistung habe ich noch nicht, aber das kann ich dir sicher nächste Woche aus einem der anderen ableiten. Steht bei mir eh als nächstes auf der Liste :wink:

1 „Gefällt mir“

Zum Screenshot:
Leistungen der beiden Solarmodule (DPro und PS), ladestand DPro, Lesitungs schwarze Steckdose (=die direkt an DPro) und Kühlbox (an 12V DPro) ermittle ich aus der Statusabfrage der DeltaPro und PS.
Leistung des Kühlschranks kommt ebenfalls vom PS weil der Kühlschrank an einem EcoFlow Schalt/Mess Plug hängt.
Waschmaschine hängt an einem Homematic Schalt/Mess Adapter.
Momentanleisting Stromzähler lese ich über ein Hichi WLAN Interface aus.
Die weiße Steckdose symbolisiert die restlichen Verbraucher im Haus. Den Wert berechne ich mir aus Lesitung Stromzähler + Abgabe PS - aller anderen 230V Verbraucher im Bild

Mittlerweile schaut das PopUp auch noch etwas besser aus und beinhaltet Tageswerte für Erzeugung und Netzbezug

1 „Gefällt mir“

Tach zusammen!
Habe auch ganz neu einen EcoFlow PowerStream Wechselrichter und setze mich ebenfalls gerade mit der API auseinander…
Wer schon einen access-/secret-key aus dem Developer-Portal hat, kann sich ja mal mit folgendem Codeschnipsel spielen:

<?php
$sn = 'HW123ABC';
$accessKey = 'DeinAccessKey';
$secretKey = 'DeinSecretKey';

$timestamp = (string)intval(microtime(true) * 1000);
$nonce = substr(base64_encode(hash("sha256", $timestamp, True)), 0, -1);
$str = 'accessKey='.$accessKey.'&nonce='.$nonce.'&timestamp='.$timestamp;
$sign = hash_hmac('sha256', $str, $secretKey);


//$url = 'https://api-e.ecoflow.com/iot-open/sign/device/list';
$url = 'https://api-e.ecoflow.com/iot-open/sign/device/quota/all?sn='.$sn;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$headers = array(
    'Content-Type: application/json',
    'accessKey:'.$accessKey,
    'nonce:'.$nonce,
    'timestamp:'.$timestamp,
    'sign:'.$sign,
    
);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$resp = json_decode(curl_exec($curl));
curl_close($curl);

echo $resp->data->{'20_1.invDemandWatts'}.PHP_EOL;
print_r($resp);
?>

Ich habe nun auch meine Freischaltung zum Developer-Portal bekommen. Und mit dem Codeschnipsel von @Strichcode kann ich von meiner DELTA Pro auch Werte lesen. Danke.
Wie funtioniert das nun aber, wenn ich z.B. die Ausgänge schalten möchte oder andere Parameter an der DELTA Pro ändern möchte?
Hat das schon jemand hier umgesetzt?

Danke für einen weiteren „Codeschnipsel“ :wink:

Gruß
Rainer

Hallo @erpe,
ich habe noch keine Powerstation, deswegen habe ich noch nichts zum schalten…
Die Doku zu Deiner Delta Pro gibt es hier: Ecoflow Developer
So wie ich das verstehe, musst Du eigentlich nur den Curl-Aufruf anpassen und in den passenden PUT-Befehl umbauen. Beispiele findest Du auf der verlinkten Seite, mein Code-Schnipsel ist im Prinzip der GET von dort, für die PHP-Syntax gibt es Online-Übersetzer wie z.B. hier:

// Generated by curl-to-PHP: http://incarnate.github.io/curl-to-php/
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api-e.ecoflow.com/iot-open/sign/device/quota');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');

curl_setopt($ch, CURLOPT_POSTFIELDS, "{\"sn\":\"DCABZ***\",\"params\":{\"cmdSet\":32,\"id\":66,\"enabled\":1}}");

$headers = array();
$headers[] = 'Content-Type: application/json;charset=UTF-8';
$headers[] = 'Accesskey: OCHzRuj6NLF7o43***';
$headers[] = 'Timestamp: 1681872798000';
$headers[] = 'Nonce: 238752';
$headers[] = 'Sign: 0f3a1b102cd9a306307b7a9a0f0a9add7b8bfc93cf11***';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'Error:' . curl_error($ch);
}
curl_close($ch);

Vielleicht kommst Du damit schon alleine etwas weiter…
Gruß
Strichcode

Ungetestet und ins blaue geschrieben vielleicht so um den X-Boost (id66) zu aktivieren:

<?php
$sn = 'HW123ABC';
$accessKey = 'DeinAccessKey';
$secretKey = 'DeinSecretKey';

$timestamp = (string)intval(microtime(true) * 1000);
$nonce = substr(base64_encode(hash("sha256", $timestamp, True)), 0, -1);
$str = 'accessKey='.$accessKey.'&nonce='.$nonce.'&timestamp='.$timestamp;
$sign = hash_hmac('sha256', $str, $secretKey);


$url = 'https://api-e.ecoflow.com/iot-open/sign/device/quota';

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($curl, CURLOPT_POSTFIELDS, "{\"sn\":".$sn.",\"params\":{\"cmdSet\":32,\"id\":66,\"enabled\":1}}");

$headers = array(
    'Content-Type: application/json;charset=UTF-8',
    'accessKey:'.$accessKey,
    'nonce:'.$nonce,
    'timestamp:'.$timestamp,
    'sign:'.$sign,
    
);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$resp = json_decode(curl_exec($curl));
if (curl_errno($curl)) {
    echo 'Error:' . curl_error($curl);
}
curl_close($curl);

print_r($resp);
?>

Hallo,
nun hat es etwas gedauert…

Aber mit viel Lesen und Suchen in der EcoFlow API Dokumentation und im Internet konnte ich nun die Funtionen der API für mein DELTA PRO erfolgreich umsetzen. Das EcoFlow Java-Script war an einigen Stellen hilfreich, da das Senden/Schalten dann doch eine Herausforderung war.

Das „EcoFlow API Main“ Script enthält die Funktionen zum Abfragen und Senden von Parametern und der Device-Liste.

Das EcoFlow API Main Script:

<?php

// this should be replaced your accessKey and secretKey from EcoFlow Open Platform

$accessKey = "Dein accessKey";
$secretKey = "Dein secretKey";
$HOST = "https://api-e.ecoflow.com";
$GET_MQTT_CERTIFICATION_URL = $HOST . "/iot-open/sign/certification";
$DEVICE_LIST_URL = $HOST . "/iot-open/sign/device/list";
$SET_QUOTA_URL = $HOST . "/iot-open/sign/device/quota";
$GET_QUOTA_URL = $HOST . "/iot-open/sign/device/quota";
$GET_ALL_QUOTA_URL = $HOST . "/iot-open/sign/device/quota/all";

function getMQTTCertification() {
    global $accessKey, $secretKey, $GET_MQTT_CERTIFICATION_URL;
    $jsonObject = [];
    $response = getHttpUriRequest("GET", $GET_MQTT_CERTIFICATION_URL, $jsonObject, $accessKey, $secretKey);
    echo "response: getMQTTCertification|" . $response;
}

function deviceList() {
    global $accessKey, $secretKey, $DEVICE_LIST_URL;
    $jsonObject = [];
    $response = getHttpUriRequest("GET", $DEVICE_LIST_URL, $jsonObject, $accessKey, $secretKey);
    if ($response['code'] === '0') {
        $data = $response['data'];
        return $data;
    }
        throw new RuntimeException('Error getting deviceList: ' . $response['message']);
}

function setQuota(string $deviceSN, string $params) {
    global $accessKey, $secretKey, $SET_QUOTA_URL;
    $json = '{"sn": "'.$deviceSN.'",'.$params.'}';
    $jsonObject = json_decode($json, true);
    $response = getHttpUriRequest("PUT", $SET_QUOTA_URL, $jsonObject, $accessKey, $secretKey);
    if ($response['code'] === '0') {
        return $response['message'];
    }
        throw new RuntimeException('Error setting quota information: ' . $response['message']);
}

function getQuota(string $deviceSN, string $params) {
    global $accessKey, $secretKey, $GET_QUOTA_URL;
    $json = '{"sn":"'.$deviceSN.'",'.$params.'}';
    $jsonObject = json_decode($json, true);
    $response = getHttpUriRequest("POST", $GET_QUOTA_URL, $jsonObject, $accessKey, $secretKey);
    if ($response['code'] === '0') {
        $data = $response['data'];

        ksort($data, SORT_STRING);

        return $data;
    }
        throw new RuntimeException('Error getting quota information: ' . $response['message']);
}

function getAllQuota(string $deviceSN) {
    global $accessKey, $secretKey, $GET_ALL_QUOTA_URL;
    $url = $GET_ALL_QUOTA_URL . "?sn=" . $deviceSN;
    $jsonObject = [];
    $response = getHttpUriRequest("GET", $url, $jsonObject, $accessKey, $secretKey);
    if ($response['code'] === '0') {
        $data = $response['data'];

        ksort($data, SORT_STRING);

        return $data;
    }
        throw new RuntimeException('Error getting all quota information: ' . $response['message']);
}


function getHttpUriRequest($httpMethod, $url, $req, $accessKey, $secretKey) {
    $nonce = createNonce(); //(string) random_int(100000, 999999);
    $timestamp = createTimestamp(); //time() * 1000;
    $signature = generateSignature($nonce, $timestamp, $req);

    $headers = array(
        'Content-Type: application/json;charset=UTF-8',
        'accessKey:'.$accessKey,
        'nonce:'.$nonce,
        'timestamp:'.$timestamp,
        'sign:'.$signature,
    );

    if ($httpMethod === 'GET') {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        $request = json_decode(curl_exec($curl), true);
        curl_close($curl);
    } elseif ($httpMethod === 'PUT') {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($req));
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        $request = json_decode(curl_exec($curl), true);
        curl_close($curl);
    } elseif ($httpMethod === 'POST') {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($req));
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        $request = json_decode(curl_exec($curl), true);
        curl_close($curl);
    } else {
        throw new Exception("HTTP method not supported");
    }
    return $request;
        throw new RuntimeException('Error getting information: ' . $request['message']);
}


/**
    * Generates a signature for the given nonce, timestamp, and data.
    *
    * The generated signature is used for authentication purposes in the EcoFlow API. It ensures the integrity and
    * security of the communication between the client and the API by verifying the authenticity of the request.
    *
    * The method follows these steps to generate the signature:
    * 1. Flattens the input `$data` array into a single-dimensional array using the `flattenData()` helper function.
    * 2. Sorts the flattened data array alphabetically by the keys using the `ksort()` function with the `SORT_STRING`
    * flag.
    * 3. Concatenates the flattened and sorted data array into a string using the `http_build_query()` function.
    * 4. Appends the access key, nonce, and timestamp to the concatenated string using the `sprintf()` function.
    * 5. Removes any leading ampersand (`&`) from the resulting base string using the `ltrim()` function.
    * 6. Encrypts the base string using the HMAC-SHA256 algorithm along with the secret key.
    * 7. Converts the resulting byte array into a hexadecimal string using the `bin2hex()` function.
    *
    * @param string $nonce The nonce value. This is a random string that is used once in each request to prevent
    *                          replay attacks.
    * @param string $timestamp The timestamp value. This is the current time in milliseconds.
    * @param array{
    *     sn?: string,
    *     cmdCode?: string,
    *     params?: array<string, string|int>
    * } $data The data to be included in the signature. This can include the serial number (sn), command code
    *     (cmdCode), and other parameters (params).
    *
    * @return string The generated signature. This is a hexadecimal string that is used for authentication in the
    *                EcoFlow API.
    *
    * @throws Exception If an error occurs during the generation of the signature.
    */
function generateSignature(string $nonce, string $timestamp, array $data): string {
    global $accessKey, $secretKey;
    // Flatten, sort, and concatenate the data array.
    $flattenedData = flattenData($data);
    ksort($flattenedData, SORT_STRING);

    // Concatenate accessKey, nonce, and timestamp.
    $signatureBase = http_build_query($flattenedData);
    $signatureBase = urldecode($signatureBase);
    $signatureBase .= sprintf('&accessKey=%s&nonce=%s&timestamp=%s', $accessKey, $nonce, $timestamp);
    $signatureBase = ltrim($signatureBase, '&');

    // Encrypt with HMAC-SHA256 and secretKey.
    $signatureBytes = hash_hmac('sha256', $signatureBase, $secretKey, true);

    // Convert bytes to hexadecimal string.
    return bin2hex($signatureBytes);
}


/**
    * Flattens a multidimensional array into a one-dimensional array with dot notation keys.
    *
    * This method is used to flatten a multidimensional array into a one-dimensional array. The keys of the
    * one-dimensional array are generated by concatenating the keys of the multidimensional array with a dot ('.').
    * The keys are prefixed with the provided prefix, if any. If the key is 0, it is replaced with an empty string.
    *
    * If the value of a key-value pair is an array, a recursive call is made to flatten the nested array. The nested
    * array is passed as the `$data` parameter, and the newly generated key is passed as the `$prefix` parameter.
    *
    * After processing all the key-value pairs, the method returns the `$flattened` array, which contains the
    * flattened version of the input `$data` array.
    *
    * @param array<int|string, array<int|string, array<int, string>|int|string>|string>|array<string, int|string> $data
    *
    * @return array<int|string, int|string> The flattened one-dimensional array with dot notation keys.
    */
function flattenData(array $data, string $prefix = ''): array {
    $flattened = [];
    foreach ($data as $key => $value) {
        if (is_integer($key)) {
            $newKey = $prefix === '' ? $key : sprintf('%s%s', $prefix, "[". $key."]");
        }
        else{
            $newKey = $prefix === '' ? $key : sprintf('%s.%s', $prefix, $key);
        }
        $newKey = is_string($newKey) ? rtrim($newKey, '.') : (string) $newKey;

        if (is_array($value)) {
            // Recursive call for nested arrays.
            $flattened = array_merge($flattened, flattenData($value, $newKey));

            continue;
        }

        // Append to a flattened array.
        $flattened[$newKey] = $value;
    }
    return $flattened;
}


/**
    * Generates a random nonce.
    *
    * This method generates a random nonce for the EcoFlow API. A nonce is a random string that is used once in each
    * request to prevent replay attacks. The nonce is generated as a random integer between 100000 and 999999, which is
    * then converted to a string.
    *
    * The purpose of the nonce is to ensure the uniqueness and integrity of each API request by including a one-time,
    * randomly generated value. This helps to protect against duplicate or replayed requests.
    *
    * The method utilises PHP's built-in `random_int()` function to generate a cryptographically secure random integer
    * within the specified range. The generated integer is then cast to a string to be included in the API request.
    *
    * @return string The randomly generated nonce as a string.
    *
    * @throws RandomException If an error occurs during the generation of the random integer, such as a failure of the
    *                         random number generator or an invalid range.
    */
function createNonce(): string {
    return (string) random_int(100000, 999999);
}


/**
    * Generates a timestamp in milliseconds.
    *
    * This method generates a timestamp for use in the EcoFlow API. The timestamp is created by instantiating a
    * DateTime object with the current time in the UTC time zone. The DateTime object is then formatted to include
    * microseconds using the format string 'U.u'. The formatted time is multiplied by 1000 to convert it to
    * milliseconds and rounded to the nearest whole number.
    *
    * The generated timestamp is used as part of the authentication process when making requests to the EcoFlow API.
    * It ensures that the timestamp is based on a standardised time reference and is not affected by local time
    * differences, making it suitable for use in a distributed system.
    *
    * @return string The generated timestamp as a string representation of the current time in milliseconds.
    *
    * @throws Exception If an error occurs during the creation of the DateTime object or when formatting the time.
    *                   The calling code should handle this exception appropriately.
    */
function createTimestamp(): string {
    $dateTime = new DateTime((string) null, new DateTimeZone('UTC'));
    $formatted = (int) $dateTime->format('U.u');

    return (string) round($formatted * 1000);
}

und hier noch ein Script zum Aufruf und Testen der Funktionen.

<?php

include_once("12345.ips.php"); //hier das EcoFlow API Main Script eintragen

$deviceSN = "DCE*****"; //sn deines Gerätes

$resp = getAllQuota($deviceSN); //liefert ein sortiertes Array mit allen Parametern/Werten
//$resp = setQuota($deviceSN, '"params":{ "cmdSet": 32, "id": 66, "enabled": 0, "xboost": 0 }'); //liefert bei Erfolg "Success"
//$resp = getQuota($deviceSN, '"params":{ "cmdSet": 32, "id": 66, "quotas": ["inv.cfgAcEnabled", "inv.cfgAcXboost"] }'); //liefert Array mit angefragten Parameter(n) und Wert(en)
//$resp = deviceList(); //liefert Array der Geräte mit sn, deviceName, online, productName

print_r($resp);

Die „gut dokumentierten“ Funktionen sind von github, die ich aber an einigen Stellen anpassen musste, damit z.B. das Senden von Befehlen überhaupt funktionierte.

Was hiervon auch mit anderen EcoFlow Geräten funktioniert, kann ich leider nicht sagen - ich habe nur ein DELTA PRO.

Viel Spaß beim Testen.

Gruß
Rainer