Skip to content

Add configuration visibility & control, robust multi-inverter energy, resilience, and a CLI (5.3.0) - #10

Open
bbarabe wants to merge 74 commits into
HammondAutomationHub:mainfrom
bbarabe:pr/upstream-5.3
Open

Add configuration visibility & control, robust multi-inverter energy, resilience, and a CLI (5.3.0)#10
bbarabe wants to merge 74 commits into
HammondAutomationHub:mainfrom
bbarabe:pr/upstream-5.3

Conversation

@bbarabe

@bbarabe bbarabe commented Aug 11, 2026

Copy link
Copy Markdown

Add configuration visibility & control, robust multi-inverter energy, resilience, and a CLI (5.3.0)

This is the resubmission promised when I closed #5 ("found an issue with battery energy — will fix before resubmitting"). It has since been through several months of daily use on a live two-inverter (master/slave) plant, plus a full review pass to integrate everything you shipped in 5.0.1/5.0.2.

The branch merges your main (5.0.2) as an ancestor, so the diff below is exactly what's new — nothing of yours is deleted or rewritten away. Where I diverge from a 5.0.2 behavior it's deliberate, called out below, and backed by measurements.

24 files changed, ~3,800 insertions / ~990 deletions across 74 commits. I kept the commits small and focused rather than squashing, so the history documents why each change exists — several of them record measurements that are worth preserving. Happy to squash on merge if you prefer.


Highlights

1. Inverter configuration: read-only sensors + a gated write action

  • The System Work Mode programming is exposed as diagnostic sensors: Work Mode, Energy Pattern, Solar Sell, Time-of-Use enable + day-of-week flags, all six TOU slots (time / power / SOC / mode), Max Solar Power, Max Sell Power, Zero Export Power.
  • A solark.configure_inverter action writes those settings — but only when the "Allow write access" config option (default off) is enabled. With it off, the integration is strictly read-only, same as today.
  • Writes always target the master inverter (equipMode == 1, resolved once and cached). After a write, settings polling briefly accelerates (~15 s for up to a minute) because the cloud's /read endpoint lags its own /set — the read-back is eventually consistent.
  • ⚠️ Reviewer note — this looks like a bug but isn't: the portal's "Max Sell Power" writes pvMaxLimit, and "Max Solar Power" writes solarMaxSellPower. The cloud field names are misleading. We verified empirically: setting Max Sell Power to a distinctive 17990 W on the portal (confirmed on the inverter LCD) changed pvMaxLimit; solarMaxSellPower held its old value. There are comments at both mapping sites so nobody "fixes" it back to the literal reading — including me; I tried once and the live test reverted me.

2. Energy correctness on multi-inverter plants

  • energy_today / energy_total now sum across every inverter in the plant instead of reporting whichever inverter the API lists first. On this two-inverter system the old behavior under-reported daily production by roughly half.
  • Deliberate divergence from 5.0.2: plant /realtime is a fallback, not the preferred source, for etoday/etotal. Its etoday agrees with the per-inverter sum exactly, but its etotal runs ~26% high on this (account-migrated) system, tracking the sum with a large fixed offset that appears in no other endpoint — the inverter list, /api/v1/plants, and the plant's own year-by-year PV history all agree within ~1%. We settled which side is right with an independent physical meter: module-level Tigo monitoring on the same array agrees with the per-inverter sum, not with etotal (the small remaining gap is DC-vs-AC conversion loss, as expected). Preferring /realtime would mirror a known-bad number and inject a one-time step into a TOTAL_INCREASING sensor, corrupting recorder statistics behind the Energy dashboard. The full reasoning lives in the _get_realtime_data docstring. (Note: the portal's overview card displays the bad etotal, so "matches the portal" is unfortunately not the same as "correct".)
  • Spurious mid-day zeros from API glitches are filtered with a bounded (30-min) retention window; after it expires, a reported zero for energy_today is accepted as the genuine daily reset, and energy_total goes unknown rather than serving stale data through a long outage.
  • New native energy sensors, all restore their totals across restarts: grid import/export energy, battery charge/discharge energy, home consumption energy (trapezoidal integration of the corresponding power sensors, owned by this integration — no dependency on the integration helper's internals).

3. Resilience

  • Total cloud outage → entities go unavailable (as in your versions), instead of anything defaulting to 0. Partial failure degrades per-sensor: if only the flow endpoint fails, power/SOC report unknown while energy and status values fetched from other endpoints stay fresh; brief gaps retain last-known status values (30-min bound).
  • A Repair issue is raised after an hour of continuous fetch failure (per sub-fetch, per entry) and clears itself on recovery.
  • Setup survives cloud hiccups during HA startup (retry-with-backoff via ConfigEntryNotReady instead of a dead "Failed to set up"), and accounts that can read telemetry but not settings (installer/limited logins, or plants with no master) keep all their normal sensors — only the config sensors stay unavailable.
  • Logins are serialized behind a lock so the two coordinators can't fire duplicate credential requests on simultaneous token expiry.

4. New sensors beyond energy

  • AC Relay Status — on/off-grid detection straight from the inverter's relay register (workdata /194), much more reliable than inferring from power flows. Matched by register number so the p2 cluster's column-name change (AcRelayStatus/194 vs the old AcRelayStatus(NA)/194) can't break it again.
  • Grid Status, Generator Status, Battery Charge/Discharge Power.

5. Architecture: HA-independent client + CLI

  • The api.py monolith is split: solark_client.py (API client, HA-independent), solark_auth.py (OAuth + legacy login), solark_logging.py (your redaction from d336443, ported verbatim), solark_errors.py. api.py remains as a compatibility shim re-exporting the same public names (SolArkCloudAPI, SolArkCloudAPIError, _redact_secrets, _redact_secret_text) with compatible signatures.
  • Because the client no longer needs HA, there's a CLI (python -m solark_cli) for poking the cloud API directly — plant data, raw endpoint dumps, settings read, gateway list, TOU slot writes. See CLI.md. Credentials come from a gitignored solark_secrets.json (template included). This is how most of the endpoint behavior above was measured.
  • Everything you shipped in 5.0.1/5.0.2 is carried: URL auto-discovery (discovery.py is byte-identical to yours), retired-host normalization, PV minPower handling, the MPPT placeholder-ramp guard, the SOC curCap guard, battery sign flags, and the log redaction — applied at every log site in the split modules.

6. Upgrade safety (please scrutinize — this was a priority)

  • No entity duplication: all nine of your sensor keys and the {entry_id}_{key} unique_id format are unchanged.
  • No entity renames, ever: the integration never rewrites registry entity_ids. Fresh installs get predictable ids via suggested_object_id; existing installs keep whatever ids (and customizations) they have, so no automation or dashboard breaks on upgrade.
  • Config entry migration v1 → v4, idempotent, verified against a pristine v1 entry: v2 seeds the write-access option (off), v3 is registry cleanup that's a no-op for your users, v4 rewrites retired hosts (mysolark.com, ecsprod-api*) to current defaults — the old api_url still serves reads but rejects writes since the p2 account migration, so entries stuck on it look healthy until the first write fails.
  • Minimum HA is now 2024.3 (hacs.json): the migration uses async_update_entry(..., version=...), which doesn't exist earlier — on older cores the old floor of 2023.5 would have bricked the entry mid-migration.
  • Timezone-sensitive API date queries use the HA-configured timezone instead of UTC (fixes wrong-day workdata/flow results in evening hours for western timezones; the cloud resets at midnight UTC).

7. Docs

  • README/QUICKSTART updated for solarkcloud.com and auto-discovery, sensor/action tables expanded, CLI.md added, Energy-dashboard guide updated for the built-in energy sensors, QUICKSTART's UTF-8 mojibake repaired, and CHANGELOG rewritten so 5.3.0 reads as one release on top of your 5.0.2.

Testing

  • Months of continuous operation on a live two-inverter Sol-Ark plant (p2 cluster account), including the Energy dashboard, TOU writes via the action, and both login paths.
  • Migration exercised from a real 5.0.0-era entry through v4.
  • Outage behavior exercised against real cloud incidents (that's where the repair issue, retention windows, and unavailable-not-zero rules come from).
  • Register mapping, /realtime behavior, and endpoint cross-checks measured with the included CLI; the decisive numbers are recorded in the relevant commit messages and docstrings.

Notes for review

  • The commit history is intentionally granular; git log --reverse reads as a narrative. The commits touching solark_client.py parsing carry the endpoint measurements inline.
  • Two behaviors intentionally differ from 5.0.2 and are flagged inline in code: /realtime as fallback (§2) and meter-first grid import/export (explicit meter fields are trusted before direction flags when existsMeter is set).
  • I'm happy to split anything out, squash, or adjust naming/conventions to your preference.

bbarabe and others added 30 commits January 28, 2026 23:16
- Add new ac_relay_status sensor from workdata endpoint
- Replace dy/store call with workdata in get_plant_data
- Add --workdata and --workdata-fields CLI flags
- AcRelayStatus: 1=Connected, 0=Disconnected (reliable during outages)
- Keep existing grid_status sensor unchanged

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove number/switch/select/time entities (writable controls)
- Add read-only config sensors for all settings (entity_category: CONFIG)
- Add solark.configure_inverter action for atomic batched writes
- Single API call for all changes, safer for dashboard visitors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migration handler now handles v2 -> v3 transition when
number/switch/select/time platforms were replaced with config sensors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove orphaned entities from old number/switch/select/time
platforms when migrating to v3 (config sensors + service).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Logs settings_coordinator data and wraps sensor creation in
try/except to catch any errors during config sensor setup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
bbarabe and others added 30 commits August 11, 2026 19:44
Port from upstream HammondAutomationHub/HomeAssistant_SolArk commit
d336443. Diagnostics downloads redacted only username/password; any
token persisted on the entry or surfaced in options was emitted in the
clear.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port OBSOLETE_BASE_URLS / OBSOLETE_API_URLS / normalize_solark_urls and
the new DEFAULT_BASE_URL from upstream HammondAutomationHub/HomeAssistant_SolArk
commits 11072e0 and 6ed5b09, replacing this fork's narrower
LEGACY_API_URLS tuple.

Upstream reached the same p2 conclusion this fork did in d640e6d, but
covers more ground:
  - also rewrites base_url (mysolark.com -> www.solarkcloud.com), which
    this fork left pointing at the retired portal host; base_url is what
    we send as Origin/Referer on every request
  - also covers ecsprod-api.solarkcloud.com, not just ecsprod-api-new
  - strips trailing slashes, so a hand-entered URL still matches

The v4 migration now runs both URLs through normalize_solark_urls
instead of matching api_url against a tuple. It stays idempotent, so
entries already migrated by d640e6d are unaffected apart from picking up
the corrected base_url.

Verified www.mysolark.com and www.solarkcloud.com currently serve the
identical portal bundle (app.d9466868.js), so nothing breaks today; this
is about not depending on a retired host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port discovery.py verbatim from upstream HammondAutomationHub/HomeAssistant_SolArk
commits 11072e0 and 6ed5b09, plus the surrounding wiring, adapted to
this fork's allow_write option and its deferred-import style in
__init__.py.

The portal ships its API root as VUE_APP_BASE_API inside
/static/js/app.<hash>.js. Reading it at setup means the next cluster
migration self-heals instead of needing another hand-patched commit like
d640e6d. A manual api_url remains as override (auto-discover off) and as
fallback when discovery fails.

Verified against the live portal: both www.solarkcloud.com and
www.mysolark.com resolve to https://p2.api.solarkcloud.com, matching the
host d640e6d set by hand. A dead portal host returns None and the
configured api_url is kept.

Divergences from upstream, all forced by this fork's shape:
  - options flow keeps allow_write_access and uses self._config_entry
    (this fork stores the entry on the private attribute to avoid
    assigning the read-only property)
  - discover_api_url is called from _resolve_urls with the HA client
    session imported inside the function, matching this module's pattern
  - no entry version bump is needed: auto_discover_api reads through
    .get(..., DEFAULT_AUTO_DISCOVER_API) everywhere and async_setup_entry
    persists it on first run

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port three fixes from upstream HammondAutomationHub/HomeAssistant_SolArk
commit 62d0b49 ('5.2'):

  - PV power now adds minPower (microinverter / AC-coupled PV) from the
    energy flow endpoint when existsMin/microOn/minPower indicate it is
    present. minPower, existsMin, microOn, genPower and existsGen are
    added to the flow key passthrough in get_plant_data so they reach
    the parser.
  - _mppt_looks_like_placeholder rejects the fixed 0/1.5/3.0/4.5 ramp the
    portal returns in some dy/store payloads, so it is no longer summed
    into pv_power as if it were live string data. The MPPT sum is also
    now a true last resort, only used when the flow endpoint gave no PV
    figure at all.
  - Battery SOC only falls back to curCap/batteryCap when curCap is
    actually populated; before, a missing curCap reported a confident 0%.

Verified against synthetic payloads: micro adds (3000+450), the
placeholder ramp is rejected, real MPPT strings still sum, and a missing
curCap no longer computes 0%.

This fork already reached upstream's other 62d0b49 conclusions
independently — signed battery power from toBat/batTo and the
gridTo/toGrid import/export fallback — and keeps its own versions, which
additionally gate on existsMeter and handle the both-flags-set ambiguity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port _get_realtime_data from upstream HammondAutomationHub/HomeAssistant_SolArk
commit 62d0b49, but wire it as a gap-filler rather than an override --
the one deliberate behavioral divergence in this integration pass.

Upstream prefers the plant realtime etoday/etotal over the inverter
list. Probed against a live two-inverter plant on the p2 cluster, the
two sources disagree: etoday matches exactly, so daily energy -- what
the HA energy dashboard actually consumes -- is identical either way.
etotal, however, reads roughly a quarter higher than the per-inverter
sum. Adopting upstream's preference would push a one-time multi-MWh
step into an existing TOTAL_INCREASING sensor and corrupt the recorder
statistics behind the energy dashboard.

So the per-inverter sum from 406ebf2 stays primary, and /realtime fills
in only when the inverter list yields nothing -- which is the behavior
upstream described in its own 5.0.1 notes ('falls back to the plant
/realtime endpoint when inverter summary values are missing') before
62d0b49 changed it to a preference.

The extra request is skipped entirely when both energy values are
already present, so the normal poll cycle is unchanged.

Verified live: energy_total is unchanged with no step, and all other
sensors are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
….3.0

Docs are updated in the same pass as the behavior change:

- README.md / QUICKSTART.md: document the auto-discover option and why it
  exists, and point at www.solarkcloud.com for finding the Plant ID
- CLI.md + solark_secrets.template.json: base_url default updated
- CHANGELOG.md: Unreleased section covering this integration pass, with
  the upstream commit for each item and an explicit note on the one
  deliberate divergence (/realtime as fallback, not preference)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rationale in the fallback commit was a hypothesis stated as fact.
Investigated properly against the live API; the conclusion (fallback,
not preference) is unchanged but the reason was wrong.

What the earlier commit claimed: the plant figure counts production
from hardware no longer in the inverter list. That is contradicted by
the plant's own year-by-year PV history, which would have to contain
the surplus and does not.

What is actually true: /realtime etotal tracks the per-inverter sum
with a FIXED offset. Three samples across ~40 min of active
production, during which the counter advanced ~10 kWh, show the
difference constant to 0.1 kWh -- so it is the same underlying
quantity carrying a legacy constant, not a different metric, not
drift. The offset appears in no other endpoint. Cross-checked
against:

  - the inverter list sum
  - /api/v1/plants (also a lagging cache; its etoday read ~26% low
    against the true mid-day value)
  - /plant/energy/{id}/total lifetime PV, from the labelled
    Load/PV/Export/Import/Charge/Discharge year-by-year series

All three agree within ~0.4%. The sibling realtime fields are sound
too: etoday matches the inverter sum exactly, emonth to within 1 kWh,
eyear to within 1.5%. Only etotal is an outlier, high by roughly a
quarter of lifetime production.

Origin of the constant remains unknown; it is recorded as unexplained
rather than guessed at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Module-level Tigo monitoring on the same array -- hardware entirely
separate from SolArk -- provides an independent lifetime production
reference:

    Tigo (independent)        baseline
    history PV series         -0.6%
    inverter list sum (used)  -0.9%
    /realtime etotal          about +26%

The three agreeing sources land within 1% of each other. The inverter
sum reading slightly under Tigo is expected physics, not error: Tigo
measures DC at the modules, inverter etotal is AC after conversion
losses. So the per-inverter sum is the correct AC production figure
and the right feed for the Energy Dashboard's solar source.

This closes the question the fallback commit was hedging. It is no
longer 'two sources disagree, prefer the safer one' -- /realtime
etotal is measurably wrong by many MWh against a physical meter.

Worth noting for the upstream report: the SolArk portal's overview
card displays etotal, so the portal contradicts both its own
production chart and the physical meter. Matching the card would
mirror a known-bad number.

No behavior change; energy_total already uses the per-inverter sum and
/realtime remains fallback-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Makes upstream's history an ancestor of this fork so a pull request
diffs against a common base instead of appearing to delete upstream's
newest work.

Before this merge the merge-base was fe7546a, from before upstream's
11072e0..e7d0653. A PR opened from this fork would have rendered as
-714 lines from api.py -- including the redaction and parse work
upstream shipped in 5.0.2 -- because this fork had ported that content
into solark_client.py / solark_auth.py / solark_logging.py rather than
merging the branch.

Conflict resolution, all in favor of this fork's module layout, which
already contains upstream's 5.0.2 behavior:

  api.py            ours (compat shim; upstream's monolith lives on in
                    solark_client/solark_auth/solark_logging)
  __init__.py       ours (upstream side was one comment we already have)
  config_flow.py    ours (upstream side was self.config_entry vs our
                    self._config_entry, deliberate -- avoids assigning
                    the read-only property -- plus import paths)
  translations      ours (superset; upstream side added nothing new)
  manifest.json     ours (5.3.0)

Docs were hand-merged rather than taken wholesale:

  CHANGELOG.md      both histories kept; added a note that this fork's
                    5.1.x/5.2.x and upstream's 5.0.1/5.0.2 were parallel,
                    so the version list is not chronological
  README.md         upstream's clearer sensor descriptions (minPower,
                    gridTo/toGrid, battery sign) adopted onto our fuller
                    18-sensor table; upstream's corrected plant-ID URL
                    and redaction note adopted; energy_today/energy_total
                    documented as summed across inverters, NOT plant
                    realtime, per the measurements in fbadcb8
  QUICKSTART.md     same treatment

Two things the auto-merge got wrong or surprising, both handled:

  - const.py silently duplicated OBSOLETE_BASE_URLS / OBSOLETE_API_URLS
    (both sides added equivalent blocks). Deduplicated; an AST scan of
    every module now reports no duplicate top-level definitions.
  - dashboards/README.md drops from 128 lines to upstream's 5-line stub.
    This is a correct merge -- we never modified it and upstream gutted
    it in 62d0b49 -- but note both repos still ship solark_flow.yaml and
    solark_dashboard.yaml, so those files are now undocumented upstream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The docstring and changelog entry carried the raw investigation: plant
id, inverter serials, exact sample tables and lifetime kWh figures. None
of that is needed to justify the decision, and it does not belong in a
repo that may be sent upstream.

Reduced to what a reader actually needs: /realtime etoday agrees with
the per-inverter sum, etotal does not (~26% high, a large fixed offset
present in no other endpoint), and which side is correct was settled
against an independent meter -- Tigo module-level monitoring agreed with
the per-inverter sum, not with etotal. So the per-inverter sum is the
figure we use.

Also replaces a real inverter serial with a placeholder in two CLI.md
examples. That predates this work but is the same class of leak.

No behavior change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the parallel fork-version sections (5.1.0/5.1.1/5.2.0 plus an
Unreleased block and a numbering apology) with one 5.3.0 entry that
describes everything this branch adds relative to upstream's latest
release. Upstream's own 5.0.x sections are kept verbatim below it.

The entry describes the end state of this branch, including the
resilience and lifecycle fixes landing in the following commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mysolark.com is retired; the CLI troubleshooting note and the repair
issue description still told users to log in there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_async_fix_grid_power_entity_id force-renamed the entity with unique_id
{entry_id}_grid_power to sensor.solark_grid_power on every setup. For
anyone upgrading from upstream (whose auto-generated id is
sensor.solark_grid_power_net) that silently breaks every automation,
dashboard, and statistics reference — and it also reverted any
user-chosen entity_id on each reload. Integrations should not rewrite
registry entity_ids; suggested_object_id already covers fresh installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_plant_data swallowed every sub-fetch error and returned an empty
dict, which parse_plant_data then padded with 0.0 defaults — so a cloud
outage or login failure recorded hours of bogus 0 W / 0 % samples into
long-term statistics while entities stayed 'available'.

Now: if every sub-fetch failed, get_plant_data raises and the
coordinator marks entities unavailable (restoring upstream's behavior).
If only the flow fetch failed, the power/SOC keys are left absent so
those entities report unknown rather than fake zeros, while energy and
status values fetched from other endpoints stay fresh. Repair-issue
health evaluation now runs on failed cycles too, so the prolonged-
failure issue can actually appear during a full outage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prime_inverters_cache() raised raw SolArkCloudAPIError before the
coordinators ran, so a transient outage while HA was booting marked the
entry 'Failed to set up' with no retry. The cache is lazy and refetches
on demand, so priming is now best-effort.

The settings coordinator's first refresh also gated the whole
integration: accounts that can read telemetry but not settings
(limited/installer logins, plants with no equipMode==1 master) went
from working sensors to endless setup retries. Settings now refresh
without raising; the config sensors just stay unavailable until a
settings poll succeeds. The data coordinator still raises
ConfigEntryNotReady, preserving retry-with-backoff for real outages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The service handler closed over the first entry's api object,
settings coordinator and allow_write_access snapshot. Because it was
registered once and never removed, toggling the Allow write access
option (which reloads the entry) had no effect until HA restarted,
writes kept using a pre-reload api object, and calling the service
after removing the entry raised a KeyError.

The handler now looks everything up in hass.data when called, and the
service is unregistered when the last config entry unloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The battery/grid/home energy sensors were built by instantiating
homeassistant.components.integration.sensor.IntegrationSensor through
inspect.signature probing and poking _attr_* privates — fragile against
that integration's constructor changes, and its registry-miss fallback
guessed source entity_ids that are wrong for a second config entry.

SolArkIntegratedEnergySensor (previously the unreachable fallback) is
now the only path: a plain coordinator entity doing the same
trapezoidal integration, restoring its total across restarts. Unique
IDs are unchanged, so existing entities keep their identity, history
and last value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_work_mode_to_str/_energy_mode_to_str returned 'Unknown (7)'-style
strings for unmapped values, which are not in the descriptions'
options lists — HA then raises 'value is not a valid option' and the
entity errors out. Unmapped values now report 'Unknown' and log the
raw value at debug level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The config-entry migration calls async_update_entry(..., version=...),
a kwarg that only exists since HA 2024.3. On older versions the
migration raised TypeError and disabled the entry. 2024.3 also covers
the suggested-object-id and repair-issue APIs this integration uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
max_solar_power wrote solarMaxSellPower and max_sell_power wrote
pvMaxLimit — crossed relative to what the API field names literally
mean (pvMaxLimit caps solar input, solarMaxSellPower caps grid sell;
consistent with the wider Deye-family cloud API). The read-only
sensors carried the same swap, so 'Max Solar Power' displayed the sell
limit and vice versa, and a configure_inverter write would have set
the wrong register.

Verified against a live system: after the swap the reported values
(sell 16500 W, solar 18000 W on a two-inverter stack) line up with the
portal's labels instead of exceeding the inverters' sell capability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The post-write settings refresh burst (up to a minute of 15 s sleeps)
was never cancelled on unload and kept refreshing a detached
coordinator. Repair-issue IDs now include the entry_id so, with two
config entries, one entry's recovery or unload no longer clears the
other's outstanding issue. (Coordinators pick up their config entry
automatically from the setup context, so no explicit kwarg is needed
at our minimum supported HA version.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The data coordinator, settings coordinator, and service calls share
one SolArkAuth; concurrent token expiry triggered parallel duplicate
logins. ensure_token now double-checks validity under an asyncio.Lock
so only one login runs at a time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The zero-filter cache for energy_today/energy_total had no TTL: after
the provider's midnight reset it substituted yesterday's total until
the first production of the morning, and through a multi-day outage it
presented stale values indefinitely.

Retention now uses the same 30-minute window as the status keys.
After it expires, a zero the API actually reported is accepted for
energy_today (that's the genuine daily reset); energy_total can never
legitimately read zero, so it reports unknown instead of holding a
stale number forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The number/select/switch/time platforms were removed in the v3
migration, and no sensor sets a translation_key (names are provided
directly), so the whole entity section was unreferenced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Setup no longer dumps the whole settings payload into the debug log or
wraps plain constructor calls in try/except blocks that could only
swallow programming errors. A rejected settings write logs the
requested updates at ERROR and defers the full payload to debug —
it is large and mostly unrelated settings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Submitting options unconditionally rewrote entry.data (reload via the
update listener) and then finished the flow (second reload via the
options update). The data write is now skipped when the resolved hosts
are unchanged, which is the common case. Removed the unused
_get_config_entry helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With no operation flags the CLI enabled every operation, then exited
with 'Missing --inverter-sn for --settings' before printing anything.
The default sweep now skips the SN-requiring operations (settings,
workdata) unless --inverter-sn is given, matching the documented
'just run it with a secrets file' usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ignoring a developer-environment directory is a personal preference,
not a project rule; keep only the solark_secrets.json ignore the CLI
needs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The troubleshooting sections told users a registry rename would run;
the integration no longer rewrites entity ids, so describe the actual
behavior (existing ids are kept, rename manually if preferred). README
now states the HA 2024.3 minimum, and QUICKSTART's UTF-8 mojibake
(arrows, menu glyphs, footer emoji) is repaired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reverts the earlier 'un-swap': a live test settled it the other way.
Changing 'Max Sell Power' on the SolArk portal to a distinctive value
(also confirmed on the inverter's LCD) changed pvMaxLimit, while
solarMaxSellPower held its old value. The cloud field names are
misleading: the portal's Max Sell Power writes pvMaxLimit and its Max
Solar Power writes solarMaxSellPower, exactly as this integration
originally mapped them. Comments now document the measurement so the
literal reading of the field names doesn't get 'fixed' back in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant