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.App.Backend.Websockets;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend;
|
namespace InnovEnergy.App.Backend;
|
||||||
|
|
||||||
|
|
@ -554,6 +555,7 @@ public class Controller : ControllerBase
|
||||||
if (!mail_success)
|
if (!mail_success)
|
||||||
{
|
{
|
||||||
Db.GetSession(authToken).Delete(newUser);
|
Db.GetSession(authToken).Delete(newUser);
|
||||||
|
return StatusCode(500, "Welcome email failed to send");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mail_success ? newUser.HidePassword():Unauthorized();
|
return mail_success ? newUser.HidePassword():Unauthorized();
|
||||||
|
|
@ -935,11 +937,19 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPost(nameof(EditInstallationConfig))]
|
[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);
|
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
|
//Send configuration changes
|
||||||
var success = await session.SendInstallationConfig(installationId, config);
|
var success = await session.SendInstallationConfig(installationId, config);
|
||||||
|
|
@ -947,17 +957,23 @@ public class Controller : ControllerBase
|
||||||
// Record configuration change
|
// Record configuration change
|
||||||
if (success)
|
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
|
var action = new UserAction
|
||||||
{
|
{
|
||||||
InstallationId = installationId,
|
InstallationId = installationId,
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.UtcNow,
|
||||||
Description = config.GetConfigurationString()
|
Description = configString
|
||||||
};
|
};
|
||||||
Console.WriteLine(action.Description);
|
|
||||||
|
|
||||||
var actionSuccess = await session.InsertUserAction(action);
|
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();
|
return Unauthorized();
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,52 @@ namespace InnovEnergy.App.Backend.DataTypes;
|
||||||
|
|
||||||
public class Configuration
|
public class Configuration
|
||||||
{
|
{
|
||||||
public Double MinimumSoC { get; set; }
|
public double? MinimumSoC { get; set; }
|
||||||
public Double GridSetPoint { get; set; }
|
public double? GridSetPoint { get; set; }
|
||||||
public CalibrationChargeType CalibrationChargeState { get; set; }
|
public CalibrationChargeType? CalibrationChargeState { get; set; }
|
||||||
public DateTime CalibrationChargeDate { get; set; }
|
public DateTime? CalibrationChargeDate { get; set; }
|
||||||
public CalibrationChargeType CalibrationDischargeState { get; set; }
|
public CalibrationChargeType? CalibrationDischargeState { get; set; }
|
||||||
public DateTime CalibrationDischargeDate { get; set; }
|
public DateTime? CalibrationDischargeDate { get; set; }
|
||||||
|
|
||||||
//For sodistoreHome installations
|
public double? MaximumDischargingCurrent { get; set; }
|
||||||
|
public double? MaximumChargingCurrent { get; set; }
|
||||||
public Double MaximumDischargingCurrent { get; set; }
|
public double? OperatingPriority { get; set; }
|
||||||
public Double MaximumChargingCurrent { get; set; }
|
public double? BatteriesCount { get; set; }
|
||||||
public Double OperatingPriority { get; set; }
|
public double? ClusterNumber { get; set; }
|
||||||
public Double BatteriesCount { 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()
|
public String GetConfigurationString()
|
||||||
{
|
{
|
||||||
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
||||||
$"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}" +
|
$"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}, " +
|
||||||
$"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}" +
|
$"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
||||||
$"BatteriesCount: {BatteriesCount}";
|
$"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
|
public enum CalibrationChargeType
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,12 @@ public class Installation : TreeNode
|
||||||
public string SerialNumber { get; set; } = "";
|
public string SerialNumber { get; set; } = "";
|
||||||
public string InverterSN { get; set; } = "";
|
public string InverterSN { get; set; } = "";
|
||||||
public string DataloggerSN { 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]
|
[Ignore]
|
||||||
public String OrderNumbers { get; set; }
|
public String OrderNumbers { get; set; }
|
||||||
public String VrmLink { get; set; } = "";
|
public String VrmLink { get; set; } = "";
|
||||||
|
public string Configuration { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
@ -296,8 +296,10 @@ public static partial class Db
|
||||||
await user.SendNewUserWelcomeMessage();
|
await user.SendNewUserWelcomeMessage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"Welcome email failed for {user.Email}");
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{145597B4-3E30-45E6-9F72-4DD43194539A}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{145597B4-3E30-45E6-9F72-4DD43194539A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}"
|
||||||
EndProject
|
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
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devices", "Devices", "{4931A385-24DC-4E78-BFF4-356F8D6D5183}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devices", "Devices", "{4931A385-24DC-4E78-BFF4-356F8D6D5183}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -30,35 +31,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Victron", "Victron", "{BD8C
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Trumpf", "Trumpf", "{DDDBEFD0-5DEA-4C7C-A9F2-FDB4636CF092}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Trumpf", "Trumpf", "{DDDBEFD0-5DEA-4C7C-A9F2-FDB4636CF092}"
|
||||||
EndProject
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{AED84693-C389-44C9-B2C0-ACB560189CF2}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{AED84693-C389-44C9-B2C0-ACB560189CF2}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|
@ -87,8 +88,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doepke", "Lib\Devices\Doepk
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amax5070", "Lib\Devices\Amax5070\Amax5070.csproj", "{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amax5070", "Lib\Devices\Amax5070\Amax5070.csproj", "{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}"
|
||||||
EndProject
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchneiderMeterDriver", "App\SchneiderMeterDriver\SchneiderMeterDriver.csproj", "{2E7E7657-3A53-4B62-8927-FE9A082B81DE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery250UP", "Lib\Devices\Battery250UP\Battery250UP.csproj", "{F2967439-A590-4D5E-9208-1B973C83AA1C}"
|
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
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sinexcel 12K TL", "Sinexcel 12K TL\Sinexcel 12K TL.csproj", "{28C16B43-E498-40DB-8ACF-D7F2A88A402F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sinexcel 12K TL", "Sinexcel 12K TL\Sinexcel 12K TL.csproj", "{28C16B43-E498-40DB-8ACF-D7F2A88A402F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.Build.0 = 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
|
{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}
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}
|
||||||
{C2B14CD4-1BCA-4933-96D9-92F40EACD2B9} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
{C2B14CD4-1BCA-4933-96D9-92F40EACD2B9} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||||
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6} = {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}
|
{2E7E7657-3A53-4B62-8927-FE9A082B81DE} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
{F2967439-A590-4D5E-9208-1B973C83AA1C} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
{F2967439-A590-4D5E-9208-1B973C83AA1C} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||||
{1045AC74-D4D8-4581-AAE3-575DF26060E6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
{1045AC74-D4D8-4581-AAE3-575DF26060E6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,52 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using MailKit.Net.Smtp;
|
using MailKit.Net.Smtp;
|
||||||
|
using MailKit.Security;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Mailer;
|
namespace InnovEnergy.Lib.Mailer;
|
||||||
|
|
||||||
|
|
||||||
public static class 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>")]
|
[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();
|
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 from = new MailboxAddress(config!.SenderName, config.SenderAddress);
|
||||||
var to = new MailboxAddress(recipientName, recipientEmailAddress);
|
var to = new MailboxAddress(recipientName, recipientEmailAddress);
|
||||||
|
|
||||||
var msg = new MimeMessage
|
var msg = new MimeMessage
|
||||||
{
|
{
|
||||||
From = { from },
|
From = { from },
|
||||||
To = { to },
|
To = { to },
|
||||||
Subject = subject,
|
Subject = subject,
|
||||||
Body = new TextPart { Text = body }
|
Body = new TextPart("plain") { Text = body }
|
||||||
};
|
};
|
||||||
|
|
||||||
using var smtp = new SmtpClient();
|
using var smtp = new SmtpClient();
|
||||||
|
|
||||||
await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, false);
|
try
|
||||||
await smtp.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword);
|
{
|
||||||
await smtp.SendAsync(msg);
|
await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, SecureSocketOptions.StartTls);
|
||||||
await smtp.DisconnectAsync(true);
|
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)")]
|
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken)")]
|
||||||
|
|
|
||||||
|
|
@ -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>
|
<Grid container>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path={routes.mainstats + '*'}
|
path={routes.mainstats + '/*'}
|
||||||
element={
|
element={
|
||||||
<MainStats
|
<MainStats
|
||||||
s3Credentials={props.s3Credentials}
|
s3Credentials={props.s3Credentials}
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path={routes.mainstats + '*'}
|
path={routes.mainstats + '/*'}
|
||||||
element={
|
element={
|
||||||
<MainStatsSalidomo
|
<MainStatsSalidomo
|
||||||
s3Credentials={props.s3Credentials}
|
s3Credentials={props.s3Credentials}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ import { FormattedMessage } from 'react-intl';
|
||||||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||||
import routes from '../../../Resources/routes.json';
|
import routes from '../../../Resources/routes.json';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
|
|
||||||
interface BatteryViewSodioHomeProps {
|
interface BatteryViewSodioHomeProps {
|
||||||
values: JSONRecordData;
|
values: JSONRecordData;
|
||||||
s3Credentials: I_S3Credentials;
|
s3Credentials: I_S3Credentials;
|
||||||
installationId: number;
|
installationId: number;
|
||||||
|
installation: I_Installation;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,16 +35,24 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
|
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
const navigate = useNavigate();
|
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 =
|
return {
|
||||||
props.values != null &&
|
BatteryId: String(index),
|
||||||
props.values?.AcDcGrowatt?.BatteriesRecords?.Batteries
|
battery: {
|
||||||
? Object.entries(props.values.AcDcGrowatt.BatteriesRecords.Batteries)
|
Voltage: inverter[`Battery${index}Voltage`],
|
||||||
.map(([BatteryId, battery]) => {
|
Current: inverter[`Battery${index}Current`],
|
||||||
return { BatteryId, battery }; // Here we return an object with the id and device
|
Power: inverter[`Battery${index}Power`],
|
||||||
})
|
Soc: inverter[`Battery${index}Soc`],
|
||||||
.sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
|
Soh: inverter[`Battery${index}Soh`],
|
||||||
: [];
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const [loading, setLoading] = useState(sortedBatteryView.length == 0);
|
const [loading, setLoading] = useState(sortedBatteryView.length == 0);
|
||||||
const handleMainStatsButton = () => {
|
const handleMainStatsButton = () => {
|
||||||
|
|
@ -157,7 +167,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
{/*<Grid container>*/}
|
{/*<Grid container>*/}
|
||||||
{/* <Routes>*/}
|
{/* <Routes>*/}
|
||||||
{/* <Route*/}
|
{/* <Route*/}
|
||||||
{/* path={routes.mainstats + '*'}*/}
|
{/* path={routes.mainstats + '/*'}*/}
|
||||||
{/* element={*/}
|
{/* element={*/}
|
||||||
{/* <MainStats*/}
|
{/* <MainStats*/}
|
||||||
{/* s3Credentials={props.s3Credentials}*/}
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
|
@ -224,8 +234,8 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
<TableCell align="center">Current</TableCell>
|
<TableCell align="center">Current</TableCell>
|
||||||
<TableCell align="center">SoC</TableCell>
|
<TableCell align="center">SoC</TableCell>
|
||||||
<TableCell align="center">SoH</TableCell>
|
<TableCell align="center">SoH</TableCell>
|
||||||
<TableCell align="center">Daily Charge Energy</TableCell>
|
{/*<TableCell align="center">Daily Charge Energy</TableCell>*/}
|
||||||
<TableCell align="center">Daily Discharge Energy</TableCell>
|
{/*<TableCell align="center">Daily Discharge Energy</TableCell>*/}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
@ -261,12 +271,14 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
sx={{
|
sx={{
|
||||||
width: '13%',
|
width: '13%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
backgroundColor:
|
||||||
backgroundColor: '#32CD32',
|
battery.Voltage < 32 || battery.Voltage > 63
|
||||||
color: 'black'
|
? '#FF033E'
|
||||||
|
: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{battery.Voltage + ' ' + 'V'}
|
{battery.Voltage + ' V'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -282,7 +294,12 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
sx={{
|
sx={{
|
||||||
width: '8%',
|
width: '8%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
backgroundColor: '#32CD32',
|
backgroundColor:
|
||||||
|
battery.Soc > 20
|
||||||
|
? '#32CD32'
|
||||||
|
: battery.Soc >= 10
|
||||||
|
? '#ffbf00'
|
||||||
|
: '#FF033E',
|
||||||
color: 'inherit'
|
color: 'inherit'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -292,32 +309,37 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
sx={{
|
sx={{
|
||||||
width: '8%',
|
width: '8%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
backgroundColor: '#32CD32',
|
backgroundColor:
|
||||||
|
battery.Soh ==100
|
||||||
|
? '#32CD32'
|
||||||
|
: battery.Soc >= 90
|
||||||
|
? '#ffbf00'
|
||||||
|
: '#FF033E',
|
||||||
color: 'inherit'
|
color: 'inherit'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{battery.Soh + ' %'}
|
{battery.Soh + ' %'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
{/*<TableCell*/}
|
||||||
sx={{
|
{/* sx={{*/}
|
||||||
width: '15%',
|
{/* width: '15%',*/}
|
||||||
textAlign: 'center',
|
{/* textAlign: 'center',*/}
|
||||||
backgroundColor: '#32CD32',
|
{/* backgroundColor: '#32CD32',*/}
|
||||||
color: 'inherit'
|
{/* color: 'inherit'*/}
|
||||||
}}
|
{/* }}*/}
|
||||||
>
|
{/*>*/}
|
||||||
{battery.DailyChargeEnergy + ' Wh'}
|
{/* {battery.DailyChargeEnergy + ' Wh'}*/}
|
||||||
</TableCell>
|
{/*</TableCell>*/}
|
||||||
<TableCell
|
{/*<TableCell*/}
|
||||||
sx={{
|
{/* sx={{*/}
|
||||||
width: '15%',
|
{/* width: '15%',*/}
|
||||||
textAlign: 'center',
|
{/* textAlign: 'center',*/}
|
||||||
backgroundColor: '#32CD32',
|
{/* backgroundColor: '#32CD32',*/}
|
||||||
color: 'inherit'
|
{/* color: 'inherit'*/}
|
||||||
}}
|
{/* }}*/}
|
||||||
>
|
{/*>*/}
|
||||||
{battery.DailyDischargeEnergy + ' Wh'}
|
{/* {battery.DailyDischargeEnergy + ' Wh'}*/}
|
||||||
</TableCell>
|
{/*</TableCell>*/}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ function Configuration(props: ConfigurationProps) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await axiosConfig
|
const res = await axiosConfig
|
||||||
.post(
|
.post(
|
||||||
`/EditInstallationConfig?installationId=${props.id}`,
|
`/EditInstallationConfig?installationId=${props.id}&product=${product}`,
|
||||||
configurationToSend
|
configurationToSend
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -404,32 +404,32 @@ function Configuration(props: ConfigurationProps) {
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{product === 3 && (
|
{/*{product === 3 && (*/}
|
||||||
<Tab
|
{/* <Tab*/}
|
||||||
value="discharge"
|
{/* value="discharge"*/}
|
||||||
label="Calibration Discharge"
|
{/* label="Calibration Discharge"*/}
|
||||||
sx={(theme) => ({
|
{/* sx={(theme) => ({*/}
|
||||||
flex: 2,
|
{/* flex: 2,*/}
|
||||||
fontWeight: 'bold',
|
{/* fontWeight: 'bold',*/}
|
||||||
borderRadius: 2,
|
{/* borderRadius: 2,*/}
|
||||||
textTransform: 'none',
|
{/* textTransform: 'none',*/}
|
||||||
color:
|
{/* color:*/}
|
||||||
activeTab === 'discharge'
|
{/* activeTab === 'discharge'*/}
|
||||||
? 'white'
|
{/* ? 'white'*/}
|
||||||
: theme.palette.text.primary,
|
{/* : theme.palette.text.primary,*/}
|
||||||
bgcolor:
|
{/* bgcolor:*/}
|
||||||
activeTab === 'discharge'
|
{/* activeTab === 'discharge'*/}
|
||||||
? theme.palette.primary.main
|
{/* ? theme.palette.primary.main*/}
|
||||||
: 'transparent',
|
{/* : 'transparent',*/}
|
||||||
'&:hover': {
|
{/* '&:hover': {*/}
|
||||||
bgcolor:
|
{/* bgcolor:*/}
|
||||||
activeTab === 'discharge'
|
{/* activeTab === 'discharge'*/}
|
||||||
? theme.palette.primary.dark
|
{/* ? theme.palette.primary.dark*/}
|
||||||
: '#eee'
|
{/* : '#eee'*/}
|
||||||
}
|
{/* }*/}
|
||||||
})}
|
{/* })}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
)}
|
{/*)}*/}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,22 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="batteryClusterNumber"
|
||||||
|
defaultMessage="Battery Cluster Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="batteryClusterNumber"
|
||||||
|
value={formValues.batteryClusterNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
|
@ -361,7 +377,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
name="s3writesecretkey"
|
name="s3writesecretkey"
|
||||||
value={
|
value={
|
||||||
formValues.s3BucketId +
|
formValues.s3BucketId +
|
||||||
'-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'
|
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
||||||
}
|
}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
|
||||||
|
|
@ -416,7 +416,7 @@ function Installation(props: singleInstallationProps) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.batteryview + '*'}
|
path={routes.batteryview + '/*'}
|
||||||
element={
|
element={
|
||||||
<BatteryView
|
<BatteryView
|
||||||
values={values}
|
values={values}
|
||||||
|
|
@ -428,7 +428,7 @@ function Installation(props: singleInstallationProps) {
|
||||||
></Route>
|
></Route>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.pvview + '*'}
|
path={routes.pvview + '/*'}
|
||||||
element={
|
element={
|
||||||
<PvView values={values} connected={connected}></PvView>
|
<PvView values={values} connected={connected}></PvView>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
path={routes.installation + installation.id + '*'}
|
path={routes.installation + installation.id + '/*'}
|
||||||
element={
|
element={
|
||||||
<Installation
|
<Installation
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
|
|
@ -92,7 +92,7 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
// return (
|
// return (
|
||||||
// <Route
|
// <Route
|
||||||
// key={installation.id}
|
// key={installation.id}
|
||||||
// path={routes.installation + installation.id + '*'}
|
// path={routes.installation + installation.id + '/*'}
|
||||||
// element={
|
// element={
|
||||||
// <Installation
|
// <Installation
|
||||||
// key={installation.id}
|
// key={installation.id}
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,16 @@ export interface JSONRecordData {
|
||||||
MaximumDischargingCurrent: number;
|
MaximumDischargingCurrent: number;
|
||||||
OperatingPriority: string;
|
OperatingPriority: string;
|
||||||
BatteriesCount: number;
|
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: {
|
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: {
|
AcDcGrowatt: {
|
||||||
AcChargeEnable: number;
|
AcChargeEnable: number;
|
||||||
ActivePowerPercentDerating: number;
|
ActivePowerPercentDerating: number;
|
||||||
|
|
@ -457,6 +484,7 @@ export interface JSONRecordData {
|
||||||
BatteryOperatingMode: string;
|
BatteryOperatingMode: string;
|
||||||
BatteryType: number;
|
BatteryType: number;
|
||||||
ChargeCutoffSoc: number;
|
ChargeCutoffSoc: number;
|
||||||
|
ConsumptionPower:number;
|
||||||
ControlPermession: number;
|
ControlPermession: number;
|
||||||
DischargeCutoffSoc: number;
|
DischargeCutoffSoc: number;
|
||||||
EmsCommunicationFailureTime: number;
|
EmsCommunicationFailureTime: number;
|
||||||
|
|
@ -495,6 +523,7 @@ export interface JSONRecordData {
|
||||||
SystemOperatingMode: string;
|
SystemOperatingMode: string;
|
||||||
TotalEnergyToGrid: number;
|
TotalEnergyToGrid: number;
|
||||||
TotalEnergyToUser: number;
|
TotalEnergyToUser: number;
|
||||||
|
TotalPvPower: number;
|
||||||
VppProtocolVerNumber: number;
|
VppProtocolVerNumber: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -614,6 +643,16 @@ export type ConfigurationValues = {
|
||||||
maximumChargingCurrent: number;
|
maximumChargingCurrent: number;
|
||||||
operatingPriority: number;
|
operatingPriority: number;
|
||||||
batteriesCount: 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 {
|
// export interface Pv {
|
||||||
|
|
@ -1040,7 +1079,17 @@ export const getHighestConnectionValue = (values: JSONRecordData) => {
|
||||||
'PvOnDc.Dc.Power',
|
'PvOnDc.Dc.Power',
|
||||||
'DcDc.Dc.Link.Power',
|
'DcDc.Dc.Link.Power',
|
||||||
'LoadOnDc.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
|
// Helper function to safely get a value from a nested path
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.batteryview + '*'}
|
path={routes.batteryview + '/*'}
|
||||||
element={
|
element={
|
||||||
<BatteryViewSalidomo
|
<BatteryViewSalidomo
|
||||||
values={values}
|
values={values}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
path={routes.installation + installation.id + '*'}
|
path={routes.installation + installation.id + '/*'}
|
||||||
element={
|
element={
|
||||||
<SalidomoInstallation
|
<SalidomoInstallation
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
routes.installation +
|
routes.installation +
|
||||||
`${installationID}` +
|
`${installationID}` +
|
||||||
'/' +
|
'/' +
|
||||||
routes.batteryview,
|
routes.live,
|
||||||
{
|
{
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { fetchDataJson } from '../Installations/fetchData';
|
||||||
import { FetchResult } from '../../../dataCache/dataCache';
|
import { FetchResult } from '../../../dataCache/dataCache';
|
||||||
import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome';
|
import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome';
|
||||||
import SodistoreHomeConfiguration from './SodistoreHomeConfiguration';
|
import SodistoreHomeConfiguration from './SodistoreHomeConfiguration';
|
||||||
|
import TopologySodistoreHome from '../Topology/TopologySodistoreHome';
|
||||||
|
|
||||||
interface singleInstallationProps {
|
interface singleInstallationProps {
|
||||||
current_installation?: I_Installation;
|
current_installation?: I_Installation;
|
||||||
|
|
@ -297,6 +298,37 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
{props.current_installation.name}
|
{props.current_installation.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</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' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Typography
|
<Typography
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
|
|
@ -434,22 +466,23 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
<Route
|
<Route
|
||||||
path={routes.live}
|
path={routes.live}
|
||||||
element={
|
element={
|
||||||
<div></div>
|
<TopologySodistoreHome
|
||||||
// <Topology
|
values={values}
|
||||||
// values={values}
|
connected={connected}
|
||||||
// connected={connected}
|
loading={loading}
|
||||||
// loading={loading}
|
batteryClusterNumber={props.current_installation.batteryClusterNumber}
|
||||||
// ></Topology>
|
></TopologySodistoreHome>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.batteryview + '*'}
|
path={routes.batteryview + '/*'}
|
||||||
element={
|
element={
|
||||||
<BatteryViewSodioHome
|
<BatteryViewSodioHome
|
||||||
values={values}
|
values={values}
|
||||||
s3Credentials={s3Credentials}
|
s3Credentials={s3Credentials}
|
||||||
installationId={props.current_installation.id}
|
installationId={props.current_installation.id}
|
||||||
|
installation={props.current_installation}
|
||||||
connected={connected}
|
connected={connected}
|
||||||
></BatteryViewSodioHome>
|
></BatteryViewSodioHome>
|
||||||
}
|
}
|
||||||
|
|
@ -474,6 +507,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
<SodistoreHomeConfiguration
|
<SodistoreHomeConfiguration
|
||||||
values={values}
|
values={values}
|
||||||
id={props.current_installation.id}
|
id={props.current_installation.id}
|
||||||
|
installation={props.current_installation}
|
||||||
></SodistoreHomeConfiguration>
|
></SodistoreHomeConfiguration>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
path={routes.installation + installation.id + '*'}
|
path={routes.installation + installation.id + '/*'}
|
||||||
element={
|
element={
|
||||||
<SodioHomeInstallation
|
<SodioHomeInstallation
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,18 @@ import MenuItem from '@mui/material/MenuItem';
|
||||||
import axiosConfig from '../../../Resources/axiosConfig';
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
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 {
|
interface SodistoreHomeConfigurationProps {
|
||||||
values: JSONRecordData;
|
values: JSONRecordData;
|
||||||
id: number;
|
id: number;
|
||||||
|
installation: I_Installation;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
|
|
@ -35,11 +43,22 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OperatingPriorityOptions = [
|
const device = props.installation.device;
|
||||||
'LoadPriority',
|
|
||||||
'BatteryPriority',
|
const OperatingPriorityOptions =
|
||||||
'GridPriority'
|
device === 3 // Growatt
|
||||||
];
|
? ['LoadPriority', 'BatteryPriority', 'GridPriority']
|
||||||
|
: device === 4 // Sinexcel
|
||||||
|
? [
|
||||||
|
'SpontaneousSelfUse',
|
||||||
|
'TimeChargeDischarge',
|
||||||
|
// 'TimeOfUsePowerPrice',
|
||||||
|
// 'DisasterStandby',
|
||||||
|
// 'ManualControl',
|
||||||
|
'PvPriorityCharging',
|
||||||
|
// 'PrioritySellElectricity'
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
const [errors, setErrors] = useState({
|
const [errors, setErrors] = useState({
|
||||||
minimumSoC: false,
|
minimumSoC: false,
|
||||||
|
|
@ -69,7 +88,21 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
operatingPriority: OperatingPriorityOptions.indexOf(
|
operatingPriority: OperatingPriorityOptions.indexOf(
|
||||||
props.values.Config.OperatingPriority
|
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) => {
|
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) => {
|
const handleSubmit = async (e) => {
|
||||||
// console.log('asked for', dayjs(formValues.calibrationChargeDate));
|
if (!validateTimeOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const configurationToSend: Partial<ConfigurationValues> = {
|
const configurationToSend: Partial<ConfigurationValues> = {
|
||||||
minimumSoC: formValues.minimumSoC,
|
minimumSoC: formValues.minimumSoC,
|
||||||
maximumDischargingCurrent: formValues.maximumDischargingCurrent,
|
maximumDischargingCurrent: formValues.maximumDischargingCurrent,
|
||||||
maximumChargingCurrent: formValues.maximumChargingCurrent,
|
maximumChargingCurrent: formValues.maximumChargingCurrent,
|
||||||
operatingPriority: formValues.operatingPriority,
|
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);
|
setLoading(true);
|
||||||
const res = await axiosConfig
|
const res = await axiosConfig
|
||||||
.post(
|
.post(
|
||||||
`/EditInstallationConfig?installationId=${props.id}`,
|
`/EditInstallationConfig?installationId=${props.id}&product=${product}`,
|
||||||
configurationToSend
|
configurationToSend
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -113,34 +179,40 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
switch (name) {
|
if (name === 'minimumSoC') {
|
||||||
case 'minimumSoC':
|
const numValue = parseFloat(value);
|
||||||
if (
|
|
||||||
/[^0-9.]/.test(value) ||
|
|
||||||
isNaN(parseFloat(value)) ||
|
|
||||||
parseFloat(value) > 100
|
|
||||||
) {
|
|
||||||
SetErrorForField(name, true);
|
|
||||||
} else {
|
|
||||||
SetErrorForField(name, false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'gridSetPoint':
|
|
||||||
if (/[^0-9.]/.test(value) || isNaN(parseFloat(value))) {
|
|
||||||
SetErrorForField(name, true);
|
|
||||||
} else {
|
|
||||||
SetErrorForField(name, false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
// invalid characters or not a number
|
||||||
break;
|
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({
|
setFormValues(prev => ({
|
||||||
...formValues,
|
...prev,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeChargeDischargeChange = (name: string, value: any) => {
|
||||||
|
setFormValues((prev) => ({
|
||||||
|
...prev,
|
||||||
[name]: value
|
[name]: value
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOkOnErrorDateModal = () => {
|
const handleOkOnErrorDateModal = () => {
|
||||||
|
|
@ -209,6 +281,34 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
autoComplete="off"
|
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' }}>
|
<div style={{ marginBottom: '5px' }}>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
|
@ -224,28 +324,73 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{device === 4 && (
|
||||||
<TextField
|
<>
|
||||||
label={
|
<div style={{ marginBottom: '5px' }}>
|
||||||
<FormattedMessage
|
<TextField
|
||||||
id="minimum_soc "
|
label={
|
||||||
defaultMessage="Minimum SoC (%)"
|
<FormattedMessage
|
||||||
|
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"
|
name="minimumSoC"
|
||||||
value={formValues.minimumSoC}
|
value={formValues.minimumSoC}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
helperText={
|
error={Boolean(errors.minimumSoC)}
|
||||||
errors.minimumSoC ? (
|
helperText={errors.minimumSoC}
|
||||||
<span style={{ color: 'red' }}>
|
|
||||||
Value should be between 0-100%
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px' }}>
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
|
@ -307,6 +452,91 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
</div>
|
</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
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,22 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="batteryClusterNumber"
|
||||||
|
defaultMessage="Battery Cluster Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="batteryClusterNumber"
|
||||||
|
value={formValues.batteryClusterNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
const singleInstallationTabs =
|
const singleInstallationTabs =
|
||||||
currentUser.userType == UserType.admin
|
currentUser.userType == UserType.admin
|
||||||
? [
|
? [
|
||||||
// {
|
{
|
||||||
// value: 'live',
|
value: 'live',
|
||||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
value: 'batteryview',
|
value: 'batteryview',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -155,10 +155,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
// {
|
{
|
||||||
// value: 'live',
|
value: 'live',
|
||||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
// },
|
},
|
||||||
// {
|
// {
|
||||||
// value: 'overview',
|
// value: 'overview',
|
||||||
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
|
@ -186,10 +186,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// value: 'live',
|
value: 'live',
|
||||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
value: 'batteryview',
|
value: 'batteryview',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -256,10 +256,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// value: 'live',
|
value: 'live',
|
||||||
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="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);
|
const roundedValue = value.toFixed(1);
|
||||||
|
|
||||||
//Filter all values less than 100 Watts
|
//Filter all values less than 1 Watts(why?)
|
||||||
if (magnitude === 0 && value < 100 && unit === 'W') {
|
if (magnitude === 0 && value < 1 && unit === 'W') {
|
||||||
//console.log('DROP THIS VALUE ' + value);
|
//console.log('DROP THIS VALUE ' + value);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -70,11 +70,15 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
props.title === 'Battery'
|
props.title === 'Battery'
|
||||||
? '165px'
|
? '165px'
|
||||||
: props.title === 'AC Loads' ||
|
: props.title === 'AC Loads' ||
|
||||||
props.title === 'DC Loads' ||
|
props.title === 'DC Loads' ||
|
||||||
props.title === 'Pv Inverter' ||
|
props.title === 'Pv Inverter' ||
|
||||||
props.title === 'Pv DC-DC'
|
props.title === 'Pv DC-DC' ||
|
||||||
? '100px'
|
props.title === 'PV' ||
|
||||||
: '150px',
|
props.title === 'Loads'
|
||||||
|
? '100px'
|
||||||
|
: props.title === 'Inverter'
|
||||||
|
? '150px'
|
||||||
|
: '150px',
|
||||||
backgroundColor: !props.data
|
backgroundColor: !props.data
|
||||||
? 'darkgrey'
|
? 'darkgrey'
|
||||||
: props.title === 'Grid Bus' ||
|
: props.title === 'Grid Bus' ||
|
||||||
|
|
@ -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' && (
|
{props.data && props.title === 'DC Link' && (
|
||||||
<PowerInputIcon
|
<PowerInputIcon
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -183,7 +198,8 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
marginTop: '4px'
|
marginTop: '4px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(props.title === 'Pv Inverter' ||
|
{(props.title === 'PV' ||
|
||||||
|
props.title === 'Pv Inverter' ||
|
||||||
props.title === 'Pv DC-DC') && (
|
props.title === 'Pv DC-DC') && (
|
||||||
<SolarPowerIcon
|
<SolarPowerIcon
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -207,7 +223,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
}}
|
}}
|
||||||
></BatteryCharging60Icon>
|
></BatteryCharging60Icon>
|
||||||
)}
|
)}
|
||||||
{(props.title === 'AC Loads' || props.title === 'DC Loads') && (
|
{(props.title === 'AC Loads' || props.title === 'DC Loads' ||props.title === 'Loads') && (
|
||||||
<OutletIcon
|
<OutletIcon
|
||||||
style={{
|
style={{
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ function InstallationTree() {
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
path={routes.installation + installation.id + '*'}
|
path={routes.installation + installation.id + '/*'}
|
||||||
element={
|
element={
|
||||||
installation.product == 0 || installation.product == 3 ? (
|
installation.product == 0 || installation.product == 3 ? (
|
||||||
<Installation
|
<Installation
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,7 @@ export const transformInputToAggregatedDataJson = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(timestampPromises);
|
const results = await Promise.all(timestampPromises);
|
||||||
|
console.log("Fetched aggregated daily results:", results);
|
||||||
currentDay = start_date;
|
currentDay = start_date;
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export interface I_Installation extends I_S3Credentials {
|
||||||
information: string;
|
information: string;
|
||||||
inverterSN: string;
|
inverterSN: string;
|
||||||
dataloggerSN: string;
|
dataloggerSN: string;
|
||||||
|
batteryClusterNumber: number;
|
||||||
parentId: number;
|
parentId: number;
|
||||||
s3WriteKey: string;
|
s3WriteKey: string;
|
||||||
s3WriteSecret: string;
|
s3WriteSecret: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue