Merge branch 'main' of https://git.innov.energy/Innovenergy/git_trunk
This commit is contained in:
commit
869a83b799
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
dotnet_version='net6.0'
|
dotnet_version='net6.0'
|
||||||
salimax_ip= '10.2.3.104'
|
salimax_ip= '10.2.3.104'
|
||||||
username='ie-entwicklung@'
|
username='ie-entwicklung'
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
dotnet_version='net6.0'
|
||||||
|
salimax_ip='10.2.4.33'
|
||||||
|
username='ie-entwicklung'
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo -e "\n============================ Build ============================\n"
|
||||||
|
|
||||||
|
dotnet publish \
|
||||||
|
./SaliMax.csproj \
|
||||||
|
-p:PublishTrimmed=false \
|
||||||
|
-c Release \
|
||||||
|
-r linux-x64
|
||||||
|
|
||||||
|
echo -e "\n============================ Deploy ============================\n"
|
||||||
|
|
||||||
|
rsync -v \
|
||||||
|
./bin/Release/$dotnet_version/linux-x64/publish/* \
|
||||||
|
ie-entwicklung@10.2.4.33:~/salimax
|
||||||
|
|
||||||
|
echo -e "\n============================ Restart Salimax sevice ============================\n"
|
||||||
|
|
||||||
|
ssh -tt \
|
||||||
|
ie-entwicklung@10.2.4.33 \
|
||||||
|
sudo systemctl restart salimax.service
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "\n============================ Print service output ============================\n"
|
||||||
|
|
||||||
|
ssh -tt \
|
||||||
|
ie-entwicklung@10.2.4.33 \
|
||||||
|
journalctl -f -u salimax.service
|
|
@ -1,9 +1,8 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
dotnet_version='net6.0'
|
dotnet_version='net6.0'
|
||||||
salimax_ip= '10.2.3.115'
|
salimax_ip='10.2.3.115'
|
||||||
username='ie-entwicklung@'
|
username='ie-entwicklung'
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ public static class Controller
|
||||||
|
|
||||||
if (statusRecord.Battery.Eoc)
|
if (statusRecord.Battery.Eoc)
|
||||||
{
|
{
|
||||||
"Batteries have reached EOC".Log();
|
"Batteries have reached EOC".LogInfo();
|
||||||
config.LastEoc = UnixTime.Now;
|
config.LastEoc = UnixTime.Now;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,18 @@ public static class Logger
|
||||||
private const Int32 MaxLogFileCount = 1000; // TODO: move to settings
|
private const Int32 MaxLogFileCount = 1000; // TODO: move to settings
|
||||||
private const String LogFilePath = "LogDirectory/log.txt"; // TODO: move to settings
|
private const String LogFilePath = "LogDirectory/log.txt"; // TODO: move to settings
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxFileSizeBytes, MaxLogFileCount);
|
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxFileSizeBytes, MaxLogFileCount);
|
||||||
|
|
||||||
public static T Log<T>(this T t) where T : notnull
|
public static T LogInfo<T>(this T t) where T : notnull
|
||||||
{
|
{
|
||||||
// _logger.LogInformation(t.ToString()); // TODO: check warning
|
_logger.LogInformation(t.ToString()); // TODO: check warning
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T LogDebug<T>(this T t) where T : notnull
|
||||||
|
{
|
||||||
|
_logger.LogDebug(t.ToString()); // TODO: check warning
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,20 +36,23 @@ internal static class Program
|
||||||
private static readonly Byte[] BatteryNodes = { 2, 3, 4, 5, 6 };
|
private static readonly Byte[] BatteryNodes = { 2, 3, 4, 5, 6 };
|
||||||
private const String BatteryTty = "/dev/ttyUSB0";
|
private const String BatteryTty = "/dev/ttyUSB0";
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("localhost", 5001);
|
||||||
|
private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("localhost", 5002);
|
||||||
|
private static readonly TcpChannel GridMeterChannel = new TcpChannel("localhost", 5003);
|
||||||
|
private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("localhost", 5004);
|
||||||
|
private static readonly TcpChannel AmptChannel = new TcpChannel("localhost", 5005);
|
||||||
|
private static readonly TcpChannel RelaysChannel = new TcpChannel("localhost", 5006);
|
||||||
|
private static readonly TcpChannel BatteriesChannel = new TcpChannel("localhost", 5007);
|
||||||
|
#else
|
||||||
private static readonly TcpChannel RelaysChannel = new TcpChannel("10.0.1.1", 502); // "192.168.1.242";
|
private static readonly TcpChannel RelaysChannel = new TcpChannel("10.0.1.1", 502); // "192.168.1.242";
|
||||||
private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("10.0.2.1", 502); // "192.168.1.2";
|
private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("10.0.2.1", 502); // "192.168.1.2";
|
||||||
private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("10.0.3.1", 502); // "192.168.1.3";
|
private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("10.0.3.1", 502); // "192.168.1.3";
|
||||||
private static readonly TcpChannel GridMeterChannel = new TcpChannel("10.0.4.1", 502); // "192.168.1.241";
|
private static readonly TcpChannel GridMeterChannel = new TcpChannel("10.0.4.1", 502); // "192.168.1.241";
|
||||||
private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("10.0.4.2", 502); // "192.168.1.241";
|
private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("10.0.4.2", 502); // "192.168.1.241";
|
||||||
private static readonly TcpChannel AmptChannel = new TcpChannel("10.0.5.1", 502); // "192.168.1.249";
|
private static readonly TcpChannel AmptChannel = new TcpChannel("10.0.5.1", 502); // "192.168.1.249";
|
||||||
|
private static readonly TcpChannel BatteriesChannel = new TcpChannel("localhost", 5007);
|
||||||
//private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("localhost", 5001);
|
#endif
|
||||||
//private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("localhost", 5002);
|
|
||||||
//private static readonly TcpChannel GridMeterChannel = new TcpChannel("localhost", 5003);
|
|
||||||
//private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("localhost", 5004);
|
|
||||||
//private static readonly TcpChannel AmptChannel = new TcpChannel("localhost", 5005);
|
|
||||||
//private static readonly TcpChannel RelaysChannel = new TcpChannel("localhost", 5006);
|
|
||||||
//private static readonly TcpChannel BatteriesChannel = new TcpChannel("localhost", 5007);
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly S3Config S3Config = new S3Config
|
private static readonly S3Config S3Config = new S3Config
|
||||||
|
@ -165,7 +168,6 @@ internal static class Program
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("press ctrl-C to stop");
|
Console.WriteLine("press ctrl-C to stop");
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
sd_notify(0, "WATCHDOG=1");
|
sd_notify(0, "WATCHDOG=1");
|
||||||
|
@ -181,15 +183,6 @@ internal static class Program
|
||||||
if (record.Relays is not null)
|
if (record.Relays is not null)
|
||||||
record.Relays.ToCsv().WriteLine();
|
record.Relays.ToCsv().WriteLine();
|
||||||
|
|
||||||
|
|
||||||
var emuMeterRegisters = record.GridMeter;
|
|
||||||
if (emuMeterRegisters is not null)
|
|
||||||
{
|
|
||||||
emuMeterRegisters.Ac.Power.Active.WriteLine("Grid Active");
|
|
||||||
//emuMeterRegisters.Ac.Power.Reactive.WriteLine("Grid Reactive");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
record.ControlConstants();
|
record.ControlConstants();
|
||||||
|
|
||||||
record.ControlSystemState();
|
record.ControlSystemState();
|
||||||
|
@ -275,9 +268,22 @@ internal static class Program
|
||||||
var gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid");
|
var gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid");
|
||||||
var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("Inverter");
|
var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("Inverter");
|
||||||
var dcDcBox = TextBlock.AlignLeft(dc48Voltage).TitleBox("DC/DC");
|
var dcDcBox = TextBlock.AlignLeft(dc48Voltage).TitleBox("DC/DC");
|
||||||
var batteryBox = TextBlock.AlignLeft(batteryVoltage.ToDisplayString(), batterySoc.ToDisplayString(), batteryCurrent.ToDisplayString(), batteryTemp.ToDisplayString()).TitleBox("Battery");
|
var batteryAvgBox = TextBlock.AlignLeft(batteryVoltage.ToDisplayString(),
|
||||||
|
batterySoc.ToDisplayString(),
|
||||||
|
batteryCurrent.ToDisplayString(),
|
||||||
|
batteryTemp.ToDisplayString())
|
||||||
|
.TitleBox("Battery");
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////// Batteries /////////////////////////
|
||||||
|
|
||||||
|
IReadOnlyList<TextBlock> batteryBoxes = s.Battery
|
||||||
|
.Devices
|
||||||
|
.Select(CreateIndividualBattery)
|
||||||
|
.ToArray(s.Battery.Devices.Count);
|
||||||
|
|
||||||
|
var individualBatteries = TextBlock.AlignLeft(batteryBoxes);
|
||||||
|
|
||||||
|
|
||||||
var totalBoxes = TextBlock.CenterVertical(gridBox,
|
var totalBoxes = TextBlock.CenterVertical(gridBox,
|
||||||
gridBusFlow,
|
gridBusFlow,
|
||||||
|
@ -291,11 +297,25 @@ internal static class Program
|
||||||
flowDcBusToDcDc,
|
flowDcBusToDcDc,
|
||||||
dcDcBox,
|
dcDcBox,
|
||||||
flowDcDcToBattery,
|
flowDcDcToBattery,
|
||||||
batteryBox);
|
batteryAvgBox,
|
||||||
|
individualBatteries);
|
||||||
totalBoxes.WriteLine();
|
totalBoxes.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TextBlock CreateIndividualBattery(Battery48TlRecord battery, Int32 i)
|
||||||
|
{
|
||||||
|
var content = TextBlock.AlignLeft(battery.Dc.Voltage.ToDisplayString(),
|
||||||
|
battery.Soc.ToDisplayString(),
|
||||||
|
battery.Dc.Current.ToDisplayString(),
|
||||||
|
battery.Temperatures.Heating);
|
||||||
|
|
||||||
|
var box = content.TitleBox($"Battery {i + 1}");
|
||||||
|
|
||||||
|
var flow = Flow.Horizontal(battery.Dc.Power);
|
||||||
|
|
||||||
|
return TextBlock.CenterVertical(flow, box);
|
||||||
|
}
|
||||||
|
|
||||||
private static TextBlock ColumnBox(String pvTitle, String busTitle, String loadTitle, TextBlock dataBox)
|
private static TextBlock ColumnBox(String pvTitle, String busTitle, String loadTitle, TextBlock dataBox)
|
||||||
{
|
{
|
||||||
return ColumnBox(pvTitle, busTitle, loadTitle, dataBox, 0);
|
return ColumnBox(pvTitle, busTitle, loadTitle, dataBox, 0);
|
||||||
|
|
|
@ -18,7 +18,7 @@ public class RelaysDevice
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
$"Failed to read from {nameof(RelaysDevice)}\n{e}".Log();
|
$"Failed to read from {nameof(RelaysDevice)}\n{e}".LogInfo();
|
||||||
|
|
||||||
// TODO: log
|
// TODO: log
|
||||||
return null;
|
return null;
|
||||||
|
@ -33,7 +33,7 @@ public class RelaysDevice
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
$"Failed to write to {nameof(RelaysDevice)}\n{e}".Log();
|
$"Failed to write to {nameof(RelaysDevice)}\n{e}".LogInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,11 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
||||||
LastEoc = UnixTime.Epoch,
|
LastEoc = UnixTime.Epoch,
|
||||||
PConstant = .5,
|
PConstant = .5,
|
||||||
GridSetPoint = 0,
|
GridSetPoint = 0,
|
||||||
BatterySelfDischargePower = 200, // TODO: multiple batteries
|
BatterySelfDischargePower = 200, // TODO: multiple batteries // no need as we multiplied by the number of the batteries
|
||||||
HoldSocZone = 1, // TODO: find better name,
|
HoldSocZone = 1, // TODO: find better name,
|
||||||
MinDcBusVoltage = 730,
|
MinDcBusVoltage = 690,
|
||||||
ReferenceDcBusVoltage = 750,
|
ReferenceDcBusVoltage = 750,
|
||||||
MaxDcBusVoltage = 770,
|
MaxDcBusVoltage = 810,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
$"Failed to write config file {configFilePath}\n{e}".Log();
|
$"Failed to write config file {configFilePath}\n{e}".LogInfo();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
$"Failed to read config file {configFilePath}, using default config\n{e}".Log();
|
$"Failed to read config file {configFilePath}, using default config\n{e}".LogInfo();
|
||||||
return Default;
|
return Default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<IsTrimmable>true</IsTrimmable>
|
<IsTrimmable>true</IsTrimmable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
||||||
<RootNamespace>Please.reload.the.project.Rider.is.stupid</RootNamespace>
|
<RootNamespace>Please.reload.the.project.Rider.is.stupid</RootNamespace>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<!-- <Import Project="../../InnovEnergy.Lib.props"/>-->
|
<Import Project="../../InnovEnergy.Lib.props"/>
|
||||||
|
|
||||||
<Import Project="../../../App/InnovEnergy.App.props" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
|
||||||
<Configurations>Debug;Release;Release-Server</Configurations>
|
|
||||||
<Platforms>AnyCPU;linux-arm</Platforms>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="../../InnovEnergy.Lib.props" />
|
<Import Project="../../InnovEnergy.Lib.props" />
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<!-- <Import Project="../../InnovEnergy.Lib.props" />-->
|
<Import Project="../../InnovEnergy.Lib.props" />
|
||||||
|
|
||||||
<Import Project="../../../App/InnovEnergy.App.props" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<!-- <Import Project="../../InnovEnergy.Lib.props" />-->
|
<Import Project="../../InnovEnergy.Lib.props" />
|
||||||
<Import Project="../../../App/InnovEnergy.App.props" />
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />
|
||||||
<ProjectReference Include="../../StatusApi/StatusApi.csproj" />
|
<ProjectReference Include="../../StatusApi/StatusApi.csproj" />
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using InnovEnergy.Lib.Devices.Battery48TL.DataTypes;
|
using InnovEnergy.Lib.Devices.Battery48TL.DataTypes;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
using InnovEnergy.Lib.SrcGen.Attributes;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Devices.Battery48TL;
|
namespace InnovEnergy.Lib.Devices.Battery48TL;
|
||||||
#pragma warning disable CS0169, CS0649
|
#pragma warning disable CS0169, CS0649
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<!-- <Import Project="../../InnovEnergy.Lib.props" />-->
|
<Import Project="../../InnovEnergy.Lib.props" />
|
||||||
|
|
||||||
<Import Project="../../../App/InnovEnergy.App.props" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Utils/Utils.csproj" />
|
<ProjectReference Include="../../Utils/Utils.csproj" />
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
|
||||||
<Configurations>Debug;Release;Release-Server</Configurations>
|
|
||||||
<Platforms>AnyCPU;linux-arm</Platforms>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="../InnovEnergy.Lib.props" />
|
<Import Project="../InnovEnergy.Lib.props" />
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../Utils/Utils.csproj" />
|
<ProjectReference Include="../Utils/Utils.csproj" />
|
||||||
<ProjectReference Include="../Time/Time.csproj" />
|
<ProjectReference Include="../Time/Time.csproj" />
|
||||||
<ProjectReference Include="../SrcGen/SrcGen.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- <Target Name="PreBuild" BeforeTargets="PreBuildEvent">-->
|
<!-- <Target Name="PreBuild" BeforeTargets="PreBuildEvent">-->
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// TODO remove for .Net 7
|
||||||
|
namespace System.Runtime.CompilerServices
|
||||||
|
{
|
||||||
|
public class RequiredMemberAttribute : Attribute { }
|
||||||
|
public class CompilerFeatureRequiredAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CompilerFeatureRequiredAttribute(string name) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis
|
||||||
|
{
|
||||||
|
public class SetsRequiredMembersAttribute : Attribute { }
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ public class TextBlock
|
||||||
return new TextBlock(alignedLines);
|
return new TextBlock(alignedLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextBlock CenterHorizontal(params Object[] things)
|
public static TextBlock CenterHorizontal(IReadOnlyList<Object> things)
|
||||||
{
|
{
|
||||||
var lines = things
|
var lines = things
|
||||||
.SelectMany(GetLines)
|
.SelectMany(GetLines)
|
||||||
|
@ -127,6 +127,10 @@ public class TextBlock
|
||||||
return new TextBlock(alignedLines);
|
return new TextBlock(alignedLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TextBlock CenterHorizontal(params Object[] things)
|
||||||
|
{
|
||||||
|
return CenterHorizontal((IReadOnlyList<Object>)things);
|
||||||
|
}
|
||||||
|
|
||||||
public static TextBlock AlignTop(params Object[] things)
|
public static TextBlock AlignTop(params Object[] things)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "6.0.0",
|
"version": "7.0.0",
|
||||||
"rollForward": "latestMajor",
|
"rollForward": "latestMajor",
|
||||||
"allowPrerelease": true
|
"allowPrerelease": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,8 @@
|
||||||
"@formatjs/cli": "^6.0.3",
|
"@formatjs/cli": "^6.0.3",
|
||||||
"@types/react-plotly.js": "^2.6.0",
|
"@types/react-plotly.js": "^2.6.0",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"eslint-plugin-formatjs": "^4.10.1"
|
"eslint-plugin-formatjs": "^4.10.1",
|
||||||
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
|
@ -19743,6 +19744,21 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "2.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||||
|
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin-prettier.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||||
|
@ -39706,6 +39722,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
|
||||||
"integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg=="
|
"integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg=="
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "2.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||||
|
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pretty-bytes": {
|
"pretty-bytes": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
"@formatjs/cli": "^6.0.3",
|
"@formatjs/cli": "^6.0.3",
|
||||||
"@types/react-plotly.js": "^2.6.0",
|
"@types/react-plotly.js": "^2.6.0",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"eslint-plugin-formatjs": "^4.10.1"
|
"eslint-plugin-formatjs": "^4.10.1",
|
||||||
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { S3Access } from "../../../dataCache/S3/S3Access";
|
||||||
import DataCache, { FetchResult } from "../../../dataCache/dataCache";
|
import DataCache, { FetchResult } from "../../../dataCache/dataCache";
|
||||||
import { LogContext } from "../../Context/LogContextProvider";
|
import { LogContext } from "../../Context/LogContextProvider";
|
||||||
import { isDefined } from "../../../dataCache/utils/maybe";
|
import { isDefined } from "../../../dataCache/utils/maybe";
|
||||||
import { Data, Layout, PlotRelayoutEvent } from "plotly.js";
|
import { Data, Icons, Layout, PlotRelayoutEvent, relayout } from "plotly.js";
|
||||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import DateRangePicker from "./DateRangePicker";
|
import DateRangePicker from "./DateRangePicker";
|
||||||
|
@ -45,6 +45,7 @@ export const fetchData = (
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
} else if (r.status === 200) {
|
} else if (r.status === 200) {
|
||||||
const text = await r.text();
|
const text = await r.text();
|
||||||
|
console.log("parsecsv", text, parseCsv(text));
|
||||||
return parseCsv(text);
|
return parseCsv(text);
|
||||||
} else {
|
} else {
|
||||||
console.error("unexpected status code");
|
console.error("unexpected status code");
|
||||||
|
@ -153,6 +154,7 @@ const ScalarGraph = () => {
|
||||||
|
|
||||||
const handleRelayout = useCallback(
|
const handleRelayout = useCallback(
|
||||||
(params: PlotRelayoutEvent) => {
|
(params: PlotRelayoutEvent) => {
|
||||||
|
console.log("relayout");
|
||||||
const xaxisRange0 = params["xaxis.range[0]"];
|
const xaxisRange0 = params["xaxis.range[0]"];
|
||||||
const xaxisRange1 = params["xaxis.range[1]"];
|
const xaxisRange1 = params["xaxis.range[1]"];
|
||||||
if (xaxisRange0 && xaxisRange1) {
|
if (xaxisRange0 && xaxisRange1) {
|
||||||
|
@ -169,10 +171,10 @@ const ScalarGraph = () => {
|
||||||
if (checkedToggles.length > 0) {
|
if (checkedToggles.length > 0) {
|
||||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
||||||
const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => {
|
const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => {
|
||||||
console.log("checkedToggles", coordinateTimeSeries, checkedToggles);
|
|
||||||
return checkedToggles.find((toggle) => toggle === path);
|
return checkedToggles.find((toggle) => toggle === path);
|
||||||
});
|
});
|
||||||
console.log("visibleGraphs", visibleGraphs);
|
|
||||||
if (visibleGraphs.length > 0) {
|
if (visibleGraphs.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: "20px" }}>
|
<div style={{ marginTop: "20px" }}>
|
||||||
|
@ -185,6 +187,8 @@ const ScalarGraph = () => {
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
{visibleGraphs.map((path) => {
|
{visibleGraphs.map((path) => {
|
||||||
const isScalar = isNumeric(coordinateTimeSeries[path].y[0]);
|
const isScalar = isNumeric(coordinateTimeSeries[path].y[0]);
|
||||||
|
console.log("graphdata", timeSeries, coordinateTimeSeries);
|
||||||
|
|
||||||
const data = isScalar
|
const data = isScalar
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
@ -202,7 +206,6 @@ const ScalarGraph = () => {
|
||||||
barnorm: "percent",
|
barnorm: "percent",
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
console.log("graphdata", coordinateTimeSeries);
|
|
||||||
return (
|
return (
|
||||||
<Plot
|
<Plot
|
||||||
key={path}
|
key={path}
|
||||||
|
@ -227,6 +230,8 @@ const ScalarGraph = () => {
|
||||||
"select2d",
|
"select2d",
|
||||||
"pan2d",
|
"pan2d",
|
||||||
"autoScale2d",
|
"autoScale2d",
|
||||||
|
"resetScale2d",
|
||||||
|
"zoom2d",
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
onRelayout={handleRelayout}
|
onRelayout={handleRelayout}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import {Box} from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import {getBoxColor} from "../../../util/graph.util";
|
import { getBoxColor } from "../../../util/graph.util";
|
||||||
|
|
||||||
|
export interface BoxDataValue {
|
||||||
|
unit: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
export type BoxData = {
|
export type BoxData = {
|
||||||
label: string;
|
label: string;
|
||||||
values: (string | number)[];
|
values: BoxDataValue[];
|
||||||
unit: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TopologyBoxProps = {
|
export type TopologyBoxProps = {
|
||||||
|
@ -18,7 +22,7 @@ const isInt = (value: number) => {
|
||||||
|
|
||||||
export const BOX_SIZE = 85;
|
export const BOX_SIZE = 85;
|
||||||
const TopologyBox = (props: TopologyBoxProps) => {
|
const TopologyBox = (props: TopologyBoxProps) => {
|
||||||
const {titleColor, boxColor} = getBoxColor(props.title);
|
const { titleColor, boxColor } = getBoxColor(props.title);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -54,19 +58,22 @@ const TopologyBox = (props: TopologyBoxProps) => {
|
||||||
>
|
>
|
||||||
{props.data && (
|
{props.data && (
|
||||||
<>
|
<>
|
||||||
{props.data.values.map((value, index) => {
|
{props.data.values.map((boxData, index) => {
|
||||||
|
console.log("boxData", boxData);
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
key={props.title + '-' + index}
|
key={props.title + "-" + index}
|
||||||
style={{marginBlockStart: "0", marginBlockEnd: "2px"}}
|
style={{ marginBlockStart: "0", marginBlockEnd: "2px" }}
|
||||||
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||||
>{`${
|
>{`${
|
||||||
props.data && props.data.values.length === 3
|
props.data && props.data.values.length === 3
|
||||||
? "L" + (index + 1) + " "
|
? "L" + (index + 1) + " "
|
||||||
: ""
|
: ""
|
||||||
}${
|
}${
|
||||||
!isInt(Number(value)) ? Number(value).toPrecision(4) : value
|
!isInt(Number(boxData.value))
|
||||||
}${props.data?.unit}`}</p>
|
? Number(boxData.value).toPrecision(4)
|
||||||
|
: boxData.value
|
||||||
|
}${boxData.unit}`}</p>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -43,9 +43,9 @@ const TopologyFlow = (props: TopologyFlowProps) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{values
|
{values
|
||||||
?.filter((value) => (value as number) !== 0)
|
?.filter((boxData) => (boxData.value as number) !== 0)
|
||||||
.map(
|
.map(
|
||||||
(value) => `${Math.round(value as number)} ${props.data?.unit}`
|
(boxData) => `${Math.round(boxData.value as number)} ${boxData.unit}`
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { SkipList } from "./skipList/skipList";
|
||||||
import { createDispatchQueue } from "./promiseQueue";
|
import { createDispatchQueue } from "./promiseQueue";
|
||||||
import { SkipListNode } from "./skipList/skipListNode";
|
import { SkipListNode } from "./skipList/skipListNode";
|
||||||
import { RecordSeries } from "./data";
|
import { RecordSeries } from "./data";
|
||||||
import { Maybe, isUndefined } from "./utils/maybe";
|
import { isUndefined, Maybe } from "./utils/maybe";
|
||||||
import { isNumber } from "./utils/runtimeTypeChecking";
|
import { isNumber } from "./utils/runtimeTypeChecking";
|
||||||
import { CsvEntry } from "../util/graph.util";
|
import { CsvEntry } from "../util/graph.util";
|
||||||
|
|
||||||
|
@ -104,20 +104,24 @@ export default class DataCache<T extends Record<string, CsvEntry>> {
|
||||||
const n = after.index - t;
|
const n = after.index - t;
|
||||||
const pn = p + n;
|
const pn = p + n;
|
||||||
|
|
||||||
let interpolated: Partial<Record<string, CsvEntry>> = {};
|
let interpolated: Record<string, CsvEntry> = {};
|
||||||
|
|
||||||
//What about string nodes? like Alarms
|
//What about string nodes? like Alarms
|
||||||
for (const k of Object.keys(dataBefore)) {
|
for (const k of Object.keys(dataBefore)) {
|
||||||
const beforeData = dataBefore[k].value;
|
const valueBefore = dataBefore[k].value;
|
||||||
const afterData = Number(dataAfter[k].value);
|
const valueAfter = dataAfter[k]?.value as Maybe<number | string>;
|
||||||
let foo = interpolated[k];
|
|
||||||
if (foo) {
|
let value: number | string;
|
||||||
foo.value = isNumber(beforeData)
|
|
||||||
? (beforeData * n + afterData * p) / pn
|
if (isUndefined(valueAfter)) {
|
||||||
: n < p
|
value = valueBefore;
|
||||||
? afterData
|
} else if (isNumber(valueBefore) && isNumber(valueAfter)) {
|
||||||
: beforeData;
|
value = (valueBefore * n + valueAfter * p) / pn;
|
||||||
|
} else {
|
||||||
|
value = n < p ? valueAfter : valueBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interpolated[k] = { value, unit: dataBefore[k].unit };
|
||||||
}
|
}
|
||||||
|
|
||||||
return interpolated as T;
|
return interpolated as T;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Datum, TypedArray } from "plotly.js";
|
import { Datum, TypedArray } from "plotly.js";
|
||||||
import {
|
import { TreeElement } from "../components/Installations/Log/CheckboxTree";
|
||||||
TreeElement,
|
|
||||||
} from "../components/Installations/Log/CheckboxTree";
|
|
||||||
import { TimeRange, UnixTime } from "../dataCache/time";
|
import { TimeRange, UnixTime } from "../dataCache/time";
|
||||||
import { DataPoint, DataRecord } from "../dataCache/data";
|
import { DataPoint, DataRecord } from "../dataCache/data";
|
||||||
import { isDefined } from "../dataCache/utils/maybe";
|
import { isDefined } from "../dataCache/utils/maybe";
|
||||||
import { BoxData } from "../components/Installations/Log/TopologyBox";
|
import {
|
||||||
|
BoxData,
|
||||||
|
BoxDataValue,
|
||||||
|
} from "../components/Installations/Log/TopologyBox";
|
||||||
|
|
||||||
export interface GraphCoordinates {
|
export interface GraphCoordinates {
|
||||||
x: Datum[] | Datum[][] | TypedArray;
|
x: Datum[] | Datum[][] | TypedArray;
|
||||||
|
@ -30,6 +31,7 @@ export const createTimes = (
|
||||||
);
|
);
|
||||||
return roundedRange.sample(oneSpan);
|
return roundedRange.sample(oneSpan);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
[path: string]: GraphCoordinates;
|
[path: string]: GraphCoordinates;
|
||||||
}
|
}
|
||||||
|
@ -95,6 +97,7 @@ interface BoxColor {
|
||||||
titleColor: string;
|
titleColor: string;
|
||||||
boxColor: string;
|
boxColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBoxColor = (boxTitle?: string): BoxColor => {
|
export const getBoxColor = (boxTitle?: string): BoxColor => {
|
||||||
switch (boxTitle) {
|
switch (boxTitle) {
|
||||||
case "Grid":
|
case "Grid":
|
||||||
|
@ -138,18 +141,33 @@ export const extractTopologyValues = (
|
||||||
const timeSeriesValue = timeSeriesData.value;
|
const timeSeriesValue = timeSeriesData.value;
|
||||||
if (isDefined(timeSeriesValue)) {
|
if (isDefined(timeSeriesValue)) {
|
||||||
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
||||||
let topologyValues: (string | number)[];
|
let topologyValues: BoxDataValue[];
|
||||||
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
|
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
|
||||||
(topologyPath) => timeSeriesValue[topologyPath]
|
(topologyPath) => timeSeriesValue[topologyPath]
|
||||||
);
|
);
|
||||||
switch (topologyKey as keyof TopologyValues) {
|
switch (topologyKey as keyof TopologyValues) {
|
||||||
case "gridToAcInConnection":
|
case "gridToAcInConnection":
|
||||||
topologyValues = [
|
topologyValues = [
|
||||||
values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0),
|
values.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
return {
|
||||||
|
value: Number(acc.value) + Number(curr.value),
|
||||||
|
unit: acc.unit,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ value: 0, unit: values[0].unit }
|
||||||
|
),
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
topologyValues = values.map((element) => element.value);
|
topologyValues = values
|
||||||
|
.filter((element) => element)
|
||||||
|
.map((element) => {
|
||||||
|
if (element) {
|
||||||
|
return { value: element.value, unit: element.unit };
|
||||||
|
}
|
||||||
|
return { value: 0, unit: "" };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
|
@ -158,7 +176,6 @@ export const extractTopologyValues = (
|
||||||
label: topologyPaths[topologyKey as keyof TopologyValues][0]
|
label: topologyPaths[topologyKey as keyof TopologyValues][0]
|
||||||
.split("/")
|
.split("/")
|
||||||
.pop(),
|
.pop(),
|
||||||
unit: values[0].unit,
|
|
||||||
} as BoxData,
|
} as BoxData,
|
||||||
};
|
};
|
||||||
}, {} as TopologyValues);
|
}, {} as TopologyValues);
|
||||||
|
@ -171,16 +188,22 @@ export const getHighestConnectionValue = (values: TopologyValues) =>
|
||||||
.filter((value) => value.includes("Connection"))
|
.filter((value) => value.includes("Connection"))
|
||||||
.reduce((acc, curr) => {
|
.reduce((acc, curr) => {
|
||||||
const value = Math.abs(
|
const value = Math.abs(
|
||||||
values[curr as keyof TopologyValues].values[0] as number
|
values[curr as keyof TopologyValues].values[0].value as number
|
||||||
);
|
);
|
||||||
return value > acc ? value : acc;
|
return value > acc ? value : acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
export const getAmount = (
|
export const getAmount = (
|
||||||
highestConnectionValue: number,
|
highestConnectionValue: number,
|
||||||
values: (string | number)[]
|
values: BoxDataValue[]
|
||||||
) => {
|
) => {
|
||||||
return Math.abs(values[0] as number) / highestConnectionValue;
|
console.log(
|
||||||
|
"getamount",
|
||||||
|
Math.abs(values[0].value as number) / highestConnectionValue,
|
||||||
|
Math.abs(values[0].value as number),
|
||||||
|
highestConnectionValue
|
||||||
|
);
|
||||||
|
return Math.abs(values[0].value as number) / highestConnectionValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CsvEntry {
|
export interface CsvEntry {
|
||||||
|
@ -189,23 +212,26 @@ export interface CsvEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseCsv = (text: string): DataRecord => {
|
export const parseCsv = (text: string): DataRecord => {
|
||||||
|
console.log("parseText", text);
|
||||||
const y = text
|
const y = text
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.filter((split) => split.length > 0)
|
.filter((split) => split.length > 0)
|
||||||
.map((l) => {
|
.map((l) => {
|
||||||
return l.split(";");
|
return l.split(";");
|
||||||
});
|
});
|
||||||
const x = y
|
return y
|
||||||
.map((fields) => {
|
.map((fields) => {
|
||||||
if (fields[0] === "/AcDc/Warnings")
|
if (fields[0].includes("LoadOnAcIsland")) {
|
||||||
console.log("warnings", fields[1], isNaN(+fields[1]));
|
console.log("fields", fields, {
|
||||||
|
[fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] },
|
||||||
|
});
|
||||||
|
}
|
||||||
if (isNaN(Number(fields[1])) || fields[1] === "") {
|
if (isNaN(Number(fields[1])) || fields[1] === "") {
|
||||||
return { [fields[0]]: { value: fields[1], unit: fields[2] } };
|
return { [fields[0]]: { value: fields[1], unit: fields[2] } };
|
||||||
}
|
}
|
||||||
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
|
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
|
||||||
})
|
})
|
||||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||||
return x;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertTreeElements = (
|
export const insertTreeElements = (
|
||||||
|
|
Loading…
Reference in New Issue