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×tamp=%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