From 4420f7373b4ce34f8057f08a1194ae02453bf358 Mon Sep 17 00:00:00 2001 From: Noe Date: Thu, 25 Sep 2025 15:06:48 +0200 Subject: [PATCH 1/5] Added Configuration for SodistoreHome devices in the frontend --- .../BatteryView/BatteryViewSodioHome.tsx | 1 - .../Configuration/Configuration.tsx | 4 +- .../src/content/dashboards/Log/graph.util.tsx | 12 + .../SodiohomeInstallations/Installation.tsx | 58 ++- .../SodistoreHomeConfiguration.tsx | 386 ++++++++++++++++++ .../SodiohomeInstallations/index.tsx | 22 +- 6 files changed, 475 insertions(+), 8 deletions(-) create mode 100644 typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSodioHome.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSodioHome.tsx index 8793d5437..494da9f42 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSodioHome.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSodioHome.tsx @@ -44,7 +44,6 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) { .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId)) : []; - console.log('battery view', sortedBatteryView); const [loading, setLoading] = useState(sortedBatteryView.length == 0); const handleMainStatsButton = () => { navigate(routes.mainstats); diff --git a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx index 229c07552..26184efc4 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx @@ -93,7 +93,7 @@ function Configuration(props: ConfigurationProps) { const { currentUser, setUser } = context; const { product, setProduct } = useContext(ProductIdContext); - const [formValues, setFormValues] = useState({ + const [formValues, setFormValues] = useState>({ minimumSoC: props.values.Config.MinSoc, gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000, calibrationChargeState: CalibrationChargeOptionsController.indexOf( @@ -151,7 +151,7 @@ function Configuration(props: ConfigurationProps) { return; } else { // console.log('asked for', dayjs(formValues.calibrationChargeDate)); - const configurationToSend: ConfigurationValues = { + const configurationToSend: Partial = { minimumSoC: formValues.minimumSoC, gridSetPoint: formValues.gridSetPoint, calibrationChargeState: formValues.calibrationChargeState, 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 acf98244f..75c15bcdf 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx @@ -323,6 +323,12 @@ export interface JSONRecordData { TruConvertDcIp: { DeviceState: string }; TsRelaysIp: { DeviceState: string }; }; + + //For SodistoerHome + MaximumChargingCurrent: number; + MaximumDischargingCurrent: number; + OperatingPriority: string; + BatteriesCount: number; }; DcDc: { @@ -602,6 +608,12 @@ export type ConfigurationValues = { calibrationChargeDate: Date | null; calibrationDischargeState: number; calibrationDischargeDate: Date | null; + + //For sodistoreHome + maximumDischargingCurrent: number; + maximumChargingCurrent: number; + operatingPriority: number; + batteriesCount: number; }; // // export interface Pv { diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index 9fa696a25..5202d5e7b 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -24,6 +24,7 @@ import { TimeSpan, UnixTime } from '../../../dataCache/time'; import { fetchDataJson } from '../Installations/fetchData'; import { FetchResult } from '../../../dataCache/dataCache'; import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome'; +import SodistoreHomeConfiguration from './SodistoreHomeConfiguration'; interface singleInstallationProps { current_installation?: I_Installation; @@ -179,6 +180,43 @@ function SodioHomeInstallation(props: singleInstallationProps) { } }; + const fetchDataForOneTime = async () => { + var timeperiodToSearch = 200; + let res; + let timestampToFetch; + + for (var i = timeperiodToSearch; i > 0; i -= 2) { + timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i)); + try { + res = await fetchDataJson(timestampToFetch, s3Credentials, false); + if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) { + break; + } + } catch (err) { + console.error('Error fetching data:', err); + return false; + } + } + + if (i <= 0) { + setConnected(false); + setLoading(false); + return false; + } + setConnected(true); + setLoading(false); + + const timestamp = Object.keys(res)[Object.keys(res).length - 1]; + setValues(res[timestamp]); + // setValues( + // extractValues({ + // time: UnixTime.fromTicks(parseInt(timestamp, 10)), + // value: res[timestamp] + // }) + // ); + return true; + }; + useEffect(() => { let path = location.split('/'); setCurrentTab(path[path.length - 1]); @@ -212,10 +250,10 @@ function SodioHomeInstallation(props: singleInstallationProps) { } } } - //Fetch only one time in configuration tab - // if (currentTab == 'configuration') { - // fetchDataForOneTime(); - // } + // Fetch only one time in configuration tab + if (currentTab == 'configuration') { + fetchDataForOneTime(); + } return () => { continueFetching.current = false; @@ -429,6 +467,18 @@ function SodioHomeInstallation(props: singleInstallationProps) { /> )} + {currentUser.userType == UserType.admin && ( + + } + /> + )} + {currentUser.userType == UserType.admin && ( { + setErrors((prevErrors) => ({ + ...prevErrors, + [field_name]: state + })); + }; + const theme = useTheme(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + const [updated, setUpdated] = useState(false); + const [dateSelectionError, setDateSelectionError] = useState(''); + const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false); + const context = useContext(UserContext); + const { currentUser, setUser } = context; + const { product, setProduct } = useContext(ProductIdContext); + + const [formValues, setFormValues] = useState>({ + minimumSoC: props.values.Config.MinSoc, + maximumDischargingCurrent: props.values.Config.MaximumChargingCurrent, + maximumChargingCurrent: props.values.Config.MaximumDischargingCurrent, + operatingPriority: OperatingPriorityOptions.indexOf( + props.values.Config.OperatingPriority + ), + batteriesCount: props.values.Config.BatteriesCount + }); + + const handleOperatingPriorityChange = (event) => { + setFormValues({ + ...formValues, + ['operatingPriority']: OperatingPriorityOptions.indexOf( + event.target.value + ) + }); + }; + + const handleSubmit = async (e) => { + // console.log('asked for', dayjs(formValues.calibrationChargeDate)); + const configurationToSend: Partial = { + minimumSoC: formValues.minimumSoC, + maximumDischargingCurrent: formValues.maximumDischargingCurrent, + maximumChargingCurrent: formValues.maximumChargingCurrent, + operatingPriority: formValues.operatingPriority + }; + + setLoading(true); + const res = await axiosConfig + .post( + `/EditInstallationConfig?installationId=${props.id}`, + configurationToSend + ) + .catch((err) => { + if (err.response) { + setError(true); + setLoading(false); + } + }); + + if (res) { + setUpdated(true); + setLoading(false); + } + }; + + const handleChange = (e) => { + const { name, value } = e.target; + + switch (name) { + case 'minimumSoC': + if ( + /[^0-9.]/.test(value) || + isNaN(parseFloat(value)) || + parseFloat(value) > 100 + ) { + SetErrorForField(name, true); + } else { + SetErrorForField(name, false); + } + break; + case 'gridSetPoint': + if (/[^0-9.]/.test(value) || isNaN(parseFloat(value))) { + SetErrorForField(name, true); + } else { + SetErrorForField(name, false); + } + break; + + default: + break; + } + + setFormValues({ + ...formValues, + [name]: value + }); + }; + + const handleOkOnErrorDateModal = () => { + setErrorDateModalOpen(false); + }; + + return ( + + + {isErrorDateModalOpen && ( + {}}> + + + {dateSelectionError} + + + + + + )} + + + + + <> +
+ + } + name="batteriesCount" + value={formValues.batteriesCount} + onChange={handleChange} + fullWidth + /> +
+ +
+ + } + name="minimumSoC" + value={formValues.minimumSoC} + onChange={handleChange} + helperText={ + errors.minimumSoC ? ( + + Value should be between 0-100% + + ) : ( + '' + ) + } + fullWidth + /> +
+ +
+ + } + name="maximumChargingCurrent" + value={formValues.maximumChargingCurrent} + onChange={handleChange} + fullWidth + /> +
+ +
+ + } + name="maximumDischargingCurrent" + value={formValues.maximumDischargingCurrent} + onChange={handleChange} + fullWidth + /> +
+ +
+ + + + + + +
+ + +
+ + {loading && ( + + )} + + {updated && ( + + Successfully applied configuration file + setUpdated(false)} + sx={{ marginLeft: '4px' }} + > + + + + )} + {error && ( + + An error has occurred + setError(false)} + sx={{ marginLeft: '4px' }} + > + + + + )} +
+
+
+
+
+
+ ); +} + +export default SodistoreHomeConfiguration; diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx index 8dbf8cf4c..c5d69ba82 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx @@ -29,7 +29,8 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { 'manage', 'overview', 'log', - 'history' + 'history', + 'configuration' ]; const [currentTab, setCurrentTab] = useState(undefined); @@ -133,6 +134,16 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { ) }, + + { + value: 'configuration', + label: ( + + ) + }, { value: 'history', label: ( @@ -213,6 +224,15 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { ) }, + { + value: 'configuration', + label: ( + + ) + }, { value: 'history', label: ( From 3795287124b1563b7baf4123a87a22599151d1ad Mon Sep 17 00:00:00 2001 From: Noe Date: Thu, 25 Sep 2025 15:07:28 +0200 Subject: [PATCH 2/5] Fixed bug in status update in Backend, update controller to support remote configuration for SodistoreHome installations --- csharp/App/Backend/Controller.cs | 2 +- csharp/App/Backend/DataTypes/Configuration.cs | 13 ++++++++++- .../App/Backend/DataTypes/Methods/ExoCmd.cs | 23 ++----------------- csharp/App/Backend/Program.cs | 2 +- .../App/Backend/Websockets/RabbitMQManager.cs | 7 ++++++ .../Backend/Websockets/WebsockerManager.cs | 6 ++--- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 30bf96887..04eb6ddd3 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -941,7 +941,7 @@ public class Controller : ControllerBase Console.WriteLine("CONFIG IS " + config.GetConfigurationString()); - // Send configuration changes + //Send configuration changes var success = await session.SendInstallationConfig(installationId, config); // Record configuration change diff --git a/csharp/App/Backend/DataTypes/Configuration.cs b/csharp/App/Backend/DataTypes/Configuration.cs index 852fde727..bc6048fd2 100644 --- a/csharp/App/Backend/DataTypes/Configuration.cs +++ b/csharp/App/Backend/DataTypes/Configuration.cs @@ -8,10 +8,21 @@ public class Configuration public DateTime CalibrationChargeDate { get; set; } public CalibrationChargeType CalibrationDischargeState { get; set; } public DateTime CalibrationDischargeDate { get; set; } + + //For sodistoreHome installations + + public Double MaximumDischargingCurrent { get; set; } + public Double MaximumChargingCurrent { get; set; } + public Double OperatingPriority { get; set; } + public Double BatteriesCount { get; set; } + public String GetConfigurationString() { return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " + - $"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}"; + $"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}" + + $"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}" + + $"BatteriesCount: {BatteriesCount}"; + } } diff --git a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs index 2f0297ca8..a0173ada5 100644 --- a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs +++ b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs @@ -381,25 +381,6 @@ public static class ExoCmd public static async Task SendConfig(this Installation installation, Configuration config) { - // This looks hacky but here we grab the vpn-Ip of the installation by its installation Name (e.g. Salimax0001) - // From the vpn server (here salidomo, but we use the vpn home ip for future-proofing) - // using var client = new HttpClient(); - // var webRequest = client.GetAsync("10.2.0.1/vpnstatus.txt"); - // var text = webRequest.ToString(); - // var lines = text!.Split(new [] { Environment.NewLine }, StringSplitOptions.None); - // var vpnIp = lines.First(l => l.Contains(installation.InstallationName)).Split(",")[1]; - // - // // Writing the config to a file and then sending that file with rsync sounds inefficient - // // We should find a better solution... - // // TODO The VPN server should do this not the backend!!! - // await File.WriteAllTextAsync("./config.json", config); - // var result = await Cli.Wrap("rsync") - // .WithArguments("./config.json") - // .AppendArgument($@"root@{vpnIp}:/salimax") - // .ExecuteAsync(); - - // return result.ExitCode == 200; - var maxRetransmissions = 4; UdpClient udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 2000; @@ -415,8 +396,8 @@ public static class ExoCmd Console.WriteLine(config.GetConfigurationString()); - //Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}"); - Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC); + Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}"); + //Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC); IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installation.VpnIp), port); diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index c32572eff..94f20f312 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -89,7 +89,7 @@ public static class Program private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo { - Title = "Innesco Backend API", + Title = "Inesco Backend API", Version = "v1" }; diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index 0c94190aa..3f6afe52d 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -148,6 +148,8 @@ public static class RabbitMqManager Int32 prevStatus; + + //This installation id does not exist in our in-memory data structure, add it. if (!WebsocketManager.InstallationConnections.ContainsKey(installationId)) { @@ -167,6 +169,11 @@ public static class RabbitMqManager WebsocketManager.InstallationConnections[installationId].Timestamp = DateTime.Now; } + if (installationId == 795) + { + Console.WriteLine("RECEIVED A HEARTBIT FROM prototype, time is "+ WebsocketManager.InstallationConnections[installationId].Timestamp); + } + installation.Status = receivedStatusMessage.Status; installation.Apply(Db.Update); diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index b87c8ffdd..57ffc7bea 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -22,12 +22,12 @@ public static class WebsocketManager Console.WriteLine("Monitoring installation table..."); foreach (var installationConnection in InstallationConnections) { - Console.WriteLine("installationConnection ID is " + installationConnection.Key + "latest timestamp is" +installationConnection.Value.Timestamp + "product is "+ installationConnection.Value.Product - + "and time diff is "+ (DateTime.Now - installationConnection.Value.Timestamp)); + Console.WriteLine("installationConnection ID is " + installationConnection.Key + ", latest timestamp is" +installationConnection.Value.Timestamp + ", product is "+ installationConnection.Value.Product + + ", and time diff is "+ (DateTime.Now - installationConnection.Value.Timestamp)); if ((installationConnection.Value.Product == (int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) || (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) || - (installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) || + (installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(4)) || (installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ) { From c182e773dd26edca91f7094ab6c43fb4e5374718 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Tue, 14 Oct 2025 16:02:57 +0200 Subject: [PATCH 3/5] added device type, inverterSN and dataloggerSN entries for sodistorehome in frontend --- .../Information/InformationSalidomo.tsx | 2 +- .../Information/InformationSodistoreHome.tsx | 494 ++++++++++++++++++ .../SodiohomeInstallations/Installation.tsx | 6 +- .../SodistorehomeInstallationForm.tsx | 321 ++++++++++++ .../content/dashboards/Tree/Information.tsx | 10 +- .../src/interfaces/InstallationTypes.tsx | 2 + 6 files changed, 830 insertions(+), 5 deletions(-) create mode 100644 typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx create mode 100644 typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx index bc77973c4..4e1deb7bd 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx @@ -286,7 +286,7 @@ function InformationSalidomo(props: InformationSalidomoProps) { marginLeft: 1, marginTop: 1, marginBottom: 1, - width: 390 + width: 440 }} > { + const { name, value } = e.target; + setFormValues({ + ...formValues, + [name]: value + }); + }; + const handleSubmit = () => { + setLoading(true); + setError(false); + updateInstallation(formValues, props.type); + }; + + const handleDelete = () => { + setLoading(true); + setError(false); + setOpenModalDeleteInstallation(true); + }; + + const deleteInstallationModalHandle = () => { + setOpenModalDeleteInstallation(false); + deleteInstallation(formValues, props.type); + setLoading(false); + + navigate(routes.salidomo_installations + routes.list, { + replace: true + }); + }; + + const deleteInstallationModalHandleCancel = () => { + setOpenModalDeleteInstallation(false); + setLoading(false); + }; + + const areRequiredFieldsFilled = () => { + for (const field of requiredFields) { + if (!formValues[field]) { + return false; + } + } + return true; + }; + + return ( + <> + {openModalDeleteInstallation && ( + + + + Do you want to delete this installation? + + +
+ + +
+
+
+ )} + + + + + + +
+ + } + name="name" + value={formValues.name} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+
+ + } + name="region" + value={formValues.region} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.region === ''} + /> +
+
+ + } + name="location" + value={formValues.location} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.location === ''} + /> +
+
+ + } + name="country" + value={formValues.country} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.country === ''} + /> +
+ +
+ + } + name="vpnIp" + value={formValues.vpnIp} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ +
+ + + + + + +
+ +
+ + } + name="inverterSN" + value={formValues.inverterSN} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ +
+ + } + name="dataloggerSN" + value={formValues.dataloggerSN} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ +
+ + } + name="information" + value={formValues.information} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ + {currentUser.userType == UserType.admin && ( + <> +
+ +
+ +
+ +
+ +
+ +
+ + )} + +
+ + + {currentUser.userType == UserType.admin && ( + + )} + + {loading && ( + + )} + {error && ( + + + setError(false)} + sx={{ marginLeft: '4px' }} + > + + + + )} + {updated && ( + + + + setUpdated(false)} // Set error state to false on click + sx={{ marginLeft: '4px' }} + > + + + + )} +
+
+
+
+
+
+ + ); +} + +export default InformationSodistorehome; diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index 5202d5e7b..ae74f26f5 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -19,7 +19,7 @@ import HistoryOfActions from '../History/History'; import BuildIcon from '@mui/icons-material/Build'; import AccessContextProvider from '../../../contexts/AccessContextProvider'; import Access from '../ManageAccess/Access'; -import Information from '../Information/Information'; +import InformationSodistorehome from '../Information/InformationSodistoreHome'; import { TimeSpan, UnixTime } from '../../../dataCache/time'; import { fetchDataJson } from '../Installations/fetchData'; import { FetchResult } from '../../../dataCache/dataCache'; @@ -413,11 +413,11 @@ function SodioHomeInstallation(props: singleInstallationProps) { + > } /> diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx new file mode 100644 index 000000000..587cb0d88 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx @@ -0,0 +1,321 @@ +import React, { useContext, useState } from 'react'; +import { + Alert, + Box, + CircularProgress, + FormControl, + IconButton, + InputLabel, + MenuItem, + Modal, + Select, + TextField, + useTheme +} from '@mui/material'; +import Button from '@mui/material/Button'; +import { Close as CloseIcon } from '@mui/icons-material'; +import { I_Installation } from 'src/interfaces/InstallationTypes'; +import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'; +import { FormattedMessage } from 'react-intl'; + +interface SodistorehomeInstallationFormPros { + cancel: () => void; + submit: () => void; + parentid: number; +} + +function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) { + const theme = useTheme(); + const [open, setOpen] = useState(true); + const [formValues, setFormValues] = useState>({ + name: '', + region: '', + location: '', + country: '', + vpnIp: '', + }); + const requiredFields = ['name', 'location', 'country', 'vpnIp']; + + const DeviceTypes = [ + { id: 3, name: 'Growatt' }, + { id: 4, name: 'Sinexcel' } + ]; + const installationContext = useContext(InstallationsContext); + const { createInstallation, loading, setLoading, error, setError } = + installationContext; + + const handleChange = (e) => { + const { name, value } = e.target; + + setFormValues({ + ...formValues, + [name]: value + }); + }; + const handleSubmit = async (e) => { + setLoading(true); + formValues.parentId = props.parentid; + formValues.product = 2; + const responseData = await createInstallation(formValues); + props.submit(); + }; + const handleCancelSubmit = (e) => { + props.cancel(); + }; + + const areRequiredFieldsFilled = () => { + for (const field of requiredFields) { + if (!formValues[field]) { + return false; + } + } + return true; + }; + + const isMobile = window.innerWidth <= 1490; + + return ( + <> + {}} + aria-labelledby="error-modal" + aria-describedby="error-modal-description" + > + + +
+ + } + name="name" + value={formValues.name} + onChange={handleChange} + required + error={formValues.name === ''} + /> +
+
+ } + name="region" + value={formValues.region} + onChange={handleChange} + required + error={formValues.region === ''} + /> +
+
+ + } + name="location" + value={formValues.location} + onChange={handleChange} + required + error={formValues.location === ''} + /> +
+ +
+ + } + name="country" + value={formValues.country} + onChange={handleChange} + required + error={formValues.country === ''} + /> +
+ +
+ } + name="vpnIp" + value={formValues.vpnIp} + onChange={handleChange} + required + error={formValues.vpnIp === ''} + /> +
+ +
+ + + + + + +
+ +
+ + } + name="inverterSN" + value={formValues.inverterSN} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ +
+ + } + name="dataloggerSN" + value={formValues.dataloggerSN} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+ +
+ + } + name="information" + value={formValues.information} + onChange={handleChange} + /> +
+
+
+ + + + + {loading && ( + + )} + + {error && ( + + + setError(false)} + sx={{ marginLeft: '4px' }} + > + + + + )} +
+
+
+ + ); +} + +export default SodistorehomeInstallationForm; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx index 15f90c68b..f5601597e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx @@ -26,6 +26,7 @@ import { InstallationsContext } from '../../../contexts/InstallationsContextProv import { UserType } from '../../../interfaces/UserTypes'; import InstallationForm from '../Installations/installationForm'; import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm'; +import SodiostorehomeInstallationForm from '../SodiohomeInstallations/SodistorehomeInstallationForm'; interface TreeInformationProps { folder: I_Folder; @@ -322,7 +323,6 @@ function TreeInformation(props: TreeInformationProps) { )} {openModalInstallation && (product == 'Salimax' || - product == 'SodistoreHome' || product == 'SodistoreMax') && ( )} + {openModalInstallation && product == 'SodistoreHome' && ( + + )} + Date: Tue, 14 Oct 2025 16:11:31 +0200 Subject: [PATCH 4/5] added inverterSN and dataloggerSN for sodistorehome in backend --- csharp/App/Backend/DataTypes/Installation.cs | 2 ++ csharp/DataCollectorWebApp/Controller.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 6596d797a..9710d81b3 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -43,6 +43,8 @@ public class Installation : TreeNode public int Product { get; set; } = (int)ProductType.Salimax; public int Device { get; set; } = 0; public string SerialNumber { get; set; } = ""; + public string InverterSN { get; set; } = ""; + public string DataloggerSN { get; set; } = ""; [Ignore] public String OrderNumbers { get; set; } diff --git a/csharp/DataCollectorWebApp/Controller.cs b/csharp/DataCollectorWebApp/Controller.cs index e5b13d481..c58c739fe 100644 --- a/csharp/DataCollectorWebApp/Controller.cs +++ b/csharp/DataCollectorWebApp/Controller.cs @@ -40,7 +40,8 @@ public class Installation public int Product { get; set; } = 0; public int Device { get; set; } = 0; public string SerialNumber { get; set; } = ""; - + public string InverterSN { get; set; } = ""; + public string DataloggerSN { get; set; } = ""; public String OrderNumbers { get; set; } public String VrmLink { get; set; } = ""; } From 612554bc05812f02cd5c2a41a2587c7bc54de041 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Mon, 20 Oct 2025 11:30:26 +0200 Subject: [PATCH 5/5] added battery count return for sodistorehome on frontend --- .../SodiohomeInstallations/SodistoreHomeConfiguration.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx index d4581cf77..9f87dec58 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx @@ -87,7 +87,8 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { minimumSoC: formValues.minimumSoC, maximumDischargingCurrent: formValues.maximumDischargingCurrent, maximumChargingCurrent: formValues.maximumChargingCurrent, - operatingPriority: formValues.operatingPriority + operatingPriority: formValues.operatingPriority, + batteriesCount:formValues.batteriesCount }; setLoading(true);