This commit is contained in:
Noe 2023-06-30 16:15:41 +02:00
commit 869a83b799
26 changed files with 294 additions and 164 deletions

View File

@ -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
@ -11,7 +11,7 @@ echo -e "\n============================ Build ============================\n"
dotnet publish \ dotnet publish \
./SaliMax.csproj \ ./SaliMax.csproj \
-c Release \ -c Release \
-r linux-x64 -r linux-x64
echo -e "\n============================ Deploy ============================\n" echo -e "\n============================ Deploy ============================\n"

View File

@ -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

View File

@ -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
@ -13,7 +12,7 @@ dotnet publish \
./SaliMax.csproj \ ./SaliMax.csproj \
-p:PublishTrimmed=false \ -p:PublishTrimmed=false \
-c Release \ -c Release \
-r linux-x64 -r linux-x64
echo -e "\n============================ Deploy ============================\n" echo -e "\n============================ Deploy ============================\n"

View File

@ -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;
} }

View File

@ -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;
} }
} }

View File

@ -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");
@ -180,16 +182,7 @@ 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();
@ -272,12 +265,25 @@ internal static class Program
var flowDcBusToDcDc = Flow.Horizontal(dcdcPower); var flowDcBusToDcDc = Flow.Horizontal(dcdcPower);
var flowDcDcToBattery = Flow.Horizontal(dcBatteryPower); var flowDcDcToBattery = Flow.Horizontal(dcBatteryPower);
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);

View File

@ -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();
} }
} }
} }

View File

@ -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;
} }
} }

View File

@ -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>

View File

@ -1,8 +1,6 @@
<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="../../Utils/Utils.csproj" /> <ProjectReference Include="../../Utils/Utils.csproj" />

View File

@ -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>

View File

@ -1,8 +1,6 @@
<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="../../Units/Units.csproj" /> <ProjectReference Include="../../Units/Units.csproj" />

View File

@ -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" />

View File

@ -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

View File

@ -1,8 +1,6 @@
<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" />
<ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" /> <ProjectReference Include="../../Protocols/Modbus/Modbus.csproj" />

View File

@ -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">-->

View File

@ -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 { }
}

View File

@ -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)
{ {

View File

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "6.0.0", "version": "7.0.0",
"rollForward": "latestMajor", "rollForward": "latestMajor",
"allowPrerelease": true "allowPrerelease": true
} }

View File

@ -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",

View File

@ -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"
} }
} }

View File

@ -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}

View File

@ -1,79 +1,86 @@
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 = {
title?: string; title?: string;
data?: BoxData; data?: BoxData;
}; };
const isInt = (value: number) => { const isInt = (value: number) => {
return value % 1 === 0; return value % 1 === 0;
}; };
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={{
visibility: props.title ? "visible" : "hidden", visibility: props.title ? "visible" : "hidden",
height: BOX_SIZE + "px", height: BOX_SIZE + "px",
width: BOX_SIZE + "px", width: BOX_SIZE + "px",
borderRadius: "4px", borderRadius: "4px",
color: "white", color: "white",
}} }}
> >
<p <p
style={{ style={{
marginBlockStart: "0", marginBlockStart: "0",
marginBlockEnd: "0", marginBlockEnd: "0",
backgroundColor: titleColor, backgroundColor: titleColor,
padding: "5px", padding: "5px",
borderTopLeftRadius: "4px", borderTopLeftRadius: "4px",
borderTopRightRadius: "4px", borderTopRightRadius: "4px",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
}} }}
> >
{props.title} {props.title}
</p> </p>
<div <div
style={{ style={{
backgroundColor: boxColor, backgroundColor: boxColor,
borderBottomLeftRadius: "4px", borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px", borderBottomRightRadius: "4px",
padding: "5px", padding: "5px",
height: "51px", height: "51px",
}} }}
> >
{props.data && ( {props.data && (
<> <>
{props.data.values.map((value, index) => { {props.data.values.map((boxData, index) => {
return ( console.log("boxData", boxData);
<p return (
key={props.title + '-' + index} <p
style={{marginBlockStart: "0", marginBlockEnd: "2px"}} key={props.title + "-" + index}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx style={{ marginBlockStart: "0", marginBlockEnd: "2px" }}
>{`${ // eslint-disable-next-line formatjs/no-literal-string-in-jsx
props.data && props.data.values.length === 3 >{`${
? "L" + (index + 1) + " " props.data && props.data.values.length === 3
: "" ? "L" + (index + 1) + " "
}${ : ""
!isInt(Number(value)) ? Number(value).toPrecision(4) : value }${
}${props.data?.unit}`}</p> !isInt(Number(boxData.value))
); ? Number(boxData.value).toPrecision(4)
})} : boxData.value
</> }${boxData.unit}`}</p>
)} );
</div> })}
</Box> </>
); )}
</div>
</Box>
);
}; };
export default TopologyBox; export default TopologyBox;

View File

@ -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

View File

@ -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;

View File

@ -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 = (