fix(gecloud): re-read EMS battery inverter settings and warn on a slot 1 override - #5109
Draft
springfall2008 wants to merge 1 commit into
Draft
springfall2008 wants to merge 1 commit into
springfall2008 wants to merge 1 commit into
Conversation
…t 1 override Under GE Cloud EMS auto-config, finding an EMS turns polling_mode off and the periodic settings refresh then re-read only the EMS device, so each battery inverter's registers were snapshotted once at startup and published stale forever. A DC discharge slot 1 changed behind Predbat's back (by an installer, the GivEnergy app or a third-party service) silently overrode the EMS and stopped the plant discharging early, with nothing in the log or the status. Re-read the battery inverters' settings hourly on EMS plants, and report any inverter whose own charge or DC discharge slot 1 is not 00:00-23:59 - the window that #3781 asks for so the inverter never overrides the EMS. The report names the inverter and the offending value, and is raised once per episode. The refresh gate now keys off `first` rather than `seconds == 0`, so a startup that only succeeds on a backoff retry still takes the snapshot at all. Fixes #5103 Co-Authored-By: Claude Opus 5 (1M context) <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.
This is an automated draft PR generated from issue #5103 — a maintainer should review it before merging.
Fixes #5103
Summary
Under GE Cloud EMS auto-config, finding an EMS sets
polling_mode = False, and the 10-minute settings refresh then gated each battery inverter's/settingsre-read onseconds == 0. The AC3s' registers were therefore snapshotted exactly once per process start and published stale thereafter, so a DC discharge slot 1 changed behind Predbat's back overrode the EMS and stopped the plant discharging every evening with nothing in the log or the status.This implements the reporter's options (1) and (2), which the triage comment picked out as the cheapest useful pair:
SETTINGS_SLOW_REFRESH_SECONDS) rather than once at startup, so the published entities and the control ledger track reality between restarts. That is 1 extra/settingsread per inverter per hour — deliberately slow, given the EMS API volume concerns in Excessive GE EMS writes? #4232. Nothing changes for non-EMS plants:polling_modeshort-circuits the gate there exactly as before.00:00-23:59— the window Predbat - Givenergy EMS - GivTCP #3781 asks for precisely so the inverter never overrides the EMS — is reported to the log and to the Predbat status, naming the inverter and the offending value. Raised once per episode (following the auth-denied precedent a few lines up) so a standing misconfiguration does not inflateerror_countevery hour, and re-armed once a clean read comes back.Option (3), writing slot 1 back to
00:00-23:59, is deliberately not implemented — see Notes.One extra fix on the same line: the refresh gate now keys off
firstrather thanseconds == 0.firststaysTrueacross startup backoff retries whilesecondskeeps climbing, so on a plant whose first successfulrun()landed on a retry the battery inverters were previously never read at all, not even once.Testing
coverage/run_pre_commit(pre-commit hooks +./run_all --quick): all hooks pass, all tests pass. Spelling verified separately withnpx cspellagainst theen-gbdictionary (the local hook is a no-op without it) — 0 issues../run_all --test ge_cloud: 96 passed, 0 failed, including the two new sub-testsems_slot_overridesandems_settings_reread.The new test fails without the fix, in two stages:
ImportError: cannot import name 'SETTINGS_SLOW_REFRESH_SECONDS'), which proves the red run but says nothing about the assertions.run()gate back toseconds == 0and dropped the check call. Result: 95 passed, 1 failed, failing on exactly the reported defect —ERROR: at the slow refresh cadence the battery inverter should be re-read, got ['ems001']. Restoring the gate returns it to 96/96.What the tests cover: the hourly-vs-10-minute cadence under EMS; the 19:00 end time from the field report being detected, logged and raised as a status; a zeroed
00:00-00:00slot 1 also counting as an override (Predbat enables the per-inverter discharge switch under EMS, so that window bars discharge entirely);HH:MM:SSand unpadded times normalising rather than false-positiving; null/unknown/non-time register values being ignored; the once-per-episode status and its re-arming after a clean read; and that neither the EMS device itself nor a non-EMS plant is ever subject to the check.Notes
00:00-23:59adds writes and, as the issue says, could fight a third-party controller that changes those slots on purpose.enable_default_options()is where it would naturally live (it already zeroes slots 2–10 every 24h and deliberately leaves slot 1 alone), gated behind a config switch. Left for a maintainer decision rather than taken unilaterally.enable_default_options()scans the same time registers for slots 2–10, but it writes and compares raw values, while this path reports and normalises first. Folding the newnormalise_register_time()into that comparison would change write behaviour on values like0:00, which is outside what this ticket asks for, so the two are left separate.record_status()clamps the sensor state to 255 characters. One inverter with one bad slot is well under; an inverter with all four slot 1 registers wrong would be clamped in the sensor state, with the full text still in the log. That is the documented behaviour of that clamp, not new here.impact()could not be run. By search: the only modified existing symbol isGECloudDirect.run, whose sole production caller isComponentBase.start(component_base.py:264) and whose signature is unchanged — only the internal refresh cadence moved. Every new symbol (SETTINGS_SLOW_REFRESH_SECONDS,EMS_SLOT_FULL_DAY_START/END,EMS_SLOT_FAMILIES,normalise_register_time,find_ems_slot_overrides,check_ems_inverter_slots,ems_slot_warned) appears only ingecloud.pyandtest_ge_cloud.py, with no name collisions elsewhere in the tree.tools/debug-journal.md's GE Cloud row informed the approach — in particular that the settings list is fetched once per process start and that the mock intest_ge_cloud.pydoes not mirror the realinitialize()unless kept in step, which is whyems_slot_warnedwas added toMockGECloudDirectalongside the real one.🤖 Generated with Claude Code