Use when building apps, scripts or integrations for EcoFlow PowerStream MicroInverter devices (STREAM series, model BK01Z). Covers API signing, MQTT connection over WebSocket, real device field names (which differ from the official docs), offline detection, and single-file PWA architecture. Trigger on any mention of EcoFlow, PowerStream, STREAM inverter, mqtt-e.ecoflow.com, or EcoFlow developer API.
Resources
12Install
npx skillscat add danielw3b/ecoflow-powerstream-pwa Install via the SkillsCat registry.
EcoFlow PowerStream MQTT & PWA Skill
When to use this skill
Use when building apps, scripts or automations for EcoFlow PowerStream MicroInverter devices
(STREAM series, model BK01Z). Covers API signing, MQTT connection, real device field names,
and single-file PWA architecture.
1. API Signing (HMAC-SHA256)
Endpoint base (EU): https://api-e.ecoflow.com
Endpoint base (Global): https://api.ecoflow.com
Signature algorithm (from official Java demo MyMapUtil.java):
- Flatten request body recursively: nested objects → dot notation (
a.b=v), arrays → bracket notation (a[0]=v) - Sort flattened keys alphabetically
- Build string:
sorted_k=v&...&accessKey=X&nonce=X×tamp=X
— accessKey/nonce/timestamp are appended after sorted params, NOT sorted into them - HMAC-SHA256 hex of that string using secretKey
function flattenObject(obj, prefix = "") {
const out = {};
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k;
if (Array.isArray(v)) {
v.forEach((item, i) => {
const ak = `${key}[${i}]`;
if (item !== null && typeof item === "object") Object.assign(out, flattenObject(item, ak));
else out[ak] = item;
});
} else if (v !== null && typeof v === "object") {
Object.assign(out, flattenObject(v, key));
} else { out[key] = v; }
}
return out;
}
function buildSign(params, accessKey, secretKey, nonce, ts) {
const flat = params ? flattenObject(params) : {};
const kvStr = Object.keys(flat).sort().map(k => `${k}=${flat[k]}`).join("&");
const payload = kvStr
? `${kvStr}&accessKey=${accessKey}&nonce=${nonce}×tamp=${ts}`
: `accessKey=${accessKey}&nonce=${nonce}×tamp=${ts}`;
return crypto.createHmac("sha256", secretKey).update(payload).digest("hex");
}2. Key REST Endpoints
GET /iot-open/sign/device/list
→ returns devices[] with .sn, .deviceName, .online (1=online, 0=offline)
GET /iot-open/sign/certification
→ returns {url, port, certificateAccount, certificatePassword}
→ use certificateAccount as MQTT clientId AND usernameNote: GET /iot-open/sign/device/quota/all?sn=X returns empty for STREAM devices — not supported via REST. Use MQTT instead.
3. MQTT Connection
Broker: mqtt-e.ecoflow.com
Port for raw TCP/TLS: 8883 — works with developer credentials
Port for WebSocket/TLS: 8084 — required for browser PWA
// Node.js (raw TLS)
const sock = tls.connect({ host: "mqtt-e.ecoflow.com", port: 8883, rejectUnauthorized: false });
// Browser (WebSocket)
const ws = new WebSocket("wss://mqtt-e.ecoflow.com:8084/mqtt", ["mqtt"]);MQTT CONNECT packet:
- clientId =
certificateAccount - username =
certificateAccount - password =
certificatePassword - keepAlive = 30s
Subscribe topics:
/open/${certificateAccount}/${sn}/quota ← live telemetry (JSON, incremental)
/open/${certificateAccount}/${sn}/status ← online/offline eventsPublish topics (set commands):
/open/${certificateAccount}/${sn}/set4. Real Device Field Names (STREAM MicroInverter)
The device sends different field names than the official documentation. Fields arrive in small incremental batches — merge into accumulated state:
| Real Field | Doc Field | Description | Unit |
|---|---|---|---|
powGetPv |
pv1InputWatts |
PV1 power | W (float, no ÷10) |
powGetPv2 |
pv2InputWatts |
PV2 power | W (float, no ÷10) |
gridConnectionPower |
invOutputWatts |
Grid feed-in | W (float) |
gridConnectionVol |
invOutputVolt |
Grid voltage | V (float) |
gridConnectionFreq |
invFreq |
Grid frequency | Hz (float) |
gridConnectionAmp |
invOutputCur |
Grid current | A (float) |
gridConnectionSta |
— | Status string | e.g. "PANEL_FEED_GRID" |
plugInInfoPvVol |
pv1InputVolt |
PV1 voltage | V (float, no ÷10) |
plugInInfoPv2Vol |
pv2InputVolt |
PV2 voltage | V (float) |
plugInInfoPvAmp |
pv1InputCur |
PV1 current | A (float) |
plugInInfoPv2Amp |
pv2InputCur |
PV2 current | A (float) |
moduleWifiRssi |
wifiRssi |
WiFi signal | dBm (float) |
feedGridModePowMax |
ratedPower |
Max feed power | W |
batSoc |
batSoc |
Battery % | int |
Doc fields that use ÷10 scaling (integer × 0.1 = real value):pv1InputVolt, pv1InputCur, pv1InputWatts, pv2InputVolt, pv2InputCur,pv2InputWatts, batInputVolt, batInputCur, batInputWatts, batTemp,invOutputVolt, invOutputCur, invOutputWatts, invFreq
5. MQTT Message Format
Quota push (device → app):
{
"powGetPv": 349.6,
"gridConnectionPower": 205.3,
"gridConnectionVol": 234.1,
"gridConnectionFreq": 49.99,
"plugInInfoPvVol": 32.31,
"plugInInfoPvAmp": 0.783
}Fields arrive in small partial batches (2–6 fields at a time). Merge into accumulated state. Full upload every ~2 min (displayPropertyFullUploadPeriod: 120000).
Status push:
{ "params": { "status": 1 } } // 1 = online, 0 = offline6. Offline Detection Strategy
Primary: Subscribe to /open/${clientId}/${sn}/status — instant online/offline
Secondary: Poll GET /iot-open/sign/device/list every 2 min — check .online field
Fallback timer: If no data for 5 min → mark offline (do NOT use short timers, causes loops)
Critical: On MQTT reconnect, do NOT reset device status to "waiting" — keep existing live/offline state. Only set "waiting" on very first subscription ever.
7. Known Limitations & Gotchas
- No historical data API for PowerStream — accumulate from live MQTT only
- Consumer API (
api.ecoflow.com/auth/login) does not work for SSO (Google) accounts - MQTT credentials from
/iot-open/sign/certificationare long-lived but scene-specific - Multiple tabs/devices can connect simultaneously with same
certificateAccountclientId — broker handles it fine - Incremental updates — never assume a full state in one message, always merge
- Binary protobuf format exists on consumer MQTT (port 8883 consumer broker) but open API delivers JSON
POST /iot-open/sign/device/quotaalways returns[8521] signature is wrong— use GET insteadGET /iot-open/sign/device/quota/allreturnscode:0but emptydatafor STREAM — not supported
8. Single-File PWA Pattern
The app works as a standalone index.html opened from file:// or served locally:
- WebSocket to
wss://mqtt-e.ecoflow.com:8084/mqttworks fromfile://origin - CORS: EcoFlow API allows browser requests with valid signatures
- Service Workers do NOT work on
file://— omit or handle gracefully localStoragefor config, Wh history, device names- Screen Wake Lock API (
navigator.wakeLock.request("screen")) keeps display on
9. AI Assistant Reference
This skill was derived from a real production app built with Claude (Anthropic, claude.ai).
The complete development conversation is available as a transcript covering:
- Protocol reverse engineering
- MQTT authentication debugging
- PWA architecture decisions
- Mobile UI iteration
Model: Claude Sonnet 4.6