Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
8a2be78c01
|
|
@ -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<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken)
|
||||
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,int product,Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
|
||||
Console.WriteLine("CONFIG IS " + config.GetConfigurationString());
|
||||
string configString = product switch
|
||||
{
|
||||
0 => config.GetConfigurationSalimax(), // Salimax
|
||||
3 => config.GetConfigurationSodistoreMax(), // SodiStoreMax
|
||||
2 => config.GetConfigurationSodistoreHome(), // SodiStoreHome
|
||||
_ => config.GetConfigurationString() // fallback
|
||||
};
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
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? 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
|
||||
|
|
|
|||
|
|
@ -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; } = "";
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,27 @@
|
|||
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 = "<Pending>")]
|
||||
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);
|
||||
|
||||
|
|
@ -21,16 +30,24 @@ public static class Mailer
|
|||
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);
|
||||
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<TValue>(Stream, JsonSerializerOptions, CancellationToken)")]
|
||||
private static async Task<MailerConfig?> ReadMailerConfig()
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA==
|
||||
|
|
@ -1 +0,0 @@
|
|||
UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA==
|
||||
|
|
@ -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
|
||||
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
exec 2>&1
|
||||
exec multilog t s25000 n4 /var/log/EmuMeter
|
||||
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
exec 2>&1
|
||||
exec softlimit -d 100000000 -s 1000000 -a 100000000 /data/EmuMeter/EmuMeter
|
||||
Binary file not shown.
|
|
@ -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
|
||||
|
|
@ -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 <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
country=US
|
||||
|
||||
network={
|
||||
ssid="$SSID"
|
||||
psk="$WIFI_PASS"
|
||||
key_mgmt=WPA-PSK
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
|
||||
echo "OS customization complete. Device: $NEW_HOSTNAME"
|
||||
|
||||
######################### Cpoy Privates Keys of Gitea Server ###########################
|
||||
|
||||
SSH_DIR="$HOME/.ssh"
|
||||
KEY_SRC="./key"
|
||||
|
||||
echo "Creating $SSH_DIR if it doesn't exist..."
|
||||
mkdir -p "$SSH_DIR"
|
||||
chmod 700 "$SSH_DIR"
|
||||
|
||||
echo "Copying keys from $KEY_SRC to $SSH_DIR..."
|
||||
cp -r "$KEY_SRC/"* "$SSH_DIR/"
|
||||
|
||||
echo "Setting permissions for files in $SSH_DIR..."
|
||||
chmod 600 "$SSH_DIR/"*
|
||||
|
||||
echo "Private key copying complete."
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 DEVICE_NUMBER"
|
||||
echo "Example: $0 3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
######################### Set Up VPN ###########################
|
||||
DEVICENAME="sodistorehome${DEVICE_NUM}"
|
||||
PASSWORD="MwBRbQb3QaX7l9XIaakq"
|
||||
|
||||
echo "Using device name: $NEW_HOSTNAME"
|
||||
|
||||
echo "Updating system..."
|
||||
sudo apt update
|
||||
sudo apt install -y openvpn bridge-utils
|
||||
|
||||
echo "Enabling SSH..."
|
||||
sudo systemctl start ssh
|
||||
sudo systemctl enable ssh
|
||||
|
||||
echo "Downloading VPN certificate..."
|
||||
wget "https://salidomo.innovenergy.ch/get_cert?name=${NEW_HOSTNAME}&pw=${PASSWORD}" -O openvpn.tar
|
||||
|
||||
echo "Moving certificate to /etc/openvpn/client/..."
|
||||
sudo mkdir -p /etc/openvpn/client
|
||||
sudo mv openvpn.tar /etc/openvpn/client/
|
||||
|
||||
echo "Extracting certificate..."
|
||||
sudo tar -xvf /etc/openvpn/client/openvpn.tar -C /etc/openvpn/client/
|
||||
|
||||
echo "Enabling VPN service..."
|
||||
sudo systemctl start openvpn-client@innovenergy.service
|
||||
sudo systemctl enable openvpn-client@innovenergy.service
|
||||
|
||||
echo "VPN setup complete. Checking interface:"
|
||||
ip -br addr
|
||||
|
||||
######################### Copy and run install_release.sh ###########################
|
||||
HOME_DIR="$HOME"
|
||||
echo "Copying install_release.sh to $HOME_DIR"
|
||||
cp install_release.sh "$HOME_DIR"
|
||||
bash "$HOME_DIR/install_release.sh"
|
||||
|
||||
######################### End ###########################
|
||||
echo "🎉🎉🎉 Okay :) We made it!!! 🌟💪 Great Job, Team! 🚀🔥🏆🙌✨💫🎯"
|
||||
echo " Please document the following VPN address:"
|
||||
cat /etc/openvpn/client/installation-ip
|
||||
|
|
@ -168,7 +168,7 @@ function BatteryView(props: BatteryViewProps) {
|
|||
<Grid container>
|
||||
<Routes>
|
||||
<Route
|
||||
path={routes.mainstats + '*'}
|
||||
path={routes.mainstats + '/*'}
|
||||
element={
|
||||
<MainStats
|
||||
s3Credentials={props.s3Credentials}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
|
|||
<Grid container>
|
||||
<Routes>
|
||||
<Route
|
||||
path={routes.mainstats + '*'}
|
||||
path={routes.mainstats + '/*'}
|
||||
element={
|
||||
<MainStatsSalidomo
|
||||
s3Credentials={props.s3Credentials}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||
import routes from '../../../Resources/routes.json';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
|
||||
interface BatteryViewSodioHomeProps {
|
||||
values: JSONRecordData;
|
||||
s3Credentials: I_S3Credentials;
|
||||
installationId: number;
|
||||
installation: I_Installation;
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -33,15 +35,23 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
|||
|
||||
const currentLocation = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const inverter = (props.values as any)?.InverterRecord;
|
||||
const batteryClusterNumber = props.installation.batteryClusterNumber;
|
||||
const sortedBatteryView = inverter
|
||||
? Array.from({ length: batteryClusterNumber }, (_, i) => {
|
||||
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
|
||||
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`],
|
||||
}
|
||||
};
|
||||
})
|
||||
.sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
|
||||
: [];
|
||||
|
||||
const [loading, setLoading] = useState(sortedBatteryView.length == 0);
|
||||
|
|
@ -157,7 +167,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
|||
{/*<Grid container>*/}
|
||||
{/* <Routes>*/}
|
||||
{/* <Route*/}
|
||||
{/* path={routes.mainstats + '*'}*/}
|
||||
{/* path={routes.mainstats + '/*'}*/}
|
||||
{/* element={*/}
|
||||
{/* <MainStats*/}
|
||||
{/* s3Credentials={props.s3Credentials}*/}
|
||||
|
|
@ -224,8 +234,8 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
|||
<TableCell align="center">Current</TableCell>
|
||||
<TableCell align="center">SoC</TableCell>
|
||||
<TableCell align="center">SoH</TableCell>
|
||||
<TableCell align="center">Daily Charge Energy</TableCell>
|
||||
<TableCell align="center">Daily Discharge Energy</TableCell>
|
||||
{/*<TableCell align="center">Daily Charge Energy</TableCell>*/}
|
||||
{/*<TableCell align="center">Daily Discharge Energy</TableCell>*/}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -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'}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
|
|
@ -282,7 +294,12 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
|||
sx={{
|
||||
width: '8%',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#32CD32',
|
||||
backgroundColor:
|
||||
battery.Soc > 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 + ' %'}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
width: '15%',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#32CD32',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
{battery.DailyChargeEnergy + ' Wh'}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
width: '15%',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#32CD32',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
{battery.DailyDischargeEnergy + ' Wh'}
|
||||
</TableCell>
|
||||
{/*<TableCell*/}
|
||||
{/* sx={{*/}
|
||||
{/* width: '15%',*/}
|
||||
{/* textAlign: 'center',*/}
|
||||
{/* backgroundColor: '#32CD32',*/}
|
||||
{/* color: 'inherit'*/}
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* {battery.DailyChargeEnergy + ' Wh'}*/}
|
||||
{/*</TableCell>*/}
|
||||
{/*<TableCell*/}
|
||||
{/* sx={{*/}
|
||||
{/* width: '15%',*/}
|
||||
{/* textAlign: 'center',*/}
|
||||
{/* backgroundColor: '#32CD32',*/}
|
||||
{/* color: 'inherit'*/}
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* {battery.DailyDischargeEnergy + ' Wh'}*/}
|
||||
{/*</TableCell>*/}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<Tab
|
||||
value="discharge"
|
||||
label="Calibration Discharge"
|
||||
sx={(theme) => ({
|
||||
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 && (*/}
|
||||
{/* <Tab*/}
|
||||
{/* value="discharge"*/}
|
||||
{/* label="Calibration Discharge"*/}
|
||||
{/* sx={(theme) => ({*/}
|
||||
{/* 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'*/}
|
||||
{/* }*/}
|
||||
{/* })}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
|
|
|
|||
|
|
@ -337,6 +337,22 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="batteryClusterNumber"
|
||||
defaultMessage="Battery Cluster Number"
|
||||
/>
|
||||
}
|
||||
name="batteryClusterNumber"
|
||||
value={formValues.batteryClusterNumber}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
@ -361,7 +377,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
name="s3writesecretkey"
|
||||
value={
|
||||
formValues.s3BucketId +
|
||||
'-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'
|
||||
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
||||
}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ function Installation(props: singleInstallationProps) {
|
|||
/>
|
||||
|
||||
<Route
|
||||
path={routes.batteryview + '*'}
|
||||
path={routes.batteryview + '/*'}
|
||||
element={
|
||||
<BatteryView
|
||||
values={values}
|
||||
|
|
@ -428,7 +428,7 @@ function Installation(props: singleInstallationProps) {
|
|||
></Route>
|
||||
|
||||
<Route
|
||||
path={routes.pvview + '*'}
|
||||
path={routes.pvview + '/*'}
|
||||
element={
|
||||
<PvView values={values} connected={connected}></PvView>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function InstallationSearch(props: installationSearchProps) {
|
|||
return (
|
||||
<Route
|
||||
key={installation.id}
|
||||
path={routes.installation + installation.id + '*'}
|
||||
path={routes.installation + installation.id + '/*'}
|
||||
element={
|
||||
<Installation
|
||||
key={installation.id}
|
||||
|
|
@ -92,7 +92,7 @@ function InstallationSearch(props: installationSearchProps) {
|
|||
// return (
|
||||
// <Route
|
||||
// key={installation.id}
|
||||
// path={routes.installation + installation.id + '*'}
|
||||
// path={routes.installation + installation.id + '/*'}
|
||||
// element={
|
||||
// <Installation
|
||||
// key={installation.id}
|
||||
|
|
|
|||
|
|
@ -329,6 +329,16 @@ export interface JSONRecordData {
|
|||
MaximumDischargingCurrent: number;
|
||||
OperatingPriority: string;
|
||||
BatteriesCount: number;
|
||||
ClusterNumber: number;
|
||||
PvNumber: number;
|
||||
|
||||
//For SodistoerHome-Growatt:
|
||||
ControlPermission:boolean;
|
||||
|
||||
//For SodistoerHome-Sinexcel: TimeChargeDischarge mode
|
||||
TimeChargeandDischargePower?: number;
|
||||
StartTimeChargeandDischargeDayandTime?: Date | null;
|
||||
StopTimeChargeandDischargeDayandTime?: Date | null;
|
||||
};
|
||||
|
||||
DcDc: {
|
||||
|
|
@ -429,6 +439,23 @@ export interface JSONRecordData {
|
|||
};
|
||||
};
|
||||
|
||||
// For SodistoreHome
|
||||
InverterRecord: {
|
||||
GridPower:number;
|
||||
Battery1Power:number;
|
||||
Battery1Soc:number;
|
||||
Battery1Soh:number;
|
||||
Battery1Voltage:number;
|
||||
Battery1Current:number;
|
||||
Battery2Power:number;
|
||||
Battery2Soc:number;
|
||||
Battery2Voltage:number;
|
||||
Battery2Current:number;
|
||||
Battery2Soh:number;
|
||||
PvPower:number;
|
||||
ConsumptionPower:number;
|
||||
};
|
||||
|
||||
AcDcGrowatt: {
|
||||
AcChargeEnable: number;
|
||||
ActivePowerPercentDerating: number;
|
||||
|
|
@ -457,6 +484,7 @@ export interface JSONRecordData {
|
|||
BatteryOperatingMode: string;
|
||||
BatteryType: number;
|
||||
ChargeCutoffSoc: number;
|
||||
ConsumptionPower:number;
|
||||
ControlPermession: number;
|
||||
DischargeCutoffSoc: number;
|
||||
EmsCommunicationFailureTime: number;
|
||||
|
|
@ -495,6 +523,7 @@ export interface JSONRecordData {
|
|||
SystemOperatingMode: string;
|
||||
TotalEnergyToGrid: number;
|
||||
TotalEnergyToUser: number;
|
||||
TotalPvPower: number;
|
||||
VppProtocolVerNumber: number;
|
||||
};
|
||||
|
||||
|
|
@ -614,6 +643,16 @@ export type ConfigurationValues = {
|
|||
maximumChargingCurrent: number;
|
||||
operatingPriority: number;
|
||||
batteriesCount: number;
|
||||
clusterNumber: number;
|
||||
PvNumber: number;
|
||||
|
||||
//For sodistoreHome-Growatt:
|
||||
controlPermission:boolean;
|
||||
|
||||
// For sodistoreHome-Sinexcel: TimeChargeDischarge mode
|
||||
timeChargeandDischargePower?: number;
|
||||
startTimeChargeandDischargeDayandTime?: Date | null;
|
||||
stopTimeChargeandDischargeDayandTime?: Date | null;
|
||||
};
|
||||
//
|
||||
// export interface Pv {
|
||||
|
|
@ -1040,7 +1079,17 @@ export const getHighestConnectionValue = (values: JSONRecordData) => {
|
|||
'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
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
|||
/>
|
||||
|
||||
<Route
|
||||
path={routes.batteryview + '*'}
|
||||
path={routes.batteryview + '/*'}
|
||||
element={
|
||||
<BatteryViewSalidomo
|
||||
values={values}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function InstallationSearch(props: installationSearchProps) {
|
|||
return (
|
||||
<Route
|
||||
key={installation.id}
|
||||
path={routes.installation + installation.id + '*'}
|
||||
path={routes.installation + installation.id + '/*'}
|
||||
element={
|
||||
<SalidomoInstallation
|
||||
key={installation.id}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
routes.installation +
|
||||
`${installationID}` +
|
||||
'/' +
|
||||
routes.batteryview,
|
||||
routes.live,
|
||||
{
|
||||
replace: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{currentTab == 'live' && values && (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
noWrap
|
||||
sx={{
|
||||
marginTop: '0px',
|
||||
marginBottom: '10px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="mode" defaultMessage="Mode:" />
|
||||
</Typography>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
color="orange"
|
||||
noWrap
|
||||
sx={{
|
||||
marginTop: '0px',
|
||||
marginBottom: '10px',
|
||||
marginLeft: '85px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
{values.Config.OperatingPriority}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
|
|
@ -434,22 +466,23 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
<Route
|
||||
path={routes.live}
|
||||
element={
|
||||
<div></div>
|
||||
// <Topology
|
||||
// values={values}
|
||||
// connected={connected}
|
||||
// loading={loading}
|
||||
// ></Topology>
|
||||
<TopologySodistoreHome
|
||||
values={values}
|
||||
connected={connected}
|
||||
loading={loading}
|
||||
batteryClusterNumber={props.current_installation.batteryClusterNumber}
|
||||
></TopologySodistoreHome>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={routes.batteryview + '*'}
|
||||
path={routes.batteryview + '/*'}
|
||||
element={
|
||||
<BatteryViewSodioHome
|
||||
values={values}
|
||||
s3Credentials={s3Credentials}
|
||||
installationId={props.current_installation.id}
|
||||
installation={props.current_installation}
|
||||
connected={connected}
|
||||
></BatteryViewSodioHome>
|
||||
}
|
||||
|
|
@ -474,6 +507,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
<SodistoreHomeConfiguration
|
||||
values={values}
|
||||
id={props.current_installation.id}
|
||||
installation={props.current_installation}
|
||||
></SodistoreHomeConfiguration>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function InstallationSearch(props: installationSearchProps) {
|
|||
return (
|
||||
<Route
|
||||
key={installation.id}
|
||||
path={routes.installation + installation.id + '*'}
|
||||
path={routes.installation + installation.id + '/*'}
|
||||
element={
|
||||
<SodioHomeInstallation
|
||||
key={installation.id}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,18 @@ import MenuItem from '@mui/material/MenuItem';
|
|||
import axiosConfig from '../../../Resources/axiosConfig';
|
||||
import { UserContext } from '../../../contexts/userContext';
|
||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import {DateTimePicker } from '@mui/x-date-pickers';
|
||||
import dayjs from 'dayjs';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
|
||||
interface SodistoreHomeConfigurationProps {
|
||||
values: JSONRecordData;
|
||||
id: number;
|
||||
installation: I_Installation;
|
||||
}
|
||||
|
||||
function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||
|
|
@ -35,11 +43,22 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const OperatingPriorityOptions = [
|
||||
'LoadPriority',
|
||||
'BatteryPriority',
|
||||
'GridPriority'
|
||||
];
|
||||
const device = props.installation.device;
|
||||
|
||||
const OperatingPriorityOptions =
|
||||
device === 3 // Growatt
|
||||
? ['LoadPriority', 'BatteryPriority', 'GridPriority']
|
||||
: device === 4 // Sinexcel
|
||||
? [
|
||||
'SpontaneousSelfUse',
|
||||
'TimeChargeDischarge',
|
||||
// 'TimeOfUsePowerPrice',
|
||||
// 'DisasterStandby',
|
||||
// 'ManualControl',
|
||||
'PvPriorityCharging',
|
||||
// 'PrioritySellElectricity'
|
||||
]
|
||||
: [];
|
||||
|
||||
const [errors, setErrors] = useState({
|
||||
minimumSoC: false,
|
||||
|
|
@ -69,7 +88,21 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
operatingPriority: OperatingPriorityOptions.indexOf(
|
||||
props.values.Config.OperatingPriority
|
||||
),
|
||||
batteriesCount: props.values.Config.BatteriesCount
|
||||
batteriesCount: props.values.Config.BatteriesCount,
|
||||
clusterNumber: props.values.Config.ClusterNumber??1,
|
||||
PvNumber: props.values.Config.PvNumber??0,
|
||||
timeChargeandDischargePower: props.values.Config?.TimeChargeandDischargePower ?? 0, // default 0 W
|
||||
startTimeChargeandDischargeDayandTime:
|
||||
props.values.Config?.StartTimeChargeandDischargeDayandTime
|
||||
? dayjs(props.values.Config.StartTimeChargeandDischargeDayandTime).toDate()
|
||||
: null,
|
||||
stopTimeChargeandDischargeDayandTime:
|
||||
props.values.Config?.StopTimeChargeandDischargeDayandTime
|
||||
? dayjs(props.values.Config.StopTimeChargeandDischargeDayandTime).toDate()
|
||||
: null,
|
||||
|
||||
// controlPermission: props.values.Config.ControlPermission??false,
|
||||
controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true",
|
||||
});
|
||||
|
||||
const handleOperatingPriorityChange = (event) => {
|
||||
|
|
@ -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<ConfigurationValues> = {
|
||||
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 && (
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<FormControlLabel
|
||||
labelPlacement="start"
|
||||
control={
|
||||
<Switch
|
||||
name="controlPermission"
|
||||
checked={Boolean(formValues.controlPermission)}
|
||||
onChange={(e) =>
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
controlPermission: e.target.checked,
|
||||
}))
|
||||
}
|
||||
sx={{ transform: "scale(1.4)", marginLeft: "15px" }}
|
||||
/>
|
||||
}
|
||||
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="controlPermission"
|
||||
defaultMessage="Control Permission"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={
|
||||
|
|
@ -224,28 +324,73 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{device === 4 && (
|
||||
<>
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="minimum_soc "
|
||||
defaultMessage="Minimum SoC (%)"
|
||||
id="clusterNumber"
|
||||
defaultMessage="Cluster Number"
|
||||
/>
|
||||
}
|
||||
name="clusterNumber"
|
||||
value={formValues.clusterNumber}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="PvNumber"
|
||||
defaultMessage="PV Number"
|
||||
/>
|
||||
}
|
||||
name="PvNumber"
|
||||
value={formValues.PvNumber}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
{/*<TextField*/}
|
||||
{/* label={*/}
|
||||
{/* <FormattedMessage*/}
|
||||
{/* id="minimum_soc "*/}
|
||||
{/* defaultMessage="Minimum SoC (%)"*/}
|
||||
{/* />*/}
|
||||
{/* }*/}
|
||||
{/* name="minimumSoC"*/}
|
||||
{/* value={formValues.minimumSoC}*/}
|
||||
{/* onChange={handleChange}*/}
|
||||
{/* helperText={*/}
|
||||
{/* errors.minimumSoC ? (*/}
|
||||
{/* <span style={{ color: 'red' }}>*/}
|
||||
{/* Value should be between {device === 4 ? '5–100' : '10–30'}%*/}
|
||||
{/* </span>*/}
|
||||
{/* ) : (*/}
|
||||
{/* ''*/}
|
||||
{/* )*/}
|
||||
{/* }*/}
|
||||
{/* fullWidth*/}
|
||||
{/*/>*/}
|
||||
<TextField
|
||||
label="Minimum SoC (%)"
|
||||
name="minimumSoC"
|
||||
value={formValues.minimumSoC}
|
||||
onChange={handleChange}
|
||||
helperText={
|
||||
errors.minimumSoC ? (
|
||||
<span style={{ color: 'red' }}>
|
||||
Value should be between 0-100%
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
error={Boolean(errors.minimumSoC)}
|
||||
helperText={errors.minimumSoC}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
|
|
@ -307,6 +452,91 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
</div>
|
||||
</>
|
||||
|
||||
{/* --- Sinexcel + TimeChargeDischarge --- */}
|
||||
{device === 4 &&
|
||||
OperatingPriorityOptions[formValues.operatingPriority] ===
|
||||
'TimeChargeDischarge' && (
|
||||
<>
|
||||
{/* Power input*/}
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label="Power (W)"
|
||||
name="timeChargeandDischargePower"
|
||||
value={formValues.timeChargeandDischargePower}
|
||||
onChange={(e) =>
|
||||
handleTimeChargeDischargeChange(e.target.name, e.target.value)
|
||||
}
|
||||
helperText="Enter a positive or negative power value"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Start DateTime */}
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DateTimePicker
|
||||
ampm={false}
|
||||
label="Start Date and Time (Start Time < Stop Time)"
|
||||
value={
|
||||
formValues.startTimeChargeandDischargeDayandTime
|
||||
? dayjs(formValues.startTimeChargeandDischargeDayandTime)
|
||||
: null
|
||||
}
|
||||
onChange={(newValue) =>
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
startTimeChargeandDischargeDayandTime: newValue
|
||||
? newValue.toDate()
|
||||
: null,
|
||||
}))
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
{/* Stop DateTime */}
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DateTimePicker
|
||||
ampm={false}
|
||||
label="Stop Date and Time (Start Time < Stop Time)"
|
||||
value={
|
||||
formValues.stopTimeChargeandDischargeDayandTime
|
||||
? dayjs(formValues.stopTimeChargeandDischargeDayandTime)
|
||||
: null
|
||||
}
|
||||
onChange={(newValue) =>
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
stopTimeChargeandDischargeDayandTime: newValue
|
||||
? newValue.toDate()
|
||||
: null,
|
||||
}))
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
|
|
|||
|
|
@ -237,6 +237,22 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="batteryClusterNumber"
|
||||
defaultMessage="Battery Cluster Number"
|
||||
/>
|
||||
}
|
||||
name="batteryClusterNumber"
|
||||
value={formValues.batteryClusterNumber}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
|
|||
|
|
@ -96,10 +96,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
const singleInstallationTabs =
|
||||
currentUser.userType == UserType.admin
|
||||
? [
|
||||
// {
|
||||
// value: 'live',
|
||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
// },
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'batteryview',
|
||||
label: (
|
||||
|
|
@ -155,10 +155,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
}
|
||||
]
|
||||
: [
|
||||
// {
|
||||
// value: 'live',
|
||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
// },
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
// {
|
||||
// value: 'overview',
|
||||
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
|
|
@ -186,10 +186,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
value: 'tree',
|
||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||
},
|
||||
// {
|
||||
// value: 'live',
|
||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
// },
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'batteryview',
|
||||
label: (
|
||||
|
|
@ -256,10 +256,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
value: 'tree',
|
||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||
},
|
||||
// {
|
||||
// value: 'live',
|
||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
// },
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'overview',
|
||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
||||
<Grid container>
|
||||
{!props.connected && !props.loading && (
|
||||
<Container
|
||||
maxWidth="xl"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '70vh'
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ color: 'black', fontWeight: 'bold' }}
|
||||
mt={2}
|
||||
>
|
||||
Unable to communicate with the installation
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{ color: 'black' }}>
|
||||
Please wait or refresh the page
|
||||
</Typography>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{props.connected && (
|
||||
<>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={12}
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'right',
|
||||
justifyContent: 'right'
|
||||
}}
|
||||
>
|
||||
{/*<div>*/}
|
||||
{/* <Typography sx={{ marginTop: '5px', marginRight: '20px' }}>*/}
|
||||
{/* Display Values*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <Switch*/}
|
||||
{/* edge="start"*/}
|
||||
{/* color="secondary"*/}
|
||||
{/* onChange={handleSwitch()}*/}
|
||||
{/* sx={{*/}
|
||||
{/* '& .MuiSwitch-thumb': {*/}
|
||||
{/* backgroundColor: 'orange'*/}
|
||||
{/* },*/}
|
||||
{/* marginLeft: '20px'*/}
|
||||
{/* }}*/}
|
||||
{/* />*/}
|
||||
{/*</div>*/}
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={12}
|
||||
style={{
|
||||
height: isMobile ? '550px' : '600px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<TopologyColumn
|
||||
centerBox={{
|
||||
title: 'Grid',
|
||||
data: props.values?.InverterRecord
|
||||
? [
|
||||
{
|
||||
value: props.values.InverterRecord.GridPower,
|
||||
unit: 'W'
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
connected:
|
||||
true
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values?.InverterRecord
|
||||
? {
|
||||
value: props.values.InverterRecord.GridPower,
|
||||
unit: 'W'
|
||||
}
|
||||
: undefined,
|
||||
amount: props.values?.InverterRecord
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.InverterRecord.GridPower
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={true}
|
||||
/>
|
||||
{/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
|
||||
<TopologyColumn
|
||||
topBox={{
|
||||
title: 'PV',
|
||||
data: props.values?.InverterRecord
|
||||
? [
|
||||
{
|
||||
value: pvPower,
|
||||
unit: 'W'
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
connected: true
|
||||
}}
|
||||
topConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'top',
|
||||
data: props.values?.InverterRecord
|
||||
? {
|
||||
value: pvPower,
|
||||
unit: 'W'
|
||||
}
|
||||
: undefined,
|
||||
amount: props.values?.InverterRecord ? getAmount(highestConnectionValue, pvPower) : 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
centerBox={{
|
||||
title: 'Inverter',
|
||||
data: props.values?.InverterRecord
|
||||
? [
|
||||
{
|
||||
value: 0,
|
||||
unit: 'W'
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
connected: true
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values?.InverterRecord
|
||||
? {
|
||||
value: totalBatteryPower,
|
||||
unit: 'W'
|
||||
}
|
||||
: undefined,
|
||||
amount: props.values?.InverterRecord
|
||||
? getAmount(highestConnectionValue, totalBatteryPower)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
bottomBox={{
|
||||
title: 'Loads',
|
||||
data: props.values?.InverterRecord
|
||||
? [
|
||||
{
|
||||
value: props.values.InverterRecord.ConsumptionPower,
|
||||
unit: 'W'
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
connected:true
|
||||
}}
|
||||
bottomConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'bottom',
|
||||
data: props.values?.InverterRecord
|
||||
? {
|
||||
value: props.values.InverterRecord.ConsumptionPower,
|
||||
unit: 'W'
|
||||
}
|
||||
: undefined,
|
||||
amount: props.values?.InverterRecord
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.InverterRecord.ConsumptionPower
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
{/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
|
||||
{Array.from({ length: props.batteryClusterNumber }).map((_, index) => {
|
||||
const i = index + 1; // battery cluster index starting from 1
|
||||
|
||||
return (
|
||||
<TopologyColumn
|
||||
key={i}
|
||||
centerBox={{
|
||||
title: `Battery C${i}`,
|
||||
data: props.values.InverterRecord
|
||||
? [
|
||||
{
|
||||
value: props.values.InverterRecord[`Battery${i}Soc`],
|
||||
unit: '%'
|
||||
},
|
||||
{
|
||||
value: props.values.InverterRecord[`Battery${i}Power`],
|
||||
unit: 'W'
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
connected: true
|
||||
}}
|
||||
isFirst={false}
|
||||
isLast={true}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopologySodistoreHome;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -72,8 +72,12 @@ function TopologyBox(props: TopologyBoxProps) {
|
|||
: props.title === 'AC Loads' ||
|
||||
props.title === 'DC Loads' ||
|
||||
props.title === 'Pv Inverter' ||
|
||||
props.title === 'Pv DC-DC'
|
||||
props.title === 'Pv DC-DC' ||
|
||||
props.title === 'PV' ||
|
||||
props.title === 'Loads'
|
||||
? '100px'
|
||||
: props.title === 'Inverter'
|
||||
? '150px'
|
||||
: '150px',
|
||||
backgroundColor: !props.data
|
||||
? 'darkgrey'
|
||||
|
|
@ -134,6 +138,17 @@ function TopologyBox(props: TopologyBoxProps) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{props.data && props.title === 'Inverter' && (
|
||||
<img
|
||||
src={inverterImage}
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
color: 'orange'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.data && props.title === 'DC Link' && (
|
||||
<PowerInputIcon
|
||||
style={{
|
||||
|
|
@ -183,7 +198,8 @@ function TopologyBox(props: TopologyBoxProps) {
|
|||
marginTop: '4px'
|
||||
}}
|
||||
>
|
||||
{(props.title === 'Pv Inverter' ||
|
||||
{(props.title === 'PV' ||
|
||||
props.title === 'Pv Inverter' ||
|
||||
props.title === 'Pv DC-DC') && (
|
||||
<SolarPowerIcon
|
||||
style={{
|
||||
|
|
@ -207,7 +223,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
|||
}}
|
||||
></BatteryCharging60Icon>
|
||||
)}
|
||||
{(props.title === 'AC Loads' || props.title === 'DC Loads') && (
|
||||
{(props.title === 'AC Loads' || props.title === 'DC Loads' ||props.title === 'Loads') && (
|
||||
<OutletIcon
|
||||
style={{
|
||||
fontSize: 30,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ function InstallationTree() {
|
|||
return (
|
||||
<Route
|
||||
key={installation.id}
|
||||
path={routes.installation + installation.id + '*'}
|
||||
path={routes.installation + installation.id + '/*'}
|
||||
element={
|
||||
installation.product == 0 || installation.product == 3 ? (
|
||||
<Installation
|
||||
|
|
|
|||
|
|
@ -614,6 +614,7 @@ export const transformInputToAggregatedDataJson = async (
|
|||
}
|
||||
|
||||
const results = await Promise.all(timestampPromises);
|
||||
console.log("Fetched aggregated daily results:", results);
|
||||
currentDay = start_date;
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export interface I_Installation extends I_S3Credentials {
|
|||
information: string;
|
||||
inverterSN: string;
|
||||
dataloggerSN: string;
|
||||
batteryClusterNumber: number;
|
||||
parentId: number;
|
||||
s3WriteKey: string;
|
||||
s3WriteSecret: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue