Repository navigation
fix(solis): probe the slot current ceiling the inverter enforces but does not report - #5309
Merged
Merged
Conversation
… slot current cap The solis.py CLI harness can now read its settings from an apps.yaml-format file (--config, resolving !secret). It supports OAuth with the Supabase refresh settings, limits a run to one inverter, and dumps every raw API reply (--dump-raw). With --write it can probe which charge or discharge currents a given slot accepts, restoring the original value afterwards. In production, the component now logs: - an allow-listed inverter summary (model, rating, firmware, pack count) - every input to the V2 slot current cap: register, slot metadata and rated current, each logged when it changes - whether "Calculated max currents" came from a register times the pack count or from the default The slot current cap is computed in slot_current_limits(). The write path gets the same result as before. This came out of GH#5187: a 3.6kW S5-EH1P3.6K-L refused every 75A slot write. Probing showed its real ceiling is 60A in both directions, on enabled and disabled slots alike. None of the API replies reports it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…does not report Issue #5187. A 3.6kW S5-EH1P3.6K-L reports 100A in CID 7224/7226 and has a 75A rated cap, but refuses any slot current above 60A. That applies in both directions, on enabled and disabled slots alike. A refused write leaves the register at its old value, so every 75A export slot sat at 0A, holding the battery. No API reply reports the 60A. On first discovery (V2 firmware, control enabled) Predbat now probes a slot it does not drive (2-6, disabled in both directions): - write the expected maximum - if refused, step down 5A until one is kept, then up 1A until one is refused - restore the slot's original value afterwards A refusal is only concluded when the inverter acknowledges the write and still holds its old value. An error, an offline datalogger or an unexpected read-back gives no verdict, and nothing is learned. Refusing everything down to 10A also learns nothing, so an unreachable inverter cannot settle on a minimum. The ceiling is saved to Storage and reloaded. It is re-checked at most once a day, and a failed re-check keeps the known value. It caps every slot write through slot_current_limits(). It also sets the planner's limits. New slot_charge_power_max and slot_discharge_power_max sensors (cap x nominal voltage) now feed inverter_limit_charge and inverter_limit_discharge (overwrite=False) and battery_rate_max, replacing the raw 4800W register figures. Also: - the limits log line names which input set each cap - the stale "Capping ... at 75A" line is removed - each probe verdict is logged - the CLI gains --probe-ceiling to run this probe live Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…replace the rated estimate The probe started from the rated-current estimate from #5189 (rated power / nominal voltage). So it could only ever confirm or lower that estimate, never find a ceiling above it. An 8kW hybrid on a 51.2V pack reads 180A in CID 7224/7226 and ran slots at 180A before #5189. The rated estimate held it at 156A and capped PV charging below what the inverter takes. - A first probe now starts at the battery-side cap (the register, lowered by any slot metadata) and steps down only on a refusal. - Once an inverter has a measured ceiling, it replaces the rated estimate in slot_current_limits(). The estimate is only a fallback until then. inverter_limit still bounds the AC side in the plan. - A daily re-check starts at the known ceiling. An unchanged one costs two writes (ceiling kept, ceiling + 1 refused) rather than a fresh search, which on a 60A inverter behind a 100A register would be about eight refused writes per direction. - The limits line marks the rated estimate "not used once probed". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…robe-ceiling runs the real one --probe-charge-current, --probe-discharge-current and --probe-slot, with probe_slot_current(), were only for finding the slot current ceiling by hand. --probe-ceiling now runs Predbat's own ensure_slot_current_probed(), so they are removed along with their test. solis_slot_current_report()'s per-slot dump goes too. The CLI now prints the one-line limits summary, which names every input and the one that set each cap, and --dump-raw still shows the raw register replies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review of #5309: - Read-only: the probe now honours switch.<prefix>_set_read_only, as every other control write does. It stays pending (logged once) and runs on the first cycle after read-only is turned off. - Timing: the probe runs last in run(), after the control writes, the publish and auto-config, and is checked every cycle. A first probe (a minute or two) no longer delays slot-1 control or startup. - Backoff: a probe that learns nothing (an error code, a clamped read-back, an offline datalogger) is retried after 1h, doubling up to a day, instead of every hour indefinitely. A success clears it. - Slow relay: a read-back still holding the old value gets one more settle before it counts as a refusal, so a late-landing write is not saved as a lower ceiling for a day. - Saved ceilings: marked loaded only after a successful load, and the probe waits for that, so a transient storage error cannot lead to a fresh probe overwriting them. A malformed file or entry is ignored without raising. - Publishing: slot_current_limits() treats an unreadable register or sysCommand max as not read. It now feeds publish_entities(), which a junk value would otherwise stop. - Log spam: the change-only limits line is keyed on the limits, not the text, so an HV pack's moving live voltage does not re-log it every minute. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 30, 2026
Merged
springfall2008
added a commit
that referenced
this pull request
Oct 2, 2026
…0-01 docs(debug-journal): fold in the 2026-10-01 queue slice (9 candidates); mark GH#5313 fixed by PR #5315, GH#5136 by PR #5293, GH#5222 by PR #5283, the iog-slot-io-adjusted row by PR #5304, GH#5068 reworked by PRs #5309/#5314; add the dynamic-load car cancel row and the GH#2649/#5302/#5324 findings
gcoan
pushed a commit
to gcoan/batpred
that referenced
this pull request
Oct 3, 2026
…); mark GH#5313 fixed by PR springfall2008#5315, GH#5136 by PR springfall2008#5293, GH#5222 by PR springfall2008#5283, the iog-slot-io-adjusted row by PR springfall2008#5304, GH#5068 reworked by PRs springfall2008#5309/springfall2008#5314; add the dynamic-load car cancel row and the GH#2649/springfall2008#5302/springfall2008#5324 findings Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #5187 / #5189 (and builds on #5308).
Problem
A 3.6 kW S5-EH1P3.6K-L (model 3102, firmware 4C0050, TOU V2, one PYLON_LV pack) reports 100 A in CID 7224/7226. #5189 capped slot currents at the 75 A rated current (3600 W / 48 V), but the inverter refuses anything above 60 A. A refused write leaves the register at its old value, so every discharge slot sat at 0 A and held the battery through each planned export. The planner meanwhile assumed 4.8 kW each way (the raw register × 48 V).
Live testing on that inverter, using the new CLI below, showed:
atRead/atReadBatchreturn onlymsg/yuanzhi/command/needLoop, with no min/max, so the existingsysCommand.maxcheck never fires on the OAuth routeinverterDetail'sbatteryC/DMaxiSetread 65 A and the BMS limits read 160 AA second inverter, an 8 kW hybrid on a 51.2 V pack, reads 180 A there and ran its slots at 180 A before #5189. The rated estimate (8000 W / 51.2 V = 156 A) has held it at 156 A since then, capping PV charging. So the rated estimate is wrong in both directions, and the cap has to be measured.
Change
Slot current ceiling probe (
ensure_slot_current_probed()). Predbat only drives slot 1. On first discovery, with control enabled and TOU V2 firmware, it picks a slot from 2–6 that is disabled in both directions, and for each direction:Guarding against an offline or erroring inverter:
When it runs:
switch.<prefix>_set_read_only. While read-only is on it stays pending, and it runs on the first cycle after read-only is turned off.The result is saved through Storage (
solis/slot_current_limits) and reloaded. It is semi-static:slot_current_limits()applies the measured ceiling to every slot write, and it replaces the #5189 rated estimate. The rated estimate is only a fallback until an inverter has been probed.inverter_limitstill bounds the AC side in the plan.Planner limits. New
sensor.<prefix>_solis_<sn>_slot_charge_power_max/_slot_discharge_power_maxsensors (slot cap × nominal voltage) are published. They now feedinverter_limit_charge/_discharge(stilloverwrite=False, so an apps.yaml value wins) andbattery_rate_max. These replace the raw 4800 W register figures from #5308. The slot power sliders' max follows too.max_charge_power/max_discharge_powerstill show the register.Diagnostics:
slot_current_limits()helper returns every input to the V2 cap. The write path gets the same result as before.charge 60.0A from the probed ceiling (register 100A, no slot metadata max, probed max 60.0A) …).Capping slot currents … at 75.0Aline is removed.CLI harness (
solis.py__main__), used to find all this:--config apps.yaml, resolving!secretfrom asecrets.yamlbeside the file. Its key map is kept in step withCOMPONENT_LISTby a test.--inverter-sn--dump-raw, which prints every reply in full via aresponse_observerhook that is off in production--probe-ceiling, which runs the production probe on its own. It needs--write, andrun()stays read-only while it runs. The manual any-slot probe used during the investigation has been removed in favour of this.Testing
test_solis.py. They cover:!secretresolutionValueError; the probe ran before the control write), and pass with them../run_all --quickpasses, and pre-commit passes on the changed files.--writeruns:inverter_limit_charge/_discharge/battery_rate_maxread 2880 W.Notes
SolisAPI.run,write_time_windows_if_changedandfetch_inverter_detailsLOW, with a single caller each. The index is stale for the new helpers, so their callers were checked by search. Everything is insidesolis.py;inverter.pyis untouched.tools/debug-journal.md's Solis row still points at the removedCapping slot currentsline and at thesysCommandslot metadata. It can be refreshed once this merges.🤖 Generated with Claude Code