1.) Login using Mobile App creds and get auth token (Only change Email and Password - Rest can stay the same)
curl -s -X POST "https://api.ecoflow.com/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "email@address.com",
"password": "base64-encoded-password",
"userType": "ECOFLOW",
"os": "ios",
"scene": "IOT_APP",
"appVersion": "3.0.1",
"osVersion": "15.4.1",
"oauth": {
"bundleId": "com.ef.EcoFlow"
}
}' | jq -r '.data.token'
2.) Use Token to retrieve device info (Replace token.value.here with the long string (JWT) from the above command)
curl -s -X GET "https://api-e.ecoflow.com/iot-service/user/device" -H "Authorization: Bearer token.value.here" | jq
soc is the current battery value
3.) Generate the mqtt.conf file with this bash script. Should look something like this
connection bridge-ecoflow
address mqtt-e.ecoflow.com:8883
remote_username app-4c384blahblahblah
remote_password f17ea2ablahblahblah
# Friendly named topics for device woofles
topic "" in 0 bridge-ecoflow/woofles/data /app/device/property/R351blahblahblah
topic "" both 0 bridge-ecoflow/woofles/set /app/1673640896999415809/R351blahblahblah/thing/property/set
topic "" both 0 bridge-ecoflow/woofles/get /app/1673640896999415809/R351blahblahblah/thing/property/get
topic "" in 0 bridge-ecoflow/woofles/set_reply /app/1673640896999415809/R351blahblahblah/thing/property/set_reply
topic "" in 0 bridge-ecoflow/woofles/get_reply /app/1673640896999415809/R351blahblahblah/thing/property/get_reply
remote_clientid ANDROID_CECAFblahblahblahblahblahblahblahblahblah15809
cleansession true
try_private true
bridge_insecure false
bridge_protocol_version mqttv311
bridge_tls_version tlsv1.2
bridge_capath /etc/ssl/certs/
3.) mosquito -c ./mqtt.conf
4.) Run the following python3 script, and see some battery data (Note: % doesn't come through often)
import sys
import json
import time
import threading
import paho.mqtt.client as mqtt
# Heartbeat interval (in seconds)
HEARTBEAT_INTERVAL = 10
state = {"watts_in": 0, "watts_out": 0, "soc": "..."}
current_sec = [HEARTBEAT_INTERVAL]
print_lock = threading.Lock()
PING_PAYLOAD = json.dumps({
"id": 12345678,
"version": "1.0",
"moduleType": 0,
"operateType": "latestQuotas",
"params": {}
})
def countdown_worker(client):
"""Counts down, clears the line, sends heartbeat, and repeats."""
while True:
for sec in range(HEARTBEAT_INTERVAL, 0, -1):
current_sec[0] = sec
with print_lock:
sys.stdout.write(f"\r\033[KHeartbeat in: {sec}")
sys.stdout.flush()
time.sleep(1)
# Reached 0: Clear line and send heartbeat ping
with print_lock:
sys.stdout.write("\r\033[K")
sys.stdout.flush()
try:
client.publish("bridge-ecoflow/woofles/get", PING_PAYLOAD)
except Exception:
pass
def on_message(client, userdata, msg):
try:
params = json.loads(msg.payload.decode()).get("params", {})
has_watts = "pd.wattsInSum" in params or "pd.wattsOutSum" in params
has_soc = "pd.soc" in params
if "pd.wattsInSum" in params:
state["watts_in"] = params["pd.wattsInSum"]
if "pd.wattsOutSum" in params:
state["watts_out"] = params["pd.wattsOutSum"]
if "pd.soc" in params:
state["soc"] = params["pd.soc"]
# If telemetry updated, print the new line and preserve the countdown below it
if has_watts or has_soc:
with print_lock:
# Erase current countdown line, print status line, then re-render countdown
sys.stdout.write(f"\r\033[KInput: {state['watts_in']} W | Output: {state['watts_out']} W | Battery SOC: {state['soc']}%\n")
sys.stdout.write(f"\r\033[KRefreshing in: {current_sec[0]}")
sys.stdout.flush()
except Exception:
pass
def on_connect(client, userdata, flags, rc, properties=None):
client.subscribe("bridge-ecoflow/woofles/data")
t = threading.Thread(target=countdown_worker, args=(client,), daemon=True)
t.start()
try:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
except AttributeError:
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
try:
client.connect("127.0.0.1", 1883, 60)
client.loop_forever()
except KeyboardInterrupt:
print("\nExiting...")