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}En — Sell |
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
Two questions about
TimeUseSettingItem's per-slot flags, raised by comparingdeye.pyagainst 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.
enableGenerationis forcedTrueon every slot (likely a bug)_self_use_slot()and_action_slot()both hardcodeTOU_FIELD["generate"]: True, so every one of the six slots is written with it enabled, in every state, always. It is neverFalseand never carried through from what the inverter already had.Reproduced against
main: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. IfenableGenerationis 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}onthrough 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-Trueis 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
TimeUseSettingItemmodels two:time{n}on— Grid ChargeenableGridChargegenTime{n}on— Gen ChargeenableGeneration(presumably)sellTime{n}En— SellOn Sunsynk that third flag is not optional:
sellTime{n}Enmust be1for 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 discardtime{n}on, so grid charge could not be written either.In
deye.pyan export slot is distinguished only by the globalworkMode = SELLING_FIRSTplus the slot's SoC target sitting below the current SoC:That may be entirely correct — DeyeCloud is a different API with a different control surface, and the documented
TimeUseSettingItemis{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