Skip to content

Repository files navigation

ecoflow-delta-monitor

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.

Purpose

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:

  1. Confirm credentials work.
  2. Save a raw response.
  3. Look at the actual keys.
  4. Map them into something sane.

This tool automates exactly that loop.

Scope

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.

Non-goals

  • 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.

Requirements

  • Python 3.12+ (3.13 is pinned via .tool-versions).
  • uv for environment + dependency management.
  • An EcoFlow developer account with API access (see below).

Setup

# 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 keys

uv run automatically uses the project venv, so you do not need to activate it.

Getting EcoFlow API credentials

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.

Commands

All commands run via uv run ecoflow .... They share a single Settings object loaded from environment variables and .env.

devices

List devices visible to your account and save the raw API response.

uv run ecoflow devices

Output: a table of (name, serial, model, online) plus samples/devices-<UTC-timestamp>.json for further inspection.

status

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 status

Output: 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.

capture-raw

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-SN

poll

Periodically 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 30

Interval must be at least 15 seconds to avoid hammering the API.

init-db

Create the SQLite schema at ECOFLOW_DB_PATH (default data/ecoflow.sqlite3). Idempotent.

uv run ecoflow init-db

mqtt-status

Fetch 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 shape

Requires 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).

--request-format (JSON vs protobuf)

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_reply and are surfaced as JsonQuotaData (online) or JsonOfflineMarker (offline) decoded messages.
  • --request-format protobuf sends 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.

MQTT telemetry behavior

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.

Failure modes the diagnostic surfaces

When mqtt-status times out without receiving routable telemetry, the error message names the specific failure mode it detected. The four canonical cases:

  1. 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.
  2. code=-2 (device offline cloud-side) — broker affirmatively reports the device offline. This is upstream's documented "device offline" path (ecoflow_utils.js lines 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.
  3. IDLE cloud-side (cloud-ACK envelopes only) — broker acknowledged our latestQuotas request 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.
  4. Mixed / unknown — broker is chatty on topics we don't decode, or sent some mix of the above three. Re-run with --debug for full wire logging.

Re-activating an idle device

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.

--wake-probe (experiment)

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 --debug

When 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.

--debug mode

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 UnroutableHeader table — anything else we don't route.

Field normalization notes

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:

  1. uv run ecoflow capture-raw --device-sn YOUR-SN
  2. Open samples/status-YOUR-SN-*.json and find the keys you care about.
  3. Add new dotted paths to the relevant CANDIDATE_PATHS entry in src/ecoflow_delta/models.py.
  4. uv run pytest -k test_normalize_status to confirm.
  5. uv run ecoflow status — fewer fields should be n/a.

Troubleshooting

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.

Security warnings

  • 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.

Development

# 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 --help

The 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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages