From 372ab2203de317c69b7a53286595a1ae3d7926c7 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Thu, 16 Apr 2026 10:12:48 +0200 Subject: [PATCH] TOU --- csharp/App/Backend/Controller.cs | 10 ++- csharp/App/Backend/DataTypes/Configuration.cs | 9 ++- .../DataTypes/Configuration.cs | 11 +++ csharp/App/SinexcelCommunication/Program.cs | 9 +++ .../SystemConfig/Config.cs | 21 ++++-- .../src/content/dashboards/Log/graph.util.tsx | 5 ++ .../SodistoreHomeConfiguration.tsx | 70 +++++++++++++++++-- typescript/frontend-marios2/src/lang/de.json | 6 +- typescript/frontend-marios2/src/lang/en.json | 6 +- typescript/frontend-marios2/src/lang/fr.json | 6 +- typescript/frontend-marios2/src/lang/it.json | 6 +- 11 files changed, 143 insertions(+), 16 deletions(-) diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index ab756e82f..82da105b9 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -1894,7 +1894,15 @@ public class Controller : ControllerBase public async Task>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,int product,Token authToken) { var session = Db.GetSession(authToken); - + + // Dynamic Pricing in Spot Price mode: forward the provider chosen on the Information tab + // so the device knows which operator's API to query for spot prices. + if (config.DynamicPricingMode == "SpotPrice") + { + var installation = Db.GetInstallationById(installationId); + config.NetworkProvider = installation?.NetworkProvider; + } + string configString = product switch { 0 => config.GetConfigurationSalimax(), // Salimax diff --git a/csharp/App/Backend/DataTypes/Configuration.cs b/csharp/App/Backend/DataTypes/Configuration.cs index a0e747a00..4c17a392a 100644 --- a/csharp/App/Backend/DataTypes/Configuration.cs +++ b/csharp/App/Backend/DataTypes/Configuration.cs @@ -24,9 +24,15 @@ public class Configuration // Sinexcel Dynamic Pricing (under GridPriority) — strings for demo; engine will parse later. public string? DynamicPricingMode { get; set; } + public string? NetworkProvider { get; set; } public string? CurrentPrice { get; set; } public string? PriceToSell { get; set; } public string? PriceToBuy { get; set; } + // TOU windows stored as "HH:mm" strings + public string? TimeToSellFrom { get; set; } + public string? TimeToSellTo { get; set; } + public string? TimeToBuyFrom { get; set; } + public string? TimeToBuyTo { get; set; } public String GetConfigurationString() { @@ -55,7 +61,8 @@ public class Configuration return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " + $"InverterNumber: {InverterNumber}, BatteriesCount: {BatteriesCount}, BatteriesCountPerInverter: [{(BatteriesCountPerInverter != null ? string.Join(", ", BatteriesCountPerInverter) : "")}], ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, ControlPermission:{ControlPermission}, "+ $"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}, " + - $"DynamicPricingMode: {DynamicPricingMode}, CurrentPrice: {CurrentPrice}, PriceToSell: {PriceToSell}, PriceToBuy: {PriceToBuy}"; + $"DynamicPricingMode: {DynamicPricingMode}, NetworkProvider: {NetworkProvider}, CurrentPrice: {CurrentPrice}, PriceToSell: {PriceToSell}, PriceToBuy: {PriceToBuy}, " + + $"TimeToSell: {TimeToSellFrom}-{TimeToSellTo}, TimeToBuy: {TimeToBuyFrom}-{TimeToBuyTo}"; } // TODO: SodistoreGrid — update configuration fields when defined diff --git a/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs b/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs index 710cdcbd8..f75c5af4b 100644 --- a/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs +++ b/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs @@ -16,5 +16,16 @@ public class Configuration public Single TimeChargeandDischargePower { get; set; } public Boolean ControlPermission { get; set; } + // Dynamic Pricing (under GridPriority) — strings for demo; engine parses when needed. + public String? DynamicPricingMode { get; set; } + public String? NetworkProvider { get; set; } + public String? CurrentPrice { get; set; } + public String? PriceToSell { get; set; } + public String? PriceToBuy { get; set; } + public String? TimeToSellFrom { get; set; } + public String? TimeToSellTo { get; set; } + public String? TimeToBuyFrom { get; set; } + public String? TimeToBuyTo { get; set; } + } diff --git a/csharp/App/SinexcelCommunication/Program.cs b/csharp/App/SinexcelCommunication/Program.cs index ffbccabad..3466f76db 100644 --- a/csharp/App/SinexcelCommunication/Program.cs +++ b/csharp/App/SinexcelCommunication/Program.cs @@ -639,6 +639,15 @@ internal static class Program status.Config.PvNumber = config.PvNumber; status.Config.ControlPermission = config.ControlPermission; + status.Config.DynamicPricingMode = config.DynamicPricingMode; + status.Config.NetworkProvider = config.NetworkProvider; + status.Config.CurrentPrice = config.CurrentPrice; + status.Config.PriceToSell = config.PriceToSell; + status.Config.PriceToBuy = config.PriceToBuy; + status.Config.TimeToSellFrom = config.TimeToSellFrom; + status.Config.TimeToSellTo = config.TimeToSellTo; + status.Config.TimeToBuyFrom = config.TimeToBuyFrom; + status.Config.TimeToBuyTo = config.TimeToBuyTo; } private static async Task SaveModbusTcpFile(StatusRecord status) diff --git a/csharp/App/SinexcelCommunication/SystemConfig/Config.cs b/csharp/App/SinexcelCommunication/SystemConfig/Config.cs index 5c6dfe708..84fda6050 100644 --- a/csharp/App/SinexcelCommunication/SystemConfig/Config.cs +++ b/csharp/App/SinexcelCommunication/SystemConfig/Config.cs @@ -22,10 +22,10 @@ public class Config //public required Decimal CheapPrice { get; set; } //public required Decimal HighPrice { get; set; } public required Double MinSoc { get; set; } - public required Double GridSetPoint { get; set; } - public required Double MaximumDischargingCurrent { get; set; } - public required Double MaximumChargingCurrent { get; set; } - public required OperatingPriority OperatingPriority { get; set; } + public required Double GridSetPoint { get; set; } + public required Double MaximumDischargingCurrent { get; set; } + public required Double MaximumChargingCurrent { get; set; } + public required OperatingPriority OperatingPriority { get; set; } public required Int16 BatteriesCount { get; set; } public required Int16 ClusterNumber { get; set; } public required Int16 PvNumber { get; set; } @@ -34,7 +34,18 @@ public class Config public required DateTime StopTimeChargeandDischargeDayandTime { get; set; } public required Single TimeChargeandDischargePower { get; set; } - public required Boolean ControlPermission { get; set; } + public required Boolean ControlPermission { get; set; } + + // Dynamic Pricing (under GridPriority) — strings for demo; engine parses when needed. + public String? DynamicPricingMode { get; set; } + public String? NetworkProvider { get; set; } + public String? CurrentPrice { get; set; } + public String? PriceToSell { get; set; } + public String? PriceToBuy { get; set; } + public String? TimeToSellFrom { get; set; } + public String? TimeToSellTo { get; set; } + public String? TimeToBuyFrom { get; set; } + public String? TimeToBuyTo { get; set; } public required S3Config? S3 { get; set; } diff --git a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx index f29b37388..677a9ee71 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx @@ -715,6 +715,11 @@ export type ConfigurationValues = { currentPrice?: string; priceToSell?: string; priceToBuy?: string; + // TOU time windows stored as "HH:mm" strings + timeToSellFrom?: string; + timeToSellTo?: string; + timeToBuyFrom?: string; + timeToBuyTo?: string; }; // // export interface Pv { diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx index 14b88be79..463c09ba5 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx @@ -28,14 +28,13 @@ import { UserContext } from '../../../contexts/userContext'; import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; import { I_Installation } from 'src/interfaces/InstallationTypes'; import { - INSTALLATION_PRESETS, buildSodistoreProPreset, getPresetsForDevice, PresetConfig } from '../Information/installationSetupUtils'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import {DateTimePicker } from '@mui/x-date-pickers'; +import { DateTimePicker, TimePicker } from '@mui/x-date-pickers'; import dayjs from 'dayjs'; import Switch from '@mui/material/Switch'; import FormControlLabel from '@mui/material/FormControlLabel'; @@ -70,7 +69,7 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { const DynamicPricingOptions = ['Disabled', 'SpotPrice', 'Tou'] as const; const dynamicPricingLabelKey: Record = { Disabled: 'dynamicPricingOff', - SpotPrice: 'dynamicPricingOn', + SpotPrice: 'dynamicPricingSpotPrice', Tou: 'dynamicPricingTou', }; @@ -140,6 +139,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { currentPrice: (props.values.Config as any).CurrentPrice?.toString() ?? '', priceToSell: (props.values.Config as any).PriceToSell?.toString() ?? '', priceToBuy: (props.values.Config as any).PriceToBuy?.toString() ?? '', + timeToSellFrom: (props.values.Config as any).TimeToSellFrom ?? '', + timeToSellTo: (props.values.Config as any).TimeToSellTo ?? '', + timeToBuyFrom: (props.values.Config as any).TimeToBuyFrom ?? '', + timeToBuyTo: (props.values.Config as any).TimeToBuyTo ?? '', }; }; @@ -286,6 +289,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { currentPrice: formValues.currentPrice, priceToSell: formValues.priceToSell, priceToBuy: formValues.priceToBuy, + timeToSellFrom: formValues.timeToSellFrom, + timeToSellTo: formValues.timeToSellTo, + timeToBuyFrom: formValues.timeToBuyFrom, + timeToBuyTo: formValues.timeToBuyTo, }; setLoading(true); @@ -692,9 +699,9 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { )} - {/* --- Sinexcel + GridPriority: Dynamic Pricing --- */} + {/* --- Sinexcel + LoadPriority: Dynamic Pricing --- */} {device === 4 && - OperatingPriorityOptions[formValues.operatingPriority] === 'GridPriority' && ( + OperatingPriorityOptions[formValues.operatingPriority] === 'LoadPriority' && ( <> @@ -725,6 +732,59 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { + {formValues.dynamicPricingMode === 'Tou' && ( + <> + {(() => { + const renderTimeField = ( + labelId: string, + key: 'timeToSellFrom' | 'timeToSellTo' | 'timeToBuyFrom' | 'timeToBuyTo' + ) => { + const raw = formValues[key]; + const parsed = raw ? dayjs(raw, 'HH:mm') : null; + return ( + + { + setFormDirty(true); + setFormValues((prev) => ({ + ...prev, + [key]: newValue ? newValue.format('HH:mm') : '', + })); + }} + renderInput={(params) => ( + + )} + /> + + ); + }; + + return ( + <> + + + +
+ {renderTimeField('timeFrom', 'timeToSellFrom')} + {renderTimeField('timeTo', 'timeToSellTo')} +
+ + + + +
+ {renderTimeField('timeFrom', 'timeToBuyFrom')} + {renderTimeField('timeTo', 'timeToBuyTo')} +
+ + ); + })()} + + )} + {formValues.dynamicPricingMode === 'SpotPrice' && ( <>
diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index a9689066b..d97f43373 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -532,11 +532,15 @@ "dynamicPricing": "Dynamische Preisgestaltung", "dynamicPricingMode": "Modus der dynamischen Preisgestaltung", "dynamicPricingOff": "Aus", - "dynamicPricingOn": "Ein", + "dynamicPricingSpotPrice": "Spot-Preis", "dynamicPricingTou": "TOU", "currentPrice": "Aktueller Preis", "priceToSell": "Verkaufspreis", "priceToBuy": "Kaufpreis", + "timeToSell": "Verkaufszeit", + "timeToBuy": "Kaufzeit", + "timeFrom": "Von", + "timeTo": "Bis", "networkProviderSetOnInformationTab": "Im Informations-Tab festlegen", "tourLanguageTitle": "Sprache", "tourLanguageContent": "Wählen Sie Ihre bevorzugte Sprache. Die Oberfläche unterstützt Englisch, Deutsch, Französisch und Italienisch.", diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index 834b50004..94e1f46e5 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -280,11 +280,15 @@ "dynamicPricing": "Dynamic Pricing", "dynamicPricingMode": "Dynamic Pricing Mode", "dynamicPricingOff": "Off", - "dynamicPricingOn": "On", + "dynamicPricingSpotPrice": "Spot Price", "dynamicPricingTou": "TOU", "currentPrice": "Current Price", "priceToSell": "Price to Sell", "priceToBuy": "Price to Buy", + "timeToSell": "Time to Sell", + "timeToBuy": "Time to Buy", + "timeFrom": "From", + "timeTo": "To", "networkProviderSetOnInformationTab": "Set on Information tab", "tourLanguageTitle": "Language", "tourLanguageContent": "Choose your preferred language. The interface supports English, German, French, and Italian.", diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index 7cc7c60b7..03c010f40 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -532,11 +532,15 @@ "dynamicPricing": "Tarification dynamique", "dynamicPricingMode": "Mode de tarification dynamique", "dynamicPricingOff": "Désactivé", - "dynamicPricingOn": "Activé", + "dynamicPricingSpotPrice": "Prix spot", "dynamicPricingTou": "TOU", "currentPrice": "Prix actuel", "priceToSell": "Prix de vente", "priceToBuy": "Prix d'achat", + "timeToSell": "Heure de vente", + "timeToBuy": "Heure d'achat", + "timeFrom": "De", + "timeTo": "À", "networkProviderSetOnInformationTab": "À définir dans l'onglet Informations", "tourLanguageTitle": "Langue", "tourLanguageContent": "Choisissez votre langue préférée. L'interface est disponible en anglais, allemand, français et italien.", diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index 61c120d16..823d98e4e 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -532,11 +532,15 @@ "dynamicPricing": "Prezzi dinamici", "dynamicPricingMode": "Modalità prezzi dinamici", "dynamicPricingOff": "Off", - "dynamicPricingOn": "On", + "dynamicPricingSpotPrice": "Prezzo spot", "dynamicPricingTou": "TOU", "currentPrice": "Prezzo attuale", "priceToSell": "Prezzo di vendita", "priceToBuy": "Prezzo di acquisto", + "timeToSell": "Orario di vendita", + "timeToBuy": "Orario di acquisto", + "timeFrom": "Da", + "timeTo": "A", "networkProviderSetOnInformationTab": "Imposta nella scheda Informazioni", "tourLanguageTitle": "Lingua", "tourLanguageContent": "Scegli la tua lingua preferita. L'interfaccia supporta inglese, tedesco, francese e italiano.",