Skip to content

DEYE: enableGeneration forced on for every TOU slot, and no per-slot export enable #4590

Description

@springfall2008

Two questions about TimeUseSettingItem's per-slot flags, raised by comparing deye.py against a Sunsynk inverter's live settings. Sunsynk is rebadged DEYE hardware, so the underlying registers should be the same, but the two clouds expose different flag sets and the difference looks significant.

1. enableGeneration is forced True on every slot (likely a bug)

_self_use_slot() and _action_slot() both hardcode TOU_FIELD["generate"]: True, so every one of the six slots is written with it enabled, in every state, always. It is never False and never carried through from what the inverter already had.

Reproduced against main:

import predbat  # noqa: F401
from tests.test_deye_api import MockDeye
from deye_const import TOU_FIELD

d = MockDeye()
d.device_rated_power["SN1"] = 8000.0
idle = {"enable": False, "soc": 0, "power": 0}
sched = {"reserve": 20, "charge": {"enable": True, "soc": 90, "power": 3000, "start": "02:00", "end": "05:00"}, "export": dict(idle)}
p = d.build_dynamic_payload("SN1", sched, current_soc=40, now_minutes=3*60)
print([s[TOU_FIELD["generate"]] for s in p["timeUseSettingItems"]])
idle (no windows)      enableGeneration : [True, True, True, True, True, True]
charge window          enableGeneration : [True, True, True, True, True, True]
export window          enableGeneration : [True, True, True, True, True, True]

The Sunsynk equivalent of this flag is genTime{n}on, which the app labels Gen Charge — per-slot charging from an external generator, alongside Grid Charge and Sell. If enableGeneration is the same register, Predbat is enabling generator charging on all six slots unconditionally, which on a system with a generator attached could actually run it.

Generator charging is not Predbat's to own — it is the user's setting, exactly like Solar Sell (#4580). The Sunsynk component carries genTime{n}on through from the last read rather than setting it, and DEYE could do the same.

The design spec hedges on this field, describing it as the "gen/PV-charge flag" (docs/superpowers/specs/2026-07-19-deye-cloud-inverter-integration-design.md), so it is worth confirming against the DeyeCloud sample code before changing behaviour. If it genuinely means "allow PV generation during this slot" then always-True is correct and harmless.

2. How does a forced export slot get armed?

This is a question rather than a bug report.

Sunsynk has three per-slot flags; DEYE's TimeUseSettingItem models two:

Sunsynk DEYE
time{n}on — Grid Charge enableGridCharge
genTime{n}on — Gen Charge enableGeneration (presumably)
sellTime{n}EnSell no equivalent

On Sunsynk that third flag is not optional: sellTime{n}En must be 1 for a forced export slot, and an export window will not arm without it. Confirmed live — and its absence from the payload also caused the API to silently discard time{n}on, so grid charge could not be written either.

In deye.py an export slot is distinguished only by the global workMode = SELLING_FIRST plus the slot's SoC target sitting below the current SoC:

export window   workMode=SELLING_FIRST
                enableGridCharge : [False, False, False, False, False, False]
                (soc, power)     : [(20,8000), (20,8000), (20,8000), (20,8000), (30,2500), (20,8000)]

That may be entirely correct — DeyeCloud is a different API with a different control surface, and the documented TimeUseSettingItem is {time, power, soc, enableGridCharge, enableGeneration} with no export enable in it. But given how load-bearing the equivalent flag turned out to be on Sunsynk, it seems worth confirming that a DEYE forced-export slot really is armed by the work mode alone, and that nothing per-slot needs setting for the battery to discharge to grid.

Worth noting neither third-party Sunsynk client writes the Sell flag either — solarsynkv3 and synkctl both use the grid-charge flag alone — so this class of omission is easy to inherit from reference implementations.

Context

Found while write-testing a Sunsynk Connect integration against a real inverter. Related to #4560, #4580 and #4581, which came from the same comparison. The Sunsynk fixes are in #4589.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions