EcoFlow IoT-Platform - offizielle API
Vor über 1,5 Jahren habe ich mich mit einer EcoFlow River 2 Pro beschäftigt. Dieses Gerät wollte ich nutzen, um PV-Überschuss zu laden und diesen mit in die Nacht zu nehmen - als Batteriespeicher quasi. Zusätzlich sollte das Gerät als USV dienen. Nachdem EcoFlow gefragt hatte, ob ich das Gerät nicht testen möchte, habe ich genau diesen Plan geschildert. Leider gab es zu diesem Zeitpunkt keine offizielle Schnittstelle, über welche ich das Vorhaben überhaupt realisieren konnte. Mit viel Reverse Engineering der App konnte ich dann per MQTT auf die Cloud-Server zugreifen und die Anfragen der App nachstellen. Aber das war extrem umständlich.
Und genau das möchte die neue IoT-Platform von EcoFlow nun besser machen. In diesem Beitrag möchte ich kurz zeigen, was genau möglich ist und wie die Geräte angesprochen werden können. Da die Dokumentation nicht vollständig ist, kann ich leider nur eine Basis liefern. Du musst dann selbst ausprobieren, was genau geht (und was nicht). Denn EcoFlow hat ja nicht nur Power Stations im Sortiment, sondern auch Wechselrichter, große Batteriespeicher und Smart Home Panels. All diese Geräte soll die IoT-Platform vereinen. Soweit die Theorie. Schauen wir uns das einmal genauer an.
Was wird benötigt?
- Ein EcoFlow-Gerät
- Ein Cloud-Konto (mit der App eingerichtet), welchem das EcoFlow-Gerät zugeordnet wird
- Ein Developer-Konto auf der IoT-Platform (gleiche Zugangsdaten wie die der App!)
- Ein Access-Key und Secret (kann auf der Platform selbst erstellt werden)
Es ist also kein Kontakt mehr mit dem Hesteller nötig. Damals habe ich die Infos/API-Schlüssel per Mail bekommen.
Video
Produkt-Links
Code-Beispiele
Beispiele (ioBroker)
Die ersten Schritte konnte ich schnell im ioBroker machen. Hier hole ich z.B. alle Geräte und lese die Infos aus und kann schon erste Werte auf meiner River 2 Pro schreiben:
// v0.2
const crypto = require('node:crypto');
const axios = require('axios');
// Hier Zugangsdaten von https://developer-eu.ecoflow.com/us/security eintragen
const accessKey = '';
const secretKey = '';
function flattenKeys(obj, prefix) {
const getPrefix = (k) => {
if (!prefix) return k;
return (Array.isArray(obj)) ? `${prefix}[${k}]` : `${prefix}.${k}`;
};
let res = {};
Object.keys(obj).forEach(k => {
if (typeof obj[k] === 'object') {
res = { ...res, ...flattenKeys(obj[k], getPrefix(k)) };
} else {
res[getPrefix(k)] = obj[k];
}
});
return res;
}
async function apiRequest(method, url, data) {
const sha256 = (str, key) => crypto.createHmac('sha256', key).update(str).digest('hex');
const nonce = String(100000 + Math.floor(Math.random() * 100000));
const timestamp = String(Date.now());
// Generate data string (sorted by keys)
let dataStr = '';
if (data) {
const flatData = flattenKeys(data);
const flatDataKeys = Object.keys(flatData);
flatDataKeys.sort();
dataStr = flatDataKeys.map(k => `${k}=${flatData[k]}`).join('&') + '&';
}
const uri = `${dataStr}accessKey=${accessKey}&nonce=${nonce}×tamp=${timestamp}`;
const sign = sha256(uri, secretKey);
const apiResponse = await axios(
{
method,
baseURL: 'https://api.ecoflow.com',
url,
data,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
accessKey,
nonce,
timestamp,
sign,
},
}
);
console.info(`Received ${apiResponse.status} from ${method} to ${url} (${uri}): ${JSON.stringify(apiResponse.data)}`);
if (apiResponse.status === 200 && apiResponse.data.code == 0) {
return apiResponse.data;
} else if (apiResponse.data.code) {
throw new Error(`${apiResponse.data.code}: ${apiResponse.data.message}`);
}
}
async function apiGetRequest(url) {
return apiRequest('get', url);
}
async function apiPostRequest(url, data) {
return apiRequest('post', url, data);
}
async function apiPutRequest(url, data) {
return apiRequest('put', url, data);
}
async function getDeviceQuota(sn, quotas) {
return apiPostRequest('/iot-open/sign/device/quota', {
sn: sn,
params: {
quotas
}
});
}
apiGetRequest('/iot-open/sign/device/list').then(listResponse => {
for (const device of listResponse.data) {
console.log(`Found device ${device.productName} with serial number ${device.sn} (online: ${device.online})`);
/*
apiGetRequest(`/iot-open/sign/device/quota/all?sn=${device.sn}`).then(reponse => {
console.info(reponse.data);
});
*/
apiPutRequest('/iot-open/sign/device/quota', {
id: 123456789,
version: '1.0',
sn: device.sn,
moduleType: 5,
operateType: 'acOutCfg',
params: {
enabled: 1,
xboost: 0,
out_voltage: 230,
out_freq: 50
}
}).then(() => {
// Ein wenig warten, bis die Werte gesetzt wurden
setTimeout(() => {
getDeviceQuota(
device.sn,
[
'mppt.cfgAcEnabled',
'mppt.cfgAcXboost',
'mppt.cfgAcOutVol',
'mppt.cfgAcOutFreq'
]
).then(quotaResponse => {
console.log(quotaResponse.data);
});
}, 5000);
});
}
});
MQTT-Verbindung
Unter /iot-open/sign/certification
kann man mit seinem key und secret Zugangsdaten für den MQTT-Server anfordern. Beispiel-Response:
{
"code": "0",
"message": "Success",
"data": {
"certificateAccount": "open-XXXXXXX",
"certificatePassword": "XXXXXX",
"url": "mqtt-e.ecoflow.com",
"port": "8883",
"protocol": "mqtts"
},
"eagleEyeTraceId": "XXXXXX",
"tid": ""
}
Mit diesen Daten kann man sich dann verbinden (MQTT über TLS). Interessante Topics sind:
/open/${certificateAccount}/${sn}/status
/open/${certificateAccount}/${sn}/quota
Auch hier können Werte gesetzt und gelesen werden.
/open/${certificateAccount}/${sn}/set
/open/${certificateAccount}/${sn}/set_reply
/open/${certificateAccount}/${sn/get
/open/${certificateAccount}/${sn}/get_reply
Fazit
Insgesamt eine gute Entwicklung, dass EcoFlow sich öffnet. Die API ist leider an manchen Stellen nicht gut dokumentiert, wodurch man nicht so richtig versteht, was man warum setzen muss (gerade in den Requests zum setzen von neuen Werten). Es gibt einige Beispiele in der Dokumentation, aber vollständig ist das nicht.
Transparenz-Hinweis (Level 2)
Für diesen Beitrag wurden mir Produkte kostenfrei zur Verfügung gestellt! Es wurden keinerlei Bedingungen, Richtlinien oder Vorgaben bezüglich der Inhalte, welche ich in meiner Bewertung äußern darf, auferlegt.
Darüber hinaus habe ich keine zusätzliche Vergütung erhalten.