Skip to content

Commit de63679

Browse files
committed
Stream AC Pro: proto commands for backupReserveSoc and state field decoding
- Replace JSON-based BatteryBackupLevel with protobuf command (field 102) - Add _decode_manual_fields to extract state fields not in proto definition: field 461 -> backupReverseSoc (read) field 1628 -> feedGridMode (read) field 380 -> relay2Onoff (read) - Fixes backupReserveSoc and feedGridMode showing as unknown in HA UI
1 parent 218ad96 commit de63679

1 file changed

Lines changed: 54 additions & 13 deletions

File tree

  • custom_components/ecoflow_cloud/devices/internal

custom_components/ecoflow_cloud/devices/internal/stream_ac.py

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,15 @@ def encode_field(fnum, val):
328328

329329
# moduleWifiRssi
330330
def numbers(self, client: EcoflowApiClient) -> list[NumberEntity]:
331+
outer_self = self
332+
333+
class ProtoBackupReserveEntity(BatteryBackupLevel):
334+
async def async_set_native_value(self_, value: float):
335+
raw = outer_self._build_proto_command(102, int(value))
336+
client.mqtt_client.publish(outer_self.device_info.set_topic, raw)
337+
331338
return [
332-
BatteryBackupLevel(
339+
ProtoBackupReserveEntity(
333340
client,
334341
self,
335342
"backupReverseSoc",
@@ -339,18 +346,7 @@ def numbers(self, client: EcoflowApiClient) -> list[NumberEntity]:
339346
"cmsMinDsgSoc",
340347
"cmsMaxChgSoc",
341348
3,
342-
lambda value: {
343-
"sn": self.device_info.sn,
344-
"cmdId": 17,
345-
"cmdFunc": 254,
346-
"dirDest": 1,
347-
"dirSrc": 1,
348-
"dest": 2,
349-
"needAck": True,
350-
"params": {
351-
"cfgBackupReverseSoc": int(value),
352-
},
353-
},
349+
lambda value: {},
354350
),
355351
]
356352

@@ -388,6 +384,49 @@ def turn_off(self_, **kwargs):
388384
def selects(self, client: EcoflowApiClient) -> list[SelectEntity]:
389385
return []
390386

387+
_MANUAL_FIELD_MAP: dict = {
388+
380: ("relay2Onoff", bool),
389+
461: ("backupReverseSoc", int),
390+
1628: ("feedGridMode", int),
391+
}
392+
393+
def _decode_manual_fields(self, pdata: bytes, raw: dict) -> None:
394+
"""Extract specific unmapped protobuf varint fields from raw pdata bytes."""
395+
396+
def decode_varint(data, pos):
397+
result, shift = 0, 0
398+
while pos < len(data):
399+
b = data[pos]
400+
pos += 1
401+
result |= (b & 0x7F) << shift
402+
if not (b & 0x80):
403+
return result, pos
404+
shift += 7
405+
return result, pos
406+
407+
pos = 0
408+
while pos < len(pdata):
409+
try:
410+
tag, pos = decode_varint(pdata, pos)
411+
field_num = tag >> 3
412+
wire_type = tag & 0x7
413+
if wire_type == 0:
414+
value, pos = decode_varint(pdata, pos)
415+
if field_num in self._MANUAL_FIELD_MAP:
416+
name, cast = self._MANUAL_FIELD_MAP[field_num]
417+
raw["params"][name] = cast(value)
418+
elif wire_type == 2:
419+
length, pos = decode_varint(pdata, pos)
420+
pos += length
421+
elif wire_type == 5:
422+
pos += 4
423+
elif wire_type == 1:
424+
pos += 8
425+
else:
426+
break
427+
except Exception:
428+
break
429+
391430
@override
392431
def _prepare_data(self, raw_data: bytes) -> dict[str, Any]:
393432
raw: dict[str, Any] = {"params": {}}
@@ -444,6 +483,8 @@ def _prepare_data(self, raw_data: bytes) -> dict[str, Any]:
444483
if packet.msg.cmd_id > 0:
445484
self._parsedata(packet, stream_ac2.StreamACChamp_cmd50_3(), raw)
446485

486+
self._decode_manual_fields(packet.msg.pdata, raw)
487+
447488
_LOGGER.info("Found %u fields", len(raw["params"]))
448489

449490
raw["timestamp"] = utcnow()

0 commit comments

Comments
 (0)