@@ -230,10 +230,8 @@ What this setup depends on:
230230 AlphaESS app. An unused slot is stored as start equal to end and reads as
231231 ` unknown ` , which Predbat cannot parse as a window, and no one-sided write can
232232 create both sides at once.
233- - ** Leave Scheduled Discharging on.** Predbat turns the charge enable switch off
234- while it moves a window, and Home Assistant refuses to author the
235- both-timers-off state because it is indistinguishable from a self-consumption
236- mode.
233+ - ** Leave Scheduled Discharging on** if you do not want Predbat's temporary
234+ charge-disable step to leave both schedules disabled while it moves a window.
237235- ** Times are rounded to the nearest 15 minutes.** Predbat reads an entity back
238236 after writing it and warns when the value differs, so keep
239237 ` inverter_clock_skew_start ` and ` inverter_clock_skew_end ` at 0 and expect that
@@ -251,9 +249,7 @@ What this setup depends on:
251249- ** Set both enable switches once before letting Predbat write.** The periodic
252250 API cannot report whether scheduled charging and discharging are on, so until
253251 Scheduled Charging and Scheduled Discharging have an answer every write is
254- refused — see * The enable flags are write-only* below. Recording both as off
255- additionally locks the time-based controls, since that is how the API is told
256- to run self-consumption.
252+ refused — see * The enable flags are write-only* below.
257253
258254If you drive the services from Predbat instead, note two things about its
259255service templates: falsy values are dropped before the call, so ` enabled: false `
@@ -275,55 +271,58 @@ was based. If the remote schedule changes before Apply, the write is rejected
275271as a conflict instead of overwriting the newer schedule. Discard the draft,
276272allow fresh values to load if necessary, review them, and stage the change
277273again. Immediate actions and services have no long-lived draft, but still start
278- from a fresh read.
279-
280- The binary sensor ** Time Based Control Active** reports whether any timed
281- charge/discharge control is enabled in whichever store governs the system
282- (either enable flag set). It turns off when the inverter runs a non-timed
283- mode such as ** Self Consumption (Plus)** — live probing showed those modes
284- flip both enable flags to ` 0 ` while keeping the configured windows stored —
285- and is unavailable while no schedule store is readable.
274+ from a fresh read. If one succeeds while a draft is open, untouched draft fields
275+ are rebased onto the new remote schedule while explicitly staged fields remain
276+ pending. An immediate action uses only its own arguments plus the last confirmed
277+ enable pair; it never consumes staged switch values before Apply.
278+
279+ The diagnostic binary sensor ** Recorded Schedule Flags Enabled** reports whether
280+ either flag in Home Assistant's last confirmed pair is on. Its unique ID remains
281+ compatible with the former ** Time Based Control Active** entity so an existing
282+ registry entry is renamed in place. It is deliberately not a working-mode
283+ sensor: AlphaESS OpenAPI exposes no authoritative way to tell ** Time Based
284+ Control** from ** Self Consumption (Plus)** .
286285
287286### The enable flags are write-only
288287
289- ` gridChargeCycle ` and ` ctrDisCycle ` decide whether scheduled charging and
290- discharging run. On the periodic store they are ** write-only** : probing on
291- issue #267 showed ` getTimeChargeBySn ` answers ` 0 ` for both however the inverter
292- is actually set, while ` setTimeChargeBySn ` acts on what it is sent.
288+ ` gridChargeCycle ` and ` ctrDisCycle ` are the scheduled charge/discharge intent
289+ sent with a periodic replacement. They are effectively ** write-only** : probing
290+ on issue #267 showed ` getTimeChargeBySn ` can answer ` 0 ` for both regardless of
291+ the inverter's app-selected working mode, while ` setTimeChargeBySn ` accepts the
292+ explicit pair it is sent.
293293
294- | sent | effect on the inverter |
294+ | sent | recorded schedule intent |
295295| --- | --- |
296- | ` 1 / 0 ` | schedule active, scheduled discharging off |
297- | ` 1 / 1 ` | both schedules enabled |
298- | ` 0 / 1 ` | scheduled discharging only |
299- | ` 0 / 0 ` | ** switches the inverter to self-consumption** |
296+ | ` 1 / 0 ` | scheduled charging on, scheduled discharging off |
297+ | ` 1 / 1 ` | both schedules on |
298+ | ` 0 / 1 ` | scheduled charging off, scheduled discharging on |
299+ | ` 0 / 0 ` | both schedules off |
300+
301+ That pair does not reveal or reliably change the app's overall working mode.
302+ In Self Consumption modes the cloud may still accept and retain schedule edits,
303+ but the inverter may not physically act on them until its mode is changed in the
304+ AlphaESS app.
300305
301306Two consequences follow, and the integration is built around them.
302307
303- ** The read is never echoed back.** A replacement built from the read would post
304- ` 0/0 ` on every write and quietly switch the inverter out of timed control.
305- Snapshots therefore drop both flags, and every write sends a value that came
306- from the user instead.
308+ ** The read is never echoed back.** A replacement built from the read could post
309+ an invented ` 0/0 ` pair on every write. Snapshots therefore drop both flags, and
310+ every write sends only values that came from the user or the last confirmed pair.
307311
308312** The switches are the record.** * Scheduled Charging* and * Scheduled
309- Discharging* are the only place that answer can live, so their published state
310- is restored across restarts and handed back to the coordinator. Until both have
311- an answer they read ** unknown** , and a schedule write is refused with a message
312- naming them rather than guessing which working mode you wanted. Editing and
313- staging still work; only the write needs the answer.
314-
315- Once both are recorded, a request to set them to ` 0/0 ` is a real one — it is how
316- the API is told to run self-consumption — so it locks the time-based controls:
317- time entities, cutoff SOC and power numbers, duration buttons, Reset,
318- Apply/Discard, and any service call carrying a window, cutoff or power. The two
319- switches stay available so the inverter can be brought back to a timed mode from
320- Home Assistant, and turning one on there is sent immediately rather than staged,
321- since Apply is unavailable in that state. Home Assistant still refuses to author
322- ` 0/0 ` by turning off the * last* enabled timer from a service.
323-
324- In legacy backup mode ` gridCharge ` and ` ctrDis ` are ordinary read-write fields
325- and none of the above applies: they are read from the store, reported as-is, and
326- a mode change made in the AlphaESS app is picked up on the next poll.
313+ Discharging* are the only place that answer can live, so a separate
314+ last-confirmed attribute is restored across restarts and handed back to the
315+ coordinator. The displayed switch can show a newer local draft without that
316+ draft becoming confirmed after a restart. Until both confirmed values are
317+ known, unrelated immediate writes are refused rather than borrowing a pending
318+ draft or guessing. Stage both switch choices and press Apply once to establish
319+ the pair.
320+
321+ Schedule controls are gated only by whether a complete governing store can be
322+ read and safely replaced. A recorded ` 0/0 ` pair does not lock them, because the
323+ same flags and schedule contents cannot tell Home Assistant which working mode
324+ the AlphaESS app selected. In legacy backup mode the flags are read-write fields,
325+ but they still are not treated as an overall working-mode endpoint.
327326
328327The diagnostic sensor ` Periodic Schedule Read ` reports:
329328
@@ -344,20 +343,24 @@ erase weekly settings, extra periods, or power values that it cannot see.
344343
345344** Settings → Devices & Services → AlphaESS → ⋮ → Download diagnostics** answers
346345most of what a report needs, without anyone having to sign API requests by hand.
347- Alongside the usual entry and entity data, the ` schedule ` section carries, per
348- inverter:
346+ Alongside the usual entry and entity data, the ` schedule ` section carries one
347+ atomic completed-poll snapshot per inverter:
349348
350349- ` governing_store ` and ` periodic_read ` — which surface the system is on;
351- - ` capabilities ` — the exact flags behind every unavailable control, so a locked
352- UI explains itself ;
350+ - ` capabilities ` — schedule read/write capability plus the explicit fact that
351+ working mode is unavailable through OpenAPI ;
353352- ` enable_intent ` / ` enable_last_sent ` — the write-only pair, which nothing else
354353 can report;
355354- ` periodic_snapshot ` and ` legacy_snapshot ` — what each store actually held,
356355 including an empty period list or a stale window the app no longer shows;
357- - ` draft ` — what is staged, which fields are dirty, and whether an Apply is in
358- flight;
356+ - ` draft ` — what was staged when that poll completed;
359357- cooldowns and consecutive poll errors.
360358
359+ ` schedule_live ` separately reports the current draft, dirty fields, and
360+ in-flight Apply state. Keeping it separate prevents a diagnostics download
361+ during a sequential multi-inverter poll from pretending that live transaction
362+ state and completed-poll data came from the same tick.
363+
361364The ` coordinator.api ` section reports the current call spacing and whether the
362365fast lane is still active, which is what to check when something else is sharing
363366the same API account. Serials are replaced with ` inverter_1 ` , ` inverter_2 ` and so
@@ -376,7 +379,7 @@ logger:
376379
377380Every OpenAPI request and **its full response** is then written out, along with
378381each staged field, each write and the enable pair sent with it, and a line
379- whenever the schedule surface locks or unlocks and what decided it. That is the
382+ whenever schedule capability changes and what decided it. That is the
380383same information a hand-signed API request would give you, without signing one.
381384Very large responses are capped rather than dropped. Requests carry no
382385credentials — those live in the headers, which are not logged — but the log does
@@ -471,8 +474,7 @@ In practice the integration runs calls at 1-second spacing: live probing of
471474the current server showed reads accepted at sub-second spacing without
472475` 6053` , and at the documented 10 seconds a two-inverter full poll took
473476minutes — blocking Home Assistant startup and delaying app-side changes
474- (such as a work-mode switch flipping `Time Based Control Active`) by up to
475- five minutes. The documented pace remains the fallback : if the server ever
477+ by up to five minutes. The documented pace remains the fallback : if the server ever
476478answers `6053`, that call is retried once and the session drops back to
47747910-second spacing permanently.
478480
0 commit comments