Skip to content

fix(planner): pin Energyplan 0.4.3 and land EV duty-cycle support - #1225

Merged
frahlg merged 22 commits into
masterfrom
fix/ev-support-integration-20260913
Sep 13, 2026
Merged

frahlg merged 22 commits into
masterfrom
fix/ev-support-integration-20260913

Conversation

@frahlg

@frahlg frahlg commented Sep 13, 2026

Copy link
Copy Markdown
Member

Pins Energyplan 0.4.3 and the Core decoder work needed to run it.

Energyplan can now finish a last interval below a full charger step, report on-power and remaining shortfall, and price on/off pulses so Core accepts the plan. This branch also carries the EV reliability, forecast-view, and battery-priority work that the pin depends on.

Local make verify passed in Energyplan. Core native tests TestNativePartialFleetAndOldResponseRejected, TestNativeRestoreFuturePVDependencyRequiresNewPlan, and TestNativeAllPhysicalTopologies passed against the pinned worker.

Source commit: 1dd3f01b09134d69f13b3100996ac9e1f915e061 (srcfl/energyplan#22).


Note

High Risk
Changes core dispatch, EV/fuse allocation, and planner validation paths where small logic errors affect grid import and charging deadlines; the Modbus proxy also exposes unauthenticated LAN access when enabled.

Overview
Pins Energyplan 0.4.3 and extends Core to accept EV duty-cycle plans: per-slot on-power (LoadpointMaxPowerW), measured-energy budgets, pulse pricing validation, and shortfall reporting when a deadline cannot be met. A new coreReservePlan path targets the same contract when the external worker is unavailable.

EV charging and fuse sharing get a substantial rework: configured phase/amp planning steps feed the MPC, session energy is tracked from meter/counter readings (not slot-averaged power), failed ev_resume retries stay scoped to the plug session, and scheduled EV demand is reserved before optional battery charge under the site limit.

Dispatch behavior shifts for arbitrage-family idle slots: batteries no longer reactively discharge to cover forecast misses (energy is held for later slots; idle plans can withdraw prior discharge). A live PV export guard adds reactive curtail caps when solar-attributable export exceeds the site ceiling, with explicit residual when caps cannot safely clear the overage.

Adds an optional Modbus TCP proxy (off by default, read-only unless opted in) on FTW’s shared inverter sessions, plus a background forecast scoring worker that pages archived issues so scoring does not block sampling.

Reviewed by Cursor Bugbot for commit 5efb386. Bugbot is set up for automated code reviews on this repo. Configure here.

cursoragent and others added 21 commits August 30, 2026 07:31
Drivers, probes and an optional LAN listener now go through one engine
keyed by host:port, so two Lua drivers can poll the same inverter (or
different unit IDs behind one gateway) without each taking a socket the
device will not accept.

modbus_proxy is off by default. When on, other integrations talk to FTW
instead of the device. Writes stay blocked unless allow_write is set —
they would bypass the control loop. Enabling it needs a restart.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Plan, heating, settings, history/savings/price cards, loadpoints and
twins now follow the same visibilitychange rule as status polling:
clear the timer while document.hidden, then one fetch and one timer
on return. Settings EV and System timers also stop when the modal
closes. Energy-flow particles skip rAF while hidden.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Keep master's shared Modbus session (#987) and graft the LAN proxy onto
it instead of a second pool. Resolve config/UI overlap with OCPP; drop
the retired CalDAV comments.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
…ation-20260913

# Conflicts:
#	go/internal/config/restart_required.go
#	go/internal/config/restart_required_test.go
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
The worker can finish a last interval below a full charger step, reports
on-power and remaining shortfall, and prices on/off pulses so Core
accepts the plan. Best-effort deadlines pass the wire gate when
shortfall is present.
@cursor

cursor Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c0605e03-afe1-49a0-915b-c051888352c8)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T07:32:18.092325Z 9e34831 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

A fleet solve can leave 1e-9 W on an EV after the deadline. Core was
rejecting that as an on-power of 0 W with a positive mean.
@cursor

cursor Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_50c44104-ee83-4816-901a-875d4c65a462)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e348317f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

energy += wh
}
}
return energy / hours

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reserve duty-cycle peak wattage at the fuse

For a partial duty-cycle action, this returns the slot-average demand (LoadpointEnergyWh / hours), while the loadpoint controller intentionally commands LoadpointMaxPowerW whenever the pulse is on. For example, a 100 Wh final interval can reserve only 400 W here but switch the charger on at 11 kW, allowing the battery allocator to consume the remaining fuse headroom and potentially exceed the site import ceiling until telemetry catches up. Use the directive's simultaneous on-power when calculating the fuse reservation.

AGENTS.md reference: AGENTS.md:L65-L68

Useful? React with 👍 / 👎.

first = err
}
}
p.wg.Wait()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Close accepted clients before waiting for proxy shutdown

When any Home Assistant or Node-RED client keeps an accepted proxy connection idle, closing only the listeners does not interrupt handleClient's read, so this wait blocks shutdown or an API-requested restart for up to the 90-second idle timeout. Track and close accepted connections, or otherwise cancel their reads, before waiting for the goroutines.

AGENTS.md reference: AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Comment thread go/cmd/ftw/main.go
Comment on lines +670 to +672
proxy, err := modbusEngine.Listen(mbBinds, cfg.ModbusProxy.AllowWrite)
if err != nil {
slog.Error("modbus proxy listen failed", "err", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report a failed proxy listener to the operator

If the configured port is occupied or cannot be bound, startup continues after only writing this log entry. A repository-wide search shows no Modbus-proxy runtime/status endpoint, so Settings continues to show the proxy as enabled without exposing that it is unavailable; operators will troubleshoot downstream integrations with no normal UI indication of the actual failure. Preserve the safe optional-module behavior, but publish the listener failure through status/config diagnostics.

AGENTS.md reference: AGENTS.md:L22-L23

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit e5f281f into master Sep 13, 2026
18 checks passed
@frahlg
frahlg deleted the fix/ev-support-integration-20260913 branch September 13, 2026 07:34
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.

2 participants