Skip to content

fix(deye): send touDays, so the TOU programme actually runs - #4591

Open
springfall2008 wants to merge 1 commit into
mainfrom
fix/deye-tou-days
Open

fix(deye): send touDays, so the TOU programme actually runs#4591
springfall2008 wants to merge 1 commit into
mainfrom
fix/deye-tou-days

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Follow-up to the note at the end of #4589, which asked whether deye.py shares the per-slot gap found on Sunsynk. It does not — but the same class of defect is present at the top level instead.

touDays was never sent

DEYE's TOU programme runs only on the days named in touDays. Predbat sent touAction: "on" without it, leaving the active days at whatever the inverter already held. A programme whose days are empty, or which omits the day the plan is for, is stored and simply never applied — the same silent failure mode as Sunsynk's dropped per-slot flags, one level up.

All four official strategy samples send the full seven-day list:

"touDays": ["SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"],

and the endpoint contract in the design spec has carried touDays[] from the start — it just never reached the payload. Sunsynk sets its seven mondayOn..sundayOn flags for exactly this reason.

All seven days is the only correct value here: Predbat re-derives a 24h programme every cycle and has no notion of a day its plan should be dormant.

The per-slot field set is complete, so #4589's failure cannot occur here

TimeUseSettingItem is exactly {time, power, soc, enableGridCharge, enableGeneration} in the official commission/sys_tou_update.py sample and in all four strategy/dynamic_control_*.py samples, and Predbat writes all five on every slot.

There is no sell/export enable to omit. That bit does exist in the underlying register — prog{n} 0x40 single-phase, 0x20 three-phase, which is what Sunsynk Connect exposes as sellTime{n}En — but the DEYE cloud does not expose it per slot, deriving export from workMode instead.

A new test pins the field set rather than trusting each call site to remember it. It was proven to fail by dropping one field:

ERROR: schedule 0 produced a slot with fields ['enableGridCharge', 'power', 'soc', 'time'],
                                     expected ['enableGeneration', 'enableGridCharge', 'power', 'soc', 'time']

Two inferences from #4580 are now confirmed from DEYE's own side

ZERO_EXPORT_TO_CT for non-export states and solarSellAction: "on" were both taken from Sunsynk hardware and shipped in #4586 flagged as unverified on DEYE. The official samples settle both:

  • dynamic_control_self_consumption.py is exactly workMode: ZERO_EXPORT_TO_CT with solarSellAction: "on"
  • three of the four samples send solarSellAction: "on"; only fully-charge omits it
  • dynamic_control_fully_charge.py uses ZERO_EXPORT_TO_CT with gridChargeAction: "on" and a high slot SOC — Predbat's charge state

Recorded in the comments where those decisions live, so the "unconfirmed on DEYE" caveat does not outlive the evidence.

Noted but deliberately not changed

The samples set enableGridCharge: True on every slot in all four strategies, controlling behaviour through the top-level gridChargeAction and the slot SOC target instead. Predbat sets it per-slot so a single payload can carry a charge window among self-use slots — something none of the samples exercise, since each is a whole-day single behaviour. Left alone; worth revisiting if a live DEYE write ever shows the per-slot flag behaving differently.

maxSellPower / maxSolarPower also appear in the export sample. They are installer-owned export caps, not Predbat's to write, so they stay out of the payload.

Tests

Test Covers
tou_days every payload names all seven days, active or idle, and the constant is seven upper-case names
slot_field_set every slot of every state carries exactly the five documented fields

All 7 DEYE suites, the full --quick run and run_pre_commit pass.

Follows up #4589

🤖 Generated with Claude Code

Follow-up to #4589's note asking whether deye.py shares the per-slot gap
found on Sunsynk. It does not - but the same class of defect is here at
the top level instead.

**touDays was never sent.** DEYE's TOU programme runs only on the days
named in touDays, and Predbat sent touAction without it, leaving the
active days at whatever the inverter already held. A programme whose days
are empty, or which omits the day the plan is for, is stored and never
applied - the same silent failure as Sunsynk's dropped flags, at a
different level. All four official strategy samples
(clientcode/strategy/dynamic_control_*.py) send the full seven-day list,
and the endpoint contract in the design spec has carried touDays[] since
the beginning; it simply never reached the payload. Sunsynk sets its seven
mondayOn..sundayOn flags for exactly this reason.

All seven is the only correct value: Predbat re-derives a 24h programme
every cycle and has no notion of a day its plan should be dormant.

**The per-slot field set is complete, so the Sunsynk failure cannot
occur.** TimeUseSettingItem is exactly {time, power, soc, enableGridCharge,
enableGeneration} in the official commission sample and in all four
strategy samples, and Predbat writes all five on every slot. There is no
sell/export enable to omit: that bit exists in the underlying register
(prog{n} 0x40 single-phase, 0x20 three-phase) but the DEYE cloud does not
expose it per slot, deriving export from workMode instead. A new test pins
the field set rather than trusting each call site to remember it - proven
to fail by dropping one field.

**Two inferences from #4580 are now confirmed from DEYE's own side.**
ZERO_EXPORT_TO_CT for non-export states and solarSellAction on were both
taken from Sunsynk hardware and flagged as unverified on DEYE. The
official self-consumption sample is exactly ZERO_EXPORT_TO_CT with
solarSellAction on; three of the four samples send solarSellAction on.
Recorded in the comments where those decisions live.

Follows up #4589

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 10:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures Predbat’s DEYE dynamic control writes include the touDays field so the inverter’s TOU programme actually runs on all days, matching DEYE’s documented/sample payload contract. This is part of the DEYE control-path (apps/predbat/deye.py) that builds and posts the 24h TOU schedule every cycle.

Changes:

  • Add touDays (all seven days) to the DEYE dynamic control payload.
  • Introduce DEYE_TOU_DAYS constant documenting and centralizing the required seven-day list.
  • Add DEYE control tests that verify touDays is always present and that each TOU slot includes the complete documented field set.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
apps/predbat/deye.py Adds touDays to the dynamic payload and updates inline rationale/comments.
apps/predbat/deye_const.py Defines DEYE_TOU_DAYS and expands documentation around TOU slot field completeness.
apps/predbat/tests/test_deye_control.py Adds tests for touDays presence and per-slot field set completeness; registers them in the DEYE suite.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

"""
failed = False
d = MockDeye().with_rating("INV1")
expected = set(TOU_FIELD.values())
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

Successfully merging this pull request may close these issues.

2 participants