diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 04eb6ddd3..1fd23700e 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -8,6 +8,7 @@ using InnovEnergy.App.Backend.Relations; using InnovEnergy.App.Backend.Websockets; using InnovEnergy.Lib.Utils; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; namespace InnovEnergy.App.Backend; @@ -554,6 +555,7 @@ public class Controller : ControllerBase if (!mail_success) { Db.GetSession(authToken).Delete(newUser); + return StatusCode(500, "Welcome email failed to send"); } return mail_success ? newUser.HidePassword():Unauthorized(); @@ -935,11 +937,19 @@ public class Controller : ControllerBase [HttpPost(nameof(EditInstallationConfig))] - public async Task>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken) + public async Task>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,int product,Token authToken) { var session = Db.GetSession(authToken); + + string configString = product switch + { + 0 => config.GetConfigurationSalimax(), // Salimax + 3 => config.GetConfigurationSodistoreMax(), // SodiStoreMax + 2 => config.GetConfigurationSodistoreHome(), // SodiStoreHome + _ => config.GetConfigurationString() // fallback + }; - Console.WriteLine("CONFIG IS " + config.GetConfigurationString()); + Console.WriteLine("CONFIG IS " + configString); //Send configuration changes var success = await session.SendInstallationConfig(installationId, config); @@ -947,17 +957,23 @@ public class Controller : ControllerBase // Record configuration change if (success) { - // Create a new UserAction object + // // Update Configuration colum in Installation table + // var installation = Db.GetInstallationById(installationId); + // + // installation.Configuration = JsonConvert.SerializeObject(config); + // + // if (!installation.Apply(Db.Update)) + // return StatusCode(500, "Failed to update installation configuration in database"); + var action = new UserAction { InstallationId = installationId, - Timestamp = DateTime.Now, - Description = config.GetConfigurationString() + Timestamp = DateTime.UtcNow, + Description = configString }; - Console.WriteLine(action.Description); - + var actionSuccess = await session.InsertUserAction(action); - return actionSuccess?Ok():Unauthorized(); + return actionSuccess ? Ok() : StatusCode(500, "Failed to record Configuration changes in History of Action"); } return Unauthorized(); diff --git a/csharp/App/Backend/DataTypes/Configuration.cs b/csharp/App/Backend/DataTypes/Configuration.cs index bc6048fd2..8c000a138 100644 --- a/csharp/App/Backend/DataTypes/Configuration.cs +++ b/csharp/App/Backend/DataTypes/Configuration.cs @@ -2,28 +2,52 @@ namespace InnovEnergy.App.Backend.DataTypes; public class Configuration { - public Double MinimumSoC { get; set; } - public Double GridSetPoint { get; set; } - public CalibrationChargeType CalibrationChargeState { get; set; } - 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 double? MinimumSoC { get; set; } + public double? GridSetPoint { get; set; } + public CalibrationChargeType? CalibrationChargeState { get; set; } + public DateTime? CalibrationChargeDate { get; set; } + public CalibrationChargeType? CalibrationDischargeState { get; set; } + public DateTime? CalibrationDischargeDate { get; set; } + + public double? MaximumDischargingCurrent { get; set; } + public double? MaximumChargingCurrent { get; set; } + public double? OperatingPriority { get; set; } + public double? BatteriesCount { get; set; } + public double? ClusterNumber { get; set; } + public double? PvNumber { get; set; } + public bool ControlPermission { get; set; } + public double? TimeChargeandDischargePower { get; set; } + public DateTime? StartTimeChargeandDischargeDayandTime { get; set; } + public DateTime? StopTimeChargeandDischargeDayandTime { get; set; } public String GetConfigurationString() { return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " + - $"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}" + - $"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}" + - $"BatteriesCount: {BatteriesCount}"; + $"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}, " + + $"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " + + $"BatteriesCount: {BatteriesCount}, ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, GrowattControlPermission:{ControlPermission}, "+ + $"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}"; } + + public string GetConfigurationSalimax() + { + return + $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}"; + } + + public string GetConfigurationSodistoreMax() + { + return + $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}"; + } + + public string GetConfigurationSodistoreHome() + { + return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " + + $"BatteriesCount: {BatteriesCount}, ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, GrowattControlPermission:{ControlPermission}, "+ + $"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}"; + } } public enum CalibrationChargeType diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 9710d81b3..fe9cb24b1 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -45,8 +45,12 @@ public class Installation : TreeNode public string SerialNumber { get; set; } = ""; public string InverterSN { get; set; } = ""; public string DataloggerSN { get; set; } = ""; + public int BatteryClusterNumber { get; set; } = 0; + public int BatteryNumber { get; set; } = 0; + public string BatterySerialNumbers { get; set; } = ""; [Ignore] public String OrderNumbers { get; set; } public String VrmLink { get; set; } = ""; + public string Configuration { get; set; } = ""; } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index f83e33cac..7d21eee60 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -296,8 +296,10 @@ public static partial class Db await user.SendNewUserWelcomeMessage(); return true; } - catch + catch (Exception ex) { + Console.WriteLine($"Welcome email failed for {user.Email}"); + Console.WriteLine(ex.ToString()); return false; } } diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index 91bcfe994..6c48122ab 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -1,26 +1,27 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collector", "App/Collector/Collector.csproj", "{E3A5F3A3-72A5-47CC-85C6-2D8E962A0EC1}" +# +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collector", "App\Collector\Collector.csproj", "{E3A5F3A3-72A5-47CC-85C6-2D8E962A0EC1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenVpnCertificatesServer", "App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj", "{CF4834CB-91B7-4172-AC13-ECDA8613CD17}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenVpnCertificatesServer", "App\OpenVpnCertificatesServer\OpenVpnCertificatesServer.csproj", "{CF4834CB-91B7-4172-AC13-ECDA8613CD17}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteSupportConsole", "App/RemoteSupportConsole/RemoteSupportConsole.csproj", "{B1268C03-66EB-4486-8BFC-B439225D9D54}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteSupportConsole", "App\RemoteSupportConsole\RemoteSupportConsole.csproj", "{B1268C03-66EB-4486-8BFC-B439225D9D54}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysTools", "Lib/SysTools/SysTools.csproj", "{4A67D79F-F0C9-4BBC-9601-D5948E6C05D3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysTools", "Lib\SysTools\SysTools.csproj", "{4A67D79F-F0C9-4BBC-9601-D5948E6C05D3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "Lib/WebServer/WebServer.csproj", "{B2627B9F-41DF-44F7-A0D1-CA71FF4A007A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "Lib\WebServer\WebServer.csproj", "{B2627B9F-41DF-44F7-A0D1-CA71FF4A007A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeterDriver", "App/EmuMeterDriver/EmuMeterDriver.csproj", "{F65F33B0-3522-4008-8D1E-47EF8E4C7AC7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeterDriver", "App\EmuMeterDriver\EmuMeterDriver.csproj", "{F65F33B0-3522-4008-8D1E-47EF8E4C7AC7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BmsTunnel", "App/BmsTunnel/BmsTunnel.csproj", "{40B45363-BE34-420B-8F87-775EE6EE3513}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BmsTunnel", "App\BmsTunnel\BmsTunnel.csproj", "{40B45363-BE34-420B-8F87-775EE6EE3513}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{145597B4-3E30-45E6-9F72-4DD43194539A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaliMax", "App/SaliMax/SaliMax.csproj", "{25073794-D859-4824-9984-194C7E928496}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaliMax", "App\SaliMax\SaliMax.csproj", "{25073794-D859-4824-9984-194C7E928496}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusApi", "Lib/StatusApi/StatusApi.csproj", "{9D17E78C-8A70-43DB-A619-DC12D20D023D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusApi", "Lib\StatusApi\StatusApi.csproj", "{9D17E78C-8A70-43DB-A619-DC12D20D023D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devices", "Devices", "{4931A385-24DC-4E78-BFF4-356F8D6D5183}" EndProject @@ -30,35 +31,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Victron", "Victron", "{BD8C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Trumpf", "Trumpf", "{DDDBEFD0-5DEA-4C7C-A9F2-FDB4636CF092}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertAc", "Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj", "{1F4B445E-459E-44CD-813E-6D725EBB81E8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertAc", "Lib\Devices\Trumpf\TruConvertAc\TruConvertAc.csproj", "{1F4B445E-459E-44CD-813E-6D725EBB81E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertDc", "Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj", "{F6F29829-C31A-4994-A698-E441BEA631C6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertDc", "Lib\Devices\Trumpf\TruConvertDc\TruConvertDc.csproj", "{F6F29829-C31A-4994-A698-E441BEA631C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBus", "Lib/Protocols/DBus/DBus.csproj", "{8C3C620A-087D-4DD6-B493-A47FC643F8DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBus", "Lib\Protocols\DBus\DBus.csproj", "{8C3C620A-087D-4DD6-B493-A47FC643F8DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modbus", "Lib/Protocols/Modbus/Modbus.csproj", "{E4AE6A33-0DEB-48EB-9D57-C0C7C63FC267}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modbus", "Lib\Protocols\Modbus\Modbus.csproj", "{E4AE6A33-0DEB-48EB-9D57-C0C7C63FC267}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VeDBus", "Lib/Victron/VeDBus/VeDBus.csproj", "{50B26E29-1B99-4D07-BCA5-359CD550BBAA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VeDBus", "Lib\Victron\VeDBus\VeDBus.csproj", "{50B26E29-1B99-4D07-BCA5-359CD550BBAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VictronVRM", "Lib/Victron/VictronVRM/VictronVRM.csproj", "{FE05DF69-B5C7-4C2E-8FB9-7776441A7622}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VictronVRM", "Lib\Victron\VictronVRM\VictronVRM.csproj", "{FE05DF69-B5C7-4C2E-8FB9-7776441A7622}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ampt", "Lib/Devices/AMPT/Ampt.csproj", "{77AF3A64-2878-4150-BCD0-F16530783165}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ampt", "Lib\Devices\AMPT\Ampt.csproj", "{77AF3A64-2878-4150-BCD0-F16530783165}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery48TL", "Lib/Devices/Battery48TL/Battery48TL.csproj", "{1C3F443A-B339-4B08-80E6-8A84817FFEC9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery48TL", "Lib\Devices\Battery48TL\Battery48TL.csproj", "{1C3F443A-B339-4B08-80E6-8A84817FFEC9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeter", "Lib/Devices/EmuMeter/EmuMeter.csproj", "{152A4168-F612-493C-BBEA-8EB26E6E2D34}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeter", "Lib\Devices\EmuMeter\EmuMeter.csproj", "{152A4168-F612-493C-BBEA-8EB26E6E2D34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "Lib/Utils/Utils.csproj", "{89A3E29C-4E57-47FE-A800-12AC68418264}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "Lib\Utils\Utils.csproj", "{89A3E29C-4E57-47FE-A800-12AC68418264}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6060", "Lib/Devices/Adam6060/Adam6060.csproj", "{4AFDB799-E6A4-4DCA-8B6D-8C0F98398461}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6060", "Lib\Devices\Adam6060\Adam6060.csproj", "{4AFDB799-E6A4-4DCA-8B6D-8C0F98398461}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Channels", "Lib/Channels/Channels.csproj", "{AF7E8DCA-8D48-498E-AB3D-208061B244DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Channels", "Lib\Channels\Channels.csproj", "{AF7E8DCA-8D48-498E-AB3D-208061B244DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend", "App/Backend/Backend.csproj", "{A56F58C2-B265-435B-A985-53B4D6F49B1A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend", "App\Backend\Backend.csproj", "{A56F58C2-B265-435B-A985-53B4D6F49B1A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Units", "Lib/Units/Units.csproj", "{C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Units", "Lib\Units\Units.csproj", "{C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemControl", "Lib/Devices/Trumpf/SystemControl/SystemControl.csproj", "{B816BB44-E97E-4E02-B80A-BEDB5B923A96}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemControl", "Lib\Devices\Trumpf\SystemControl\SystemControl.csproj", "{B816BB44-E97E-4E02-B80A-BEDB5B923A96}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{AED84693-C389-44C9-B2C0-ACB560189CF2}" ProjectSection(SolutionItems) = preProject @@ -87,8 +88,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doepke", "Lib\Devices\Doepk EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amax5070", "Lib\Devices\Amax5070\Amax5070.csproj", "{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SofarInverter", "Lib\Devices\SofarInverter\SofarInverter.csproj", "{2C7F3D89-402B-43CB-988E-8D2D853BEF44}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchneiderMeterDriver", "App\SchneiderMeterDriver\SchneiderMeterDriver.csproj", "{2E7E7657-3A53-4B62-8927-FE9A082B81DE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery250UP", "Lib\Devices\Battery250UP\Battery250UP.csproj", "{F2967439-A590-4D5E-9208-1B973C83AA1C}" @@ -109,7 +108,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SinexcelCommunication", "Ap EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sinexcel 12K TL", "Sinexcel 12K TL\Sinexcel 12K TL.csproj", "{28C16B43-E498-40DB-8ACF-D7F2A88A402F}" EndProject - Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -248,10 +246,6 @@ Global {09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Release|Any CPU.Build.0 = Release|Any CPU - {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Release|Any CPU.Build.0 = Release|Any CPU {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -331,7 +325,6 @@ Global {73B97F6E-2BDC-40DA-84A7-7FB0264387D6} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {C2B14CD4-1BCA-4933-96D9-92F40EACD2B9} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} {09E280B0-43D3-47BD-AF15-CF4FCDD24FE6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} - {2C7F3D89-402B-43CB-988E-8D2D853BEF44} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} {2E7E7657-3A53-4B62-8927-FE9A082B81DE} = {145597B4-3E30-45E6-9F72-4DD43194539A} {F2967439-A590-4D5E-9208-1B973C83AA1C} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} {1045AC74-D4D8-4581-AAE3-575DF26060E6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} diff --git a/csharp/Lib/Mailer/Mailer.cs b/csharp/Lib/Mailer/Mailer.cs index 74781ce14..87563cef0 100644 --- a/csharp/Lib/Mailer/Mailer.cs +++ b/csharp/Lib/Mailer/Mailer.cs @@ -1,35 +1,52 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using MailKit.Net.Smtp; +using MailKit.Security; using MimeKit; namespace InnovEnergy.Lib.Mailer; - public static class Mailer { [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public static async Task Send(String recipientName, String recipientEmailAddress, String subject, String body) + public static async Task Send(string recipientName, string recipientEmailAddress, string subject, string body) { var config = await ReadMailerConfig(); + + Console.WriteLine("=============== SMTP CONFIG LOADED =============="); + Console.WriteLine($"Config full path: {Path.GetFullPath(MailerConfig.DefaultFile)}"); + Console.WriteLine($"SMTP host: {config!.SmtpServerUrl}"); + Console.WriteLine($"SMTP port: {config.SmtpPort}"); + Console.WriteLine($"SMTP username: {config.SmtpUsername}"); + Console.WriteLine($"Sender: {config.SenderName} <{config.SenderAddress}>"); + Console.WriteLine("=================================================="); + var from = new MailboxAddress(config!.SenderName, config.SenderAddress); var to = new MailboxAddress(recipientName, recipientEmailAddress); - - var msg = new MimeMessage + + var msg = new MimeMessage { From = { from }, To = { to }, Subject = subject, - Body = new TextPart { Text = body } + Body = new TextPart("plain") { Text = body } }; using var smtp = new SmtpClient(); - - await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, false); - await smtp.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword); - await smtp.SendAsync(msg); - await smtp.DisconnectAsync(true); + + try + { + await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, SecureSocketOptions.StartTls); + await smtp.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword); + await smtp.SendAsync(msg); + await smtp.DisconnectAsync(true); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + throw; // keep while testing + } } [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.DeserializeAsync(Stream, JsonSerializerOptions, CancellationToken)")] diff --git a/firmware/2025-02-24.json b/firmware/2025-02-24.json deleted file mode 100644 index 1309e125b..000000000 --- a/firmware/2025-02-24.json +++ /dev/null @@ -1 +0,0 @@ -UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA== \ No newline at end of file diff --git a/firmware/2025-02-25.json b/firmware/2025-02-25.json deleted file mode 100644 index 1309e125b..000000000 --- a/firmware/2025-02-25.json +++ /dev/null @@ -1 +0,0 @@ -UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA== \ No newline at end of file diff --git a/firmware/Cerbo_with_Biwatt/rc.local b/firmware/Cerbo_with_Biwatt/rc.local new file mode 100755 index 000000000..9f8368cd9 --- /dev/null +++ b/firmware/Cerbo_with_Biwatt/rc.local @@ -0,0 +1,40 @@ +#!/bin/sh -e +mount -o remount,rw / + +# Redirect all output to a log file +exec > /data/log/rc.local.log 2>&1 +# Set root password non-interactively +echo "Setting root password..." +echo "root:salidomo" | /usr/sbin/chpasswd + +# Check the exit status of chpasswd +if [ $? -eq 0 ]; then + echo "Root password set successfully." +else + echo "Failed to set root password." +fi + +# Remove existing timezone link (if it exists) +if [ -L /etc/localtime ]; then + echo "Removing existing timezone link..." + rm /etc/localtime +fi + +# Create a symbolic link to the desired timezone +echo "Creating symbolic link to timezone..." +ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime + +# Set VPN service symlink +echo "Creating symbolic link to VPN service..." +find /data/innovenergy/openvpn -type f -exec chmod 777 {} \; +vpn_service_path="/data/innovenergy/openvpn/service" +vpn_symlink_path="/service/openvpn" +ln -s "$vpn_service_path" "$vpn_symlink_path" + +# # Set EmuMeter service symlink +# echo "Creating symbolic link to EmuMeter service..." +# EmuMeter_service_path="/data/EmuMeter/service" +# EmuMeter_symlink_path="/service/EmuMeter" +# ln -s "$EmuMeter_service_path" "$EmuMeter_symlink_path" + +exit 0 diff --git a/firmware/EmuMeter/EmuMeter b/firmware/EmuMeter/EmuMeter new file mode 100755 index 000000000..92a9148e1 Binary files /dev/null and b/firmware/EmuMeter/EmuMeter differ diff --git a/firmware/EmuMeter/service/log/run b/firmware/EmuMeter/service/log/run new file mode 100755 index 000000000..62e2f8679 --- /dev/null +++ b/firmware/EmuMeter/service/log/run @@ -0,0 +1,3 @@ +#!/bin/sh +exec 2>&1 +exec multilog t s25000 n4 /var/log/EmuMeter diff --git a/firmware/EmuMeter/service/log/supervise/lock b/firmware/EmuMeter/service/log/supervise/lock new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/EmuMeter/service/log/supervise/status b/firmware/EmuMeter/service/log/supervise/status new file mode 100644 index 000000000..d3c7dab3a Binary files /dev/null and b/firmware/EmuMeter/service/log/supervise/status differ diff --git a/firmware/EmuMeter/service/run b/firmware/EmuMeter/service/run new file mode 100755 index 000000000..a077f4205 --- /dev/null +++ b/firmware/EmuMeter/service/run @@ -0,0 +1,3 @@ +#!/bin/sh +exec 2>&1 +exec softlimit -d 100000000 -s 1000000 -a 100000000 /data/EmuMeter/EmuMeter diff --git a/firmware/EmuMeter/service/supervise/lock b/firmware/EmuMeter/service/supervise/lock new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/EmuMeter/service/supervise/status b/firmware/EmuMeter/service/supervise/status new file mode 100644 index 000000000..f4097cffa Binary files /dev/null and b/firmware/EmuMeter/service/supervise/status differ diff --git a/firmware/SodistoreHome/SodistoreHomeSetup/install_release.sh b/firmware/SodistoreHome/SodistoreHomeSetup/install_release.sh new file mode 100755 index 000000000..683a0a4cf --- /dev/null +++ b/firmware/SodistoreHome/SodistoreHomeSetup/install_release.sh @@ -0,0 +1,131 @@ +#!/bin/bash +set -euo pipefail + +# === Settings === +SERVER="91.92.155.224" +REMOTE_PATH="/home/ubuntu/Sodistorehome/release-package.tar.gz" + +TARGET_DIR="$HOME/SodiStoreHome" +SCRIPT_DIR="$TARGET_DIR/scripts" +MODPOLL_DIR="$TARGET_DIR/ModpollDir" + +# Change this if your config file has a different name (e.g., config.json, config.yaml, etc.) +CONFIG_FILE="config.json" + +SERVICE_NAME="SodiStoreHome.service" +SERVICE_PATH="/etc/systemd/system/$SERVICE_NAME" + +echo "📦 Downloading release package from server..." +mkdir -p "$TARGET_DIR" +scp -i ~/.ssh/InnovEnergy.pem.priv "ubuntu@$SERVER:$REMOTE_PATH" "$TARGET_DIR/release-package.tar.gz" + +echo "📂 Extracting package..." +tar -xzf "$TARGET_DIR/release-package.tar.gz" -C "$TARGET_DIR" + +echo "📁 Ensuring directories exist..." +mkdir -p "$SCRIPT_DIR" "$MODPOLL_DIR" \ + "$TARGET_DIR/csvFile" "$TARGET_DIR/FailedUploads" "$TARGET_DIR/JsonLogDirectory" + +echo "📥 Placing files (no bin/; put under $TARGET_DIR)" +# Stuff that used to go to bin/ now goes directly under $TARGET_DIR +if [ -f "$TARGET_DIR/libSystem.IO.Ports.Native.so" ]; then + echo " - libSystem.IO.Ports.Native.so -> $TARGET_DIR/" + # already in place after extract; nothing to do +fi + +if [ -f "$TARGET_DIR/GrowattCommunication" ]; then + echo " - Setting executable on GrowattCommunication" + chmod +x "$TARGET_DIR/GrowattCommunication" +fi + +# modpoll stays in ModpollDir +if [ -f "$TARGET_DIR/modpoll" ]; then + echo " - Moving modpoll -> $MODPOLL_DIR/" + mv -f "$TARGET_DIR/modpoll" "$MODPOLL_DIR/" + chmod +x "$MODPOLL_DIR/modpoll" +fi + +# Move Python files to scripts/ (unchanged behavior) +shopt -s nullglob +PY_FILES=( "$TARGET_DIR"/*.py ) +if [ ${#PY_FILES[@]} -gt 0 ]; then + echo " - Moving Python files -> $SCRIPT_DIR/" + mv -f "${PY_FILES[@]}" "$SCRIPT_DIR/" +fi +shopt -u nullglob + +# 2) Place systemd service under /etc/systemd/system/ +if [ -f "$TARGET_DIR/$SERVICE_NAME" ]; then + echo "🛠️ Installing systemd service to $SERVICE_PATH (requires sudo)..." + sudo install -m 0644 "$TARGET_DIR/$SERVICE_NAME" "$SERVICE_PATH" + echo " - Reloading systemd daemon..." + sudo systemctl daemon-reload + # Enable but don't start automatically; comment out if you don't want this: + if ! systemctl is-enabled --quiet "$SERVICE_NAME"; then + echo " - Enabling service $SERVICE_NAME" + sudo systemctl enable "$SERVICE_NAME" + fi +else + echo "⚠️ WARNING: $SERVICE_NAME not found in $TARGET_DIR. Skipping service install." +fi + +# 3) Place the config file under $TARGET_DIR +if [ -f "$TARGET_DIR/$CONFIG_FILE" ]; then + echo "📝 Config file already in $TARGET_DIR: $CONFIG_FILE" +elif [ -f "$TARGET_DIR/config" ]; then + echo " - Moving 'config' -> $TARGET_DIR/$CONFIG_FILE" + mv -f "$TARGET_DIR/config" "$TARGET_DIR/$CONFIG_FILE" +else + echo "⚠️ WARNING: Config file '$CONFIG_FILE' not found in extracted package." + echo " If the filename differs, set CONFIG_FILE accordingly at the top of this script." +fi + +# 4) csvFile/, FailedUploads/, JsonLogDirectory/ were created earlier. + +# 5) Place log, start, stop, restart under $TARGET_DIR and make them executable +for f in log start stop restart; do + if [ -f "$TARGET_DIR/$f" ]; then + echo " - Ensuring $f is in $TARGET_DIR and executable" + chmod +x "$TARGET_DIR/$f" + elif [ -f "$TARGET_DIR/$f.sh" ]; then + echo " - Moving $f.sh -> $TARGET_DIR/$f and making executable" + mv -f "$TARGET_DIR/$f.sh" "$TARGET_DIR/$f" + chmod +x "$TARGET_DIR/$f" + else + echo "⚠️ NOTE: '$f' script not found in extracted package." + fi +done + +# --- ModbusTCP Integration --- +echo "Installing systemd Modbus TCP service..." +MODBUS_TARGET_DIR="$TARGET_DIR/ModbusTCP" +sudo cp "$MODBUS_TARGET_DIR/ModbusTCP.service" /etc/systemd/system/ + +echo "Preparing Python virtual environment..." +cd "$TARGET_DIR" +python3 -m venv venv +source venv/bin/activate +pip install watchdog +pip install pymodbus==2.5.3 +pip install pyinstaller +deactivate + +echo "Granting permission to bind port 502..." +sudo setcap 'cap_net_bind_service=+ep' "$MODBUS_TARGET_DIR/dist/modbus_tcp_server" + +# echo "Enabling ModbusTCP systemd service..." +sudo systemctl daemon-reload +sudo systemctl enable --now ModbusTCP.service + +# Remove existing timezone link (if it exists) +if [ -L /etc/localtime ]; then + echo "Removing existing timezone link..." + sudo rm /etc/localtime +fi + +# Create a symbolic link to the desired timezone +echo "Creating symbolic link to timezone..." +sudo ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime + +# Starting the SodistoreHome service +sudo systemctl restart SodiStoreHome.service diff --git a/firmware/SodistoreHome/SodistoreHomeSetup/setup_OS_Customization_and_VPN.sh b/firmware/SodistoreHome/SodistoreHomeSetup/setup_OS_Customization_and_VPN.sh new file mode 100644 index 000000000..44f3c50ea --- /dev/null +++ b/firmware/SodistoreHome/SodistoreHomeSetup/setup_OS_Customization_and_VPN.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# Raspberry Pi OS Customization Setup Script +set -e + +############################### Change Raspberry Pi Hostname ################################## + +if [ -z "$1" ]; then + echo "Usage: $0 DEVICE_NUMBER" + exit 1 +fi + +DEVICE_NUM=$(printf "%04d" "$1") +NEW_HOSTNAME="sodistorehome${DEVICE_NUM}" +NEW_USER="inesco" +NEW_PASS="Sodistore0918425" +SSID="inesco" +WIFI_PASS="inesco25" + +echo "Creating user $NEW_USER..." +if id "$NEW_USER" &>/dev/null; then + echo "User $NEW_USER already exists" +else + sudo useradd -m -s /bin/bash "$NEW_USER" + echo "${NEW_USER}:${NEW_PASS}" | sudo chpasswd + sudo usermod -aG sudo "$NEW_USER" +fi + +echo "Setting static hostname to $NEW_HOSTNAME..." +sudo hostnamectl --static set-hostname "$NEW_HOSTNAME" + +# Update /etc/hosts for hostname resolution +if grep -q "^127.0.1.1" /etc/hosts; then + sudo sed -i "s/^127.0.1.1.*/127.0.1.1\t$NEW_HOSTNAME/" /etc/hosts +else + echo "127.0.1.1 $NEW_HOSTNAME" | sudo tee -a /etc/hosts +fi + +echo "Disabling default 'pi' user (if exists)..." +if id pi &>/dev/null; then + sudo passwd -l pi +else + echo "User 'pi' does not exist, skipping disabling." +fi + +echo "Configuring Wi-Fi..." +sudo tee /etc/wpa_supplicant/wpa_supplicant.conf > /dev/null < { + const index = i + 1; // Battery1, Battery2, ... - const sortedBatteryView = - props.values != null && - props.values?.AcDcGrowatt?.BatteriesRecords?.Batteries - ? Object.entries(props.values.AcDcGrowatt.BatteriesRecords.Batteries) - .map(([BatteryId, battery]) => { - return { BatteryId, battery }; // Here we return an object with the id and device - }) - .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId)) - : []; + return { + BatteryId: String(index), + battery: { + Voltage: inverter[`Battery${index}Voltage`], + Current: inverter[`Battery${index}Current`], + Power: inverter[`Battery${index}Power`], + Soc: inverter[`Battery${index}Soc`], + Soh: inverter[`Battery${index}Soh`], + } + }; + }) + : []; const [loading, setLoading] = useState(sortedBatteryView.length == 0); const handleMainStatsButton = () => { @@ -157,7 +167,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) { {/**/} {/* */} {/* Current SoC SoH - Daily Charge Energy - Daily Discharge Energy + {/*Daily Charge Energy*/} + {/*Daily Discharge Energy*/} @@ -261,12 +271,14 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) { sx={{ width: '13%', textAlign: 'center', - - backgroundColor: '#32CD32', - color: 'black' + backgroundColor: + battery.Voltage < 32 || battery.Voltage > 63 + ? '#FF033E' + : '#32CD32', + color: 'inherit' }} > - {battery.Voltage + ' ' + 'V'} + {battery.Voltage + ' V'} 20 + ? '#32CD32' + : battery.Soc >= 10 + ? '#ffbf00' + : '#FF033E', color: 'inherit' }} > @@ -292,32 +309,37 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) { sx={{ width: '8%', textAlign: 'center', - backgroundColor: '#32CD32', + backgroundColor: + battery.Soh ==100 + ? '#32CD32' + : battery.Soc >= 90 + ? '#ffbf00' + : '#FF033E', color: 'inherit' }} > {battery.Soh + ' %'} - - {battery.DailyChargeEnergy + ' Wh'} - - - {battery.DailyDischargeEnergy + ' Wh'} - + {/**/} + {/* {battery.DailyChargeEnergy + ' Wh'}*/} + {/**/} + {/**/} + {/* {battery.DailyDischargeEnergy + ' Wh'}*/} + {/**/} ))} diff --git a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx index 26184efc4..eee0f1849 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx @@ -169,7 +169,7 @@ function Configuration(props: ConfigurationProps) { setLoading(true); const res = await axiosConfig .post( - `/EditInstallationConfig?installationId=${props.id}`, + `/EditInstallationConfig?installationId=${props.id}&product=${product}`, configurationToSend ) .catch((err) => { @@ -404,32 +404,32 @@ function Configuration(props: ConfigurationProps) { } })} /> - {product === 3 && ( - ({ - flex: 2, - fontWeight: 'bold', - borderRadius: 2, - textTransform: 'none', - color: - activeTab === 'discharge' - ? 'white' - : theme.palette.text.primary, - bgcolor: - activeTab === 'discharge' - ? theme.palette.primary.main - : 'transparent', - '&:hover': { - bgcolor: - activeTab === 'discharge' - ? theme.palette.primary.dark - : '#eee' - } - })} - /> - )} + {/*{product === 3 && (*/} + {/* ({*/} + {/* flex: 2,*/} + {/* fontWeight: 'bold',*/} + {/* borderRadius: 2,*/} + {/* textTransform: 'none',*/} + {/* color:*/} + {/* activeTab === 'discharge'*/} + {/* ? 'white'*/} + {/* : theme.palette.text.primary,*/} + {/* bgcolor:*/} + {/* activeTab === 'discharge'*/} + {/* ? theme.palette.primary.main*/} + {/* : 'transparent',*/} + {/* '&:hover': {*/} + {/* bgcolor:*/} + {/* activeTab === 'discharge'*/} + {/* ? theme.palette.primary.dark*/} + {/* : '#eee'*/} + {/* }*/} + {/* })}*/} + {/* />*/} + {/*)}*/} diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx index 7dbb2e0e5..ce2f2afa3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx @@ -337,6 +337,22 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { /> +
+ + } + name="batteryClusterNumber" + value={formValues.batteryClusterNumber} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+
} diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx index 0ea92d9ba..a2b2898be 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx @@ -18,7 +18,7 @@ function InstallationSearch(props: installationSearchProps) { return ( { 'PvOnDc.Dc.Power', 'DcDc.Dc.Link.Power', 'LoadOnDc.Power', - 'Battery.Dc.Power' + 'Battery.Dc.Power', + 'AcDcGrowatt.MeterPower', + 'AcDcGrowatt.TotalPvPower', + 'AcDcGrowatt.BatteriesRecords.Power', + 'AcDcGrowatt.BatteriesRecords.TotalChargeEnergy', + 'AcDcGrowatt.ConsumptionPower', + 'InverterRecord.GridPower', + 'InverterRecord.PvPower', + 'InverterRecord.Battery1Power', + 'InverterRecord.Battery2Power', + 'InverterRecord.ConsumptionPower' ]; // Helper function to safely get a value from a nested path diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index db2a2ddb0..e8ac17615 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -361,7 +361,7 @@ function SalidomoInstallation(props: singleInstallationProps) { /> { routes.installation + `${installationID}` + '/' + - routes.batteryview, + routes.live, { replace: true } diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index ae74f26f5..d8bae8c68 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -25,6 +25,7 @@ import { fetchDataJson } from '../Installations/fetchData'; import { FetchResult } from '../../../dataCache/dataCache'; import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome'; import SodistoreHomeConfiguration from './SodistoreHomeConfiguration'; +import TopologySodistoreHome from '../Topology/TopologySodistoreHome'; interface singleInstallationProps { current_installation?: I_Installation; @@ -297,6 +298,37 @@ function SodioHomeInstallation(props: singleInstallationProps) { {props.current_installation.name}
+ + {currentTab == 'live' && values && ( +
+ + + + + {values.Config.OperatingPriority} + +
+ )} +
- // + } /> } @@ -474,6 +507,7 @@ function SodioHomeInstallation(props: singleInstallationProps) { } /> diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx index 9f78d2235..1b3601874 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx @@ -83,7 +83,7 @@ function InstallationSearch(props: installationSearchProps) { return ( { @@ -81,20 +114,53 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { }); }; +// Add time validation function + const validateTimeOnly = () => { + if (formValues.startTimeChargeandDischargeDayandTime && + formValues.stopTimeChargeandDischargeDayandTime) { + const startHours = formValues.startTimeChargeandDischargeDayandTime.getHours(); + const startMinutes = formValues.startTimeChargeandDischargeDayandTime.getMinutes(); + const stopHours = formValues.stopTimeChargeandDischargeDayandTime.getHours(); + const stopMinutes = formValues.stopTimeChargeandDischargeDayandTime.getMinutes(); + + const startTimeInMinutes = startHours * 60 + startMinutes; + const stopTimeInMinutes = stopHours * 60 + stopMinutes; + + if (startTimeInMinutes >= stopTimeInMinutes) { + setDateSelectionError('Stop time must be later than start time'); + setErrorDateModalOpen(true); + return false; + } + } + return true; + }; + const handleSubmit = async (e) => { - // console.log('asked for', dayjs(formValues.calibrationChargeDate)); + if (!validateTimeOnly()) { + return; + } const configurationToSend: Partial = { minimumSoC: formValues.minimumSoC, maximumDischargingCurrent: formValues.maximumDischargingCurrent, maximumChargingCurrent: formValues.maximumChargingCurrent, operatingPriority: formValues.operatingPriority, - batteriesCount:formValues.batteriesCount + batteriesCount:formValues.batteriesCount, + clusterNumber:formValues.clusterNumber, + PvNumber:formValues.PvNumber, + timeChargeandDischargePower: formValues.timeChargeandDischargePower, + startTimeChargeandDischargeDayandTime: formValues.startTimeChargeandDischargeDayandTime + ? new Date(formValues.startTimeChargeandDischargeDayandTime.getTime() - formValues.startTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000) + : null, + stopTimeChargeandDischargeDayandTime: formValues.stopTimeChargeandDischargeDayandTime + ? new Date(formValues.stopTimeChargeandDischargeDayandTime.getTime() - formValues.stopTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000) + : null, + controlPermission:formValues.controlPermission }; setLoading(true); const res = await axiosConfig .post( - `/EditInstallationConfig?installationId=${props.id}`, + `/EditInstallationConfig?installationId=${props.id}&product=${product}`, configurationToSend ) .catch((err) => { @@ -113,34 +179,40 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { 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; + if (name === 'minimumSoC') { + const numValue = parseFloat(value); - default: - break; + // invalid characters or not a number + if (/[^0-9.]/.test(value) || isNaN(numValue)) { + SetErrorForField(name, 'Invalid number format'); + } else { + const minsocRanges = { + 3: { min: 10, max: 30 }, + 4: { min: 5, max: 100 }, + }; + + const { min, max } = minsocRanges[device] || { min: 10, max: 30 }; + + if (numValue < min || numValue > max) { + SetErrorForField(name, `Value should be between ${min}-${max}%`); + } else { + // ✅ valid → clear error + SetErrorForField(name, ''); + } + } } - setFormValues({ - ...formValues, + setFormValues(prev => ({ + ...prev, + [name]: value, + })); + }; + + const handleTimeChargeDischargeChange = (name: string, value: any) => { + setFormValues((prev) => ({ + ...prev, [name]: value - }); + })); }; const handleOkOnErrorDateModal = () => { @@ -209,6 +281,34 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { autoComplete="off" > <> + {device === 3 && ( +
+ + setFormValues((prev) => ({ + ...prev, + controlPermission: e.target.checked, + })) + } + sx={{ transform: "scale(1.4)", marginLeft: "15px" }} + /> + } + + label={ + + } + /> +
+ )} +
-
- +
+ + } + name="clusterNumber" + value={formValues.clusterNumber} + onChange={handleChange} + fullWidth /> - } +
+ +
+ + } + name="PvNumber" + value={formValues.PvNumber} + onChange={handleChange} + fullWidth + /> +
+ + )} + + +
+ {/**/} + {/* }*/} + {/* name="minimumSoC"*/} + {/* value={formValues.minimumSoC}*/} + {/* onChange={handleChange}*/} + {/* helperText={*/} + {/* errors.minimumSoC ? (*/} + {/* */} + {/* Value should be between {device === 4 ? '5–100' : '10–30'}%*/} + {/* */} + {/* ) : (*/} + {/* ''*/} + {/* )*/} + {/* }*/} + {/* fullWidth*/} + {/*/>*/} + - Value should be between 0-100% - - ) : ( - '' - ) - } + error={Boolean(errors.minimumSoC)} + helperText={errors.minimumSoC} fullWidth /> +
@@ -307,6 +452,91 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
+ {/* --- Sinexcel + TimeChargeDischarge --- */} + {device === 4 && + OperatingPriorityOptions[formValues.operatingPriority] === + 'TimeChargeDischarge' && ( + <> + {/* Power input*/} +
+ + handleTimeChargeDischargeChange(e.target.name, e.target.value) + } + helperText="Enter a positive or negative power value" + fullWidth + /> +
+ + {/* Start DateTime */} +
+ + + setFormValues((prev) => ({ + ...prev, + startTimeChargeandDischargeDayandTime: newValue + ? newValue.toDate() + : null, + })) + } + renderInput={(params) => ( + + )} + /> + +
+ + {/* Stop DateTime */} +
+ + + setFormValues((prev) => ({ + ...prev, + stopTimeChargeandDischargeDayandTime: newValue + ? newValue.toDate() + : null, + })) + } + renderInput={(params) => ( + + )} + /> + +
+ + )} +
+
+ + } + name="batteryClusterNumber" + value={formValues.batteryClusterNumber} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+
- // }, + { + value: 'live', + label: + }, { value: 'batteryview', label: ( @@ -155,10 +155,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { } ] : [ - // { - // value: 'live', - // label: - // }, + { + value: 'live', + label: + }, // { // value: 'overview', // label: @@ -186,10 +186,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { value: 'tree', icon: }, - // { - // value: 'live', - // label: - // }, + { + value: 'live', + label: + }, { value: 'batteryview', label: ( @@ -256,10 +256,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { value: 'tree', icon: }, - // { - // value: 'live', - // label: - // }, + { + value: 'live', + label: + }, { value: 'overview', label: diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/TopologySodistoreHome.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/TopologySodistoreHome.tsx new file mode 100644 index 000000000..ef980b1a8 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Topology/TopologySodistoreHome.tsx @@ -0,0 +1,290 @@ +import React, { useContext, useState } from 'react'; +import { + CircularProgress, + Container, + Grid, + Switch, + Typography +} from '@mui/material'; +import TopologyColumn from './topologyColumn'; +import { + getAmount, + getHighestConnectionValue, + JSONRecordData +} from '../Log/graph.util'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; + +interface TopologySodistoreHomeProps { + values: JSONRecordData; + connected: boolean; + loading: boolean; + batteryClusterNumber:number; +} + +function TopologySodistoreHome(props: TopologySodistoreHomeProps) { + if (props.values === null && props.connected == true) { + return null; + } + const highestConnectionValue = + props.values != null ? getHighestConnectionValue(props.values) : 0; + + const { product, setProduct } = useContext(ProductIdContext); + + const [showValues, setShowValues] = useState(false); + + const handleSwitch = () => () => { + setShowValues(!showValues); + }; + + const isMobile = window.innerWidth <= 1490; + + const totalBatteryPower: number = Number( + props.values && props.values.InverterRecord + ? Array.from({ length: props.batteryClusterNumber }).reduce( + (sum: number, _, index) => { + const i = index + 1; + + const rawPower = + props.values.InverterRecord[`Battery${i}Power`] as unknown; + + const power = Number(rawPower) || 0; + + return sum + power; + }, + 0 + ) + : 0 + ); + + const pvPower = + props.values?.InverterRecord?.PvPower ?? + ['PvPower1', 'PvPower2', 'PvPower3', 'PvPower4'] + .map((key) => props.values?.InverterRecord?.[key] ?? 0) + .reduce((sum, val) => sum + val, 0); + + return ( + + + {!props.connected && !props.loading && ( + + + + Unable to communicate with the installation + + + Please wait or refresh the page + + + )} + + {props.connected && ( + <> + + {/*
*/} + {/* */} + {/* Display Values*/} + {/* */} + {/* */} + {/*
*/} +
+ + + + {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/} + + {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/} + {Array.from({ length: props.batteryClusterNumber }).map((_, index) => { + const i = index + 1; // battery cluster index starting from 1 + + return ( + + ); + })} + + + )} +
+
+ ); +} + +export default TopologySodistoreHome; diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx index 20524eb38..0c7adf1e9 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx @@ -39,8 +39,8 @@ function formatPower(value, unit) { const roundedValue = value.toFixed(1); - //Filter all values less than 100 Watts - if (magnitude === 0 && value < 100 && unit === 'W') { + //Filter all values less than 1 Watts(why?) + if (magnitude === 0 && value < 1 && unit === 'W') { //console.log('DROP THIS VALUE ' + value); return 0; } @@ -70,11 +70,15 @@ function TopologyBox(props: TopologyBoxProps) { props.title === 'Battery' ? '165px' : props.title === 'AC Loads' || - props.title === 'DC Loads' || - props.title === 'Pv Inverter' || - props.title === 'Pv DC-DC' - ? '100px' - : '150px', + props.title === 'DC Loads' || + props.title === 'Pv Inverter' || + props.title === 'Pv DC-DC' || + props.title === 'PV' || + props.title === 'Loads' + ? '100px' + : props.title === 'Inverter' + ? '150px' + : '150px', backgroundColor: !props.data ? 'darkgrey' : props.title === 'Grid Bus' || @@ -134,6 +138,17 @@ function TopologyBox(props: TopologyBoxProps) { /> )} + {props.data && props.title === 'Inverter' && ( + + )} + {props.data && props.title === 'DC Link' && ( - {(props.title === 'Pv Inverter' || + {(props.title === 'PV' || + props.title === 'Pv Inverter' || props.title === 'Pv DC-DC') && ( )} - {(props.title === 'AC Loads' || props.title === 'DC Loads') && ( + {(props.title === 'AC Loads' || props.title === 'DC Loads' ||props.title === 'Loads') && (