diff --git a/frontend/src/components/StatePanel.tsx b/frontend/src/components/StatePanel.tsx
index 8e80af2..442529b 100644
--- a/frontend/src/components/StatePanel.tsx
+++ b/frontend/src/components/StatePanel.tsx
@@ -8,6 +8,37 @@ export type StatePanelProps = {
nowIndex: number
}
+function deyeModeLabel(s: SlotData): string {
+ const m = s.deye_physical_mode
+ if (m === 'SELL') return 'SELL'
+ if (m === 'CHARGE') return 'CHARGE'
+ if (m === 'PASSIVE') return 'PASSIVE'
+ return '—'
+}
+
+function deyeModeBadge(s: SlotData): { label: string; klass: string; title: string } {
+ const m = s.deye_physical_mode
+ if (m === 'SELL') {
+ return {
+ label: 'SELL',
+ klass: 'bg-orange-500/15 text-orange-200 ring-1 ring-orange-500/35',
+ title: 'SELL (selling first): reg142=0, reg178=32 (grid peak shaving off)',
+ }
+ }
+ if (m === 'CHARGE') {
+ return {
+ label: 'CHARGE',
+ klass: 'bg-sky-500/15 text-sky-200 ring-1 ring-sky-500/35',
+ title: 'CHARGE (grid charge): TOU grid_charge enabled v time pointech; reg178=48',
+ }
+ }
+ return {
+ label: 'PASSIVE',
+ klass: 'bg-slate-600/40 text-slate-200 ring-1 ring-slate-500/30',
+ title: 'PASSIVE (ZERO): reg142=deye_zero_export_mode; reg178=48',
+ }
+}
+
/** Stav segmentu pro jeden track */
export type TrackSegment = {
widthPct: number
@@ -431,6 +462,21 @@ function StatePanelRaw({ slots, nowIndex }: StatePanelProps) {
+ Deye režim (plán):{' '}
+ {(() => {
+ const b = deyeModeBadge(slots[nowIndex]!)
+ return (
+
+ {b.label}
+
+ )
+ })()}
+ {deyeModeLabel(slots[nowIndex]!)}
+
diff --git a/frontend/src/hooks/useDashboardData.ts b/frontend/src/hooks/useDashboardData.ts
index 4c3b73e..0b4b248 100644
--- a/frontend/src/hooks/useDashboardData.ts
+++ b/frontend/src/hooks/useDashboardData.ts
@@ -94,6 +94,7 @@ function emptySlot(iso: string): SlotData {
battery_setpoint_w: null,
grid_power_w: null,
grid_setpoint_w: null,
+ deye_physical_mode: null,
load_power_w: null,
gen_port_power_w: null,
pv_a_forecast_w: null,
@@ -118,6 +119,7 @@ function emptySlot(iso: string): SlotData {
function mergeInterval(s: SlotData, p: PlanningIntervalDto): void {
s.battery_setpoint_w = p.battery_setpoint_w ?? s.battery_setpoint_w
s.grid_setpoint_w = p.grid_setpoint_w ?? s.grid_setpoint_w
+ if (p.deye_physical_mode != null) s.deye_physical_mode = p.deye_physical_mode
s.ev1_setpoint_w = p.ev1_setpoint_w ?? s.ev1_setpoint_w
s.ev2_setpoint_w = p.ev2_setpoint_w ?? s.ev2_setpoint_w
if (s.ev1_setpoint_w == null && s.ev2_setpoint_w == null && p.ev_charge_power_w != null) {
diff --git a/frontend/src/pages/Planning.tsx b/frontend/src/pages/Planning.tsx
index 4119c3d..c835957 100644
--- a/frontend/src/pages/Planning.tsx
+++ b/frontend/src/pages/Planning.tsx
@@ -187,6 +187,7 @@ function syntheticForecastOnlyInterval(
battery_setpoint_w: null,
battery_soc_target_pct: null,
grid_setpoint_w: null,
+ deye_physical_mode: null,
ev1_setpoint_w: null,
ev2_setpoint_w: null,
heat_pump_enabled: null,
@@ -284,8 +285,6 @@ function axiosDetail(e: unknown): string {
function deyeSetpointLabel(i: PlanningIntervalDto): string {
const battery_w = i.battery_setpoint_w ?? 0
const grid_w = i.grid_setpoint_w ?? 0
- const is_exporting = battery_w < -500 || grid_w < -500
- const is_charging = battery_w > 500
const tgt = i.battery_soc_target_pct
const targetSoc = tgt != null ? Math.min(95, Math.round(Number(tgt))) : 80
@@ -295,14 +294,49 @@ function deyeSetpointLabel(i: PlanningIntervalDto): string {
return `${s}kW`
}
- if (is_exporting) {
+ const pm = (i.deye_physical_mode ?? '').toString().trim().toUpperCase()
+ if (pm === 'SELL') {
const tpPowerW = Math.abs(battery_w)
- return `⬇ ${fmtKw(tpPowerW)} | reg178 bit4–5=10 (grid PS off)`
+ return `SELL | ⬇ ${fmtKw(tpPowerW)} | reg142=0 reg178=32`
}
- if (is_charging) {
- return `⬆ ${fmtKw(battery_w)} | grid=yes | SOC→${targetSoc}%`
+ if (pm === 'CHARGE') {
+ return `CHARGE | ⬆ ${fmtKw(Math.max(0, battery_w))} | grid=yes | SOC→${targetSoc}%`
+ }
+
+ // PASSIVE (ZERO): doplň informaci o variantě 108/109 podle pravidel (bez wattových prahů).
+ if (grid_w < 0 && battery_w >= 0) return 'PASSIVE | FVE→síť (108=0)'
+ if (grid_w > 0 && battery_w <= 0) return 'PASSIVE | držet bat. (109=0)'
+ return 'PASSIVE | max/max'
+}
+
+function deyeModeBadge(i: PlanningIntervalDto): { label: string; klass: string; title: string } {
+ const m = (i.deye_physical_mode ?? 'PASSIVE').toString().trim().toUpperCase()
+ const battery_w = i.battery_setpoint_w ?? 0
+ const grid_w = i.grid_setpoint_w ?? 0
+
+ if (m === 'SELL') {
+ return {
+ label: 'SELL',
+ klass: 'bg-orange-500/15 text-orange-200 ring-1 ring-orange-500/35',
+ title: 'SELL (selling first): reg142=0, reg178=32 (grid peak shaving off)',
+ }
+ }
+ if (m === 'CHARGE') {
+ return {
+ label: 'CHARGE',
+ klass: 'bg-sky-500/15 text-sky-200 ring-1 ring-sky-500/35',
+ title: 'CHARGE (grid charge): TOU grid_charge enabled v time pointech; reg178=48',
+ }
+ }
+
+ let variant = 'max/max'
+ if (grid_w < 0 && battery_w >= 0) variant = 'FVE→síť (108=0)'
+ else if (grid_w > 0 && battery_w <= 0) variant = 'držet bat. (109=0)'
+ return {
+ label: 'PASSIVE',
+ klass: 'bg-slate-600/40 text-slate-200 ring-1 ring-slate-500/30',
+ title: `PASSIVE (ZERO): ${variant}; reg142=deye_zero_export_mode; reg178=48`,
}
- return '~ 2kW | hold'
}
function tableRowClass(
@@ -1114,7 +1148,22 @@ export default function Planning() {
{i.battery_setpoint_w ?? '—'} |
- {deyeSetpointLabel(i)}
+
+ {(() => {
+ const b = deyeModeBadge(i)
+ return (
+
+ {b.label}
+
+ )
+ })()}
+
+ {deyeSetpointLabel(i)}
+
+
|
{i.battery_soc_target_pct != null
diff --git a/frontend/src/types/dashboard.ts b/frontend/src/types/dashboard.ts
index 3733b26..1f43928 100644
--- a/frontend/src/types/dashboard.ts
+++ b/frontend/src/types/dashboard.ts
@@ -8,6 +8,8 @@ export type SlotData = {
grid_power_w: number | null
/** Plánovaný výkon sítě (W) – budoucí sloty. */
grid_setpoint_w: number | null
+ /** Fyzický režim Deye pro slot (PASSIVE/SELL/CHARGE) z plánu. */
+ deye_physical_mode: 'PASSIVE' | 'SELL' | 'CHARGE' | null
load_power_w: number | null
gen_port_power_w: number | null
pv_a_forecast_w: number | null
diff --git a/frontend/src/types/plan.ts b/frontend/src/types/plan.ts
index 2a652bc..3d17b6a 100644
--- a/frontend/src/types/plan.ts
+++ b/frontend/src/types/plan.ts
@@ -15,6 +15,8 @@ export type PlanningIntervalDto = {
battery_setpoint_w: number | null
battery_soc_target_pct: number | null
grid_setpoint_w: number | null
+ /** Explicitní fyzický režim Deye pro slot (PASSIVE/SELL/CHARGE). */
+ deye_physical_mode?: 'PASSIVE' | 'SELL' | 'CHARGE' | null
ev1_setpoint_w: number | null
ev2_setpoint_w: number | null
ev_charge_power_w?: number | null
|