tolwi / hassio-ecoflow-cloud

EcoFlow Cloud Integration for Home Assistant
281 stars 47 forks source link

Feature Request: Delta Pro Ultra support #193

Open RyanWor opened 5 months ago

RyanWor commented 5 months ago

I would love to integrate my DPU with HA. I am happy to help with any discovery or testing.

foxthefox commented 5 months ago

First we have to find the suitable data for integration. I have made a small writup how to connect to the mqtt broker via node-red: https://gist.github.com/foxthefox/11ac35433c228085a8f5e25d5651632a If you can make such a setup, it would be great.

After going to the APP to look at data of the DPU, something should be logged in debug4 , that should be postet here (needed for displaying values) If there are commands from the APP possible, triggering them would cause messages in debug5, that should be also postet here (needed for commands from HA)

jasonjan2003 commented 4 months ago

I was able to set up node-red with my DPU, but everything just shows up as Invalid JSON string. I haven't used node-red before, but the nodes do show as connected, and entries in the debug log coincide with me manipulating the Ecoflow App. Any ideas?

2/9/2024, 12:19:13 PM[node: d9ecc011d0be04ea](https://homeassistant.address/api/hassio_ingress/.../#)
msg : string[19]
"Invalid JSON string"
foxthefox commented 4 months ago

Hmm, not so clear for me. The debug says something in relation data going into HA. If that is true then its far beyond the scope of test setup. But without seeing the node sketch, I can only guess.

Anyhow, as you wrote that the debug log coincide with the action via the App, its a good sign. So we have to focus on the data which is triggering the log. If you have used the debug-nodes, then each of them posts its message into the right hand side log. The entry of that log starts with the topic and is followed by the message itself. May be it is necessary to click on the msg text or even a second time when there is only "object" written. After clicking and expanding the message, it should be readable text containing also {},[],: If this is shown, we are very close to our goal. If you hover on the message, there are two small icons on the right edge. The one which is "Copy value" should be clicked and and the content pasted here. There might be different JSON-strings and there might be some with the same structure/content. It would be great if we could catch one of each type.

jasonjan2003 commented 4 months ago

I figured out how to show the raw debug content. Unfortunately, it is not in JSON format. See table below. I hope the way everything is organized makes sense. For each combination of Function and Action, entries are sorted from top to bottom by order in which they were received. The payload shown below is in decimal format. I'll try to take a crack at reverse engineering some of the shorter ones to start, but everyone feel free to give it a shot...

I saw America/Chicago and ios when I viewed some of the payloads in a hex editor; those are accurate.

Here's the log in CSV files, in decimal form and hex form: mqtt_log_dec.csv mqtt_log_hex.csv

Edit: There are more entries in the files. I couldn't fit everything in the comment.

Function Action Debug node # Topic PayloadSize Payload QOS Retain
MainPage Open 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 42 [10,40,10,2,8,31,16,32,24,2,32,1,40,1,56,3,64,2,72,103,80,2,88,1,112,247,189,167,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 3 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get 18 [10,16,16,32,24,32,112,132,190,167,136,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 4 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get_reply 737 [10,85,10,71,8,3,16,0,24,100,32,0,40,30,48,0,56,60,64,1,72,90,80,10,88,1,96,10,104,136,14,112,132,7,120,120,128,1,172,2,136,1,208,5,144,1,0,152,1,0,160,1,1,168,1,175,9,178,1,15,65,109,101,114,105,99,97,47,67,104,105,99,97,103,111,16,2,64,2,72,3,112,132,190,167,136,1,10,40,10,26,10,24,8,1,24,89,37,0,0,208,193,45,0,0,192,69,56,174,167,1,64,90,72,10,88,28,16,2,64,2,72,4,112,132,190,167,136,1,10,12,16,6,64,3,72,28,112,132,190,167,136,1,10,135,2,10,248,1,8,131,128,128,8,16,148,18,24,4,80,0,88,0,96,0,104,0,112,1,120,10,130,1,0,168,1,89,176,1,1,184,1,1,192,1,136,14,200,1,160,56,208,1,173,167,1,216,1,0,224,1,100,232,1,30,205,2,0,0,37,67,213,2,0,0,23,67,221,2,0,0,0,0,229,2,0,0,0,0,237,2,0,0,0,0,245,2,0,0,0,0,253,2,0,0,0,0,133,3,238,141,147,66,141,3,234,250,37,66,149,3,161,55,15,66,157,3,0,0,0,0,165,3,0,0,0,0,173,3,0,0,0,0,181,3,0,0,0,0,189,3,0,0,0,0,197,3,196,167,36,67,205,3,0,0,0,0,213,3,0,0,0,0,221,3,0,0,0,0,160,6,0,168,6,0,176,6,0,184,6,255,255,255,255,15,192,6,255,255,255,255,15,200,6,255,255,255,255,15,208,6,0,216,6,0,224,6,0,232,6,0,200,7,0,208,7,255,255,255,255,15,216,7,255,255,255,255,15,224,7,255,255,255,255,15,232,7,0,240,7,0,248,7,0,128,8,0,16,2,64,2,72,1,112,132,190,167,136,1,10,197,2,10,182,2,8,0,168,1,4,176,1,16,184,1,228,16,192,1,0,200,1,1,200,2,2,208,2,0,216,2,60,224,2,1,232,2,1,240,2,212,181,6,248,2,140,205,6,128,3,60,136,3,0,144,3,0,152,3,0,160,3,0,168,3,0,237,3,129,85,213,66,245,3,219,249,126,190,253,3,0,0,0,0,133,4,0,0,208,65,141,4,126,61,134,60,149,4,253,237,235,56,157,4,126,61,134,60,165,4,246,156,52,60,173,4,30,158,52,60,181,4,250,154,21,29,189,4,7,233,52,59,197,4,0,0,0,0,205,4,0,0,0,0,213,4,0,0,0,0,221,4,4,33,242,66,229,4,78,126,0,63,237,4,4,33,242,66,245,4,70,22,178,62,253,4,11,235,240,66,133,5,146,159,155,62,141,5,11,235,240,66,149,5,0,0,0,0,157,5,4,33,242,66,165,5,0,0,0,0,173,5,8,134,113,67,181,5,0,0,0,0,189,5,0,0,0,0,197,5,0,0,0,0,205,5,0,0,0,0,213,5,0,0,0,0,221,5,169,26,244,66,229,5,217,196,173,63,237,5,0,0,0,0,245,5,0,0,0,0,253,5,0,0,0,0,133,6,0,0,0,0,141,6,0,0,36,66,149,6,0,0,40,66,157,6,0,0,40,66,165,6,0,0,36,66,168,6,37,181,6,0,0,0,0,16,2,64,2,72,2,112,132,190,167,136,1] 0 FALSE
MainPage Open 3 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get 18 [10,16,16,32,24,32,112,141,194,167,136,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 4 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get_reply 737 [10,85,10,71,8,3,16,0,24,100,32,0,40,30,48,0,56,60,64,1,72,90,80,10,88,1,96,10,104,136,14,112,132,7,120,120,128,1,172,2,136,1,208,5,144,1,0,152,1,0,160,1,1,168,1,175,9,178,1,15,65,109,101,114,105,99,97,47,67,104,105,99,97,103,111,16,2,64,2,72,3,112,141,194,167,136,1,10,40,10,26,10,24,8,1,24,89,37,0,0,254,194,45,0,0,192,69,56,173,167,1,64,90,72,10,88,28,16,2,64,2,72,4,112,141,194,167,136,1,10,12,16,6,64,3,72,28,112,141,194,167,136,1,10,135,2,10,248,1,8,131,128,128,8,16,148,18,24,4,80,0,88,0,96,0,104,0,112,1,120,10,130,1,0,168,1,89,176,1,1,184,1,1,192,1,136,14,200,1,160,56,208,1,173,167,1,216,1,0,224,1,100,232,1,30,205,2,0,0,37,67,213,2,0,0,23,67,221,2,0,0,0,0,229,2,0,0,0,0,237,2,0,0,0,0,245,2,0,0,0,0,253,2,0,0,0,0,133,3,238,141,147,66,141,3,234,250,37,66,149,3,161,55,15,66,157,3,0,0,0,0,165,3,0,0,0,0,173,3,0,0,0,0,181,3,0,0,0,0,189,3,0,0,0,0,197,3,196,167,36,67,205,3,0,0,0,0,213,3,0,0,0,0,221,3,0,0,0,0,160,6,0,168,6,0,176,6,0,184,6,255,255,255,255,15,192,6,255,255,255,255,15,200,6,255,255,255,255,15,208,6,0,216,6,0,224,6,0,232,6,0,200,7,0,208,7,255,255,255,255,15,216,7,255,255,255,255,15,224,7,255,255,255,255,15,232,7,0,240,7,0,248,7,0,128,8,0,16,2,64,2,72,1,112,141,194,167,136,1,10,197,2,10,182,2,8,0,168,1,4,176,1,16,184,1,228,16,192,1,0,200,1,1,200,2,2,208,2,0,216,2,60,224,2,1,232,2,1,240,2,194,181,6,248,2,250,204,6,128,3,60,136,3,0,144,3,0,152,3,0,160,3,0,168,3,0,237,3,137,65,213,66,245,3,213,120,153,191,253,3,0,0,0,0,133,4,0,0,254,66,141,4,68,91,131,60,149,4,121,158,154,48,157,4,68,91,131,60,165,4,194,219,35,60,173,4,58,207,7,60,181,4,58,195,60,57,189,4,234,60,86,60,197,4,73,33,162,40,205,4,0,0,0,0,213,4,0,0,0,0,221,4,203,166,242,66,229,4,142,3,28,63,237,4,203,166,242,66,245,4,166,126,175,62,253,4,145,239,240,66,133,5,172,27,152,62,141,5,145,239,240,66,149,5,0,0,0,0,157,5,203,166,242,66,165,5,0,0,0,0,173,5,46,203,113,67,181,5,0,0,0,0,189,5,0,0,0,0,197,5,0,0,0,0,205,5,0,0,0,0,213,5,0,0,0,0,221,5,75,203,243,66,229,5,159,75,172,63,237,5,0,0,0,0,245,5,0,0,0,0,253,5,0,0,0,0,133,6,0,0,0,0,141,6,0,0,36,66,149,6,0,0,40,66,157,6,0,0,40,66,165,6,0,0,36,66,168,6,37,181,6,0,0,0,0,16,2,64,2,72,2,112,141,194,167,136,1] 0 FALSE
MainPage Open 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 49 [10,47,10,9,8,128,216,155,174,6,16,175,9,16,32,24,2,32,1,40,1,56,3,64,2,72,105,80,9,88,1,112,139,198,167,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 42 [10,40,10,2,8,31,16,32,24,2,32,1,40,1,56,3,64,2,72,103,80,2,88,1,112,140,198,167,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 36 [10,34,16,32,24,2,32,1,40,1,56,3,64,32,72,89,88,1,112,140,198,167,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 6 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set_reply 35 [10,33,16,2,24,32,32,1,40,1,56,3,64,32,72,89,88,1,96,1,112,140,198,167,136,1,120,129,58,128,1,3,136,1,1] 0 FALSE
MainPage Open 3 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get 18 [10,16,16,32,24,32,112,153,202,167,136,1,186,1,3,105,111,115] 0 FALSE
MainPage Open 4 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get_reply 737 [10,85,10,71,8,3,16,0,24,100,32,0,40,30,48,0,56,60,64,1,72,90,80,10,88,1,96,10,104,136,14,112,132,7,120,120,128,1,172,2,136,1,208,5,144,1,0,152,1,0,160,1,1,168,1,175,9,178,1,15,65,109,101,114,105,99,97,47,67,104,105,99,97,103,111,16,2,64,2,72,3,112,153,202,167,136,1,10,40,10,26,10,24,8,1,24,89,37,0,0,184,193,45,0,0,192,69,56,173,167,1,64,90,72,10,88,28,16,2,64,2,72,4,112,153,202,167,136,1,10,12,16,6,64,3,72,28,112,153,202,167,136,1,10,135,2,10,248,1,8,131,128,128,8,16,148,18,24,4,80,0,88,0,96,0,104,0,112,1,120,10,130,1,0,168,1,89,176,1,1,184,1,1,192,1,136,14,200,1,160,56,208,1,173,167,1,216,1,0,224,1,100,232,1,30,205,2,0,0,35,67,213,2,0,0,11,67,221,2,0,0,0,0,229,2,0,0,0,0,237,2,0,0,0,0,245,2,0,0,0,0,253,2,0,0,0,0,133,3,95,25,120,66,141,3,234,250,37,66,149,3,161,55,15,66,157,3,0,0,0,0,165,3,0,0,0,0,173,3,0,0,0,0,181,3,0,0,0,0,189,3,0,0,0,0,197,3,122,10,35,67,205,3,0,0,0,0,213,3,0,0,0,0,221,3,0,0,0,0,160,6,0,168,6,0,176,6,0,184,6,255,255,255,255,15,192,6,255,255,255,255,15,200,6,255,255,255,255,15,208,6,0,216,6,0,224,6,0,232,6,0,200,7,0,208,7,255,255,255,255,15,216,7,255,255,255,255,15,224,7,255,255,255,255,15,232,7,0,240,7,0,248,7,0,128,8,0,16,2,64,2,72,1,112,153,202,167,136,1,10,197,2,10,182,2,8,0,168,1,4,176,1,16,184,1,228,16,192,1,0,200,1,1,200,2,2,208,2,0,216,2,60,224,2,1,232,2,1,240,2,208,181,6,248,2,136,205,6,128,3,60,136,3,0,144,3,0,152,3,0,160,3,0,168,3,0,237,3,248,83,213,66,245,3,174,71,97,190,253,3,0,0,0,0,133,4,0,0,184,65,141,4,60,180,131,60,149,4,189,254,150,53,157,4,60,180,131,60,165,4,145,214,35,60,173,4,58,247,22,60,181,4,152,169,202,42,189,4,158,182,72,60,197,4,202,134,79,37,205,4,0,0,0,0,213,4,0,0,0,0,221,4,41,223,241,66,229,4,240,81,3,63,237,4,41,223,241,66,245,4,163,40,177,62,253,4,7,195,241,66,133,5,84,115,154,62,141,5,7,195,241,66,149,5,0,0,0,0,157,5,41,223,241,66,165,5,0,0,0,0,173,5,24,209,113,67,181,5,0,0,0,0,189,5,0,0,0,0,197,5,0,0,0,0,205,5,0,0,0,0,213,5,0,0,0,0,221,5,69,198,243,66,229,5,179,55,171,63,237,5,0,0,0,0,245,5,0,0,0,0,253,5,0,0,0,0,133,6,0,0,0,0,141,6,0,0,36,66,149,6,0,0,40,66,157,6,0,0,40,66,165,6,0,0,36,66,168,6,37,181,6,0,0,0,0,16,2,64,2,72,2,112,153,202,167,136,1] 0 FALSE
MainPage Return 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 42 [10,40,10,2,8,31,16,32,24,2,32,1,40,1,56,3,64,2,72,103,80,2,88,1,112,130,188,190,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
MainPage Return 3 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get 18 [10,16,16,32,24,32,112,161,192,190,136,1,186,1,3,105,111,115] 0 FALSE
MainPage Return 4 /app/MQTT_USER_ID/DEVICE_ID/thing/property/get_reply 737 [10,85,10,71,8,3,16,0,24,100,32,0,40,30,48,0,56,60,64,1,72,90,80,10,88,1,96,10,104,136,14,112,132,7,120,120,128,1,172,2,136,1,208,5,144,1,0,152,1,0,160,1,1,168,1,175,9,178,1,15,65,109,101,114,105,99,97,47,67,104,105,99,97,103,111,16,2,64,2,72,3,112,161,192,190,136,1,10,40,10,26,10,24,8,1,24,89,37,0,0,216,193,45,0,0,192,69,56,150,167,1,64,90,72,10,88,28,16,2,64,2,72,4,112,161,192,190,136,1,10,12,16,6,64,3,72,28,112,161,192,190,136,1,10,135,2,10,248,1,8,131,128,128,8,16,148,18,24,4,80,0,88,0,96,0,104,0,112,1,120,10,130,1,0,168,1,89,176,1,1,184,1,1,192,1,136,14,200,1,160,56,208,1,150,167,1,216,1,0,224,1,100,232,1,30,205,2,0,0,40,67,213,2,0,0,18,67,221,2,0,0,0,0,229,2,0,0,0,0,237,2,0,0,0,0,245,2,0,0,0,0,253,2,0,0,0,0,133,3,86,112,134,66,141,3,191,17,39,66,149,3,106,118,19,66,157,3,0,0,0,0,165,3,0,0,0,0,173,3,0,0,0,0,181,3,0,0,0,0,189,3,0,0,0,0,197,3,43,157,39,67,205,3,0,0,0,0,213,3,0,0,0,0,221,3,0,0,0,0,160,6,0,168,6,0,176,6,0,184,6,255,255,255,255,15,192,6,255,255,255,255,15,200,6,255,255,255,255,15,208,6,0,216,6,0,224,6,0,232,6,0,200,7,0,208,7,255,255,255,255,15,216,7,255,255,255,255,15,224,7,255,255,255,255,15,232,7,0,240,7,0,248,7,0,128,8,0,16,2,64,2,72,1,112,161,192,190,136,1,10,197,2,10,182,2,8,0,168,1,4,176,1,16,184,1,228,16,192,1,0,200,1,1,200,2,2,208,2,0,216,2,60,224,2,1,232,2,1,240,2,172,181,6,248,2,228,204,6,128,3,60,136,3,0,144,3,0,152,3,0,160,3,0,168,3,0,237,3,6,65,213,66,245,3,221,36,134,190,253,3,0,0,0,0,133,4,0,0,216,65,141,4,68,211,98,63,149,4,152,238,235,56,157,4,68,211,98,63,165,4,58,6,36,60,173,4,91,94,153,60,181,4,30,170,74,42,189,4,218,14,211,60,197,4,0,0,0,0,205,4,195,245,200,63,213,4,0,0,0,0,221,4,241,238,241,66,229,4,114,180,5,63,237,4,241,238,241,66,245,4,104,86,179,62,253,4,131,56,241,66,133,5,40,164,151,62,141,5,131,56,241,66,149,5,0,0,0,0,157,5,241,238,241,66,165,5,0,0,0,0,173,5,186,147,113,67,181,5,0,0,0,0,189,5,0,0,0,0,197,5,0,0,0,0,205,5,0,0,0,0,213,5,0,0,0,0,221,5,38,205,243,66,229,5,67,136,173,63,237,5,0,0,0,0,245,5,0,0,0,0,253,5,0,0,0,0,133,6,0,0,0,0,141,6,0,0,36,66,149,6,0,0,40,66,157,6,0,0,40,66,165,6,0,0,36,66,168,6,37,181,6,0,0,0,0,16,2,64,2,72,2,112,161,192,190,136,1] 0 FALSE
DC_OUTPUT-USB ON 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 42 [10,40,10,2,8,1,16,32,24,2,32,1,40,1,56,3,64,2,72,68,80,2,88,1,112,190,145,182,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
DC_OUTPUT-USB ON 6 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set_reply 35 [10,33,16,2,24,32,32,1,40,1,56,3,64,2,72,68,88,1,96,1,112,190,145,182,136,1,120,129,58,128,1,3,136,1,1] 0 FALSE
DC_OUTPUT-USB OFF 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 36 [10,34,16,32,24,2,32,1,40,1,56,3,64,2,72,68,88,1,112,221,144,186,136,1,128,1,19,136,1,1,186,1,3,105,111,115] 0 FALSE
DC_OUTPUT-USB OFF 6 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set_reply 35 [10,33,16,2,24,32,32,1,40,1,56,3,64,2,72,68,88,1,96,1,112,221,144,186,136,1,120,129,58,128,1,3,136,1,1] 0 FALSE
foxthefox commented 4 months ago

Very good job. That is very useful and organized. Unfortunately it is not userfriendly as the the nice JSON replies of the other devices. But there is already some good starting point with the Powerstream, which is having similiar format. The messages in HEX could be decoded with https://protobuf-decoder.netlify.app and in lucky case at least the header structure could be same with powerstream. It looks in the first moment like that, but needs a deeper dive. First field is always the payload (the command) when it is from ..../set and at least that looks promising.

jasonjan2003 commented 4 months ago

Cool! I looked into the get_reply messages with 737 elements, and here's what I identified:

Table 1 Byte Range Field Number Type Content Name
0.-87 1 protobuf <See Table 2>
Table 2 Byte Range Field Number Type Content Name
0.-73 1 protobuf <See Table 3>
73-75 1 varint As uint: 3 Unknown
75-77 2 varint As uint: 0 Unknown
77-79 1 varint As uint: 3 Unknown
79-85 2 varint As uint: 0 Unknown
Table 3: Mostly Settings Byte Range Field Number Type Content Name
0-2 1 varint As uint: 3 Unknown
2-4 2 varint As uint: 0 Unknown
4-6 3 varint As uint: 100 Unknown
6-8 4 varint As uint: 0 Backup reserve (0: Disabled, 1: Enabled)
8-10 5 varint As uint: 30 Backup reserve level [%]
10-12 6 varint As uint: 0 Unknown
12-14 7 varint As uint: 60 Backup reserve solar charging level [%]
14-16 8 varint As uint: 0 Battery Preconditioning (0: Disabled, 1: Enabled)
16-18 9 varint As uint: 90 Charge Limit [%]
18-20 10 varint As uint: 10 Discharge Limit [%]
20-22 11 varint As uint: 1 Unknown
22-24 12 varint As uint: 10 Unknown
24-27 13 varint As uint: 1900 AC Charging Speed [W]
27-30 14 varint As uint: 900 Power In/Out [W]
30-32 15 varint As uint: 120 Device Timeout [min] (0=Never)
32-36 16 varint As uint: 300 LCD screen timeout [sec] (0=Never)
36-40 17 varint As uint: 240 12V DC Timeout [min] (0=Never)
40-43 18 varint As uint: 0 AC Timeout [min] (0=Never)
43-46 19 varint As uint: 0 Unknown
46-49 20 varint As uint: 1 Unknown
49-53 21 varint As uint: 1199 Unknown
53-71 22 string America/Chicago Timezone

Edit: not set_reply, but get_reply

foxthefox commented 4 months ago

The structure of the message of powerstream is like that:

message Header
{
    optional bytes pdata = 1;
    optional int32 src = 2;
    optional int32 dest = 3;
    optional int32 d_src= 4;
    optional int32 d_dest = 5;
    optional int32 enc_type = 6;
    optional int32 check_type = 7;
    optional int32 cmd_func = 8;
    optional int32 cmd_id = 9;
    optional int32 data_len = 10;
    optional int32 need_ack = 11;
    optional int32 is_ack = 12;
    optional int32 seq = 14;
    optional int32 product_id = 15;
    optional int32 version = 16;
    optional int32 payload_ver = 17;
    optional int32 time_snap = 18;
    optional int32 is_rw_cmd = 19;
    optional int32 is_queue = 20;
    optional int32 ack_type= 21;
    optional string code = 22;
    optional string from = 23;
    optional string module_sn = 24;
    optional string device_sn = 25;
}

Pdata the first slot, the payload. On issue #54 is some more information to it.

get_reply is mostly the answer from the device with status data and energy values. The commands are in set and set_reply is the ack to it.

my next post will have some comments to yours, which was in the meantime showing up (while writing this on a train ride)

foxthefox commented 4 months ago

Comment to table 1,2,3 You perfectly got my idea. Table2 with field number 1 makes the payload, which is table 3. Sometimes one telegram can contain multiple messages. The 87 out 737 is the first message. And luckily you already have some good hits for value naming👍

foxthefox commented 4 months ago

I have spent some time on the data. The commands are quite clear now, based on the examples:

Interesting is the fact that the device serial number is not contained in the commands, that is different to the plug and powerstream.

What is not clear is the value 15 in ACAlwaysOn, maybe some percentage?

Also interesting that all manipulation with LabFeature resultet in the same message.

Above Table3 (message with cmdId=3 from get_reply) should be probably repeated with different modes of operation and settings to validate the data. I would guess that field 3, 12 is some kind of percentage, and field 6,11,19,20 could be some kind of on/off status. Field 1 could be some kind of error/status indicator and field 21 is the townId.

I have also checked the other parts of the get_reply. My first guesses: message with cmdId = 4

message with cmdId = 2

message with cmdId = 1

The message with cmdId=28 is empty.

I am not sure what date the App provides, but hopefully some values of these messages are matching to the values shown in the App.

jasonjan2003 commented 4 months ago

What is not clear is the value 15 in ACAlwaysOn, maybe some percentage?

Indeed. After trying several values, this 15 turns out to be the minimum backup reserve percentage:

batteryReserve

The minimum battery reserve value is set by the app to be 5% higher than the discharge limit (see image below). Since the app enforces this relationship on its own, it might be a good idea to perform this check for this HA integration.

energyManagement

message with cmdId = 4

(as float, very likely; someone with 2+ batteries can verify) field 5 capacity of battery (correct) field 8 charge limit (correct) field 9 discharge limit

message with cmdId = 1

field 24 power setting, max ac charging power (uint) field 25 maximum power of setup, seems correct, max output of setup (x1 inverter + x1 battery), using "Power In/out" port field 41/42/56 ~temperatures if degF~ 41: total input power [W], float (but rounded to integer?); 42: total output power [W], float; 56: total input power [W], float field 48/49/50/51 ~some kind of percenetages~, AC 20A output power [W], float, from left to right looking at the inverter LCD. I would guess 52/53/54 are the AC 20A/AC30A/POWER IO output power [W] values. I don't have the plugs to test them right now.

A new function command for renaming the unit (cmdId=65) Function Action Debugnode# Topic PayloadSize Payload QOS Retain
Rename "HomeBattery" 5 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set 53 0A330A0D0A0B486F6D65426174746572791020180220012801380340024841500D580170BCBD93D001800113880101BA0103696F73 0 FALSE
Rename "HomeBattery" 6 /app/MQTT_USER_ID/DEVICE_ID/thing/property/set_reply 35 0A2110021820200128013803400248415801600170BCBD93D00178813A800103880101 0 FALSE
foxthefox commented 4 months ago

I think we are good progressing. Can you check if the 5% margin for minimum battery reserve value is automatically adjusted when you change the discharge limit? Meaning that in the condition of switched ON of Backup Reserve there will be 2 outgoing set-telegrams, one for discharge level and one for the backupreserve.

I think the float 56 is not the total input power, it is more the AC-In power, which is the total when no other source is connected.

I think we should also check some USB charging resulting in power, volt, amps. Based on your feedback I have made a new guess and implementation. When using the last get_reply from the csv I could find such a matching

{
  dpu_3: {
    unknown1: 3,
    unknown2: 0,
    unknown3: 100,
    bkpResEn: 0,
    bkpResLev: 30,
    unknown6: 0,
    bkpResSolLev: 60,
    batPreCondEn: 1,
    chaLim: 90,
    dischaLim: 10,
    unknown11: 1,
    unknown12: 10,
    acSpeed: 1800,
    power: 900,
    devTimeout: 120,
    lcdTimeout: 300,
    dc12vTimeout: 720,
    acTimeout: 0,
    unknown19: 0,
    unknown20: 1,
    town: 1199,
    area: 'America/Chicago'
  },
  dpu_4: {
    unknown1: 1,
    unknown3: 89,
    unknown4: -27,
    capacity5: 6144,
    unknown7: 21398,
    chaLim: 90,
    dischaLim: 10,
    unknown11: 28
  },
  dpu_1: {
    sysVer1: 16777219,
    unknown16: '',
    unknown21: 89,
    maxAcPower24: 1800,
    maxPowerPowerPort25: 7200,
    totalInputPower41: 168,
    totalOutputPower42: 146,
    usbC1power43: 0,
    usbC2power44: 0,
    usbA1power45: 0,
    usbA2power46: 0,
    dcPower47: 0,
    ac1outBackupPower48: 67.21940612792969,
    ac2outBackupPower49: 41.767330169677734,
    ac1outOnlinePower50: 36.865638732910156,
    ac2outOnlinePower51: 0,
    acout30APower52: 0,
    acoutL14Power53: 0,
    ioPower54: 0,
    totalInputPower56: 167.6139373779297,
    solarLowInputPower57: 0,
    solarHighInputPower58: 0,
    float59: 0
  },
  dpu_2: {
    unknown1: 0,
    unknown21: 4,
    watthBatt23: 2148,
    freq43: 60,
    watthOut46: 105132,
    watthIn47: 108132,
    freq48: 60,
    volt61: 106.62699890136719,
    current62: -0.2619999945163727,
    float63: 0,
    float64: 27,
    usbC1Volt65: 0.8860361576080322,
    usbC1Current66: 0.0001125011476688087,
    usbC2Colt67: 0.8860361576080322,
    usbC2Current68: 0.010011250153183937,
    usbA1Volt69: 0.018721750006079674,
    usbA1Current70: 1.800022533370721e-13,
    usbA2Volt71: 0.025763917714357376,
    usbA2Current72: 0,
    dcVolt73: 1.5700000524520874,
    dcCurrent74: 0,
    ac1outBackupVolt75: 120.96668243408203,
    ac1outBackupCurrent76: 0.5222846269607544,
    ac2outBackupvolt77: 120.96668243408203,
    ac2outBackupCurrent78: 0.3502686023712158,
    ac1outOnlineVolt79: 120.6103744506836,
    ac1outOnlineCurrent80: 0.2961742877960205,
    ac2outOnlineVolt81: 120.6103744506836,
    ac2outOnlineCurrent82: 0,
    ac30AVolt83: 120.96668243408203,
    ac30ACurrent84: 0,
    acL14Volt85: 241.57705688476562,
    acL14Current86: 0,
    solarLowVolt87: 0,
    solarLowcurrent88: 0,
    solarHighVolt89: 0,
    solarHighCurrent90: 0,
    acInVolt91: 121.90068054199219,
    acInCurrent92: 1.3557208776474,
    powerIOInVolt93: 0,
    powerIOInCurrent94: 0,
    powerIOOutVolt95: 0,
    powerIOOutCurrent96: 0,
    temp97: 41,
    temp98: 42,
    temp99: 42,
    temp100: 41
  }

The number at the end is always the field number. Temperatures could also be in °C, if that would fit.

You can also post another get_reply message for me to have another guess.

foxthefox commented 4 months ago

One more question, the USB-C and USB-A can only be switched via the HW-button on the device, but is there an indication to the off/on status in the App? So we have to search for it.

Edit: I created a small helper to get the sequence of values out of different get_reply messages, for easier check of changes. It comes up like this:

ac1outBackupVolt75: {
      get_reply: 121.06448364257812,
      get_reply1: 121.32576751708984,
      get_reply2: 120.93585968017578,
      get_reply3: 120.96668243408203
    },

So, if you could prepare some get_reply messages with definition of the boundary condition or better to say what was changed (through APP or configuration) to the previous, the I can search for the changes and identify the datapoint.

muenchris commented 4 months ago

Has anyone decoded the heartbeat telegrams yet that come in on /app/devices/property/SerialNumber ? I see four command IDs: 1, 28, 2 and 4. But the pData field contains no protobuf decoded message like the other telegrams coming in from get_reply. There is something in the pData field but cannot be decoded with the Protobuf Decoder.

muenchris commented 4 months ago

message with cmdId = 4

field 3 is SoC (state of charge) field 7 is minutes remaining (as float, very likely; someone with 2+ batteries can verify) field 5 capacity of battery (correct) field 8 charge limit (correct) field 9 discharge limit

foxthefox commented 4 months ago

Has anyone decoded the heartbeat telegrams yet that come in on /app/devices/property/SerialNumber ? I see four command IDs: 1, 28, 2 and 4. But the pData field contains no protobuf decoded message like the other telegrams coming in from get_reply. There is something in the pData field but cannot be decoded with the Protobuf Decoder.

Nice to see that you are able to see the telegrams and that you can crosscheck/validate assumptions. In general there are cmdId1,2,3,4 where we made already good progress, cmdId 28 is somehow empty. What we know so far, you can see in the previous comments. As I see, SoC and remainTime is demystified additionally. For evaluation I made some small programs, if you could post some telegrams and the changed condition would be great. E.g. USB-C turned on -> send get request -> get_reply message USB-C turned off -> send get request -> get_reply message USB-A-turned on -> send get request -> get_reply message USB-A turned off -> send get request -> get_reply message DC turned on -> send get request -> get_reply message DC turned off -> send get request -> get_reply message Solar turned on -> send get request -> get_reply message (and hopefully solar connected) ..... With that the status should be shown somewhere and the voltage/current/power should show up. So we would progress in demystifing the telegrams.

Edit: the HEX string is enough (as above, without comma) Edit2: inserted "-> send get request" in the sequence of steps, to get the whole (big) telegram

muenchris commented 4 months ago

Yes I can decode some of the values in cmd 1-4 from your descriptions above and my own research. But I do have to send a "get" publish to the MQTT server in order to get valid 1-4 telegrams. There are hearbeat telegrams coming in on the /app/devices/property/serial that the app seams to use to update the screen with current AC and DC in/out power values. But the pdata is not in the same format as the 1-4 telegrams when responding to the "get" message.

I ran the pdata through the https://protobuf-decoder.netlify.app/ decoder but it could not make sense of it. cmdid 28 is empty during the hearbeat as well. but 1,2 and 4 contain some data in pdata - might either be encrypted or not protobuf:

CMDID2: sends: ab a3 0b a2 a6 13 a2 b3 1b a2 43 b3 63 a2 a3 6b a2 a2 6b a1 a3 73 a1 a3 7b a1 a3 43 a1 a2 4b a1 a2 53 a1 1a 13 a5 5b a1 52 64 a5 23 a0 9f 2b a0 a3 33 a0 a3 3b a0 a3 03 a0 a3 0b a0 a3 4e a0 6c 54 70 e1 56 a0 9e a9 d4 1c 5e a0 a3 a3 a3 a3 26 a7 a3 a3 6f e1 2e a7 2e 3a c4 99 36 a7 19 c1 cd 98 3e a7 2e 3a c4 99 06 a7 c8 23 ce 98 0e a7 3f 9a 97 8a 16 a7 34 6b 9f 9a 1e a7 fb 9a 97 b5 66 a7 a3 a3 a3 a3 6e a7 a3 a3 a3 a3 76 a7 a3 a3 a3 a3 7e a7 b7 7f 4d e1 46 a7 a3 a3 a3 a3 4e a7 b7 7f 4d e1 56 a7 a3 a3 a3 a3 5e a7 8d de 51 e1 26 a6 a3 a3 a3 a3 2e a6 8d de 51 e1 36 a6 a3 a3 a3 a3 3e a6 b7 7f 4d e1 06 a6 29 1f 50 9c 0e a6 02 0f d3 e0 16 a6 a3 a3 a3 a3 1e a6 a3 a3 a3 a3 66 a6 a3 a3 a3 a3 6e a6 a3 a3 a3 a3 76 a6 a3 a3 a3 a3 7e a6 c6 bc 52 e1 46 a6 29 13 98 9e 4e a6 a3 a3 a3 a3 56 a6 a3 a3 a3 a3 5e a6 a3 a3 a3 a3 26 a5 a3 a3 a3 a3 2e a5 a3 a3 ab e1 36 a5 a3 a3 ab e1 3e a5 a3 a3 ab e1 06 a5 a3 a3 ab e1 0b a5 ba 16 a5 a3 a3 a3 a3

CMDID1: much shorter but much more frequent sends: d3 04 06 06 64 45 a3 05 9c e6 67 45

CMDID4: sends 9d 80 9f 96 8f da b2 97 97 31 55 ba 97 97 57 d2 af 2d 8b d7 f3 df 90 cf 9b

foxthefox commented 4 months ago

These extractions are somehow not decodedable (maybe some missing starting bytes). Could you post the whole get_reply messages which are shorter but frequently incomming?

For sending the request for whole update, I will provide some advice.

foxthefox commented 4 months ago

The following approach should work for the request of full update telegrams. The ef-mqtt should be copied (it stays with the broker configuration node) and an inject node must be connected to the newly copied publishing node. It should look like this. Bildschirmfoto 2024-02-16 um 08 14 39

Inside the inject node

Bildschirmfoto 2024-02-16 um 08 15 11

The get request contains a number (... ,132,190,167,..) which is an identifier for the message and could be found in the reply. It might be necessary to change the number for each request, but I would think that it is not needed (only the presence of it is needed).

muenchris commented 4 months ago

Sorry but I am a CSharp developer integrating this into another project and am not familiar with NodeRed.

All the forced "get" messages that reply with "get_reply" can be encoded just fine and you have done all these already.

What's missing are the hearbeat telegrams that come in on /app/device/property/serial# and do not require any publish on any kind.

Right now my workaround is to publish the "get" command periodically and then get the "get_reply" back. That works great but I rather be a "silent" listener on the heartbeat.

muenchris commented 4 months ago

Here the full HEX from the hearbeats: CmdID: 1 0a 2e 0a 0c 21 f6 f4 74 68 b7 51 f7 b4 60 ab b7 10 02 18 20 20 01 30 01 38 03 40 02 48 01 50 0c 58 01 70 f4 d3 f6 01 78 81 3a 80 01 03 88 01 01

This CMDID: 2 0a da 02 0a b6 02 ee e6 4e e7 e3 56 e7 f6 5e e7 02 f6 26 e7 e6 2e e7 e7 2e e4 e4 36 e4 e6 3e e4 da 06 e4 e7 0e e4 e7 16 e4 2d 5e e0 1e e4 65 36 e0 66 e5 da 6e e5 e6 76 e5 e6 7e e5 e6 46 e5 e6 4e e5 e6 0b e5 1e f5 30 a4 13 e5 f4 65 26 58 1b e5 e6 e6 e6 e6 63 e2 e6 e6 c6 a4 6b e2 e6 f2 f6 da 73 e2 9a 26 93 dd 7b e2 e6 f2 f6 da 43 e2 01 1d cf da 4b e2 36 47 2d dd 53 e2 28 4e 71 d1 5b e2 43 88 3a dd 23 e2 e6 e6 e6 e6 2b e2 e6 e6 e6 e6 33 e2 e6 e6 e6 e6 3b e2 fd 67 0b a4 03 e2 e6 e6 e6 e6 0b e2 fd 67 0b a4 13 e2 ae f6 d9 d9 1b e2 73 6c 14 a4 63 e3 e6 e6 e6 e6 6b e3 73 6c 14 a4 73 e3 e6 e6 e6 e6 7b e3 fd 67 0b a4 43 e3 c2 40 16 d9 4b e3 3e e3 96 a5 53 e3 e6 e6 e6 e6 5b e3 e6 e6 e6 e6 23 e3 e6 e6 e6 e6 2b e3 e6 e6 e6 e6 33 e3 e6 e6 e6 e6 3b e3 75 d9 09 a4 03 e3 1d dc 34 d9 0b e3 e6 e6 e6 e6 13 e3 e6 e6 e6 e6 1b e3 e6 e6 e6 e6 63 e0 e6 e6 e6 e6 6b e0 e6 e6 de a4 73 e0 e6 e6 ce a4 7b e0 e6 e6 ca a4 43 e0 e6 e6 d6 a4 4e e0 c4 53 e0 e6 e6 e6 e6 10 02 18 20 20 01 28 01 30 01 38 03 40 02 48 02 50 b6 02 70 e6 d5 f6 01 78 81 3a 80 01 03 88 01 01

And CMDid=4: 0a 3b 0a 19 ed f0 ef e6 ff bf c2 e7 e7 eb 24 ca e7 e7 27 a2 df 70 c7 a7 b9 af e0 bf f7 10 02 18 20 20 01 28 01 30 01 38 03 40 02 48 04 50 19 70 e7 d5 f6 01 78 81 3a 80 01 03 88 01 01

foxthefox commented 4 months ago

However, if you are able to get the get_reply messages then let’s still try to demystify all the values which are incoming. The above is partly a follow up of the idea of the left 4 sockets (as per our last messages) and is not validated. Despite having said the above, this interpretation only covers approximately half of the possible values. What’s most important are the positions of the values which have commands (needed for initialising).

I agree that it would be good to listen only to the frequent incoming updates which is the follow up of the initialising with the get-reply message.

However, I do not own the DPU, so I am only able to participate when others that do contribute messages for evaluation.

So it would be helpful if you could sent get_replies after changing one condition at a time ( see above exemplary sequence).

Edit: This post refers to the second last post

foxthefox commented 4 months ago

What could be seen in the heartbeats:

Edit. CMDID: 2 at least has the same size in pdata 0...313 as the get_reply message, so it seems that this is a full message. Which is nice for comparison.

muenchris commented 4 months ago

Yes It looks like the messages are encrypted in the pdata. The app must be able to read them as it updates the main screen without sending the "get" messages. Only on screen change the app sends the "get". This is generally a good design as it keep the amount of incoming messages to the MQTT broker small. The encryption is annoying but generally also a good measure from Ecroflow. Their public API - for which you need a developer account - does not seam to have the a heartbeat and uses JSON in all MQTT telegrams, but I am still waiting for my approval from Ecoflow to verify.

jasonjan2003 commented 4 months ago

I think we are good progressing. Can you check if the 5% margin for minimum battery reserve value is automatically adjusted when you change the discharge limit? Meaning that in the condition of switched ON of Backup Reserve there will be 2 outgoing set-telegrams, one for discharge level and one for the backupreserve.

When Backup reserve is enabled:

  1. if Discharge limit <= (Backup reserve - 5%), only 1 set message is sent:
    • cmd Id 88 to new Discharge limit value.
  2. if (Backup reserve - 5%) < Discharge limit, 2 set messages are sent:
    • cmd Id 94 to
      • enable (1), and
      • new Backup reserve value, then
    • cmd Id 88 to new Discharge limit value.

I think the float 56 is not the total input power, it is more the AC-In power, which is the total when no other source is connected.

That makes a lot of sense! I don't have another input source right now.

Switching between DC Output/USB switch on the app: dc output / usb disabled 0A550A47080310001864200028233000383C4001485A500A5801600A68EC0E70840778788001AC028801F001900100980100A00101A801AF09B2010F416D65726963612F4368696361676F10024002480370B3FA81D2030A280A1A0A180801185A250000B8C12D0000C04538A2A801405A480A581C10024002480470B3FA81D2030A0C10064003481C70B3FA81D2030A87020AF8010883808008109412180450005800600068007001780A820100A8015AB00101B80101C001880EC801A038D001A2A801D80100E00164E8011ECD0200002543D50200001243DD0200000000E50200000000ED0200000000F50200000000FD020000000085036E8A87428D03A86222429503A10B14429D0300000000A50300000000AD0300000000B50300000000BD0300000000C503FA072543CD0300000000D50300000000DD0300000000A00602A80600B00600B806FFFFFFFF0FC006FFFFFFFF0FC806FFFFFFFF0FD00600D80600E00600E80600C80700D007FFFFFFFF0FD807FFFFFFFF0FE007FFFFFFFF0FE80700F00700F8070080080010024002480170B3FA81D2030AC5020AB6020800A80104B00110B801E410C00100C80101C80202D00200D8023CE00201E80201F00289B806F802C1CF0680033C880300900300980300A00300A80300ED0339F4D542F503F85363BEFD030000000085040000B8418D046986B13C95043CC33C379D046986B13CA50483D7233CAD04F60BA03CB5043298F135BD04AE0C433CC50400000000CD04EC51B83ED50400000000DD04505AF242E504D298053FED04505AF242F5042A77AA3EFD04AB9AF04285059384993E8D05AB9AF0429505000000009D05505AF242A50500000000AD057E7A7143B50500000000BD0500000000C50500000000CD0500000000D50500000000DD05AD3CF442E505589BAA3FED0500000000F50500000000FD05000000008506000000008D0600003042950600002C429D0600003442A50600003042A80627B5060000000010024002480270B3FA81D203
dc output / usb enabled 0A550A47080310001864200028233000383C4001485A500A5801600A68EC0E70840778788001AC028801F001900100980100A00101A801AF09B2010F416D65726963612F4368696361676F10024002480370EBAA87D2030A280A1A0A180801185A2500003CC22D0000C045389DA801405A480A581C10024002480470EBAA87D2030A0C10064003481C70EBAA87D2030A87020AF8010883808008109612180450005800600068007001780A820100A8015AB00101B80101C001880EC801A038D0019DA801D80100E00164E8011ECD0200002743D50200002243DD0200000000E50200000000ED0200000000F50200000000FD02000000008503FD84AC428D0393FF20429503345E11429D0300000000A50300000000AD0300000000B50300000000BD0300000000C50327322743CD0300000000D50300000000DD0300000000A00602A80600B00600B806FFFFFFFF0FC006FFFFFFFF0FC806FFFFFFFF0FD00600D80600E00600E80600C80700D007FFFFFFFF0FD807FFFFFFFF0FE007FFFFFFFF0FE80700F00700F8070080080010024002480170EBAA87D2030AC5020AB602081FA80104B00110B801E410C00100C80101C80202D00200D8023CE00201E80201F002F7B706F802AFCF0680033C880300900300980300A00300A80300ED03DDE4D542F5030AD743BFFD0300000000850400003C428D04EB70A24095047B549E2B9D04EB70A240A504E8CB4C3CAD04548AA240B504BC9BEE3BBD04FDAEA040C504A6CF233CCD0400004C41D50400000000DD048109F342E5042C67143FED048109F342F5049DE2A93EFD049B5AF1428505AAF3A03E8D059B5AF1429505000000009D058109F342A50500000000AD050E327243B50500000000BD0500000000C50500000000CD0500000000D50500000000DD0542B0F542E505B677B03FED0500000000F50500000000FD05000000008506000000008D0600003042950600002C429D0600003442A50600003042A80627B5060000000010024002480270EBAA87D203

Some observations:

michaelahern commented 4 months ago

Was digging into the heartbeats and pdata values as well. It looks like the decoding mechanism is to XOR the pdata bytes with the seq field from the message header. Something as simple as:

header.msg.pdata.forEach((element, index) => {
    pdataDec[index] = element ^ header.msg.seq;
});

You should be able to run the output through the protobuf decoder afterwards. Attaching protobuf message defs for the following heartbeat cmdId's...

1: AppShowHeartbeatReport.proto 2: BackendRecordHeartbeatReport.proto 3: AppParaHeartbeatReport.proto 4: BpInfoReport.proto

michaelahern commented 4 months ago

Also, for reference, attaching the protobuf file descriptor used to regenerate the above messages. Run this through the protobuf decoder as well.

muenchris commented 4 months ago

Was digging into the heartbeats and pdata values as well. It looks like the decoding mechanism is to XOR the pdata bytes with the seq field from the message header. Something as simple as:

header.msg.pdata.forEach((element, index) => {
    pdataDec[index] = element ^ header.msg.seq;
});

You should be able to run the output through the protobuf decoder afterwards. Attaching protobuf message defs for the following heartbeat cmdId's...

1: AppShowHeartbeatReport.proto 2: BackendRecordHeartbeatReport.proto 3: AppParaHeartbeatReport.proto 4: BpInfoReport.proto

Nice! I was hoping they do a simple XOR as that is the simplest version of "obfuscation". I will give it a try tomorrow with my code.

Thank you!

Edit: as a CSharp developer I see something weird with the XOR here that javascript might just "ignore": The pdata is a byte array but in my header the seq is defined as

  optional int32 seq = 14;

XOR of a byte with an integer yields very strange results in CSharp.

I am not sure if javascript "upgrades" the byte to an integer in your loop and then XORs the 32bit with the 32bit seq or if javascript "downgrades" the seq to a byte and only unses the first or last 8 bits of the integer to do the XOR on the byte array.

Or is the seq defined as a "byte" in the Header and I still have it defined as int32? EDIT: Only the last significant byte is used by Javascript and that works in CSharp as well

Also in general: With "proto3" the tag "optional" is no longer necessary as all values without tag are treated as optional. EDIT: While Protobuf declares it as no longer necessary it looks like the CSharp ProtoCompiler treats "optional" differently then if the tag is not there. So better have the tag :)

I will try different options to see which one works

foxthefox commented 4 months ago

@jasonjan2003 dc_on changed also the dcVolt73 or outAdsVol (new protobuf) to 12.75, which proves the DC output. the unknown1 (changed to 31) is now called recordFlag so it might not be the right indicator for dc is on. but I discovered a change in showFlag from 2324 to 2326 so the 2nd bit could be the indicator for DC on/off

@michaelahern Awesome proto files and the XOR works. Maybe some adjustmenst needed, the

in cmdId=1 or now AppShowHeartbeatReport, the xxTimeTaskType, xxTimeTaskIndex, xxTimeTaskMode is to be treated as unint32, which gives a strange number. I have changed it to int32 at the chg.. and it returns a "nice" -1, most likely the indication that there is no task setup. So this might be crosschecked with a device with time task setup.

Here is the output:

    chgTimeTaskType: -1,
    chgTimeTaskIndex: -1,
    chgTimeTaskMode: -1,
    chgTimeTaskParam: 0,
    chgTimeTaskTable_0: 0,
    chgTimeTaskTable_1: 0,
    chgTimeTaskTable_2: 0,
    dsgTimeTaskNotice: 0,
    dsgTimeTaskType: 4294967295,
    dsgTimeTaskIndex: 4294967295,
    dsgTimeTaskMode: 4294967295,
    dsgTimeTaskParam: 0,
    dsgTimeTaskTable_0: 0,
    dsgTimeTaskTable_1: 0,
    dsgTimeTaskTable_2: 0
foxthefox commented 4 months ago

While implementing the data model, I got some questions.

What is the response when the DPU is not connected to the Cloud (no Internet access, powered off)? When checking the "get" for a disconnected powerstream gives a short message and when decoded it is -2.

There is a "solarOnlyFlag" in the dataset, can this mode be activated from the App? There is a "sysWorkMode" in the dataset, can the work mode changed via App?

Is there an AC Discharge direct activation from the App? Looking at the time task it seems that there is also a AC discharge mode can be scheduled.

muenchris commented 4 months ago

Not sure was you mean by "AC Discharge" what would that do? There is a AC on/off button but that turns all AC Ports on/off. I would love to have that for each port individually but dont see that in the App. The "Automation" tab allows to set a "AC Discharge "If then" time of day based rule. I assume it will turn AC on at the time set, but there is no "off" setting - not quite clear what that will do without the "off". The same can be set for AC Charging and DC Discharging.

Solar Only is kind of there: The app has an "Energy Management" tab. It allows to set a "Backup reserve". If that is set to 12% (which is the minimum as I can tell), then the DPU only uses Solar until its down to 12% below that number it takes power from the AC Side. If the Backup reserve is off, it always uses all available charging sources.

Can you elaborate what you understand as "Work Mode"? I dont see any setting with this name in the app, but maybe it has a different name.

As for the response if the DPU is not connected to the cloud, I need to do test this when I am back from my trip (in three weeks ). Maybe somebody else with the DPU can test this

foxthefox commented 4 months ago

Thank, you. Exactly in time tasks also called automation there is AC Discharge,Charge and DC Discharge and I thought that this kind of switching off/on can also be done elsewhere.

In "AppParaHeartbeatReport" is "sys_work_mode", I have renamed it from word to work.

RyanWor commented 2 months ago

Apologies that I have been fairly inactive since opening this issue. I have a second DPU now as well as a SHP2 (the SHP2 should be installed in the next week or two). I am happy to help test or troubleshoot anything if it will help develop support for these devices. Thanks!

EDIT: I also see someone else opened a discussion topic on this as well. I referenced this open issue/feature request there as well https://github.com/tolwi/hassio-ecoflow-cloud/discussions/218

foxthefox commented 2 months ago

I was working on DeltaProUltra in the meantime, when I am sitting in the train back home, I will make further explanations in next comment.

I also worked with SHP, so maybe there are some matching parts. For the SHP2 I suggest to do similar investigation as the ones for DPU. I would guess that SHP2 is also using protobuf, but we will see during investigation.

foxthefox commented 2 months ago

Since I am still struggling to understand the HA development and especially how to setup test environment with code change recognition, I focussed on gateway functionality of my existing work. So I have enhanced my adapter for ioBroker with bidirectional data exchange with HA. That exchange is based on the discovery feature of MQTT service. With that feature is no configuration or yaml needed. Unfortunately it requires to install the iobroker system which is based on nodejs. But that’s not a big deal and does not require a lot of resources. For me it works pretty well, fits to my needs and if any changes are needed I can quickly publish a new release. As a big advantage ALL datapoints from the EF devices are processed (not only the ones of most interest). The adapter as a gateway also reduces the HA load, hence only changed data is transferred to HA. A quick introduction I will add when having a keyboard in front of me.

Edit: Website: https://www.iobroker.net/#en/intro linux cmd(e.g. on raspberry pi) : curl -sLf https://iobroker.net/install.sh | bash - Adapter Repo: https://github.com/foxthefox/ioBroker.ecoflow-mqtt

The adapter is not yet in the offical list of adapters, but via expert mode the adapter can be installed from npm/github. Maybe I will find time to make a small gist with screenshots. But actually it is not more complex than HA.

RyanWor commented 2 months ago

I was working on DeltaProUltra in the meantime, when I am sitting in the train back home, I will make further explanations in next comment.

I also worked with SHP, so maybe there are some matching parts. For the SHP2 I suggest to do similar investigation as the ones for DPU. I would guess that SHP2 is also using protobuf, but we will see during investigation.

My SHP2 should be installed in the next two weeks. Once it is up and running I will setup a test environment and see if I can contribute some investigation on the SHP2 values. Thanks again for all your work on this!

foxthefox commented 1 month ago

Is your SHP2 installed yet?

RyanWor commented 1 month ago

Is your SHP2 installed yet?

The install was just finished late Saturday evening. I haven't had much time to play around with it yet given the holiday yesterday. I am also still waiting on one additional power I/O cable to get delivered later this week for my second DPU/battery but that may not matter much for testing purposes.

I plan to review this thread and setup test/investigation environment this weekend so I can help contribute findings. If there is anything specific beyond what's already been covered here I need to do or specific points of information you would like back please let me know. I will try and get it done by next week. Thanks!

RyanWor commented 1 month ago

Sorry it's going to be a little bit longer before I can setup the test environment and assist on the SHP2 front. I got a serious unexpected real life test of the system as we got hit hard by sudden storms w/ 120+ mph wind in Houston late last week and were without power until early this week (the SHP2 and dual DPU/batteries worked wonderfully to cover the gaps in generator power). I am just now getting back to work and trying to return to normal life so just need a bit longer on my end to assist here. Apologies for the delays and thanks for the patience.

foxthefox commented 1 month ago

sad to hear about the storm, but glad that the EF ecosystem has leaveraged the situation. take your time we are not in a hurry

RyanWor commented 1 month ago

I was able to set up node-red with my DPU, but everything just shows up as Invalid JSON string. I haven't used node-red before, but the nodes do show as connected, and entries in the debug log coincide with me manipulating the Ecoflow App. Any ideas?

2/9/2024, 12:19:13 PM[node: d9ecc011d0be04ea](https://homeassistant.address/api/hassio_ingress/.../#)
msg : string[19]
"Invalid JSON string"

Can you tell me how you got the full debug msg output to display please? I have my MQTT flow setup and it is connecting and seeing messages come through as I interact with my SHP2 from the app but just as you described here all messages are only showing "Invalid JSON string" for me currently.

foxthefox commented 1 month ago

I think that you should change the message type from JSON to buffer in the mqqt-in node. Then you will receive strange strings in the debug log. But you also have a small icon „raw“ at the message. Click on this and it should be a HEX string which we need.

RyanWor commented 1 month ago

I think that you should change the message type from JSON to buffer in the mqqt-in node. Then you will receive strange strings in the debug log. But you also have a small icon „raw“ at the message. Click on this and it should be a HEX string which we need.

Ah okay, thank you. That's what I just tested a bit ago and I am indeed getting the decimal strings now. If I expand a debug message and click "raw"

I don't seem to be getting hex output tho, rather lots strange characters: image

I think I actually need to leave it as string and then I can see the hex output, might just require copying them one by one to build the full string out for each payload. image

RyanWor commented 1 month ago

Okay I think I figured out a way to make that easier. Had GPT-4o create code for Excel VBA to do the conversion. Tested it with Jason's values from his excel first just to confirm it was working correctly (it had a formatting issue at first but was able to get it seemingly perfect in the next iteration.

Here is the VBA code just in case anyone else finds it's useful in the future:

Function DecToHexString(decString As String) As String
    Dim decArray() As String
    Dim hexString As String
    Dim i As Integer
    Dim hexValue As String

    ' Remove brackets and split the string into an array
    decString = Replace(decString, "[", "")
    decString = Replace(decString, "]", "")
    decArray = Split(decString, ",")

    ' Convert each decimal to hex and build the hex string
    For i = LBound(decArray) To UBound(decArray)
        hexValue = WorksheetFunction.Dec2Hex(CLng(decArray(i)), 2)
        hexString = hexString & " " & hexValue
    Next i

    ' Trim leading space and return
    DecToHexString = Trim(hexString)
End Function
foxthefox commented 1 month ago

Good progress, I think if you get into the line called buffer, all the values are in one set of 733 values. Or it is needed to click on rectangle “raw“.

RyanWor commented 1 month ago

Okay so starting to collect values on the SHP2 now. Looks like the main payload is pretty big (1896 length). When I paste it in hex format into the protobuf decoder it only seems to decode the first 129 values which equates to about 13 fields. The data I can decode looks good, pretty sure I already spotted grid input wattage in some of that. However, the rest of the payload is shown as "Left over bytes".

I know there has to be more in this output since I can see things like my time zone, address, and GPS coordinates as plain text when I look at those payloads in the raw output.

RyanWor commented 1 month ago

Gonna go ahead and share what I have just in some initial testing so far but since I know this data contains some personal information of mine (address, GPS coordinates, etc.) I am going to put it in a Google Sheets and can grant your access once you click the link and request access: https://docs.google.com/spreadsheets/d/1RGt7lxu7dCPBS62eblS5QGBN6Zff6rr21oQwyhx8fKo/edit?usp=sharing

(Also if you think it makes sense to start a new issue on the SHP2 stuff and leave this thread just for the DPU I can do that as well.)

foxthefox commented 1 month ago

I will create tomorow a google account.

Okay so starting to collect values on the SHP2 now. Looks like the main payload is pretty big (1896 length). When I paste it in hex format into the protobuf decoder it only seems to decode the first 129 values which equates to about 13 fields. The data I can decode looks good, pretty sure I already spotted grid input wattage in some of that. However, the rest of the payload is shown as "Left over bytes".

I know there has to be more in this output since I can see things like my time zone, address, and GPS coordinates as plain text when I look at those payloads in the raw output.

Usually there should be no "left over bytes". I would guess that there is something not correctly derived. I have checked another way of convenient capturing. If the mqtt-in node is set to "base64 string" it gives still some unreadable string, but better than a normal string. This base64 encoded string can be copied when hoovering over the right icon "Copy text". This base64 text can also be pasted into the protobuf decoder. After let it decode, it also shows a nice HEX string. For comparison reasons you should try this way also, there should be no "left over bytes" with this approach.

RyanWor commented 1 month ago

Tested with base64 and pasting that into protobuff decoder and still get a lot of leftover bytes. Maybe node red or EcoFlow is truncating the payload and it spills over into another message? If you'd rather me share through Office/OneDrive I can do that too if it's easier for you and you don't have to create a Google account.

RyanWor commented 1 month ago

Just granted you access. Also pasted in the base64 encoded values I got yesterday after changing my node-red nodes/flow. I have to run here in a bit but if you want me to pull anything further once you have looked over the data I got so far I can do that when I get back later today. Thanks again for your help!

foxthefox commented 1 month ago

Thanks, I already started to decode and to put some data back to the table.

first insight:

would be good to have some cyclic telegrams at debug2 (with larger debug node size)

More things I will directly put into the table