FE implementace deye modu
This commit is contained in:
@@ -8,6 +8,37 @@ export type StatePanelProps = {
|
|||||||
nowIndex: number
|
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 */
|
/** Stav segmentu pro jeden track */
|
||||||
export type TrackSegment = {
|
export type TrackSegment = {
|
||||||
widthPct: number
|
widthPct: number
|
||||||
@@ -431,6 +462,21 @@ function StatePanelRaw({ slots, nowIndex }: StatePanelProps) {
|
|||||||
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||||
Energetický tok
|
Energetický tok
|
||||||
</p>
|
</p>
|
||||||
|
<p className="mb-2 text-[10px] text-slate-500">
|
||||||
|
Deye režim (plán):{' '}
|
||||||
|
{(() => {
|
||||||
|
const b = deyeModeBadge(slots[nowIndex]!)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center rounded-md px-2 py-0.5 text-[10px] font-semibold ${b.klass}`}
|
||||||
|
title={b.title}
|
||||||
|
>
|
||||||
|
{b.label}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
<span className="ml-2 font-mono text-slate-500">{deyeModeLabel(slots[nowIndex]!)}</span>
|
||||||
|
</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<TrackRow label="Síť" segments={gridSegs} nowIndex={nowIndex} showNowLabel />
|
<TrackRow label="Síť" segments={gridSegs} nowIndex={nowIndex} showNowLabel />
|
||||||
<TrackRow label="Baterie" segments={batSegs} nowIndex={nowIndex} />
|
<TrackRow label="Baterie" segments={batSegs} nowIndex={nowIndex} />
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ function emptySlot(iso: string): SlotData {
|
|||||||
battery_setpoint_w: null,
|
battery_setpoint_w: null,
|
||||||
grid_power_w: null,
|
grid_power_w: null,
|
||||||
grid_setpoint_w: null,
|
grid_setpoint_w: null,
|
||||||
|
deye_physical_mode: null,
|
||||||
load_power_w: null,
|
load_power_w: null,
|
||||||
gen_port_power_w: null,
|
gen_port_power_w: null,
|
||||||
pv_a_forecast_w: null,
|
pv_a_forecast_w: null,
|
||||||
@@ -118,6 +119,7 @@ function emptySlot(iso: string): SlotData {
|
|||||||
function mergeInterval(s: SlotData, p: PlanningIntervalDto): void {
|
function mergeInterval(s: SlotData, p: PlanningIntervalDto): void {
|
||||||
s.battery_setpoint_w = p.battery_setpoint_w ?? s.battery_setpoint_w
|
s.battery_setpoint_w = p.battery_setpoint_w ?? s.battery_setpoint_w
|
||||||
s.grid_setpoint_w = p.grid_setpoint_w ?? s.grid_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.ev1_setpoint_w = p.ev1_setpoint_w ?? s.ev1_setpoint_w
|
||||||
s.ev2_setpoint_w = p.ev2_setpoint_w ?? s.ev2_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) {
|
if (s.ev1_setpoint_w == null && s.ev2_setpoint_w == null && p.ev_charge_power_w != null) {
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ function syntheticForecastOnlyInterval(
|
|||||||
battery_setpoint_w: null,
|
battery_setpoint_w: null,
|
||||||
battery_soc_target_pct: null,
|
battery_soc_target_pct: null,
|
||||||
grid_setpoint_w: null,
|
grid_setpoint_w: null,
|
||||||
|
deye_physical_mode: null,
|
||||||
ev1_setpoint_w: null,
|
ev1_setpoint_w: null,
|
||||||
ev2_setpoint_w: null,
|
ev2_setpoint_w: null,
|
||||||
heat_pump_enabled: null,
|
heat_pump_enabled: null,
|
||||||
@@ -284,8 +285,6 @@ function axiosDetail(e: unknown): string {
|
|||||||
function deyeSetpointLabel(i: PlanningIntervalDto): string {
|
function deyeSetpointLabel(i: PlanningIntervalDto): string {
|
||||||
const battery_w = i.battery_setpoint_w ?? 0
|
const battery_w = i.battery_setpoint_w ?? 0
|
||||||
const grid_w = i.grid_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 tgt = i.battery_soc_target_pct
|
||||||
const targetSoc = tgt != null ? Math.min(95, Math.round(Number(tgt))) : 80
|
const targetSoc = tgt != null ? Math.min(95, Math.round(Number(tgt))) : 80
|
||||||
|
|
||||||
@@ -295,14 +294,49 @@ function deyeSetpointLabel(i: PlanningIntervalDto): string {
|
|||||||
return `${s}kW`
|
return `${s}kW`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_exporting) {
|
const pm = (i.deye_physical_mode ?? '').toString().trim().toUpperCase()
|
||||||
|
if (pm === 'SELL') {
|
||||||
const tpPowerW = Math.abs(battery_w)
|
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) {
|
if (pm === 'CHARGE') {
|
||||||
return `⬆ ${fmtKw(battery_w)} | grid=yes | SOC→${targetSoc}%`
|
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(
|
function tableRowClass(
|
||||||
@@ -1114,7 +1148,22 @@ export default function Planning() {
|
|||||||
<CenaCell i={i} nowMs={nowMs} />
|
<CenaCell i={i} nowMs={nowMs} />
|
||||||
<td className="pr-2 font-mono tabular-nums text-slate-300">{i.battery_setpoint_w ?? '—'}</td>
|
<td className="pr-2 font-mono tabular-nums text-slate-300">{i.battery_setpoint_w ?? '—'}</td>
|
||||||
<td className="max-w-[200px] whitespace-normal break-words pr-2 text-slate-300">
|
<td className="max-w-[200px] whitespace-normal break-words pr-2 text-slate-300">
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{(() => {
|
||||||
|
const b = deyeModeBadge(i)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center rounded-md px-2 py-0.5 text-[10px] font-semibold ${b.klass}`}
|
||||||
|
title={b.title}
|
||||||
|
>
|
||||||
|
{b.label}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
<span className="text-slate-400" title={deyeSetpointLabel(i)}>
|
||||||
{deyeSetpointLabel(i)}
|
{deyeSetpointLabel(i)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="pr-2 font-mono tabular-nums text-slate-300">
|
<td className="pr-2 font-mono tabular-nums text-slate-300">
|
||||||
{i.battery_soc_target_pct != null
|
{i.battery_soc_target_pct != null
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export type SlotData = {
|
|||||||
grid_power_w: number | null
|
grid_power_w: number | null
|
||||||
/** Plánovaný výkon sítě (W) – budoucí sloty. */
|
/** Plánovaný výkon sítě (W) – budoucí sloty. */
|
||||||
grid_setpoint_w: number | null
|
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
|
load_power_w: number | null
|
||||||
gen_port_power_w: number | null
|
gen_port_power_w: number | null
|
||||||
pv_a_forecast_w: number | null
|
pv_a_forecast_w: number | null
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export type PlanningIntervalDto = {
|
|||||||
battery_setpoint_w: number | null
|
battery_setpoint_w: number | null
|
||||||
battery_soc_target_pct: number | null
|
battery_soc_target_pct: number | null
|
||||||
grid_setpoint_w: 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
|
ev1_setpoint_w: number | null
|
||||||
ev2_setpoint_w: number | null
|
ev2_setpoint_w: number | null
|
||||||
ev_charge_power_w?: number | null
|
ev_charge_power_w?: number | null
|
||||||
|
|||||||
Reference in New Issue
Block a user