API Key PowerStream Ecoflow

Hello à tous,

J’ai lu pas mal sur le net qu’il était impossible de récupérer une API key pour le powerstream de chez Ecoflow. En demandant celle pour ma delta 2 max j’ai quand même posé la question pour le powerstream et voici la réponse :

Vous avez déjà réussi à récupérer quelque chose du powerstream ?

Bonne journée

Mathis

Hello, merci pour la découverte de cette API, je cherchais justement à obtenir des infos du PowerStream.

Ancienne API

J’arrive déjà à obtenir pour mes Delta Pro des infos via :

curl https://api.ecoflow.com/iot-service/open/api/device/queryDeviceQuota?sn=...

{"code":"0","message":"Success","data":{"soc":7,"remainTime":672,"wattsOutSum":0,"wattsInSum":0},"eagleEyeTraceId":"ea1a2a1e7f17049059312765100d0007","tid":""}

Mais pour le PowerStream j’obtiens :

{"code":"6012","message":"the device is offline","eagleEyeTraceId":"ea1a2a1e7f17049062169888792d0007","tid":""}

Alors que le PS est bien Online dans l’application mobile.

C’est peut-être lié à la réponse que tu as obtenue : le PowerStream est « spécial » est doit être interrogé via leur nouvelle API.

Nouvelle API

Mais en testant avec les mêmes appKey/secretKey sur l’API proposée par Ecoflow dans la documentation de developer.ecoflow.com, je me prends une erreur :

Endpoint : https://api-e.ecoflow.com/iot-open/sign/device/quota/all?sn=...

{"code":"8521","message":"signature is wrong","tid":"90fe1d3883924f55b8147fcbd54dde0e.304.17049060759698281"}

Pourtant j’ai bien signé le message conformément à leur documentation, d’ailleurs j’arrive bien à reproduire la signature d’exemple.

J’ai obtenu mes clés d’API il y a un an en écrivant au support Ecoflow. Désormais, il faut passer par developer.ecoflow.com, mais il faut attendre une approbation. Je n’ai pas encore reçu l’approbation, c’est peut-être pour ça que la signature n’apparaît pas correcte.

Si j’ai des nouvelles, je te redis.

J’ai re-testé ce matin : la signature était bonne, mais je générais mal le timestamp (attendu en millisecondes, je le générais en secondes).

J’ai désormais bien accès au PowerStream via la nouvelle API (documentation API PowerStream), exemple pour moi ce matin :

{
    "code": "0",
    "message": "Success",
    "data": {
        "20_1.pv2Temp": 200,
        "20_1.invOutputWatts": 0,
        "20_1.pv2WarningCode": 0,
        "20_1.pv2InputVolt": 0,
        "20_1.pv2RelayStatus": 0,
        "20_1.wirelessWarnCode": 0,
        "20_1.batInputVolt": 485,
        "20_1.invErrCode": 0,
        "20_1.dynamicWatts": 0,
        "20_1.batStatue": 5,
        "20_1.pv2OpVolt": 0,
        "20_1.invOnOff": 1,
        "20_1.invOpVolt": 2304,
        "20_1.installCountry": 18002,
        "20_1.llcOpVolt": 26,
        "20_1.feedProtect": 1,
        "20_1.batTemp": 140,
        "20_1.upperLimit": 100,
        "20_1.invInputVolt": 0,
        "20_1.pv1ErrCode": 128,
        "20_1.pv1InputVolt": 0,
        "20_1.invFreq": 500,
        "20_1.heartbeatFrequency": 15,
        "20_1.chgRemainTime": 143999,
        "20_134.task10": {
            "taskIndex": 9,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.llcTemp": 190,
        "20_134.task11": {
            "taskIndex": 10,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.pv1Temp": 200,
        "20_1.pv2InputCur": 0,
        "20_1.batOpVolt": 540,
        "20_1.permanentWatts": 500,
        "20_1.bpType": 1,
        "20_1.llcWarningCode": 0,
        "20_1.invOutputCur": 10,
        "20_1.pv1InputWatts": 0,
        "20_1.lowerLimit": 0,
        "20_1.invTemp": 0,
        "20_1.llcStatue": 5,
        "20_1.pv1WarnCode": 0,
        "20_1.wirelessErrCode": 0,
        "20_1.batWarningCode": 0,
        "20_1.pv1RelayStatus": 0,
        "20_1.llcErrCode": 0,
        "20_1.invStatue": 1,
        "20_1.batInputWatts": 0,
        "20_1.pv2Statue": 1,
        "20_1.batErrCode": 0,
        "20_1.dsgRemainTime": 143999,
        "20_1.batSoc": 1,
        "20_1.invBrightness": 33,
        "20_134.task7": {
            "taskIndex": 6,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.pv2ErrCode": 128,
        "20_1.invWarnCode": 0,
        "20_134.task6": {
            "taskIndex": 5,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.invRelayStatus": 0,
        "20_134.task9": {
            "taskIndex": 8,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_134.task8": {
            "taskIndex": 7,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_134.task3": {
            "taskIndex": 2,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_134.task2": {
            "taskIndex": 1,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_134.task5": {
            "taskIndex": 4,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_134.task4": {
            "taskIndex": 3,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.llcInputVolt": 540,
        "20_1.updateTime": "2024-01-11 05:02:55",
        "20_134.task1": {
            "taskIndex": 0,
            "type": 0,
            "timeRange": {
                "isConfig": false,
                "timeData": 0,
                "timeMode": 0,
                "startTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "stopTime": {
                    "sec": 0,
                    "week": 0,
                    "min": 0,
                    "hour": 0,
                    "month": 0,
                    "year": 0,
                    "day": 0
                },
                "isEnable": false
            }
        },
        "20_1.supplyPriority": 1,
        "20_1.ratedPower": 8000,
        "20_1.batInputCur": 0,
        "20_1.pv1Statue": 1,
        "20_134.updateTime": "2024-01-11 04:33:04",
        "20_1.pv1OpVolt": 0,
        "20_1.pv1InputCur": 0,
        "20_1.installTown": 0,
        "20_1.pv2InputWatts": 0
    },
    "tid": "de6a4c29db754c7f8446b99fd32cbc5a.301.17049597217118689"
}
1 « J'aime »

Bonjour,
je viens d’acheter le powerstream, et découvre qu’il serait maintenant possible d’ intégrer les infos dans jeedom.
d’après vous, il est maintenant possible de récupérer ces infos dans le plugin Ecoflow existant ?

Hello @c-geek ,
nenes1704 m’a demandé si je pouvais éventuellement intégrer sa Powerstream dans le plugin EcoFlow.
Je ne suis pas un expert dans ce qui est des appels API mais je me penche sur la question…

Je butté pas mal sur leur doc car j’ai l’impression qu’il me manque des infos/connaissances.
J’ai tenté ça avec ma River pro 2 :

curl -X GET \
https://api-e.ecoflow.com/iot-open/sign/device/quota/all?sn=P2EBZ7XE9150XXX \
-H 'Content-Type: application/json;charset=UTF-8' \
-H 'accessKey: 5db8844a5ab74a9c837773eb1c0d4XXX' \
-H 'nonce: 123456' \
-H 'timestamp: 1706977644123' \
-H 'sign: e26cef6ff141feb4fd2fb43a54e688be795378d8694156ff7c643cc074530a91'

Avec pour réponse : signature is wrong.
J’ai pourtant bien généré ma signature avec la chaine de paramètres que j’ai encryptée avec la secretkey puis passée en base64.
Je ne vois pas non plus comment je peux générer le paramètre « nonce » pour lequel j’ai mis 123456 par défaut.
Est-ce que tu utilises l’appKey en tant que accessKey?
Dans le doc Ecoflow, ils parlent de param1 et param2 et je ne sais pas trop comment les utiliser dans ma request initiale.

Aurais-tu encore un sample de tes tests?

Merci
Lionel

Bonjour,

je ne suis pas un expert mais je serais interressé de savoir comment vous avez généré les informations demandé, comme la signature par exemple.
Je viens de recevoir mes accès et de créer un couple accesskey/secretkey. Mais je bloque sur la suite.

Cordialement,

Mathieu

1 « J'aime »

Hello @m_arth ,
en fait j’ai suvi le lien originel de @Mathis90 .
Il y a une page qui détaille un peu plus la méthodo :
https://developer-eu.ecoflow.com/us/document/generalInfo
Après, tout le reste c’est de la manipulation d’un script bash ou directement dans le shell.

Si jamais tu progresses, fais moi signe. Je suis toujours intéressé par ajouter cette feature (ou partager le code avec d’autres bien sûr)

Lionel

Salut, oui j’ai vu, mais rien compris à la partie signature :slight_smile:
Si je trouve un peu de temps je vais voir pour me mettre sur le sujet.

Mathieu

Bonjour, désolé je ne vois vos réponses que maintenant suite à un MP.

Voici le code de signature, je l’ai fait en Rust mais n’importe quel langage de programmation peut le faire :

pub static ACCESS_KEY: &str = "clé d'accès fournie par https://developer-eu.ecoflow.com/us";
pub static SECRET_KEY: &str = "clé secrète fournie par https://developer-eu.ecoflow.com/us";

async fn quota(serial_number: &String) -> anyhow::Result<()> {
    let nonce = 537642;
    let timestamp = time::SystemTime::now().duration_since(time::UNIX_EPOCH)?.as_millis().to_string();
    let signed = format!("sn={}&accessKey={}&nonce={}&timestamp={}", serial_number, ACCESS_KEY, nonce, timestamp);
    let signature = sign(signed.as_str(), SECRET_KEY);
    println!("{}", signature);
    let req = reqwest::Client::builder()
        .build()?
        .get(reqwest::Url::parse(format!("https://api-e.ecoflow.com/iot-open/sign/device/quota/all?sn={}", serial_number).as_str()).unwrap())
        .header("accessKey", ACCESS_KEY)
        .header("timestamp", timestamp)
        .header("nonce", nonce)
        .header("sign", signature);
    println!("{:?}", req);
    let response = req.send().await?;
    let res: String = response
        .text()
        .await?;
    println!("{:?}", res);

    Ok(())
}

Bonjour,

J’ai téléchargé le code d’exemple de l’API Ecoflow pour me faire la main sur les données exposées… pas moyen de trouver la quantité d’énergie produite dans la journée !
La documentation parle de l’interrogation de chaque type d’appareil de leur gamme mais pas les données globales.

Quelqu’un a-t’il cherché (et trouvé !) comment récupérer cette information SVP ?

NB : le code d’exemple fourni dans la doc de l’API pour la fonction getAllQuota est vérolé : il ne faut pas passer le numéro de série en argument, cette partie étant construite dans la classe HttpUtil.
String url = GET_ALL_QUOTA_URL + « ?sn= » + sn; => String url = GET_ALL_QUOTA_URL;

Merci et bonne journée !

Bonsoir,

Après avoir pas mal galéré avec l’API Ecoflow, je vous partage mon code PHP qui fonctionne sur la nouvelle API et qui renvoit toutes les infos de chaque périphériques. Vous pouvez le mettre à la racine de votre Jeedom.

Voici le lien : GitHub - jsys/ecoflow: Pilotage du matériel Ecoflow

Ensuite je me suis créé un plugin script (commande type json, requete : data>moninfo) qui me permet de récupérer les infos dont j’ai besoin.

Voilà. Si ça peut servir.

Tu pourrais nous en dire plus sur la methode.
Car j’ai mis le fichier PHP a la racine de jeedom.
si je tape http://IPjedomm/ecoflow.php?sn=XXXXXX
j’obtiens les informations de ma delta. Mais comment les rendres exploitable sur jeedom?

Je suis un peu hors sujet, mais je voulais vous soumettre un truc :

Je pense qu’on peut se servir d’un Ecoflow Powerstream comme inverseur si on a pas besoin de beaucoup de puissance. Juste besoin de 2 prises.

J’ai une installation photovoltaïque en autoconsommation, je vais acheté une batterie Ecoflow pour recharger plus facilement mon hybride avec le soleil.

Mais j’aurai un surplus que je souhaite utiliser, la nuit notamment.

Ma solution :

En sortie Powerstream : branché sur une simple prise pour alimenter la maison.

En entrée Powerstream : la batterie (prise et câble dédiés).

En entrée Batterie : charge de la batterie par une simple prise quand la production solaire le permet.

La limite est de 600W pour alimenter la maison, cela suffit pour alimenter totalement quand il n’y a pas de gros consommateur, ou alléger la consommation si il y en a. On peut passer à 1200W avec 2 systèmes etc…

Est-ce OK ?

Bonjour à tous,
Je possède un Ecoflow powerocean avec des panneaux solaires
J’arrive à lire les données temps réel mais j’aimerai lire l’historique .
Vous avez une idée ?
Voilà le code python qui fonctionne :
import hmac
import json
import random
import requests
import time

def hmac_sha256(data, key):
hashed = hmac.new(key.encode(‹ utf-8 ›), data.encode(‹ utf-8 ›), hashlib.sha256).digest()
return ‹  ›.join(format(byte, ‹ 02x ›) for byte in hashed)

def get_qstring(params): return ‹ & ›.join([f"{key}={params[key]}" for key in sorted(params.keys())])

def get_api(url, key, secret, params=None):
nonce = str(random.randint(100000, 999999))
timestamp = str(int(time.time() * 1000))
headers = {‹ accessKey ›:key,‹ nonce ›:nonce,‹ timestamp ›:timestamp}
sign_str = (get_qstring(params) + ‹ & › if params else ‹  ›) + get_qstring(headers)
headers[‹ sign ›] = hmac_sha256(sign_str, secret)
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200: return response.json()
else: log.error(f"get_api: {response.text}")

if name == « main »:

url = ‹ https://api.ecoflow.com ›
path = ‹ /iot-open/sign/device/quota/all ›
key = ‹ xxx ›
secret = ‹ xxx ›
sn = ‹ xxx ›
payload = get_api(f"{url}{path}",key,secret,{‹ sn ›:sn})
print(json.dumps(payload,indent=2))