Open sporttiger1000 opened 1 year ago
+1
+1
+1 How can i participate with needed data?
I tried adding the powerstream via DIAGNOSTIC type, but the diagnostic dump does not reveal anything useful:
"data": { "data": {}, "set_commands": {}, "get_commands": {}, "raw_data": [] }
I have also a Powerstream and I can help with testing. But I do not have knowledge in python coding.
I tried to read out the inverter via node red and mqtt. i get data However, the structure is not as clean as with the batteries. I get the data as a buffer. The buffer position of the data is constantly changing. I cannot assign the values to the inverter.
Can anyone say to me how i can help? LG Heradon
Can anyone say to me how i can help? LG Heradon
Hi, first of all EcoFlow has to enable the API access to the PowerStream devices. Currently the response is { "Content-Length" : "49", "Content-Type" : "application/json; charset=UTF-8", "Vary" : "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", "Date" : "Mon, 12 Jun 2023 12:38:31 GMT" } { "message" : "the device is offline", "code" : "6012" }
I called Ecoflow and was told that Ecoflow can activate the API if I send the serial number, I can do this and then run tests if I'm told exactly how to run them ;)
LG Heradon
MQTT Connection to Powerstream works the same as to my Delta2 but I cant read the message it seams to be encryptet. Messages look like this in the MQTT Explorer:
� ��ň�� �5@ H�P�X�pӸ
Tell me if i can provide Infos you need
I have requested API access from Ecoflow. The answer was disappointing: Unfortunately, we currently have no intention of opening an API to access PowerStream through third-party programs
the data packet from MQTT is a bytearray with the first byte being a packet type id
The micro inverter serial number always starts with HW51, which is appended to the end of each packet. I believe HW52 is a smart socket.
Field | Type | Name |
---|---|---|
1 | UInt32 | invErrCode |
2 | UInt32 | invWarnCode |
3 | UInt32 | pv1ErrCode |
4 | UInt32 | pv1WarnCode |
5 | UInt32 | pv2ErrCode |
6 | UInt32 | pv2WarningCode |
7 | UInt32 | batErrCode |
8 | UInt32 | batWarningCode |
9 | UInt32 | llcErrCode |
10 | UInt32 | llcWarningCode |
11 | UInt32 | pv1Statue |
12 | UInt32 | pv2Statue |
13 | UInt32 | batStatue |
14 | UInt32 | llcStatue |
15 | UInt32 | invStatue |
16 | Int32 | pv1InputVolt |
17 | Int32 | pv1OpVolt |
18 | Int32 | pv1InputCur |
19 | Int32 | pv1InputWatts |
20 | Int32 | pv1Temp |
21 | Int32 | pv2InputVolt |
22 | Int32 | pv2OpVolt |
23 | Int32 | pv2InputCur |
24 | Int32 | pv2InputWatts |
25 | Int32 | pv2Temp |
26 | Int32 | batInputVolt |
27 | Int32 | batOpVolt |
28 | Int32 | batInputCur |
29 | Int32 | batInputWatts |
30 | Int32 | batTemp |
31 | UInt32 | batSoc |
32 | Int32 | llcInputVolt |
33 | Int32 | llcOpVolt |
34 | Int32 | llcTemp |
35 | Int32 | invInputVolt |
35 | Int32 | invOpVolt |
37 | Int32 | invOutputCur |
38 | Int32 | invOutputWatts |
39 | Int32 | invTemp |
40 | Int32 | invFreq |
41 | Int32 | invDcCur |
42 | Int32 | bpType |
43 | Int32 | invRelayStatus |
44 | Int32 | pv1RelayStatus |
45 | Int32 | pv2RelayStatus |
46 | UInt32 | installCountry |
47 | UInt32 | installTown |
48 | UInt32 | permanentWatts |
49 | UInt32 | dynamicWatts |
50 | UInt32 | supplyPriority |
51 | UInt32 | lowerLimit |
52 | UInt32 | upperLimit |
53 | UInt32 | invOnOff |
54 | UInt32 | wirelessErrCode |
55 | UInt32 | wirelessWarnCode |
56 | UInt32 | invBrightness |
57 | UInt32 | heartbeatFrequency |
58 | UInt32 | ratedPower |
Offset | Type | Name |
---|---|---|
1 | UInt32 | watth |
Still trying to get my head around this, there seems to be upto 11 tasks each one seems to have it's own set within a single byte array. I assume it has to do with the ability within the app to set it between prioritising charging the battery or powering the house at different times of the day with the inverter, or switching a socket on and offer.
Offset | Type | Name |
---|---|---|
1 | UInt32 | index |
2 | ??? (variable length) | timeRange |
3 | UInt32 | type |
This is an array of smaller units with this data structure
Field | Type | Name |
---|---|---|
1 | UInt32 | timestamp |
2 | SInt32 | timezone |
3 | UInt32 | invToGridPower |
4 | UInt32 | invToPlugPower |
5 | Int32 | batteryPower |
6 | UInt32 | pv1OutputPower |
7 | UInt32 | pv2OutputPower |
Same payload as 135 power history
the data packet from MQTT is a bytearray with the first byte being a packet type id
The micro inverter serial number always starts with HW51, which is appended to the end of each packet. I believe HW52 is a smart socket.
Packet Types
- 1 heartbeat
- 32 total energy report
- 133 timing task
- 135 power history
- 137 power report
1 heartbeat
Field Type Name 1 UInt32 invErrCode 2 UInt32 invWarnCode 3 UInt32 pv1ErrCode 4 UInt32 pv1WarnCode 5 UInt32 pv2ErrCode 6 UInt32 pv2WarningCode 7 UInt32 batErrCode 8 UInt32 batWarningCode 9 UInt32 llcErrCode 10 UInt32 llcWarningCode 11 UInt32 pv1Statue 12 UInt32 pv2Statue 13 UInt32 batStatue 14 UInt32 llcStatue 15 UInt32 invStatue 16 Int32 pv1InputVolt 17 Int32 pv1OpVolt 18 Int32 pv1InputCur 19 Int32 pv1InputWatts 20 Int32 pv1Temp 21 Int32 pv2InputVolt 22 Int32 pv2OpVolt 23 Int32 pv2InputCur 24 Int32 pv2InputWatts 25 Int32 pv2Temp 26 Int32 batInputVolt 27 Int32 batOpVolt 28 Int32 batInputCur 29 Int32 batInputWatts 30 Int32 batTemp 31 UInt32 batSoc 32 Int32 llcInputVolt 33 Int32 llcOpVolt 34 Int32 llcTemp 35 Int32 invInputVolt 35 Int32 invOpVolt 37 Int32 invOutputCur 38 Int32 invOutputWatts 39 Int32 invTemp 40 Int32 invFreq 41 Int32 invDcCur 42 Int32 bpType 43 Int32 invRelayStatus 44 Int32 pv1RelayStatus 45 Int32 pv2RelayStatus 46 UInt32 installCountry 47 UInt32 installTown 48 UInt32 permanentWatts 49 UInt32 dynamicWatts 50 UInt32 supplyPriority 51 UInt32 lowerLimit 52 UInt32 upperLimit 53 UInt32 invOnOff 54 UInt32 wirelessErrCode 55 UInt32 wirelessWarnCode 56 UInt32 invBrightness 57 UInt32 heartbeatFrequency 58 UInt32 ratedPower
32 total energy report
Offset Type Name 1 UInt32 watth
133 timing task
Still trying to get my head around this, there seems to be upto 11 tasks each one seems to have it's own set within a single byte array. I assume it has to do with the ability within the app to set it between prioritising charging the battery or powering the house at different times of the day with the inverter, or switching a socket on and offer.
Offset Type Name 1 UInt32 index 2 ??? (variable length) timeRange 3 UInt32 type
135 power history
This is an array of smaller units with this data structure
Field Type Name 1 UInt32 timestamp 2 SInt32 timezone 3 UInt32 invToGridPower 4 UInt32 invToPlugPower 5 Int32 batteryPower 6 UInt32 pv1OutputPower 7 UInt32 pv2OutputPower
137 power report
Same payload as
135 power history
What's the next step?😊
Easiest options in order are:
I am trying option 3 but I'm unfamiliar with home assistant development, python and reading binary packets so it may take me a while.
Have a look at the discussion here and search for Powerstream. Maybe someone can find useful information... https://forum.iobroker.net/topic/54929/adapter-f%C3%BCr-ecoflow-einbindung/153?lang=en
Have a look at the discussion here and search for Powerstream. Maybe someone can find useful information... https://forum.iobroker.net/topic/54929/adapter-f%C3%BCr-ecoflow-einbindung/153?lang=en
They are working out what I posted above, although they haven't got all the field names yet
Okay I have been reading through how one of the integrations this one takes insperation from being hassio-ecoflow and it does kinda make sense to me. I think to get it to work with this integration the ecoflow_mqtt.on_message
method to use a deserialiser rather than JSON decoder.
No idea if this will work, I haven't tried it but potential deserialiser but this is what I have been working on so far.
import struct
from typing import Any, Callable, Iterable
def _parse_dict(data: bytes, types: Iterable[tuple[str, int, Callable[[bytes], Any]]]):
result = dict[str, Any]()
index = 0
_len = len(data)
for (name, size, callback) in types:
if name is not None:
result[name] = callback(data[index:index + size])
index += size
if index >= _len:
break
return result
def _to_int(data: bytes):
return int.from_bytes(data, "little")
def _to_uint(data: bytes):
return struct.unpack('B', data[0])[0]
def _to_utf8(data: bytes):
try:
return data.decode("utf-8")
except:
return None
def parse_powerstream_heartbeat(data: bytes):
return _parse_dict(data, [
("invErrCode", 1, _to_uint),
("invWarnCode", 1, _to_uint),
("pv1ErrCode", 1, _to_uint),
("pv1WarnCode", 1, _to_uint),
("pv2ErrCode", 1, _to_uint),
("pv2WarningCode", 1, _to_uint),
("batErrCode", 1, _to_uint),
("batWarningCode", 1, _to_uint),
("llcErrCode", 1, _to_uint),
("llcWarningCode", 1, _to_uint),
("pv1Statue", 1, _to_uint),
("pv2Statue", 1, _to_uint),
("batStatue", 1, _to_uint),
("llcStatue", 1, _to_uint),
("invStatue", 1, _to_uint),
("pv1InputVolt", 1, _to_int),
("pv1OpVolt", 1, _to_int),
("pv1InputCur", 1, _to_int),
("pv1InputWatts", 1, _to_int),
("pv1Temp", 1, _to_int),
("pv2InputVolt", 1, _to_int),
("pv2OpVolt", 1, _to_int),
("pv2InputCur", 1, _to_int),
("pv2InputWatts", 1, _to_int),
("pv2Temp", 1, _to_int),
("batInputVolt", 1, _to_int),
("batOpVolt", 1, _to_int),
("batInputCur", 1, _to_int),
("batInputWatts", 1, _to_int),
("batTemp", 1, _to_int),
("batSoc", 1, _to_uint),
("llcInputVolt", 1, _to_int),
("llcOpVolt", 1, _to_int),
("llcTemp", 1, _to_int),
("invInputVolt", 1, _to_int),
("invOpVolt", 1, _to_int),
("invOutputCur", 1, _to_int),
("invOutputWatts", 1, _to_int),
("invTemp", 1, _to_int),
("invFreq", 1, _to_int),
("invDcCur", 1, _to_int),
("bpType", 1, _to_int),
("invRelayStatus", 1, _to_int),
("pv1RelayStatus", 1, _to_int),
("pv2RelayStatus", 1, _to_int),
("installCountry", 1, _to_uint),
("installTown", 1, _to_uint),
("permanentWatts", 1, _to_uint),
("dynamicWatts", 1, _to_uint),
("supplyPriority", 1, _to_uint),
("lowerLimit", 1, _to_uint),
("upperLimit", 1, _to_uint),
("invOnOff", 1, _to_uint),
("wirelessErrCode", 1, _to_uint),
("wirelessWarnCode", 1, _to_uint),
("invBrightness", 1, _to_uint),
("heartbeatFrequency", 1, _to_uint),
("ratedPower", 1, _to_uint),
("serialNo", 16, _to_utf8)
])
I'm guessing on the size of the serial no field but the serial no is 16 characters. May also need to add some offset at the start as the first byte is the command and I'm unsure if I need to handle it in the parser or not.
According to the ioBroker forum it is a Protobuf encoded message for which a standard deserializer should already exist.
protobuff is the serializer/deserializer https://protobuf.dev/
Ecoflow even use it to build the Ecoflow App
I have played around with messages and decoding them. Sometimes is a stacking of protobufs. This worked for me to get the data:
message inverter_heartbeat {
optional uint32 invErrCode = 1;
optional uint32 invWarnCode = 3;
optional uint32 pv1ErrCode = 2;
optional uint32 pv1WarnCode = 4;
optional uint32 pv2ErrCode = 5;
optional uint32 pv2WarningCode = 6;
optional uint32 batErrCode = 7;
optional uint32 batWarningCode = 8;
optional uint32 llcErrCode = 9;
optional uint32 llcWarningCode = 10;
optional uint32 pv1Status = 11;
optional uint32 pv2Status = 12;
optional uint32 batStatus = 13;
optional uint32 llcStatus = 14;
optional uint32 invStatus = 15;
optional int32 pv1InputVolt = 16;
optional int32 pv1OpVolt = 17;
optional int32 pv1InputCur = 18;
optional int32 pv1InputWatts = 19;
optional int32 pv1Temp = 20;
optional int32 pv2InputVolt = 21;
optional int32 pv2OpVolt = 22;
optional int32 pv2InputCur = 23;
optional int32 pv2InputWatts = 24;
optional int32 pv2Temp = 25;
optional int32 batInputVolt = 26;
optional int32 batOpVolt = 27;
optional int32 batInputCur = 28;
optional int32 batInputWatts = 29;
optional int32 batTemp = 30;
optional uint32 batSoc = 31;
optional int32 llcInputVolt = 32;
optional int32 llcOpVolt = 33;
optional int32 llcTemp = 34;
optional int32 invInputVolt = 35;
optional int32 invOpVolt = 36;
optional int32 invOutputCur = 37;
optional int32 invOutputWatts = 38;
optional int32 invTemp = 39;
optional int32 invFreq = 40;
optional int32 invDcCur = 41;
optional int32 bpType = 42;
optional int32 invRelayStatus = 43;
optional int32 pv1RelayStatus = 44;
optional int32 pv2RelayStatus = 45;
optional uint32 installCountry = 46;
optional uint32 installTown = 47;
optional uint32 permanentWatts = 48;
optional uint32 dynamicWatts = 49;
optional uint32 supplyPriority = 50;
optional uint32 lowerLimit = 51;
optional uint32 upperLimit = 52;
optional uint32 invOnOff = 53;
optional uint32 wirelessErrCode = 54;
optional uint32 wirelessWarnCode = 55;
optional uint32 invBrightness = 56;
optional uint32 heartbeatFrequency = 57;
optional uint32 ratedPower = 58;
}
message PowerItem
{
optional uint32 timestamp = 1;
optional sint32 timezone = 2;
optional uint32 inv_to_grid_power = 3;
optional uint32 inv_to_plug_power = 4;
optional int32 battery_power = 5;
optional uint32 pv1_output_power = 6;
optional uint32 pv2_output_power = 7;
}
message PowerPack
{
optional uint32 sys_seq = 1;
repeated PowerItem sys_power_stream = 2;
}
message EnergyItem
{
optional uint32 timestamp = 1;
optional int32 item = 2;
optional bytes watt = 3; // unknown structure so far
}
message EnergyPack
{
optional uint32 sys_seq = 1;
repeated EnergyItem sys_energy_stream = 2;
}
message Header
{
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;
}
message HeaderMessage {
optional Header header = 1;
}
message InverterMessage {
optional inverter_heartbeat inverter = 1;
optional Header header = 2;
}
message PowerMessageProto {
optional PowerPack powerpack = 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;
}
message PowerMessage {
PowerMessageProto item = 1;
}
message EnergyMessageProto {
optional EnergyPack energypack = 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;
}
message EnergyMessage{
optional EnergyMessageProto item = 1;
}
With this definition it is possible to look for
Hope it helps for your work
I am very pleased they are similar to what I have worked out as well, I am going through all the fields to make sure that the numbers match what I have. If you are familiar with this I have a pull request that I am working on over here: https://github.com/tolwi/hassio-ecoflow-cloud/pull/66
ecopacket.proto
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;
}
message SendHeaderMsg
{
optional Header msg = 1;
}
message SendMsgHart
{
optional int32 link_id = 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 ack_type = 13;
optional int32 seq = 14;
optional int32 time_snap = 15;
optional int32 is_rw_cmd = 16;
optional int32 is_queue = 17;
optional int32 product_id = 18;
optional int32 version = 19;
}
platform.proto
syntax = "proto3";
message EnergyItem
{
optional uint32 timestamp = 1;
optional uint32 watth_type = 2;
optional uint32 watth = 3;
}
message EnergyTotalReport
{
optional uint32 watth_seq = 1;
optional EnergyItem watth_item = 2;
}
message BatchEnergyTotalReport
{
optional uint32 watth_seq = 1;
repeated EnergyItem watth_item = 2;
}
message EnergyTotalReportAck
{
optional uint32 result = 1;
optional uint32 watth_seq = 2;
optional uint32 watth_type = 3;
}
message EventRecordItem
{
optional uint32 timestamp = 1;
optional uint32 sys_ms = 2;
optional uint32 event_no = 3;
optional float event_detail = 4;
}
message EventRecordReport
{
optional uint32 event_ver = 1;
optional uint32 event_seq = 2;
repeated EventRecordItem event_item = 3;
}
message EventInfoReportAck
{
optional uint32 result = 1;
optional uint32 event_seq = 2;
optional uint32 event_item_num =3;
}
message ProductNameSet
{
optional string name = 1;
}
message ProductNameSetAck
{
optional uint32 result = 1;
}
message ProductNameGet { }
message ProductNameGetAck
{
optional string name = 3;
}
message RTCTimeGet { }
message RTCTimeGetAck
{
optional uint32 timestamp = 1;
optional int32 timezone = 2;
}
message RTCTimeSet
{
optional uint32 timestamp = 1;
optional int32 timezone = 2;
}
message RTCTimeSetAck
{
optional uint32 result = 1;
}
message country_town_message
{
optional uint32 country = 1;
optional uint32 town = 2;
}
enum PlCmdSets
{
PL_NONE_CMD_SETS = 0;
PL_BASIC_CMD_SETS = 1;
PL_EXT_CMD_SETS = 254;
}
enum PlCmdId
{
PL_CMD_ID_NONE = 0;
PL_CMD_ID_XLOG = 16;
PL_CMD_ID_WATTH = 32;
}
powerstream.proto
syntax = "proto3";
message InverterHeartbeat {
optional uint32 inv_err_code = 1;
optional uint32 inv_warn_code = 3;
optional uint32 pv1_err_code = 2;
optional uint32 pv1_warn_code = 4;
optional uint32 pv2_err_code = 5;
optional uint32 pv2_warning_code = 6;
optional uint32 bat_err_code = 7;
optional uint32 bat_warning_code = 8;
optional uint32 llc_err_code = 9;
optional uint32 llc_warning_code = 10;
optional uint32 pv1_statue = 11;
optional uint32 pv2_statue = 12;
optional uint32 bat_statue = 13;
optional uint32 llc_statue = 14;
optional uint32 inv_statue = 15;
optional int32 pv1_input_volt = 16;
optional int32 pv1_op_volt = 17;
optional int32 pv1_input_cur = 18;
optional int32 pv1_input_watts = 19;
optional int32 pv1_temp = 20;
optional int32 pv2_input_volt = 21;
optional int32 pv2_op_volt = 22;
optional int32 pv2_input_cur = 23;
optional int32 pv2_input_watts = 24;
optional int32 pv2_temp = 25;
optional int32 bat_input_volt = 26;
optional int32 bat_op_volt = 27;
optional int32 bat_input_cur = 28;
optional int32 bat_input_watts = 29;
optional int32 bat_temp = 30;
optional uint32 bat_soc = 31;
optional int32 llc_input_volt = 32;
optional int32 llc_op_volt = 33;
optional int32 llc_temp = 34;
optional int32 inv_input_volt = 35;
optional int32 inv_op_volt = 36;
optional int32 inv_output_cur = 37;
optional int32 inv_output_watts = 38;
optional int32 inv_temp = 39;
optional int32 inv_freq = 40;
optional int32 inv_dc_cur = 41;
optional int32 bp_type = 42;
optional int32 inv_relay_status = 43;
optional int32 pv1_relay_status = 44;
optional int32 pv2_relay_status = 45;
optional uint32 install_country = 46;
optional uint32 install_town = 47;
optional uint32 permanent_watts = 48;
optional uint32 dynamic_watts = 49;
optional uint32 supply_priority = 50;
optional uint32 lower_limit = 51;
optional uint32 upper_limit = 52;
optional uint32 inv_on_off = 53;
optional uint32 wireless_err_code = 54;
optional uint32 wireless_warn_code = 55;
optional uint32 inv_brightness = 56;
optional uint32 heartbeat_frequency = 57;
optional uint32 rated_power = 58;
}
message PermanentWattsPack
{
optional uint32 permanent_watts = 1;
}
message SupplyPriorityPack
{
optional uint32 supply_priority = 1;
}
message BatLowerPack
{
optional int32 lower_limit = 1;
}
message BatUpperPack
{
optional int32 upper_limit = 1;
}
message BrightnessPack
{
optional int32 brightness = 1;
}
message PowerItem
{
optional uint32 timestamp = 1;
optional sint32 timezone = 2;
optional uint32 inv_to_grid_power = 3;
optional uint32 inv_to_plug_power = 4;
optional int32 battery_power = 5;
optional uint32 pv1_output_power = 6;
optional uint32 pv2_output_power = 7;
}
message PowerPack
{
optional uint32 sys_seq = 1;
repeated PowerItem sys_power_stream = 2;
}
message PowerAckPack
{
optional uint32 sys_seq = 1;
}
message NodeMassage
{
optional string sn = 1;
optional bytes mac = 2;
}
message MeshChildNodeInfo
{
optional uint32 topology_type = 1;
optional uint32 mesh_protocol = 2;
optional uint32 max_sub_device_num = 3;
optional bytes parent_mac_id = 4;
optional bytes mesh_id = 5;
repeated NodeMassage sub_device_list = 6;
}
Basically I have added only the stacking of messages for HeaderMessage, InverterMessage, PowerMessage, EnergyMessage, the basis for this is the same as yours. One exception, in the Header I deleted the pdata, that didn't work for. At this position the actual data, so Header is more an Appendix to the data part at first position.
Basically I have added only the stacking of messages for HeaderMessage, InverterMessage, PowerMessage, EnergyMessage, the basis for this is the same as yours. One exception, in the Header I deleted the pdata, that didn't work for. At this position the actual data, so Header is more an Appendix to the data part at first position.
I am starting to think that I have got some of the proto wrong as I am only seem to be getting a few values from the inverter heartbeat, I am seeing a lot of packets that have no data in it. Are you finding that?
Did you take over my protodefinition? Fo me the deletion of the pdata=1 in the Header was important. The first id is the inverter heart beat and then is the header starting from 2. In the other cases the header must be included verbose.
What concerns the received data.
I have the impression that some messages must be requested with a mqtt message to the cloud, similar to a command for setting something. Otherwise I would have no idea how the different screens in the app get their content
Ich habe heute etwas rumgetüftelt und habe die Proto files in der App gefunden. nur sind die komisch codiert, ich habe dann chatgpt zur hilfe genommen, das mir dann es in klartext ausgegeben hat. Jetzt ist die Frage, ob jemand weiß, wie man von der einen darstellung auf die klartext dartstellung kommt:
Hier mal das, was ich in der App gefunden habe:
\n\u0016wn511_socket_sys.proto\"\u00ca\u0001\n\brtc_data\u0012\u0011\n\u0004week\u0018\u0001 \u0001(\u0005H\u0000\u0088\u0001\u0001\u0012\u0010\n\u0003sec\u0018\u0002 \u0001(\u0005H\u0001\u0088\u0001\u0001\u0012\u0010\n\u0003min\u0018\u0003 \u0001(\u0005H\u0002\u0088\u0001\u0001\u0012\u0011\n\u0004hour\u0018\u0004 \u0001(\u0005H\u0003\u0088\u0001\u0001\u0012\u0010\n\u0003day\u0018\u0005 \u0001(\u0005H\u0004\u0088\u0001\u0001\u0012\u0012\n\u0005month\u0018\u0006 \u0001(\u0005H\u0005\u0088\u0001\u0001\u0012\u0011\n\u0004year\u0018\u0007 \u0001(\u0005H\u0006\u0088\u0001\u0001B\u0007\n\u0005_weekB\u0006\n\u0004_secB\u0006\n\u0004_minB\u0007\n\u0005_hourB\u0006\n\u0004_dayB\b\n\u0006_monthB\u0007\n\u0005_year\"\u009e\u0001\n\u0013time_range_strategy\u0012\u0011\n\tis_config\u0018\u0001 \u0001(\b\u0012\u0011\n\tis_enable\u0018\u0002 \u0001(\b\u0012\u0011\n\ttime_mode\u0018\u0003 \u0001(\u0005\u0012\u0011\n\ttime_data\u0018\u0004 \u0001(\u0005\u0012\u001d\n\nstart_time\u0018\u0005 \u0001(\u000b2\t.rtc_data\u0012\u001c\n\tstop_time\u0018\u0006 \u0001(\u000b2\t.rtc_data\"\u001f\n\u0010plug_ack_message\u0012\u000b\n\u0003ack\u0018\u0001 \u0001(\r\"\u00a5\u0004\n\u0013plug_heartbeat_pack\u0012\u0015\n\berr_code\u0018\u0001 \u0001(\rH\u0000\u0088\u0001\u0001\u0012\u0016\n\twarn_code\u0018\u0002 \u0001(\rH\u0001\u0088\u0001\u0001\u0012\u0014\n\u0007country\u0018\u0003 \u0001(\rH\u0002\u0088\u0001\u0001\u0012\u0011\n\u0004town\u0018\u0004 \u0001(\rH\u0003\u0088\u0001\u0001\u0012\u0014\n\u0007max_cur\u0018\u0005 \u0001(\u0005H\u0004\u0088\u0001\u0001\u0012\u0011\n\u0004temp\u0018\u0006 \u0001(\u0005H\u0005\u0088\u0001\u0001\u0012\u0011\n\u0004freq\u0018\u0007 \u0001(\u0005H\u0006\u0088\u0001\u0001\u0012\u0014\n\u0007current\u0018\b \u0001(\u0005H\u0007\u0088\u0001\u0001\u0012\u0011\n\u0004volt\u0018\t \u0001(\u0005H\b\u0088\u0001\u0001\u0012\u0012\n\u0005watts\u0018\n \u0001(\u0005H\t\u0088\u0001\u0001\u0012\u0013\n\u0006switch\u0018\u000b \u0001(\bH\n\u0088\u0001\u0001\u0012\u0017\n\nbrightness\u0018\f \u0001(\u0005H\u000b\u0088\u0001\u0001\u0012\u0016\n\tmax_watts\u0018\r \u0001(\u0005H\f\u0088\u0001\u0001\u0012 \n\u0013heartbeat_frequency\u0018\u000e \u0001(\u0005H\r\u0088\u0001\u0001\u0012\u0018\n\u000bmesh_enable\u0018\u000f \u0001(\u0005H\u000e\u0088\u0001\u0001B\u000b\n\t_err_codeB\f\n\n_warn_codeB\n\n\b_countryB\u0007\n\u0005_townB\n\n\b_max_curB\u0007\n\u0005_tempB\u0007\n\u0005_freqB\n\n\b_currentB\u0007\n\u0005_voltB\b\n\u0006_wattsB\t\n\u0007_switchB\r\n\u000b_brightnessB\f\n\n_max_wattsB\u0016\n\u0014_heartbeat_frequencyB\u000e\n\f_mesh_enable\"*\n\u0013plug_switch_message\u0012\u0013\n\u000bplug_switch\u0018\u0001 \u0001(\r\"%\n\u000fbrightness_pack\u0012\u0012\n\nbrightness\u0018\u0001 \u0001(\u0005\"\u001f\n\fmax_cur_pack\u0012\u000f\n\u0007max_cur\u0018\u0001 \u0001(\u0005\"]\n\u0010time_task_config\u0012\u0011\n\ttask_name\u0018\u0001 \u0001(\t\u0012(\n\ntime_range\u0018\u0002 \u0001(\u000b2\u0014.time_range_strategy\u0012\f\n\u0004type\u0018\u0003 \u0001(\u0005\"N\n\u0015time_task_config_post\u0012\r\n\u0005index\u0018\u0001 \u0001(\u0005\u0012&\n\u000btask_config\u0018\u0002 \u0001(\u000b2\u0011.time_task_config\"D\n\tPowerItem\u0012\u0011\n\ttimestamp\u0018\u0001 \u0001(\r\u0012\u0010\n\btimezone\u0018\u0002 \u0001(\u0011\u0012\u0012\n\nplug_power\u0018\u0003 \u0001(\r\"B\n\tPowerPack\u0012\u000f\n\u0007sys_seq\u0018\u0001 \u0001(\r\u0012$\n\u0010sys_power_stream\u0018\u0002 \u0003(\u000b2\n.PowerItem\"\u001f\n\fPowerAckPack\u0012\u000f\n\u0007sys_seq\u0018\u0001 \u0001(\r\"#\n\u000emax_watts_pack\u0012\u0011\n\tmax_watts\u0018\u0001 \u0001(\u0005\"%\n\u000emesh_ctrl_pack\u0012\u0013\n\u000bmesh_enable\u0018\u0001 \u0001(\r\"\u001b\n\bret_pack\u0012\u000f\n\u0007ret_sta\u0018\u0001 \u0001(\bb\u0006proto3
So sieht das dann in klartext aus: wn511_socket_sys.proto
syntax = "proto3";
message rtc_data {
optional int32 week = 1 [default = 1];
optional int32 sec = 2 [default = 1];
optional int32 min = 3 [default = 1];
optional int32 hour = 4 [default = 1];
optional int32 day = 5 [default = 1];
optional int32 month = 6 [default = 1];
optional int32 year = 7 [default = 1];
}
message time_range_strategy {
optional bool is_config = 1;
optional bool is_enable = 2;
optional int32 time_mode = 3;
optional int32 time_data = 4;
optional rtc_data start_time = 5;
optional rtc_data stop_time = 6;
}
message plug_ack_message {
optional int32 ack = 1;
}
message plug_heartbeat_pack {
optional int32 err_code = 1 [default = 1];
optional int32 warn_code = 2 [default = 1];
optional int32 country = 3 [default = 1];
optional int32 town = 4 [default = 1];
optional int32 max_cur = 5 [default = 1];
optional int32 temp = 6 [default = 1];
optional int32 freq = 7 [default = 1];
optional int32 current = 8 [default = 1];
optional int32 volt = 9 [default = 1];
optional int32 watts = 10 [default = 1];
optional bool switch = 11;
optional int32 brightness = 12 [default = 1];
optional int32 max_watts = 13 [default = 1];
optional int32 heartbeat_frequency = 14 [default = 1];
optional bool mesh_enable = 15;
}
message plug_switch_message {
optional int32 plug_switch = 1;
}
message brightness_pack {
optional int32 brightness = 1;
}
message max_cur_pack {
optional int32 max_cur = 1;
}
message time_task_config {
optional string task_name = 1;
optional time_range_strategy time_range = 2;
optional int32 type = 3;
}
message time_task_config_post {
optional int32 index = 1;
optional time_task_config task_config = 2;
}
message PowerItem {
optional int64 timestamp = 1;
optional string timezone = 2;
optional int32 plug_power = 3;
}
message PowerPack {
optional int32 sys_seq = 1;
repeated PowerItem sys_power_stream = 2;
}
message PowerAckPack {
optional int32 sys_seq = 1;
}
message max_watts_pack {
optional int32 max_watts = 1;
}
message mesh_ctrl_pack {
optional int32 mesh_enable = 1;
}
message ret_pack {
optional bool ret_sta = 1;
}
Ich habe heute etwas rumgetüftelt und habe die Proto files in der App gefunden. nur sind die komisch codiert, ich habe dann chatgpt zur hilfe genommen, das mir dann es in klartext ausgegeben hat. Jetzt ist die Frage, ob jemand weiß, wie man von der einen darstellung auf die klartext dartstellung kommt:
Hier mal das, was ich in der App gefunden habe:
I did somethiong very similar to you expect I used basic Java thing I hacked together to reverse enginier it:
package org.example;
import com.google.protobuf.Descriptors;
public class Main {
public static void main(String[] args) {
String[] x = new String[]{"\n\u000fwn511_sys.proto\"Ä\u0014\n\u0012inverter_heartbeat--- protobuf line from app goes here"};
Descriptors.FileDescriptor descriptor = Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(x, new Descriptors.FileDescriptor[]{});
System.out.println(descriptor.toProto());
}
}
It doesn't quite output the protobuf syntax but it is very close and easy to translate.
I really like the word "rumgetüftelt" it seems like a combination of rummage (search) and scuffle (confused pushing or struggle/fight)
Did you take over my protodefinition? Fo me the deletion of the pdata=1 in the Header was important. The first id is the inverter heart beat and then is the header starting from 2. In the other cases the header must be included verbose.
I have tried both, there is an error in the protobuf posted the line PowerMessageProto item = 1;
should be optional PowerMessageProto item = 1;
I have the impression that some messages must be requested with a mqtt message to the cloud, similar to a command for setting something. Otherwise I would have no idea how the different screens in the app get their content
I have been monitoring that as well, there are a number of payloads when I launch the app on my phone that have the last few bytes of the packet ending with android
which seems to be my phone requesting data.
@foxthefox could you run this payload through your proto descriptor and let me know what the output is please?
Python
'b\n/\n\x04\xe0\x03\xef\x04\x105\x18 \x01(\x01@\x14H\x01P\x04X\x01\x80\x01\x03\x88\x01\x03\xca\x01\x10XXXXXXXXXXXXXXXX'
Hex Bytes
0a2f0a04e003ef0410351820200128014014480150045801800103880103ca011058585858585858585858585858585858
Using the Protobuf Decoder I am seeing a field 60 which appears to be an int32 which seems to have a number floating around 550-700 across various payloads I have collected. Unsure what it is, perhaps some kind of remain watts available to be exported?
@foxthefox could you run this payload through your proto descriptor and let me know what the output is please?
Hex Bytes
0a2f0a04e003ef0410351820200128014014480150045801800103880103ca011058585858585858585858585858585858
converts to (not having a definition for something after "optional uint32 ratedPower = 58" ) {"header":{"src":53,"dest":32,"dSrc":1,"dDest":1,"cmdFunc":20,"cmdId":1,"dataLen":4,"needAck":1,"version":3,"payloadVer":3,"deviceSn":"XXXXXXXXXXXXXXXX"}} {"inverter":{"invErrCode":4,"pv1ErrCode":53,"invWarnCode":32,"pv1WarnCode":1,"pv2ErrCode":1,"batWarningCode":20,"llcErrCode":1,"llcWarningCode":4,"pv1Status":88,"pv1InputVolt":3,"pv1OpVolt":3,"pv2Temp":16}}
EDIT. if I add optional int32 Unknown59 = 59; optional int32 Bat_Minutes = 60; to the header then I get for "InverterMessage": {"inverter":{"invErrCode":4,"pv1ErrCode":53,"invWarnCode":32,"pv1WarnCode":1,"pv2ErrCode":1,"batWarningCode":20,"llcErrCode":1,"llcWarningCode":4,"pv1Status":88,"pv1InputVolt":3,"pv1OpVolt":3,"pv2Temp":16,"Bat_Minutes":623}}
Did you take over my protodefinition? Fo me the deletion of the pdata=1 in the Header was important. The first id is the inverter heart beat and then is the header starting from 2. In the other cases the header must be included verbose.
I have tried both, there is an error in the protobuf posted the line
PowerMessageProto item = 1;
should beoptional PowerMessageProto item = 1;
I have the impression that some messages must be requested with a mqtt message to the cloud, similar to a command for setting something. Otherwise I would have no idea how the different screens in the app get their content
I have been monitoring that as well, there are a number of payloads when I launch the app on my phone that have the last few bytes of the packet ending with
android
which seems to be my phone requesting data.
Most likely it would be better to have it optional. In my testing I first check for the HeaderMessage only. After it is found a correct header I extract the cmdId and make a specific check e.g. if it is cmdId=136 then I check for PowerMessage. At this point I expect that the Message has the correct part in it (and not to be optional). But probably it is better to have it optional.
Ah field 60 as battery minutes remaining would make sense :)
As for the payload I posted I am getting the wrong results regardless of weather I use your proto defenition or mine:
Mine:
msg {
pdata: "\340\003\357\004"
src: 53
dest: 32
d_src: 1
d_dest: 1
cmd_id: 1
data_len: 4
need_ack: 1
version: 3
payload_ver: 3
device_sn: "XXXXXXXXXXXXXXXX"
}
e003ef04
battery_minutes: 623
foxthefox:
inverter {
pv1ErrCode: 53
invWarnCode: 32
pv1WarnCode: 1
pv2ErrCode: 1
batWarningCode: 20
llcErrCode: 1
llcWarningCode: 4
pv1Status: 1
pv1InputVolt: 3
pv1OpVolt: 3
}
Here is the code I am using for you're protobuf:
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: foxthefox/foxthefox.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19\x66oxthefox/foxthefox.proto\"\xcf\t\n\x12inverter_heartbeat\x12\x12\n\ninvErrCode\x18\x01 \x01(\r\x12\x13\n\x0binvWarnCode\x18\x03 \x01(\r\x12\x12\n\npv1ErrCode\x18\x02 \x01(\r\x12\x13\n\x0bpv1WarnCode\x18\x04 \x01(\r\x12\x12\n\npv2ErrCode\x18\x05 \x01(\r\x12\x16\n\x0epv2WarningCode\x18\x06 \x01(\r\x12\x12\n\nbatErrCode\x18\x07 \x01(\r\x12\x16\n\x0e\x62\x61tWarningCode\x18\x08 \x01(\r\x12\x12\n\nllcErrCode\x18\t \x01(\r\x12\x16\n\x0ellcWarningCode\x18\n \x01(\r\x12\x11\n\tpv1Status\x18\x0b \x01(\r\x12\x11\n\tpv2Status\x18\x0c \x01(\r\x12\x11\n\tbatStatus\x18\r \x01(\r\x12\x11\n\tllcStatus\x18\x0e \x01(\r\x12\x11\n\tinvStatus\x18\x0f \x01(\r\x12\x14\n\x0cpv1InputVolt\x18\x10 \x01(\x05\x12\x11\n\tpv1OpVolt\x18\x11 \x01(\x05\x12\x13\n\x0bpv1InputCur\x18\x12 \x01(\x05\x12\x15\n\rpv1InputWatts\x18\x13 \x01(\x05\x12\x0f\n\x07pv1Temp\x18\x14 \x01(\x05\x12\x14\n\x0cpv2InputVolt\x18\x15 \x01(\x05\x12\x11\n\tpv2OpVolt\x18\x16 \x01(\x05\x12\x13\n\x0bpv2InputCur\x18\x17 \x01(\x05\x12\x15\n\rpv2InputWatts\x18\x18 \x01(\x05\x12\x0f\n\x07pv2Temp\x18\x19 \x01(\x05\x12\x14\n\x0c\x62\x61tInputVolt\x18\x1a \x01(\x05\x12\x11\n\tbatOpVolt\x18\x1b \x01(\x05\x12\x13\n\x0b\x62\x61tInputCur\x18\x1c \x01(\x05\x12\x15\n\rbatInputWatts\x18\x1d \x01(\x05\x12\x0f\n\x07\x62\x61tTemp\x18\x1e \x01(\x05\x12\x0e\n\x06\x62\x61tSoc\x18\x1f \x01(\r\x12\x14\n\x0cllcInputVolt\x18 \x01(\x05\x12\x11\n\tllcOpVolt\x18! \x01(\x05\x12\x0f\n\x07llcTemp\x18\" \x01(\x05\x12\x14\n\x0cinvInputVolt\x18# \x01(\x05\x12\x11\n\tinvOpVolt\x18$ \x01(\x05\x12\x14\n\x0cinvOutputCur\x18% \x01(\x05\x12\x16\n\x0einvOutputWatts\x18& \x01(\x05\x12\x0f\n\x07invTemp\x18\' \x01(\x05\x12\x0f\n\x07invFreq\x18( \x01(\x05\x12\x10\n\x08invDcCur\x18) \x01(\x05\x12\x0e\n\x06\x62pType\x18* \x01(\x05\x12\x16\n\x0einvRelayStatus\x18+ \x01(\x05\x12\x16\n\x0epv1RelayStatus\x18, \x01(\x05\x12\x16\n\x0epv2RelayStatus\x18- \x01(\x05\x12\x16\n\x0einstallCountry\x18. \x01(\r\x12\x13\n\x0binstallTown\x18/ \x01(\r\x12\x16\n\x0epermanentWatts\x18\x30 \x01(\r\x12\x14\n\x0c\x64ynamicWatts\x18\x31 \x01(\r\x12\x16\n\x0esupplyPriority\x18\x32 \x01(\r\x12\x12\n\nlowerLimit\x18\x33 \x01(\r\x12\x12\n\nupperLimit\x18\x34 \x01(\r\x12\x10\n\x08invOnOff\x18\x35 \x01(\r\x12\x17\n\x0fwirelessErrCode\x18\x36 \x01(\r\x12\x18\n\x10wirelessWarnCode\x18\x37 \x01(\r\x12\x15\n\rinvBrightness\x18\x38 \x01(\r\x12\x1a\n\x12heartbeatFrequency\x18\x39 \x01(\r\x12\x12\n\nratedPower\x18: \x01(\r\"\xb1\x01\n\tPowerItem\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x10\n\x08timezone\x18\x02 \x01(\x11\x12\x19\n\x11inv_to_grid_power\x18\x03 \x01(\r\x12\x19\n\x11inv_to_plug_power\x18\x04 \x01(\r\x12\x15\n\rbattery_power\x18\x05 \x01(\x05\x12\x18\n\x10pv1_output_power\x18\x06 \x01(\r\x12\x18\n\x10pv2_output_power\x18\x07 \x01(\r\"B\n\tPowerPack\x12\x0f\n\x07sys_seq\x18\x01 \x01(\r\x12$\n\x10sys_power_stream\x18\x02 \x03(\x0b\x32\n.PowerItem\";\n\nEnergyItem\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x0c\n\x04item\x18\x02 \x01(\x05\x12\x0c\n\x04watt\x18\x03 \x01(\x0c\"E\n\nEnergyPack\x12\x0f\n\x07sys_seq\x18\x01 \x01(\r\x12&\n\x11sys_energy_stream\x18\x02 \x03(\x0b\x32\x0b.EnergyItem\"\x91\x03\n\x06Header\x12\x0b\n\x03src\x18\x02 \x01(\x05\x12\x0c\n\x04\x64\x65st\x18\x03 \x01(\x05\x12\r\n\x05\x64_src\x18\x04 \x01(\x05\x12\x0e\n\x06\x64_dest\x18\x05 \x01(\x05\x12\x10\n\x08\x65nc_type\x18\x06 \x01(\x05\x12\x12\n\ncheck_type\x18\x07 \x01(\x05\x12\x10\n\x08\x63md_func\x18\x08 \x01(\x05\x12\x0e\n\x06\x63md_id\x18\t \x01(\x05\x12\x10\n\x08\x64\x61ta_len\x18\n \x01(\x05\x12\x10\n\x08need_ack\x18\x0b \x01(\x05\x12\x0e\n\x06is_ack\x18\x0c \x01(\x05\x12\x0b\n\x03seq\x18\x0e \x01(\x05\x12\x12\n\nproduct_id\x18\x0f \x01(\x05\x12\x0f\n\x07version\x18\x10 \x01(\x05\x12\x13\n\x0bpayload_ver\x18\x11 \x01(\x05\x12\x11\n\ttime_snap\x18\x12 \x01(\x05\x12\x11\n\tis_rw_cmd\x18\x13 \x01(\x05\x12\x10\n\x08is_queue\x18\x14 \x01(\x05\x12\x10\n\x08\x61\x63k_type\x18\x15 \x01(\x05\x12\x0c\n\x04\x63ode\x18\x16 \x01(\t\x12\x0c\n\x04\x66rom\x18\x17 \x01(\t\x12\x11\n\tmodule_sn\x18\x18 \x01(\t\x12\x11\n\tdevice_sn\x18\x19 \x01(\t\"(\n\rHeaderMessage\x12\x17\n\x06header\x18\x01 \x01(\x0b\x32\x07.Header\"Q\n\x0fInverterMessage\x12%\n\x08inverter\x18\x01 \x01(\x0b\x32\x13.inverter_heartbeat\x12\x17\n\x06header\x18\x02 \x01(\x0b\x32\x07.Header\"\xbb\x03\n\x11PowerMessageProto\x12\x1d\n\tpowerpack\x18\x01 \x01(\x0b\x32\n.PowerPack\x12\x0b\n\x03src\x18\x02 \x01(\x05\x12\x0c\n\x04\x64\x65st\x18\x03 \x01(\x05\x12\r\n\x05\x64_src\x18\x04 \x01(\x05\x12\x0e\n\x06\x64_dest\x18\x05 \x01(\x05\x12\x10\n\x08\x65nc_type\x18\x06 \x01(\x05\x12\x12\n\ncheck_type\x18\x07 \x01(\x05\x12\x10\n\x08\x63md_func\x18\x08 \x01(\x05\x12\x0e\n\x06\x63md_id\x18\t \x01(\x05\x12\x10\n\x08\x64\x61ta_len\x18\n \x01(\x05\x12\x10\n\x08need_ack\x18\x0b \x01(\x05\x12\x0e\n\x06is_ack\x18\x0c \x01(\x05\x12\x0b\n\x03seq\x18\x0e \x01(\x05\x12\x12\n\nproduct_id\x18\x0f \x01(\x05\x12\x0f\n\x07version\x18\x10 \x01(\x05\x12\x13\n\x0bpayload_ver\x18\x11 \x01(\x05\x12\x11\n\ttime_snap\x18\x12 \x01(\x05\x12\x11\n\tis_rw_cmd\x18\x13 \x01(\x05\x12\x10\n\x08is_queue\x18\x14 \x01(\x05\x12\x10\n\x08\x61\x63k_type\x18\x15 \x01(\x05\x12\x0c\n\x04\x63ode\x18\x16 \x01(\t\x12\x0c\n\x04\x66rom\x18\x17 \x01(\t\x12\x11\n\tmodule_sn\x18\x18 \x01(\t\x12\x11\n\tdevice_sn\x18\x19 \x01(\t\"0\n\x0cPowerMessage\x12 \n\x04item\x18\x01 \x01(\x0b\x32\x12.PowerMessageProto\"\xbe\x03\n\x12\x45nergyMessageProto\x12\x1f\n\nenergypack\x18\x01 \x01(\x0b\x32\x0b.EnergyPack\x12\x0b\n\x03src\x18\x02 \x01(\x05\x12\x0c\n\x04\x64\x65st\x18\x03 \x01(\x05\x12\r\n\x05\x64_src\x18\x04 \x01(\x05\x12\x0e\n\x06\x64_dest\x18\x05 \x01(\x05\x12\x10\n\x08\x65nc_type\x18\x06 \x01(\x05\x12\x12\n\ncheck_type\x18\x07 \x01(\x05\x12\x10\n\x08\x63md_func\x18\x08 \x01(\x05\x12\x0e\n\x06\x63md_id\x18\t \x01(\x05\x12\x10\n\x08\x64\x61ta_len\x18\n \x01(\x05\x12\x10\n\x08need_ack\x18\x0b \x01(\x05\x12\x0e\n\x06is_ack\x18\x0c \x01(\x05\x12\x0b\n\x03seq\x18\x0e \x01(\x05\x12\x12\n\nproduct_id\x18\x0f \x01(\x05\x12\x0f\n\x07version\x18\x10 \x01(\x05\x12\x13\n\x0bpayload_ver\x18\x11 \x01(\x05\x12\x11\n\ttime_snap\x18\x12 \x01(\x05\x12\x11\n\tis_rw_cmd\x18\x13 \x01(\x05\x12\x10\n\x08is_queue\x18\x14 \x01(\x05\x12\x10\n\x08\x61\x63k_type\x18\x15 \x01(\x05\x12\x0c\n\x04\x63ode\x18\x16 \x01(\t\x12\x0c\n\x04\x66rom\x18\x17 \x01(\t\x12\x11\n\tmodule_sn\x18\x18 \x01(\t\x12\x11\n\tdevice_sn\x18\x19 \x01(\t\"2\n\rEnergyMessage\x12!\n\x04item\x18\x01 \x01(\x0b\x32\x13.EnergyMessageProto')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'foxthefox.foxthefox_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_globals['_INVERTER_HEARTBEAT']._serialized_start=30
_globals['_INVERTER_HEARTBEAT']._serialized_end=1261
_globals['_POWERITEM']._serialized_start=1264
_globals['_POWERITEM']._serialized_end=1441
_globals['_POWERPACK']._serialized_start=1443
_globals['_POWERPACK']._serialized_end=1509
_globals['_ENERGYITEM']._serialized_start=1511
_globals['_ENERGYITEM']._serialized_end=1570
_globals['_ENERGYPACK']._serialized_start=1572
_globals['_ENERGYPACK']._serialized_end=1641
_globals['_HEADER']._serialized_start=1644
_globals['_HEADER']._serialized_end=2045
_globals['_HEADERMESSAGE']._serialized_start=2047
_globals['_HEADERMESSAGE']._serialized_end=2087
_globals['_INVERTERMESSAGE']._serialized_start=2089
_globals['_INVERTERMESSAGE']._serialized_end=2170
_globals['_POWERMESSAGEPROTO']._serialized_start=2173
_globals['_POWERMESSAGEPROTO']._serialized_end=2616
_globals['_POWERMESSAGE']._serialized_start=2618
_globals['_POWERMESSAGE']._serialized_end=2666
_globals['_ENERGYMESSAGEPROTO']._serialized_start=2669
_globals['_ENERGYMESSAGEPROTO']._serialized_end=3115
_globals['_ENERGYMESSAGE']._serialized_start=3117
_globals['_ENERGYMESSAGE']._serialized_end=3167
# @@protoc_insertion_point(module_scope)
import foxthefox
packet = foxthefox.InverterMessage()
packet.ParseFromString(b'\n/\n\x04\xe0\x03\xef\x04\x105\x18 \x01(\x01@\x14H\x01P\x04X\x01\x80\x01\x03\x88\x01\x03\xca\x01\x10XXXXXXXXXXXXXXXX')
print(packet)
The protoc command I am using is protoc --python_out=. *.proto
wonder if I am missing something. What language are you using @foxthefox?
Edit: I am wondering if this note on the python protobuf page has something to do with it:
Sharing Messages Between Python and C++
Prior to the 4.21.0 version of the Protobuf Python API, Python apps could share messages with C++ using a native extension. Starting in the 4.21.0 API version, sharing messages between Python and C++ is not supported by the default install. To enable this capability when working with the 4.x and later versions of the Protobuf Python API, define the environment variable, PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp, and ensure that the Python/C++ extension is installed.
@foxthefox did you parse the payload using the Header
and inverter_heartbeat
seperately or did you use the InverterMessage
?
If it is InverterMessage
there is something very strange going on as it seems to be parsing the same data into the Header
and invert_heartbeat
objects as I am showing in this table:
Field No | Header Field | Type | Header Value | Inverter Field | Type | Inverter Value |
---|---|---|---|---|---|---|
1 | invErrCode | uint32 | 4 | |||
2 | src | int32 | 53 | pv1ErrCode | uint32 | 53 |
3 | dest | int32 | 32 | invWarnCode | uint32 | 32 |
4 | d_src | int32 | 1 | pv1WarnCode | uint32 | 1 |
5 | d_dest | int32 | 1 | pv2ErrCode | uint32 | 1 |
8 | cmd_func | int32 | 20 | batWarningCode | uint32 | 20 |
9 | cmd_id | int32 | 1 | llcErrCode | uint32 | 1 |
10 | data_len | int32 | 4 | llcWarningCode | uint32 | 4 |
11 | need_ack | int32 | 1 | pv1Status | uint32 | 88 |
15 | version | int32 | 3 | pv1InputVolt | int32 | 3 |
17 | payload_ver | int32 | 3 | pv1OpVolt | int32 | 3 |
25 | deviceSn | string | XXXXXXXXXXXXXXXX | pv2Temp | int32 | 16 |
I can see that need_ack
and pv1Status
don't match but oddly the ASCII code for X
is 88 so possibly mixing up with the serial number?
While I can't say 100% for sure but I believe that pv2Temp
is unlikely to be 16C as the ambiant tempurature around the time this message was received was higher than that and I can't see the PowerStream being cooler than ambiant as it doesn't have a fan.
I have taken the same payload I had above and change the serial number to ABCDEFGHIJKLMNOP
I would be interested to see if the values for pv1Status
and pv2Temp
change.
0a2f0a04e003ef0410351820200128014014480150045801800103880103ca01104142434445464748494a4b4c4d4e4f50
Ah field 60 as battery minutes remaining would make sense :) This was an assumption from a protobuf definition in the above mentioned ioBroker script.
Mine:
msg { pdata: "\340\003\357\004" src: 53 dest: 32 d_src: 1 d_dest: 1 cmd_id: 1 data_len: 4 need_ack: 1 version: 3 payload_ver: 3 device_sn: "XXXXXXXXXXXXXXXX" } e003ef04 battery_minutes: 623
You have requested the Header including the pdata portion on position 1.
foxthefox:
inverter { pv1ErrCode: 53 invWarnCode: 32 pv1WarnCode: 1 pv2ErrCode: 1 batWarningCode: 20 llcErrCode: 1 llcWarningCode: 4 pv1Status: 1 pv1InputVolt: 3 pv1OpVolt: 3 }
I always check for the whole InverterMessage and (inverterheartbeat + header with my stacking above). For the try with field 60, I only posted the inverter part.
The protoc command I am using is
protoc --python_out=. *.proto
wonder if I am missing something. What language are you using @foxthefox? I am using nodejs for my testing, you can find it here https://forum.iobroker.net/topic/66743/ecoflow-connector-script-zur-dynamischen-leistungsanpassung/8?_=1689405453194Edit: I am wondering if this note on the python protobuf page has something to do with it:
I have no idea if there is something wrong. I have no experience with python, but looking at your code, especially to the google definition, I wonder if these hardcoded start and end tags are feasable. The messages I am getting are in various sizes, even they contain the heartbeat. But depending how much from the heartbeat is inside the message, the heartbeat portion is different in size.
In case of the JS I have understood that you let the message decode and according the definitions and their optionality the library finds the correct ones. Something similiar I would expect from the python library, I hope thats possible. But maybe I am misinterpreting these start and end definitions.
@foxthefox did you parse the payload using the
Header
andinverter_heartbeat
seperately or did you use theInverterMessage
?0a2f0a04e003ef0410351820200128014014480150045801800103880103ca01104142434445464748494a4b4c4d4e4f50
Your observation is absolutely correct. it's double using the information I was already wondering if it could be right, that there are so strange error codes in my decoding.
I again checked your hex string as well as my previous captured ones and now its more reasonable. Your string converts to {"item":{"inverter":{"Bat_Minutes":623},"src":53,"dest":32,"dSrc":1,"dDest":1,"cmdFunc":20,"cmdId":1,"dataLen":4,"needAck":1,"version":3,"payloadVer":3,"deviceSn":"ABCDEFGHIJKLMNOP"}}
Whereby I have checked for the InverterMessage and adapted to the following proto:
message inverter_heartbeat {
optional uint32 invErrCode = 1;
optional uint32 invWarnCode = 3;
optional uint32 pv1ErrCode = 2;
optional uint32 pv1WarnCode = 4;
optional uint32 pv2ErrCode = 5;
optional uint32 pv2WarningCode = 6;
optional uint32 batErrCode = 7;
optional uint32 batWarningCode = 8;
optional uint32 llcErrCode = 9;
optional uint32 llcWarningCode = 10;
optional uint32 pv1Status = 11;
optional uint32 pv2Status = 12;
optional uint32 batStatus = 13;
optional uint32 llcStatus = 14;
optional uint32 invStatus = 15;
optional int32 pv1InputVolt = 16;
optional int32 pv1OpVolt = 17;
optional int32 pv1InputCur = 18;
optional int32 pv1InputWatts = 19;
optional int32 pv1Temp = 20;
optional int32 pv2InputVolt = 21;
optional int32 pv2OpVolt = 22;
optional int32 pv2InputCur = 23;
optional int32 pv2InputWatts = 24;
optional int32 pv2Temp = 25;
optional int32 batInputVolt = 26;
optional int32 batOpVolt = 27;
optional int32 batInputCur = 28;
optional int32 batInputWatts = 29;
optional int32 batTemp = 30;
optional uint32 batSoc = 31;
optional int32 llcInputVolt = 32;
optional int32 llcOpVolt = 33;
optional int32 llcTemp = 34;
optional int32 invInputVolt = 35;
optional int32 invOpVolt = 36;
optional int32 invOutputCur = 37;
optional int32 invOutputWatts = 38;
optional int32 invTemp = 39;
optional int32 invFreq = 40;
optional int32 invDcCur = 41;
optional int32 bpType = 42;
optional int32 invRelayStatus = 43;
optional int32 pv1RelayStatus = 44;
optional int32 pv2RelayStatus = 45;
optional uint32 installCountry = 46;
optional uint32 installTown = 47;
optional uint32 permanentWatts = 48;
optional uint32 dynamicWatts = 49;
optional uint32 supplyPriority = 50;
optional uint32 lowerLimit = 51;
optional uint32 upperLimit = 52;
optional uint32 invOnOff = 53;
optional uint32 wirelessErrCode = 54;
optional uint32 wirelessWarnCode = 55;
optional uint32 invBrightness = 56;
optional uint32 heartbeatFrequency = 57;
optional uint32 ratedPower = 58;
optional int32 Unknown59 = 59;
optional int32 Bat_Minutes = 60;
}
message InverterMessageProto {
optional inverter_heartbeat inverter = 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;
}
message InverterMessage{
optional InverterMessageProto item =1;
}
My longest heartbeat capture decodes to {"item":{"inverter":{"invErrCode":4096,"pv1ErrCode":128,"invWarnCode":0,"pv1WarnCode":0,"pv2ErrCode":128,"pv2WarningCode":0,"batErrCode":0,"batWarningCode":0,"llcErrCode":0,"llcWarningCode":0,"pv1Status":1,"pv2Status":1,"batStatus":5,"llcStatus":5,"invStatus":11,"pv1InputVolt":11,"pv1OpVolt":620,"pv1InputCur":3,"pv1InputWatts":3,"pv1Temp":370,"pv2InputVolt":11,"pv2OpVolt":620,"pv2InputCur":0,"pv2InputWatts":0,"pv2Temp":370,"batInputVolt":460,"batOpVolt":620,"batInputCur":0,"batInputWatts":0,"batTemp":290,"batSoc":1,"llcInputVolt":620,"llcOpVolt":17,"llcTemp":0,"invInputVolt":17,"invOpVolt":0,"invOutputCur":0,"invOutputWatts":0,"invTemp":380,"invFreq":0,"bpType":2,"invRelayStatus":8,"pv1RelayStatus":0,"pv2RelayStatus":0,"installCountry":17477,"installTown":0,"permanentWatts":1500,"dynamicWatts":0,"supplyPriority":0,"lowerLimit":0,"upperLimit":100,"invOnOff":1,"wirelessErrCode":0,"wirelessWarnCode":0,"invBrightness":101,"heartbeatFrequency":2,"ratedPower":6000,"Unknown59":382,"Bat_Minutes":5999},"src":53,"dest":32,"dSrc":1,"dDest":1,"cmdFunc":20,"cmdId":1,"dataLen":180,"needAck":1,"version":3,"payloadVer":3,"deviceSn":"HW51xxxxx"}}
It looks much nicer, whereby my only concern is the 620 at some of the "xxVolt" which could be 62.0V but most likely not the correct value for the Delta Max. I must admit that these captures are made not everything connected and during startup of power stream.
I have made some more testing, most likely the decoding is correct and there are some fallback values which are inserted when a function is not used/connected/working. So 62.0V is an indication for not working or so, it reads 42,4V correctly when the solar panel is connected. Similar applies to the Unknown59 and the Batt_time(60), if no battery is connected, it switches to 143999. With this testing it is also clear for me that position 59 is the remaining battery charging time and position 60 is the remaining discharge time.
Guys... you are crazy! Thank you for your hard work!🥳
@foxthefox Have you tried putting a packet with the cmd id of 134 into Protobuf Decoder? I am mostly guessing but I think that it includes a complete inverter heartbeat
Edit: Looking at it some more it appears to be 2 protobuf messages concatinated being a cmd id 1 and a cmd id 134. I will have to try and work out how to split them.
Edit2: Ok it seems the Ecoflow have a way of sending multiple messages and the Ecopacket/FrameInfo is the representation of that and is what the data_len field is for. The mobile app makes use of a class called TreatyEngine which appears to be an interface that doesn't have an implementation which seems to do the splitting of the frames. Going to play around with it and see what I can come up with.
Only once I had a message with id 134 captured (196 bytes). The protobuf decoder shows 2 messages with field number = 1, the first is most likely an inverter heartbeat and the consecutive header. The second message seems to be a power message followed by the header. My check on the HeaderMessage only catches the second header.
If this message is not so often distributed, I would skip that for the moment. I am still trying to find a way to get the energy values out of the message.
The one capture of an almost complete inverter heartbeat was cmdId=1 and had a length of 304 bytes.
Yeah I found the same thing, what I did is get the length of message I could read and remove that from the end of the payload and put it back through the parser and I got both payloads successfully. I do get that packet when I open the mobile app which might help you spot it.
This is my code for it:
payload = b'xxx'
while True:
print(payload)
print("--")
# print(payload.hex())
# print("--")
ecopacket = ecopacket_pb2.SendHeaderMsg()
ecopacket.ParseFromString(payload)
print(ecopacket)
cmd_id = ecopacket.msg.cmd_id
if cmd_id == 1:
pdata = powerstream_pb2.InverterHeartbeat()
elif cmd_id == 32:
# 1 grid | 2 plug | 3 to battery (Math.abs) | 4 from battery (Math.abs) | 5 plug single total | 6 plug use time
pdata = platform_pb2.BatchEnergyTotalReport()
elif cmd_id == 136 or cmd_id == 134:
pdata = powerstream_pb2.PowerPack()
print(ecopacket.msg.pdata.hex())
pdata.ParseFromString(ecopacket.msg.pdata)
print(pdata)
packetLength = len(payload) - ecopacket.ByteSize()
if packetLength <= 0:
break
payload = payload[:packetLength]
print ("###")
Hello, this works well in my system, so thanks a lot to all who participated in developing the Integration! One question, though: Do you think it‘s possible to enhance this further by adding a possibility to set a value on the Powerstream, eg. the AC output Power value?
Hello, this works well in my system, so thanks a lot to all who participated in developing the Integration! One question, though: Do you think it‘s possible to enhance this further by adding a possibility to set a value on the Powerstream, eg. the AC output Power value?
I'm looking as well for a function to set the AC output value. Many thanks for the integration.
Unless I'm miss understanding what your are asking for the field called "Inverter Output Watts" is the field you are after.
If not there are a load of other fields that are disabled by default in the diagnostics box, if you scroll to the bottom there is a link that will say something like "+10 entities not shown". If you press that it will show a load of new entities that have been grayed out. If you tap on the one you want, a menu will open, if you press the cog (top right) there is an option to enable the entity and press save.
The fields that you may be invested in are "Inverter Output Current" and "Inverter Output Potential".
Just be aware that it may take a little time for that field to display a value
„Inverter Output Watts“ reports the power output from the inverter to the grid as a sensor value only, but it is not possible to change it to another value, so that the inverter increases or decreases the power output. For example, if I start my computer, the power required from the grid increases. Right now I use the EcoFlow App to manually adjust the output of the inverter accordingly by moving the slider in the App to a higher value. I‘d like to automate this change of the power output in HA (I retrieve the grid consumption by a Tibber Pulse sensor integrated to HA). For this I would need some sort of input field in the integration.
Ah is ee what your asking take a look over here: https://github.com/tolwi/hassio-ecoflow-cloud/pull/66#issuecomment-1641830105
Yes, I think this is the same topic. I‘ve read some of the comments in the other thread and if writing the „other loads“ value may cause damage to the memory, emulating a Smart Plug to achieve a dynamic output seems to be the much better way to do it. I will follow the discussion in the other thread now. Thanks for pointing to it!
I have no clue what IO Broker is and if a similar solution is possible in HA
I used the IOBroker script to write a node.js server that changes the output value....https://github.com/bogdancs92/ecoflow-powerstream-nodejs
from HA i do a rest call with a param that sets the output power for the powerstream
Hi all, I saw that Ecoflow recently published their new smart inverter: PowerStream. Does anyone know if this can be supported as well and if you can eg. Control the electrical feedback power to the grid?
Thanks Valentin