using System;
using InnovEnergy.App.SinexcelCommunication.DataTypes;
using InnovEnergy.App.SinexcelCommunication.SystemConfig;
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
namespace InnovEnergy.App.SinexcelCommunication.ESS;
/*
public static class DynamicPricingEngine
{
public static readonly TimeSpan CheapStart = TimeSpan.FromHours(22); // 22:00
public static readonly TimeSpan CheapEnd = TimeSpan.FromHours(6); // 06:00
// Expensive (High tariff)
public static readonly TimeSpan HighStart = TimeSpan.FromHours(17); // 17:00
public static readonly TimeSpan HighEnd = TimeSpan.FromHours(21); // 21:00
///
/// Call this from your main loop. It sets statusrecord.Mode.
///
/// liveSpotPrice is only needed when DynamicPricingMode == SpotPrice.
/// If your inverter cannot directly force Charge/Discharge, set inverterSupportsDirectForce=false
/// and it will execute via TimeChargeDischarge + rolling short time window.
///
public static void Apply(
DateTime nowLocal,
Decimal? liveSpotPrice,
StatusRecord statusrecord,
Boolean inverterSupportsDirectForce,
Int32 rollingWindowMinutes = 10)
{
if (statusrecord == null) throw new ArgumentNullException(nameof(statusrecord));
if (statusrecord.Config == null) throw new ArgumentNullException(nameof(statusrecord.Config));
var c = statusrecord.Config;
// 0) Manual override (optional)
if (!c.DynamicPricingEnabled)
{
Console.WriteLine(" Dynamic pricing is disabled");
return;
}
if (statusrecord.InverterRecord.OperatingPriority == OperatingPriority.ModeNotSynched)
{
Console.WriteLine(" Inverter mode are not synched ");
return;
}
/*
}
// 1) Base operating mode: explicit modes ignore dynamic pricing
if (c.OperatingPriority == OperatingPriority.GridPriority)
{
SetMode(statusrecord, OperatingPriority.GridPriority);
return;
}
if (c.OperatingPriority == OperatingPriority.BatteryPriority)
{
SetMode(statusrecord, OperatingPriority.BatteryPriority);
return;
}
// 2) OperatingMode == OptimizeSelfUse -> dynamic pricing can apply
var desired = DecideDesiredAction(nowLocal, liveSpotPrice, c);
if (desired == DesiredAction.OptimizeSelfUse)
{
SetMode(statusrecord, OperatingPriority.LoadPriority);
return;
}
// 3) Execute desired action
if (inverterSupportsDirectForce)
{
statusrecord.Mode = desired == DesiredAction.Charge
? BatteryMode.Charge
: BatteryMode.Discharge;
return;
}
// 4) Inverter limitation: execute via TimeChargeDischarge rolling window
statusrecord.Mode = BatteryMode.TimeChargeDischarge;
var (start, end) = MakeRollingWindow(nowLocal, rollingWindowMinutes);
if (desired == DesiredAction.Charge)
{
c.TimeChargeStart = start;
c.TimeChargeEnd = end;
// clear discharge window to avoid overlap
c.TimeDischargeStart = TimeSpan.Zero;
c.TimeDischargeEnd = TimeSpan.Zero;
}
else // Discharge
{
c.TimeDischargeStart = start;
c.TimeDischargeEnd = end;
// clear charge window to avoid overlap
c.TimeChargeStart = TimeSpan.Zero;
c.TimeChargeEnd = TimeSpan.Zero;
}
}
private static void SetMode( StatusRecord statusrecord, OperatingPriority o)
{
var operatingMode = o switch
{
OperatingPriority.LoadPriority => WorkingMode.SpontaneousSelfUse,
OperatingPriority.BatteryPriority => WorkingMode.TimeChargeDischarge,
OperatingPriority.GridPriority => WorkingMode.PrioritySellElectricity,
_ => WorkingMode.SpontaneousSelfUse
};
if (statusrecord.InverterRecord.OperatingPriority != OperatingPriority.ModeNotSynched)
{
foreach (var inv in statusrecord?.InverterRecord.Devices)
{
inv.WorkingMode = operatingMode;
}
}
else
{
foreach (var inv in statusrecord?.InverterRecord.Devices)
{
Console.WriteLine(" Inverter mode are not synched");
inv.WorkingMode = WorkingMode.SpontaneousSelfUse;
}
}
return;
}
// ----------------------------
// Decision logic
// ----------------------------
private static DesiredAction DecideDesiredAction(DateTime nowLocal, decimal? liveSpotPrice, Config c)
{
if (c.DynamicPricingMode == DynamicPricingMode.Disabled)
return DesiredAction.OptimizeSelfUse;
TimeSpan now = nowLocal.TimeOfDay;
if (c.DynamicPricingMode == DynamicPricingMode.Tou)
{
bool isCheap = IsInTimeWindow(now,CheapStart, CheapEnd);
bool isHigh = IsInTimeWindow(now, HighStart, HighEnd);
// Priority: cheap -> charge, then high -> discharge
if (isCheap) return DesiredAction.Charge;
if (isHigh) return DesiredAction.Discharge;
return DesiredAction.OptimizeSelfUse;
}
if (c.DynamicPricingMode == DynamicPricingMode.SpotPrice)
{
if (!liveSpotPrice.HasValue)
return DesiredAction.OptimizeSelfUse; // safe fallback
if (c.CheapPrice >= c.HighPrice)
throw new ArgumentException("Config error: CheapPrice must be lower than HighPrice.");
decimal p = liveSpotPrice.Value;
if (p <= c.CheapPrice) return DesiredAction.Charge;
if (p >= c.HighPrice) return DesiredAction.Discharge;
}
return DesiredAction.OptimizeSelfUse;
}
// ----------------------------
// Helpers
// ----------------------------
///
/// [start, end) window, supports overnight. start==end means disabled.
///
private static Boolean IsInTimeWindow(TimeSpan now, TimeSpan start, TimeSpan end)
{
if (start == end) return false;
// Same-day
if (start < end)
return now >= start && now < end;
// Overnight
return now >= start || now < end;
}
private static (TimeSpan start, TimeSpan end) MakeRollingWindow(DateTime nowLocal, int minutes)
{
if (minutes <= 0) throw new ArgumentOutOfRangeException(nameof(minutes));
var start = nowLocal.TimeOfDay;
var end = (nowLocal + TimeSpan.FromMinutes(minutes)).TimeOfDay;
return (start, end);
}
}
}*/