Skip to content
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

Open
sporttiger1000 opened this issue Jun 2, 2023 · 49 comments
Open

PowerStream Support? #54

sporttiger1000 opened this issue Jun 2, 2023 · 49 comments
Milestone

Comments

@sporttiger1000
Copy link

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

@shaggee
Copy link

shaggee commented Jun 4, 2023

+1

1 similar comment
@heradon
Copy link

heradon commented Jun 6, 2023

+1

@ChristianFFB
Copy link

+1
How can i participate with needed data?

@dallmerzerbe
Copy link

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": [] }

@Technikfreak72
Copy link

I have also a Powerstream and I can help with testing. But I do not have knowledge in python coding.

@fightervenom
Copy link

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.
Screenshot_20230608-174401

@heradon
Copy link

heradon commented Jun 12, 2023

Can anyone say to me how i can help?
LG Heradon

@ChristianFFB
Copy link

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"
}

@heradon
Copy link

heradon commented Jun 12, 2023

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

@sani007zh
Copy link

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

@mattwells
Copy link
Contributor

mattwells commented Jun 25, 2023

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

@TheHangMan97
Copy link

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?😊

@mattwells
Copy link
Contributor

Easiest options in order are:

  1. Ask tolwi nicely
  2. Find a developer who is familiar with home assistant and reading binary packets and ask them nicely
  3. Learn how to do it yourself

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.

@011gwerc01
Copy link

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

@mattwells
Copy link
Contributor

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

@mattwells
Copy link
Contributor

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.

@mattwells
Copy link
Contributor

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.

@mattwells mattwells mentioned this issue Jun 27, 2023
5 tasks
@buffcode
Copy link
Contributor

buffcode commented Jul 3, 2023

According to the ioBroker forum it is a Protobuf encoded message for which a standard deserializer should already exist.

@mattwells
Copy link
Contributor

mattwells commented Jul 3, 2023

protobuff is the serializer/deserializer https://protobuf.dev/

Ecoflow even use it to build the Ecoflow App

@foxthefox
Copy link
Contributor

foxthefox commented Jul 11, 2023

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

  • HeaderMessage
  • InverterMessage
  • PowerMessage
  • EnergyMessage

Hope it helps for your work

@mattwells
Copy link
Contributor

mattwells commented Jul 11, 2023

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

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;
}

@foxthefox
Copy link
Contributor

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.

@mattwells
Copy link
Contributor

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?

@foxthefox
Copy link
Contributor

foxthefox commented Jul 14, 2023

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 did have the power reporting as cmdId=136
  • there are a lot of messages going around
  • most messages are the inverter heartbeat messages
  • less is energy and power
  • I did have occasions where the power messages was long time not received, but several energy ones
  • most messages you receive after starting the power stream, including all kinds
  • I did have messages with cmdId=124 and they were filled with 0, but that may be caused from my non-permament setup
  • the heartbeat messages with the full content are very rare (after start I did have one), the most ones are stripped to some (approx. 2-6) values
  • the energy messages give not a clear result when checking with https://protobuf-decoder.netlify.app, therefore the 3rd part after sequence is bytes in my case

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

@felixcrafter54
Copy link

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;
}

@mattwells
Copy link
Contributor

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)

@mattwells
Copy link
Contributor

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.

@mattwells
Copy link
Contributor

mattwells commented Jul 14, 2023

@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

@mattwells
Copy link
Contributor

mattwells commented Jul 14, 2023

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
Copy link
Contributor

foxthefox commented Jul 14, 2023

@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}}

@foxthefox
Copy link
Contributor

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.

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.

@mattwells
Copy link
Contributor

mattwells commented Jul 14, 2023

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.

@mattwells
Copy link
Contributor

@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

@foxthefox
Copy link
Contributor

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?_=1689405453194

Edit: 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
Copy link
Contributor

@foxthefox did you parse the payload using the Header and inverter_heartbeat seperately or did you use the InverterMessage?

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.

@foxthefox
Copy link
Contributor

foxthefox commented Jul 15, 2023

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.

@TheHangMan97
Copy link

Guys... you are crazy! Thank you for your hard work!🥳

@mattwells
Copy link
Contributor

mattwells commented Jul 16, 2023

@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.

@foxthefox
Copy link
Contributor

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.

@mattwells
Copy link
Contributor

mattwells commented Jul 16, 2023

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 ("###")

@tolwi tolwi added this to the v0.11.0 milestone Jul 17, 2023
@inftygit
Copy link

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?

@svenerbe
Copy link

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.

@mattwells
Copy link
Contributor

mattwells commented Jul 20, 2023

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

@inftygit
Copy link

„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.

@mattwells
Copy link
Contributor

Ah is ee what your asking take a look over here: #66 (comment)

@inftygit
Copy link

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!

@bogdancs92
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests