diff --git a/assets/js/components/Battery/BatterySuggestionIcon.stories.ts b/assets/js/components/Battery/BatterySuggestionIcon.stories.ts index bc5a4c105..a0eeeaa43 100644 --- a/assets/js/components/Battery/BatterySuggestionIcon.stories.ts +++ b/assets/js/components/Battery/BatterySuggestionIcon.stories.ts @@ -2,7 +2,7 @@ import BatterySuggestionIcon from "./BatterySuggestionIcon.vue"; import { ICON_SIZE } from "@/types/evcc"; import type { Meta, StoryFn } from "@storybook/vue3"; -const actions = ["normal", "hold", "charge", "holdcharge"] as const; +const actions = ["normal", "hold", "charge", "holdcharge", "discharge"] as const; export default { title: "Battery/BatterySuggestionIcon", diff --git a/assets/js/components/Battery/BatterySuggestionIcon.vue b/assets/js/components/Battery/BatterySuggestionIcon.vue index df313a213..de285d0e5 100644 --- a/assets/js/components/Battery/BatterySuggestionIcon.vue +++ b/assets/js/components/Battery/BatterySuggestionIcon.vue @@ -8,6 +8,11 @@ > + + diff --git a/assets/js/types/evcc.ts b/assets/js/types/evcc.ts index b5a541e59..f80151321 100644 --- a/assets/js/types/evcc.ts +++ b/assets/js/types/evcc.ts @@ -1110,7 +1110,7 @@ export interface Battery { /** Battery optimizer suggestion for a home battery. */ export interface BatterySuggestion { /** Suggested operation mode. */ - action: "normal" | "hold" | "charge" | "holdcharge"; + action: "normal" | "hold" | "charge" | "holdcharge" | "discharge"; /** Recommended charge power in W. */ charge?: number; /** Recommended discharge power in W. */ diff --git a/core/site_optimizer.go b/core/site_optimizer.go index 0fc3fff5e..ab1283a51 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -120,6 +120,10 @@ const ( actionCharge = "charge" ) +// actionDischarge is the battery-to-grid discharge advisory. It has no matching +// api.BatteryMode, so it always reads as actionable. +const actionDischarge = "discharge" + // evSuggestion notifies when the optimizer's advisory action for a device changes const evSuggestion = "suggestion" @@ -179,6 +183,9 @@ func currentSlotSuggestion(detail batteryDetail, res optimizer.BatteryResult, gr case idle && gridExporting: // idle while exporting: surplus is exported instead of charged s.Action = api.BatteryHoldCharge.String() + case discharge > suggestionThreshold && gridExporting: + // discharging while exporting means battery-to-grid discharge + s.Action = actionDischarge default: s.Action = api.BatteryNormal.String() } diff --git a/core/site_optimizer_test.go b/core/site_optimizer_test.go index 595593c3f..d5ed31b1f 100644 --- a/core/site_optimizer_test.go +++ b/core/site_optimizer_test.go @@ -295,7 +295,8 @@ func TestCurrentSlotSuggestion(t *testing.T) { {"battery pv charge (no import)", batteryTypeBattery, 3000, 0, false, true, "normal"}, {"battery hold (idle while importing)", batteryTypeBattery, 0, 0, true, false, "hold"}, {"battery holdcharge (idle while exporting)", batteryTypeBattery, 0, 0, false, true, "holdcharge"}, - {"battery discharge", batteryTypeBattery, 0, 2000, true, false, "normal"}, + {"battery discharge (self-consumption while importing)", batteryTypeBattery, 0, 2000, true, false, "normal"}, + {"battery grid discharge (discharge while exporting)", batteryTypeBattery, 0, 2000, false, true, "discharge"}, {"battery idle balanced", batteryTypeBattery, 0, 0, false, false, "normal"}, {"loadpoint charge", batteryTypeLoadpoint, 11000, 0, false, false, "charge"}, {"loadpoint stop", batteryTypeLoadpoint, 0, 0, false, false, "stop"}, diff --git a/i18n/en.json b/i18n/en.json index ac2eb6526..2266e6117 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -49,6 +49,7 @@ "optimizer": { "action": { "charge": "Charge from grid", + "discharge": "Discharge to grid", "hold": "Prevent discharging", "holdcharge": "Prevent charging", "normal": "Normal operation" diff --git a/server/mcp/openapi.json b/server/mcp/openapi.json index 177d919e9..1903503b7 100644 --- a/server/mcp/openapi.json +++ b/server/mcp/openapi.json @@ -3530,7 +3530,8 @@ "normal", "hold", "charge", - "holdcharge" + "holdcharge", + "discharge" ] }, "charge": { diff --git a/server/openapi.state.yaml b/server/openapi.state.yaml index 7dd1746a0..c9c522b20 100644 --- a/server/openapi.state.yaml +++ b/server/openapi.state.yaml @@ -408,6 +408,7 @@ components: - hold - charge - holdcharge + - discharge charge: description: Recommended charge power in W. type: number