New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PowerStream Support? #54
Comments
+1 |
1 similar comment
+1 |
+1 |
I tried adding the powerstream via DIAGNOSTIC type, but the diagnostic dump does not reveal anything useful: |
I have also a Powerstream and I can help with testing. But I do not have knowledge in python coding. |
Can anyone say to me how i can help? |
Hi, first of all EcoFlow has to enable the API access to the PowerStream devices. |
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. � Tell me if i can provide Infos you need I have requested API access from Ecoflow. |
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 taskStill trying to get my head around this, there seems to be upto 11 tasks each one
135 power historyThis is an array of smaller units with this data structure
137 power reportSame payload as |
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... |
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 |
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.
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: #66
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;
}
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;
}
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. |
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? 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. Hier mal das, was ich in der App gefunden habe:
So sieht das dann in klartext aus:
|
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) |
I have tried both, there is an error in the protobuf posted the line
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 |
@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
|
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? |
converts to (not having a definition for something after "optional uint32 ratedPower = 58" ) EDIT. |
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:
foxthefox:
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 Edit: I am wondering if this note on the python protobuf page has something to do with it:
|
@foxthefox did you parse the payload using the If it is
I can see that While I can't say 100% for sure but I believe that I have taken the same payload I had above and change the serial number to
|
You have requested the Header including the pdata portion on position 1.
I always check for the whole InverterMessage and (inverterheartbeat + header with my stacking above).
I have no idea if there is something wrong. 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. |
Your observation is absolutely correct. it's double using the information I again checked your hex string as well as my previous captured ones and now its more reasonable. Whereby I have checked for the InverterMessage and adapted to the following proto:
My longest heartbeat capture decodes to 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. |
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). If this message is not so often distributed, I would skip that for the moment. 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! |
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: #66 (comment) |
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 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
The text was updated successfully, but these errors were encountered: