Repository navigation
Conversation
…car hold (#5419) (#5470) "Hold for car" on an inverter without timed pause set the battery discharge rate to 0 for the whole car slot, even while the house battery was charging in a charge window. A charging battery cannot feed the car, so the write did nothing useful - and some GivEnergy hybrids (GIV-HY3.6 / Gen 1 via GivTCP) will not grid-charge at all with the discharge rate at 0 and the SoC on the reserve. The battery then sat at reserve for the whole cheap window while the status read "Charging, Hold for car" (#5419, #5467). While the inverter's own status is "Charging", the hold now skips the rate write, as it already skipped the reserve bump (#3899), and logs that it has deferred. Once charging stops (Hold charging, Demand), both apply as before. The test now reads this inverter's status rather than the fleet-wide one, which can carry over from an earlier inverter. Inverters with timed pause still pause discharge during the charge: it is one latched write that blocks only discharge, and no report ties it to the charging fault. Tests: car_charge_no_reserve_ratchet now expects the maximum rate while charging (it failed on main with "Discharge rate should be 1000 got 0"), and car_charge2_no_pause pins the rate hold still applying under Hold charging. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… that have the switch (#5371) (#5543) When the window still on the inverter started more than set_window_minutes away, the window and the enable were both left alone. A window that has just finished reads as tomorrow's, so the charge enable stayed on all day whenever another charge was planned within 24 hours. On a GivEnergy 3-phase unit that one switch arms every charge slot, and the unused slots, held at 00:00-00:00, are live for the first minute of the day - an 11 kW grid import at midnight. The leave-it branch came from #2993, for an inverter with no enable switch, where turning the charge off means rewriting the window. It is now kept for those only. With a switch the switch goes off, the times are left alone, and it comes back on within set_window_minutes of the next start. Co-authored-by: CI <ci@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e rate only limits grid charging (#5082) (#5542) * fix(inverter): no Freeze Export on GivEnergy 3-phase, where the charge rate only limits grid charging (#5082) A GivEnergy 3-phase unit has no Battery Pause and its charge rate register only limits charging from the grid, so a Freeze Export ended in a write that left solar filling the battery while the plan modelled it as exported. The component now marks the charge rate entity grid_charge_only - GE Cloud on the percentage charge rate when the device lists neither pause_battery nor battery_charge_power, the hub when the model it reports contains "3ph" - and the inverter clears its Freeze Export support on that mark. Freeze Export is also turned off when any inverter lacks it rather than only the first, and the export_more_solar warning and the GE Cloud discovery record follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(inverter): read and write the rate per inverter, so a mixed GE Cloud fleet sees the 3-phase unit (#5359) Review of the Freeze Export change found that in a GE Cloud fleet mixing a unit with a power register and a 3-phase one, the 3-phase inverter was bound to no rate entity at all: auto-config withheld the percentage rate for the whole fleet when any device had a power register, so its grid_charge_only mark was never read and Freeze Export stayed on. GE Cloud auto-config now binds the percentage rate per device, for a device with no power register only. The inverter asks its own entry which control it is bound to rather than whether the setting exists for the fleet, which is the read and write fault in #5359: a power-register inverter no longer reads a silent 100% from an empty percentage entry, and each inverter is written through the control it has. The docs now say the AC-coupled 3-phase model is treated the same way as a precaution, not as a confirmed hardware limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: CI <ci@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(gateway): add isolated display-only mode * fix(gateway): isolate optional display connection health * Add app attribute to gateway display state update Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: CI <ci@example.com>
…te (#5532) * fix(gateway): never enable discharge/export after a failed window write For a direct Hub battery, adjust_force_export wrote the discharge start and end as two separate set_discharge_slot commands, then enabled the scheduled discharge and switched to export mode regardless of whether the endpoint writes were applied. schedule_write_ok only withheld the later commit record. When the end write was refused after the start was accepted, the Hub was left enabled with a stale wrapping window (e.g. 02:55-00:01) and actively exporting. Send both endpoints in one full-serial set_discharge_slot for direct Hub batteries and verify both endpoint read-backs before continuing. Any refusal, staged or partial ACK, timeout, publication error or swallowed async-bridge exception returns before the enable write, export mode and commit, leaving the commit pending for the next cycle to retry. Both endpoints are tracked in the control ledger and share one backoff key. Read-only, entity ownership and serial checks refuse before publishing. Coordinators keep their staged separate-endpoint sequence; other providers, disable and midnight windows are unchanged. Mirrors the charge-window fix in #5531. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf * fix(gateway): detect coordinator staging before discharge ownership checks A coordinator whose discharge end entity was not (yet) owned by the gateway was refused with an ownership error instead of keeping its staged separate-endpoint sequence. Identify coordinators first, as that decision only needs the entity names and discovery record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf * fix(gateway): stop an armed export when its window move is refused; no re-poll of a refusal - When the paired window write fails while scheduled export is already enabled, turn the schedule off and return to Eco instead of leaving the previous window exporting on a plan that has moved away from it (skipped in read-only mode). - A refusal of the exact paired command ends the attempt immediately: the poll stops when it arrives, and a retry inside the ack window is not published (_send_control would suppress it anyway), so a refused write no longer holds the engine for the full poll timeout per retry. - Drop the unreachable end-serial comparison (the end entity name is derived from the start, so it carries the same serial) and name the test case after what it exercises. - Document that an exception inside a running event loop reaches the caller as None via run_async's thread, not as an exception. - Test the write/moved counters for the paired path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf * test(gateway): a successful move of an exporting schedule stays enabled The fail-safe disable applies only to a failed window write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf * fix(gateway): only an explicit Hub refusal turns off an exporting schedule The fail-safe disable fired on any unverified window write, including a slow but successful one (ACK outside the poll, older firmware without an applied block, telemetry not yet landed). That stopped a correctly re-windowed export for a cycle and toggled enable/mode. Take the disable path only when the Hub refused the exact paired command; an unconfirmed write leaves the running export alone and keeps the commit pending for the next cycle. Document that execute.py still reports "Exporting" for a cycle whose window was refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: CI <ci@example.com>
) * fix(gateway): send direct battery charge windows as paired slots * fix(gateway): separate coordinator staging from async publication failure * fix(gateway): detect coordinator staging before charge ownership checks A coordinator whose charge end entity was not (yet) owned by the gateway was refused with an ownership error, so its charge window was never written, instead of keeping its staged separate-endpoint sequence. Identify coordinators first, as that decision only needs the entity names and discovery record. Same ordering as the discharge side in #5532. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: CI <ci@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…topus lists it as completed (#5520) (#5533) * fix(octopus): keep the dispatch rate on a half hour a planned dispatch ran in until it is listed as completed (#5520) A planned Octopus Intelligent dispatch only covers what is still to come: the Octopus API component advances the start of a running one to now, and an ended one is dropped, while Octopus lists it as completed 15 to 60 minutes later. In between the half hour went back to the day rate, and as today's cost is priced from the current rates the day's cost stepped up after each half hour of car charging and fell back when the completed dispatch arrived. Each cycle, after the slot confirmation check, remember the half hour a started planned dispatch is running in unless the car was cancelled in it, and put it back into the slots as a finished dispatch until a completed dispatch overlaps it or OCTOPUS_DISPATCH_HOLD_MINUTES (90) have passed. A dispatch still to start is never held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(octopus): only hold a half hour a planned dispatch is running in on the exact clock (#5520) A planned entry still listed after its end passed the running check while its half hour was current, so one first seen after it ended was held without ever being seen running - and dispatch_billed_off_peak() takes an ended entry as finished, so an AWAY one became a phantom cheap half hour. Require the exact clock to be before the dispatch end as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: CI <ci@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…correct drifted Solcast ceiling cites (#5551) Folds GH#5545 (PV source precedence, Solcast-direct no-fallback and the staleness-key requirement) into the Solcast integration row, and adds a symptom row for GH#5549 (reserve at/above soc_max empties the charge- candidate scan, the kernel freeze interaction and the discard trap). Corrects the two drifted line cites on the GH#4730 max_kwh entry (9999 init now solcast.py:1718, ceiling warning now solcast.py:1538). Co-authored-by: CI <ci@example.com> Co-authored-by: Claude Code <noreply@anthropic.com>
…5553) * feat(load): forecast new installs from today's recorded load (#5548) The weighted-bucket load forecast (days_previous_auto) only sampled whole calendar days before today, so on a new install any time of day not yet recorded on a previous day got no history at all, and with under a day of history the forecast was skipped entirely. A system installed at midday forecast its first two nights at roughly base load even when this morning's overnight load was already recorded. For a slot with no sample on any previous day, compute_load_forecast_history now uses, in order: - today's sample at the same time of day, when that time has passed; - the average of all load recorded so far, once at least an hour exists; - base load (previously 0). Today never contributes while a previous day has data for the slot, so an established install's forecast is unchanged and the fallbacks fall away once a full day is recorded. Today's elapsed slots read today's own load, which keeps the in-day adjustment neutral instead of letting it scale up (to 2x, carried into tomorrow) a forecast already built from today. The caller no longer requires a day of history; {} is returned only when nothing has been recorded, leaving the days_previous fallback in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(load): search yesterday under 24h of history; keep pre-install hours at base load Review follow-ups on #5553: - load_minutes_age counts whole 24-hour periods, so a 20:00 install is still age 0 at 14:00 the next day and the previous-day loop searched nothing: tonight's slots took the recorded average instead of yesterday's recorded evening. Yesterday is now always searched (unrecorded buckets are skipped). - Elapsed slots of today before the install were filled with the recorded average. load_today_comparison counts them as base load actual, so an 11:00 install read at 14:00 as a large over-forecast and the in-day adjustment dropped to its 0.5x floor, cutting tonight's plan. They now stay at base load. Regression tests cover both, the second through load_today_comparison itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
mgazza
marked this pull request as ready for review
October 16, 2026 07:04
This branch has not been deployed
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.
A paired Hub can show fresh GivEnergy Cloud measurements while Cloud retains control. Display-only transport cannot publish battery, EV or executable-plan commands. Explicit display off blocks cached operating data at the final sink while preserving bounded metadata delivery.
Integrated onto main
d4c3a9af, retaining current charge/discharge window helpers, three-phase attributes and charger control. Optional display startup and outages do not block the healthy Cloud inverter source. The earlier #5504 fix is included in this consolidated implementation and should not be merged separately.Validation on the current-base integration: 442 focused Python tests passed (one canonical-runner fixture test deselected); the full quick suite passed, with four slow tests skipped. Pre-commit passed. Actual preference-save/SQL/config through Components, TLS MQTT and native receiver lifecycle passed, plus token-refresh/rejection/reconnect isolation. GitNexus staged audit: nine files, ten affected flows, HIGH aggregate scope, reviewed.
This set supports one paired Hub per site: GivEnergy Cloud can control while a parked Hub displays fresh measurements, and a controlling Hub can hide its operating display. A second display Hub alongside a Hub controller needs a separate target/transport extension. Cloud measured data remains unavailable for unsupported compositions, including EMS/Gateway and separate-PV sites.
Companions: engine #5529, SaaS #2159, firmware #482. Current tested heads: engine
a46365ec, SaaS98b5a7eb9, firmware50404218. Compatible engine and firmware must precede an exact-site SaaS canary. Eligibility flags remain default off.An independent reviewer found no actionable issues in the integration against current main/develop. Physical S3 pilot acceptance, live broker ACLs and real-device flash persistence are still pending. The joined proof uses isolated SQL, synthetic source data and a native receiver/storage adapter; it does not claim a physical inverter write cycle or automatic control handoff.