Repository navigation
Conversation
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
fix(telemetry): derive Home use from energy and preserve history gaps
chore(release): version packages
* feat(agents): expose scoped diagnostic API reads Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Expose the full technical read API and debugging context --------- Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
* Keep URL-encoded diagnostic path parameters intact * Keep diagnostic path struct tags unchanged * Give diagnostic exports their own five-minute call lifetime
…fl#1684) * feat(web): the Access tab names the car and charger in a request A request waiting for the owner on the box page showed a car by its 32-digit id. GET /api/agents now adds names to each request: for every argument of kind loadpoint or car that the box still knows, what the box calls it. The page shows the name and falls back to the id. The args stay the box's own ids, as before. Only this local, owner-only route carries names; protocol.Request and everything an agent or FTW Cloud reads are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep diagnostic agent lists in step with named requests --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
feat(web): the box page shows the owner's cars
…0261018 Keep browser logins for 30 days across Core restarts
…0261018 Order test-client shutdown and cancel after command admission
fix(history): keep P1 household and PV energy observations
…1018 fix: separate current Home use from its energy mean
…1946) * fix(localzone): name the zone by the rules the clock runs on A Docker box that mounts the host's /etc/localtime ran on the host's rules but told clients it was on Etc/UTC: Debian links /etc/localtime to Etc/UTC, so the mount overwrites that file, and /etc/timezone keeps the image's name. Clients then read the household clock (a car's ready time, a goal's days) an hour or two off. Every name candidate must now have time.Local's rules from 1970 to 2038, and a UTC name needs a clock that is UTC. A copied /etc/localtime is named by the zoneinfo file with the same bytes, else the first with the same rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(site): the home's time zone is a stored fact of the site A Docker box runs on UTC whatever house it is in, so a car goal of 17:00 was met at 18:00 in a Stockholm winter. Core now reads the household clock from site.time_zone, set once at start, instead of the host's zone. - localzone.Use sets time.Local (and TZ for workers) from the stored zone before any goroutine reads the clock; Name reports it. - site.time_zone is validated, shown under Settings → Site, written by the on-box setup from the browser, and listed as a restart reason. - site.time_zone.propose lets the app and the web client offer their zone. The box takes it only while it keeps none (nothing stored, host on UTC), then restarts once. A kept zone answers E_PRECONDITION with args.tz. - Cached local-day energy is tied to the zone it was computed on; a new zone clears it and the days are computed again from UTC buckets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…open contactor (srcfl#1952) Moves the pin to srcfl/device-drivers@107eb609 (srcfl#204). Only the Tesla Wall Connector driver changes. The test copy in go/internal/drivers/testdata was still the version from srcfl#978; it now matches the bundled driver, and the tests follow its contract: Core refuses control of the read-only driver, legs at 0 V carry no power, and an open contactor is idle. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…battery storage (srcfl#1950) * feat(history): the figure for Skatteverket's energy tax refund after battery storage A Swedish household gets back the energy tax on grid electricity it stored in a battery and later fed back to the same grid (11 kap. 13 § LSE). History now works out that figure for one calendar year, month by month, with the hourly basis as CSV. The count stays low wherever the readings cannot tell where energy went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(history): a meter that changes identity stays one meter The home box's Sungrow moved from a MAC-based to a serial-based device id in September, so the ledger holds two grid meters, batteries and solar assets. The report took that for two batteries and for meters that fell silent, and gave no figure for the year. Assets of one kind that never read at the same time now form one meter; the hours between them are still owed and stay incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(history): count the house's use as grid energy first, never more than the readings allow The proportional mix overstated. Nothing says how much the battery holds, and with the content taken as a full battery, grid energy the house had already used stayed in the mix and was credited to the evening's export: 5 kWh from the grid at night, 5 kWh to the house in the morning, 5 kWh of solar at midday and 5 kWh to the grid in the evening returned about 0.6 kWh a day where the truth is 0. What the house takes from the battery now counts as grid energy first, and what goes to the grid as other energy first. Grid energy enters after the least efficiency its own run of battery readings allows, so a short run or one cut by a silent battery cannot borrow an efficiency; other energy enters whole. A full battery sheds grid energy first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(history): play each hour in the order that credits least, and keep the charge loss A third review found the strict account could still overstate: - It played discharge before charge, so the house never took grid energy that came in earlier in the same hour (4 kWh where the least figure the flows allow is 0). - It dropped the charge loss, so the account fell below what the battery held; the house then took other energy the real battery still had, and the export reached grid energy early (1230.8 Wh against 0). - A new battery asset that took over within an hour kept the old account. Each hour now plays: other energy in, the export out (other energy first), grid energy in after the least efficiency with its loss kept as other energy, the house's share (grid energy first), then a full battery sheds grid energy first. A battery asset that takes over starts a new account. The review's cases are tests and fail on the previous code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…cfl#1951) Skatteverket's refund of energy tax after battery storage is worked out hour by hour for a calendar year and may be claimed until three years after the year ends. With two years of hours, the start of an older year was already daily totals and counted as incomplete. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…gure (srcfl#1953) Sites with more than one battery had no figure, because one shared account let one battery's exports carry another's grid energy. Each battery meter now keeps its own account, size and run efficiencies, and for each battery solar, a car and the other batteries count first in every hour. A driver's battery size goes to the meter with an asset of its name, so a new identity keeps it. The hourly CSV has one row per battery and hour. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): version packages * chore(release): the changelog names each battery's size Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…fl#1958) The strict order counted the house's use of a battery as grid energy first and its export as other energy first, so a battery with solar almost never got a figure: the home box gave 0 for 2026. Now whatever leaves a battery, to the house or the grid, carries the grid's share of what it held, and energy enters after the battery's efficiency over the year, so losses are never refunded. Within an hour the charge mixes in before the discharge. The hourly bounds, complete-hour rules and per-battery accounts stay. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…te meter again (srcfl#1961) Moves the pin to srcfl/device-drivers@d662c3eb (srcfl#208). Only the Zap driver changes. A Zap on firmware 7.x serves its local API in data model v2, and zap 3.1.1 read only the old names, so FTW lost its site meter. The new tests use the payload a Zap-HAN on 7.0.3 sent, and they fail against 3.1.1. read_pv and read_battery on 7.x are not covered yet. Signed-off-by: David Mozart <dmozartandraws@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…for a restart (srcfl#1960) * test(site): drop r3 and r4 known lines fixed on master, give r4 room for a restart A3 and A4 (srcfl#1876, srcfl#1878, srcfl#1916) and E1 (srcfl#1787) merged before the R1-R5 scenarios did, so r3-25 and r4-25 failed every full run since 28 Oct with "passes now: delete its known line". r4 also failed "the day's Home use kept within 1 %" in 1 run of 5. The day may have 10 % unknown time, counted in whole ledger minutes; when the car's unknown reading after the restart crossed a minute boundary, two minutes were marked and the 17 min day reached 12.2 %. r4 now waits 25 min instead of 10 before the hold, as s7 waits 15 min. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(site): r3 judges the measured SoC at the planner floor With its known lines gone, r3's floor criteria are gates, and both read Core's order. A battery handed back to the inverter reads as a 0 W order and would pass while it runs on to the inverter's 5 %. The measured SoC must now stay at or above the floor too. Review of srcfl#1960. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ware 7.x (srcfl#1962) Moves the pin to srcfl/device-drivers@e463c653 (srcfl#209). Only the Zap driver changes. The new tests use the payload a Zap on 7.0.3 sent for a simulated Solis. Signed-off-by: David Mozart <dmozartandraws@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1964) * fix(config): a battery soc_max of 0 no longer blocks charging Settings saved a blank number field as 0. Since soc_max became the dispatch charge ceiling, a battery whose Max SoC was saved that way never charged, manual Charge included. Core treats soc_max <= 0 as unset, and Settings saves a cleared number field as unset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(config): leave the shared battery map alone and test the stored path Validate runs on shallow copies of the live config, so the zero drop clones the batteries map before it writes. A new test loads a zero that an older Core stored in the config database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…rcfl#1969) * fix(control): a battery that may not export holds the grid above 0 W In planner_self, planner_cheap and passive arbitrage the reactive path aimed a discharging battery at 0 W with a symmetric dead band. Each drop in house load then sold battery energy until the next correction, about as often as the site imported. While such a battery discharges more than the dead band, the target is now at least grid_tolerance_w, so the band ends at 0 W. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(control): bias the no-export target on the planned discharge, not the reading The bias keyed on the battery's measured power, so an idle battery under a legacy plan with a negative grid target discharged toward it for one tick and exported. It now applies whenever the plain target would have the battery discharge more than the dead band. Charging from PV surplus no longer sees the bias at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
) The budget sentence still said Core allows 500 ms or 5 s. Core also gives 1.5 s with an active car, skips the call when less than 500 ms of the first slot remain, caps time_limit_s at 5.6 s, and keeps a reply margin besides the 50 ms for validation and publication. State what go/internal/mpc/energyplan.go does today. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… demand_elapsed_hours (srcfl#1966) With 15-minute prices Core replans at :15, :30 and :45, and bindDemandCharges sent only the tariff hours that start at or after slot 0. The hour already running was left out of the peak-power tariff the planner sees, so a plan could set a new monthly peak in it, and today's completed peak counted twice in the top-N mean. Core now sends the hour in progress with the import measured so far (the site meter's hourly counter, else the history; uncovered time counts at no less than slot 0's forecast import) and puts today's completed peak in today's group. Only a planner whose handshake announces demand_elapsed_hours gets this shape; any other planner, including the bundled one, gets exactly the request it got before. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…l#1968) Core fixed time_limit_s from the budget at the frozen execution start. With forecast caution, the caution call follows the point call and its deadline counts from the time left in the slot now. After a point call that used its whole limit, the caution call could ask for 1.5 s with about 950 ms left before its deadline, so a worker that uses its full time limit answered after Core had given up, and Core published the point plan. Each call's time_limit_s now fits the time before that call's deadline, minus Core's reply margin, after request preparation. A call with its full budget left keeps it. Below Core's no-call threshold the worker is not started. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
srcfl#1973) srcfl/device-drivers#203 skips the battery emit when every battery reading is 0, so a PV-only Growatt no longer draws a phantom battery beside the site's real one (srcfl#1669). The pin moves e463c653 to e07d2703; growatt is the only bundled driver that changes. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…srcfl#1972) xorath, 5 Nov 2026: a battery charged 2.3 kW while FTW asked it to discharge 812 W. The box's page said "Not following"; no phone heard. The new control.not_following rule reads the box's own answer to "Are we in control?" (the control_feedback GET /api/status serves) at most every 30 s on the HealthTick, and fires once a device function has been not_following for ten minutes. It fires once until the device follows again, with an hour's cooldown on top. On by default for owners with notifications on (Fredrik, 5 Nov 2026); a box whose stored rules predate the kind takes the default. The words come from the push catalogue. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…1977) verify.py skipped execution only on hosts it does not know. On a known host whose worker the bundle leaves out, such as macOS when only the required Linux workers ship, it failed with a KeyError instead of checking the bundle. It now verifies every file and skips execution on any host without its own worker; --host-binary prints nothing there, so make native-solver-test skips the process tests as before. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bundle the worker from srcfl/energyplan 847936a (0.7.0, tag v0.7.0-beta.1) for linux-arm64 and linux-amd64. The macOS worker is no longer bundled; srcfl#1977 lets verify.py check such a bundle and skip execution on a Mac. The worker lists demand_elapsed_hours, so Core (srcfl#1966) sends it the demand hour in progress and the day's completed peak in that day's group, and the native demand test now requires the feature. The contract describes the feature and the demand-charge fields, each call's time limit since srcfl#1968, the batteries' total recovery rule and optional reply diagnostics, and it stays identical to Energyplan's copy. The worker version pins and the README follow. Licenses and forecast schemas are unchanged; the notices list two more microlp modifications. Protocol v1 is unchanged. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
# Conflicts: # go/internal/control/sitesim_field_test.go
A fresh pilot reads as charger_pilot_limit only when it is 0.5 A below the order and within 0.5 A of the limit that explains the draw. A valid pilot past its 90 s window becomes unknown and leaves the driver's limit standing, so a late Easee cloud read no longer turns load balancing into a warning. The charger's own limit still explains a draw when a fresh pilot comes without a current limit. Co-Authored-By: Claude Opus 5.5 <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.
Temporary CI run for srcfl#1905 at e874c43. The test workflow triggers on PRs targeting master.