A small, read-only telemetry collector for the EcoFlow DELTA 3 Plus that talks to the official EcoFlow IoT Open API. It exists to prove API access, discover the response shape for your specific firmware, normalize a useful handful of metrics, and (optionally) persist readings to SQLite for later analysis.
It is intentionally not:
- A dashboard.
- A control plane. There are no commands that change device state.
- A Home Assistant / ioBroker bridge.
- A general "EcoFlow SDK".
It does speak EcoFlow's consumer-app MQTT broker for live telemetry on
models like the DELTA 3 Plus where EcoFlow has blocked the REST quota
endpoint — see mqtt-status below.
EcoFlow's device JSON varies across firmware versions and models, and the public docs lag behind reality. The fastest way to build anything on top of the API is to:
- Confirm credentials work.
- Save a raw response.
- Look at the actual keys.
- Map them into something sane.
This tool automates exactly that loop.
v1 is read-only:
- List devices on the account.
- Fetch the "quota all" payload for a specific serial number.
- Normalize a small set of fields (battery %, input/output watts, solar/grid input watts, AC/DC output enabled).
- Persist normalized readings to SQLite.
- Save raw API responses to
samples/for inspection.
- No control commands (no
setAcOutput,setChargeLimit, etc.). - No web UI or HTTP server.
- No Home Assistant or ioBroker integration.
- No multi-account / multi-tenant abstraction.
- Python 3.12+ (3.13 is pinned via
.tool-versions). uvfor environment + dependency management.- An EcoFlow developer account with API access (see below).
# Install all runtime + dev dependencies into .venv
uv sync
# Create your local config file
cp .env.example .env
# then edit .env and paste your real keysuv run automatically uses the project venv, so you do not need to
activate it.
EcoFlow exposes a developer platform that issues an accessKey /
secretKey pair for HMAC-signed requests:
Sign in with the same account that owns your DELTA 3 Plus, request API access
if your account doesn't already have it, and copy the access key + secret key
into .env. The secret key is shown once — store it securely.
Your DELTA 3 Plus must already be paired with the EcoFlow app on this account; the API only sees devices that the account owns.
All commands run via uv run ecoflow .... They share a single Settings
object loaded from environment variables and .env.
List devices visible to your account and save the raw API response.
uv run ecoflow devicesOutput: a table of (name, serial, model, online) plus
samples/devices-<UTC-timestamp>.json for further inspection.
Fetch the full device quota (the "all variables" payload) for one DELTA 3 Plus and normalize the small set of fields this tool understands.
uv run ecoflow status --device-sn YOUR-DELTA-SN
# or, if ECOFLOW_DEFAULT_DEVICE_SN is set in .env:
uv run ecoflow statusOutput: a rich panel with each normalized field. Missing fields show as
n/a and a warning at the bottom lists which normalized fields were not
found — that is your cue to inspect the saved sample and update the
candidate paths in src/ecoflow_delta/models.py.
The raw JSON is saved to samples/status-<sn>-<UTC-timestamp>.json.
Save both the device-list response and the quota-all response without trying to normalize anything. Use this when you want to inspect the actual JSON shape your firmware returns.
uv run ecoflow capture-raw --device-sn YOUR-DELTA-SNPeriodically poll the device, normalize, and insert into SQLite (if the
database file exists — run init-db once first). Prints a compact one-line
summary per tick and exits cleanly on Ctrl+C.
uv run ecoflow init-db
uv run ecoflow poll --device-sn YOUR-DELTA-SN --interval 30Interval must be at least 15 seconds to avoid hammering the API.
Create the SQLite schema at ECOFLOW_DB_PATH (default data/ecoflow.sqlite3).
Idempotent.
uv run ecoflow init-dbFetch live telemetry via the consumer-app MQTT broker. The DELTA 3 Plus
(D361 series) blocks the IoT Open API /quota/all endpoint, so MQTT is
currently the only path to live data for that model.
uv run ecoflow mqtt-status # one-shot, default 30s timeout
uv run ecoflow mqtt-status --continuous # stream until Ctrl+C
uv run ecoflow mqtt-status --debug # wire-level visibility
uv run ecoflow mqtt-status --request-format protobuf # A/B test legacy wire shapeRequires ECOFLOW_CONSUMER_EMAIL and ECOFLOW_CONSUMER_PASSWORD in
.env (the same credentials the official EcoFlow mobile app uses, not
the developer Access/Secret key pair).
The DELTA 3 Plus (D361 / DELTA_PRO_3) appears to require the JSON wire
format for the latestQuotas request — the activation signal cloud-side
that prevents the code=-2 "device offline" response we'd otherwise
hit. Citation: tolwi's hassio-ecoflow-cloud DELTA_PRO_3 class
(custom_components/ecoflow_cloud/devices/internal/delta_pro_3.py) does
not override get_quota_message, so it inherits the JSON default
from BaseInternalDevice.get_quota_message
(custom_components/ecoflow_cloud/devices/__init__.py:203-211). The
regular DELTA_3 class does override to protobuf
(internal/delta3.py:539-549). The asymmetry is deliberate.
--request-format json(the default) sends UTF-8 JSON matching tolwi's known-working DELTA_PRO_3 path. Replies arrive as JSON on/app/<userId>/<SN>/thing/property/get_replyand are surfaced asJsonQuotaData(online) orJsonOfflineMarker(offline) decoded messages.--request-format protobufsends the legacy protobuf-encoded request we shipped first. Useful for A/B testing — if JSON doesn't help, this flag lets you confirm the protobuf path still works for any protobuf-friendly EcoFlow device on the account.
If neither format produces telemetry, we've empirically exhausted the protocol-level levers we know about and the next step is the "must open the EcoFlow app to re-activate" wall described under "Failure modes" below.
EcoFlow's cloud only pushes live telemetry while it considers a device
"active." Active state is bound to the official EcoFlow mobile app's
authenticated session and persists for roughly 15 minutes after the
app is closed. While the device is active, mqtt-status returns full
state in <100ms. While idle, the broker acknowledges our latestQuotas
requests with a minimal envelope (~7 bytes, no payload) — surfaced as
an UnroutableHeader whose is_cloud_ack() predicate returns True.
When mqtt-status times out without receiving routable telemetry, the
error message names the specific failure mode it detected. The four
canonical cases:
- ZERO messages — broker accepted our subscription but sent nothing. Usually means the connection is fine but the cloud has no state to forward. Power the device on / wake it up.
code=-2(device offline cloud-side) — broker affirmatively reports the device offline. This is upstream's documented "device offline" path (ecoflow_utils.jslines 318-322): the EcoFlow cloud has lost contact with the unit. Not a project bug — power-cycle the device or wait for it to reconnect.- IDLE cloud-side (cloud-ACK envelopes only) — broker acknowledged
our
latestQuotasrequest with a content-free 7-byte response but never forwarded any telemetry. This is the "active session expired" case: the cloud is willing to talk to us but won't ask the device anything until you re-activate it. - Mixed / unknown — broker is chatty on topics we don't decode, or
sent some mix of the above three. Re-run with
--debugfor full wire logging.
To get telemetry right now: open the EcoFlow app once, then run
mqtt-status. The 15-minute active window starts from when you close
the app.
For long-running collection, mirror upstream's 5-minute poll cadence:
uv run ecoflow mqtt-status --continuous --poll-interval 300--poll-interval defaults to 300 (5 minutes) when --continuous is
set, and 0 (one-shot, no re-publish) otherwise. Pass 0 to disable
re-publishing explicitly.
To test whether a fresh authentication cycle re-activates an idle
device autonomously (this is one of two experimental levers — we don't
yet know if either works), use --reauth-interval:
uv run ecoflow mqtt-status --continuous --poll-interval 300 --reauth-interval 600 --debug--reauth-interval is experimental and may be removed once we
understand whether it helps. Every N seconds it cleanly disconnects,
re-runs the consumer auth flow with fresh credentials, reconnects, and
resumes streaming. Auth failures are retried with exponential backoff
capped at 60s.
The other experimental lever is --wake-probe. The IoT Open developer
API endpoint /iot-open/sign/device/quota/all is blocked for the
DELTA 3 Plus (returns HTTP 422 with code != "0"), but the act of
making the developer-signed call itself may serve as a wake-up signal
cloud-side. This is the open hypothesis; we don't yet know if it works.
uv run ecoflow mqtt-status --wake-probe --debug # one-shot probe + listen
uv run ecoflow mqtt-status --continuous --wake-probe --wake-interval 120 --debugWhen set, the CLI fires one wake probe before MQTT connect (and,
optionally, every --wake-interval seconds inside the continuous
loop). Each probe logs the HTTP status, EcoFlow code, latency in ms,
and EcoFlow message. The probe never raises — its whole purpose is
to tell you "we tried, here's what happened." If it works, you'll see
telemetry arrive shortly after a probe; if it doesn't, you'll just see
a stream of http_ok=False code=2010 latency=…ms lines and the device
will stay idle.
If you experiment with this and find it does (or doesn't) re-activate
your device, please leave a note in samples/ and we'll either promote
the path or remove the flag.
In --debug mode, the CLI:
- Subscribes to the
/app/<userId>/#wildcard to see every message the broker delivers for your account. - Logs every incoming topic + byte length to stderr.
- Renders unroutable headers as separate visual blocks so you can
distinguish at a glance:
device offline (cloud reports code=-2, seq=…)— affirmative offline marker.cloud-ACK envelope (seq=…, hex=…)— content-free acknowledgement.- a yellow
UnroutableHeadertable — anything else we don't route.
The DELTA 3 Plus, like most EcoFlow devices, returns a deeply nested and
firmware-dependent JSON payload. Different firmwares put the same logical
value (e.g. "battery state of charge") under different keys — soc,
bmsMaster.soc, data.params.soc, etc.
src/ecoflow_delta/models.py defines a list of candidate dotted paths per
normalized field. normalize_status() walks the candidates in order and
keeps the first non-None, coerce-able value it finds.
Discovery workflow:
uv run ecoflow capture-raw --device-sn YOUR-SN- Open
samples/status-YOUR-SN-*.jsonand find the keys you care about. - Add new dotted paths to the relevant
CANDIDATE_PATHSentry insrc/ecoflow_delta/models.py. uv run pytest -k test_normalize_statusto confirm.uv run ecoflow status— fewer fields should ben/a.
EcoFlowConfigError: missing credentials
Your .env is empty or ECOFLOW_ACCESS_KEY / ECOFLOW_SECRET_KEY are not
set. Re-copy .env.example and fill them in.
EcoFlowAuthError: 401 or 403
The signing string is wrong, the timestamp is too far from the EcoFlow
server's clock, or the keys are wrong. Check the system clock is reasonable,
then regenerate the keys at developer.ecoflow.com.
EcoFlowAPIError: code=...
The API returned a non-zero code. The message usually says why. Common
causes: device serial doesn't belong to the account, device offline, rate
limit exceeded.
status reports many n/a fields
Expected on first run. Use capture-raw, look at the JSON, and update the
candidate paths in models.py. The current candidates are educated guesses,
not authoritative.
sqlite3.OperationalError: database is locked
Another process is writing to the same file. Stop other poll instances
and retry.
- Do not commit
.env. It is in.gitignore. The access key alone is not enough to control your device, but combined with the secret key it authenticates the API as you. - Do not commit
samples/*.json. They may contain your device serial number, account-internal IDs, and firmware version. They are gitignored by default; inspect manually before sharing. - Do not paste keys into chat tools / screenshots / issue trackers. Rotate keys at developer.ecoflow.com if exposed.
# Run tests with coverage
uv run pytest -v
# Lint
uv run ruff check .
# Format
uv run ruff format .
# Type check
uv run pyright src tests
# Explore the CLI
uv run ecoflow --helpThe signing implementation in src/ecoflow_delta/signing.py is based on the
behavior documented by EcoFlow's IoT Open API developer platform. Treat it as
a starting point — re-verify against the current official docs and test
against a real account before depending on it for anything important.