This commit is contained in:
Sina Blattmann 2023-04-24 09:36:18 +02:00
commit 6fc414a2c0
23 changed files with 289 additions and 615 deletions

View File

@ -16,7 +16,7 @@ public class Installation : TreeNode
public Double Long { get; set; } public Double Long { get; set; }
public String S3Bucket { get; set; } = ""; public String S3Bucket { get; set; } = "";
public String S3Url { get; set; } = ""; public String S3KeySecret { get; set; } = "";
} }

View File

@ -1,3 +1,5 @@
using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.S3; using InnovEnergy.App.Backend.S3;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
@ -11,17 +13,13 @@ public static class InstallationMethods
public static String BucketName(this Installation installation) public static String BucketName(this Installation installation)
{ {
return $"s3://{installation.Id}-{BucketNameSalt}"; return $"{installation.Id}-{BucketNameSalt}";
} }
public static async Task<Boolean> RenewS3BucketUrl(this Installation installation) public static async Task<Boolean> RenewS3BucketUrl(this Installation installation)
{ {
return await RenewS3BucketUrl(installation, TimeSpan.FromDays(1)); installation.RevokeKey();
} installation.S3KeySecret = await installation.CreateKey();
public static async Task<Boolean> RenewS3BucketUrl(this Installation installation, TimeSpan validity)
{
installation.S3Url = await S3Access.ReadOnly.SignUrl(installation.BucketName(), validity);
return Db.Update(installation); return Db.Update(installation);
} }
@ -128,3 +126,39 @@ public static class InstallationMethods
} }
public static class ExoCmd
{
private static readonly Command Exo = Cli.Wrap("exo");
private static String ConfigFile = "./exoscale.toml";
public static async Task<String> CreateKey(this Installation installation)
{
if (installation.Id != 1) return "help"; //Todo remove me I am for debugging
var preParse = await Exo
.WithArguments("iam access-key create " + installation.BucketName()
+ " --operation get-sos-object"
+ " --resource sos/bucket:" + installation.BucketName()
+ " -C " + ConfigFile
+ " -O text")
.ExecuteBufferedAsync();
return preParse.StandardOutput.Split("\t")[2] + ";" + preParse.StandardOutput.Split("\t")[3];
}
public static async void RevokeKey(this Installation installation)
{
try
{
await Exo
.WithArguments("iam access-key revoke " + installation.S3KeySecret.Split(";", 2)[0] + " -f " + " -C " + ConfigFile)
.ExecuteAsync();
}
catch
{
// todo Fill me there is no key for this installation
}
}
}

View File

@ -1,4 +1,4 @@
{ {
"Key": "EXO44d2979c8e570eae81ead564", "Key": "EXOb6d6dc1880cdd51f1ebc6692",
"Secret": "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU" "Secret": "kpIey4QJlQFuWG_WoTazcY7kBEjN2f_ll2cDBeg64m4"
} }

View File

@ -9,4 +9,9 @@ public static class S3Access
{ {
public static S3Cmd ReadOnly => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadOnlyKey.json"))!; public static S3Cmd ReadOnly => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadOnlyKey.json"))!;
public static S3Cmd ReadWrite => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadWriteKey.json"))!; public static S3Cmd ReadWrite => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadWriteKey.json"))!;
public static async Task<String> CreateKey(String bucketName)
{
throw new NotImplementedException();
}
} }

View File

@ -1,3 +1,4 @@
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.SaliMax; namespace InnovEnergy.App.SaliMax;
@ -68,39 +69,6 @@ public static class AsciiArt
); );
} }
public static String CreateVerticalPad(String value, Int32 contentVerticalWidth, Boolean direction, String name)
{
var v = value.PadLeft(contentVerticalWidth);
var n = name.PadLeft(contentVerticalWidth);
if (direction) // up
{
var horizontal = "".PadLeft(contentVerticalWidth - 4, ' ').V();
return StringUtils.JoinLines(
n,
horizontal,
horizontal,
v,
horizontal,
horizontal
);
}
else // down
{
var horizontal = "".PadLeft(contentVerticalWidth - 4, ' ').V();
return StringUtils.JoinLines(
horizontal,
horizontal,
v,
horizontal,
horizontal,
n
);
}
}
public static String CreateVerticalArrow(Decimal power, Int32 width = 0) public static String CreateVerticalArrow(Decimal power, Int32 width = 0)
{ {
var flow = "V".NewLine() + "V".NewLine() + power.W().NewLine() + "V".NewLine() + "V"; var flow = "V".NewLine() + "V".NewLine() + power.W().NewLine() + "V".NewLine() + "V";

View File

@ -42,6 +42,9 @@ public static class AvgBatteriesStatus
? OperatingTemperature ? OperatingTemperature
: Cold, : Cold,
TotalCurrent = stati.Average(b => b.TotalCurrent),
EocReached = stati.All(b => b.EocReached),
}; };
return new CombinedStatus<Battery48TLStatus> return new CombinedStatus<Battery48TLStatus>

View File

@ -5,7 +5,7 @@ public static class Control
public static Decimal ControlGridPower(this StatusRecord status, Decimal targetPower) public static Decimal ControlGridPower(this StatusRecord status, Decimal targetPower)
{ {
return ControlPower(status.GridMeterStatus!.ActivePowerL123, targetPower, status.SalimaxConfig!.PConstant); return ControlPower(status.GridMeterStatus!.Ac.ActivePower, targetPower, status.SalimaxConfig!.PConstant);
} }
public static Decimal ControlInverterPower(this StatusRecord status, Decimal targetInverterPower) public static Decimal ControlInverterPower(this StatusRecord status, Decimal targetInverterPower)
@ -18,7 +18,7 @@ public static class Control
public static Decimal ControlBatteryPower(this StatusRecord status, Decimal targetBatteryPower, UInt16 i = 0) //this will use the avg batteries public static Decimal ControlBatteryPower(this StatusRecord status, Decimal targetBatteryPower, UInt16 i = 0) //this will use the avg batteries
{ {
return ControlPower(status.BatteriesStatus![i].Dc.Power, targetBatteryPower, status.SalimaxConfig!.PConstant); return ControlPower(status.BatteriesStatus!.Combined.Dc.Power, targetBatteryPower, status.SalimaxConfig!.PConstant);
} }
public static Decimal ControlLowBatterySoc(this StatusRecord status) public static Decimal ControlLowBatterySoc(this StatusRecord status)
@ -29,14 +29,14 @@ public static class Control
public static Decimal LowerLimit(params Decimal[] deltas) => deltas.Max(); public static Decimal LowerLimit(params Decimal[] deltas) => deltas.Max();
public static Decimal UpperLimit(params Decimal[] deltas) => deltas.Min(); public static Decimal UpperLimit(params Decimal[] deltas) => deltas.Min();
private static Decimal HoldMinSocCurve(StatusRecord s, UInt16 i = 0) private static Decimal HoldMinSocCurve(StatusRecord s)
{ {
// TODO: explain LowSOC curve // TODO: explain LowSOC curve
var a = -2 * s.SalimaxConfig!.SelfDischargePower / s.SalimaxConfig.HoldSocZone; var a = -2 * s.SalimaxConfig!.SelfDischargePower / s.SalimaxConfig.HoldSocZone;
var b = -a * (s.SalimaxConfig.MinSoc + s.SalimaxConfig.HoldSocZone); var b = -a * (s.SalimaxConfig.MinSoc + s.SalimaxConfig.HoldSocZone);
return s.BatteriesStatus![i].Soc * a + b; //this will use the avg batteries return s.BatteriesStatus!.Combined.Soc * a + b; //this will use the avg batteries
} }
private static Decimal ControlPower(Decimal measurement, Decimal target, Decimal p) private static Decimal ControlPower(Decimal measurement, Decimal target, Decimal p)

View File

@ -5,6 +5,9 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Time.Unix;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums;
using static InnovEnergy.App.SaliMax.SaliMaxRelays.RelayState; using static InnovEnergy.App.SaliMax.SaliMaxRelays.RelayState;
@ -68,69 +71,69 @@ public static class Controller
//Grid-Tied 400V/50 Hz //Grid-Tied 400V/50 Hz
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 9, } => 9,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 10, } => 10,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 11, } => 11,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 12, } => 12,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 13, } => 13,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 14, } => 14,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 15, } => 15,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz InverterStatus.GridType: AcDcGridType.GridTied400V50Hz
} => 16, } => 16,
//Island 400V / 50Hz //Island 400V / 50Hz
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 17, } => 17,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 18, } => 18,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 19, } => 19,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 20, } => 20,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, //this is wrong SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, //this is wrong
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 21, } => 21,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 22, } => 22,
{ {
SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 23, } => 23,
{ {
SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed,
InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz InverterStatus.GridType: AcDcGridType.Island400V50Hz
} => 24, } => 24,
@ -149,7 +152,7 @@ public static class Controller
var lastEocTime = GetLastEocTime(statusRecord); var lastEocTime = GetLastEocTime(statusRecord);
var timeSinceLastEoc = UnixTime.Now - lastEocTime; var timeSinceLastEoc = UnixTime.Now - lastEocTime;
_numberOfInverters = statusRecord.InverterStatus!.NumberOfConnectedSlaves; _numberOfInverters = (UInt16)statusRecord.InverterStatus!.NumberOfConnectedSlaves ;
_mustChargeFlag = timeSinceLastEoc > MaxTimeWithoutEoc; _mustChargeFlag = timeSinceLastEoc > MaxTimeWithoutEoc;
var noGridMeter = statusRecord.GridMeterStatus == null; var noGridMeter = statusRecord.GridMeterStatus == null;
@ -263,7 +266,7 @@ public static class Controller
goal = "Calibration Charge"; goal = "Calibration Charge";
delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig.MaxInverterPower); delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig.MaxInverterPower);
} }
else if (statusRecord.AvgBatteriesStatus!.Soc < statusRecord.SalimaxConfig.MinSoc) // TODO else if (statusRecord.BatteriesStatus!.Combined.Soc < statusRecord.SalimaxConfig.MinSoc) // TODO
{ {
goal = $"reach min SOC (Min soc: {statusRecord.SalimaxConfig.MinSoc})"; goal = $"reach min SOC (Min soc: {statusRecord.SalimaxConfig.MinSoc})";
delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig
@ -285,7 +288,7 @@ public static class Controller
delta = inverterAc2DcLimitPower; delta = inverterAc2DcLimitPower;
} }
var batteryChargingLimitPower = statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus[0]!.MaxChargingPower); var batteryChargingLimitPower = statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus!.Combined.MaxChargingPower);
if (delta > batteryChargingLimitPower) if (delta > batteryChargingLimitPower)
{ {
@ -304,12 +307,12 @@ public static class Controller
} }
var batteryDischargingLimitPower = var batteryDischargingLimitPower =
statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus[0]!.MaxDischargingPower); // TODO change to avg battery statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus.Combined.MaxDischargingPower); // TODO change to avg battery
if (delta < batteryDischargingLimitPower) if (delta < batteryDischargingLimitPower)
{ {
limitReason = limitReason =
$"limited by max battery discharging power: {statusRecord.BatteriesStatus[0]!.MaxDischargingPower}";// TODO change to avg battery $"limited by max battery discharging power: {statusRecord.BatteriesStatus.Combined.MaxDischargingPower}";// TODO change to avg battery
delta = batteryDischargingLimitPower; delta = batteryDischargingLimitPower;
} }
@ -448,14 +451,21 @@ public static class Controller
} }
private static UnixTime GetLastEocTime(StatusRecord statusRecord) private static UnixTime GetLastEocTime(StatusRecord statusRecord)
{ if (statusRecord.BatteriesStatus != null)
{ {
if (statusRecord.BatteriesStatus[0]!.Soc >= 100 && statusRecord.BatteriesStatus[1]!.Soc >= 100 ) if (statusRecord.BatteriesStatus!.Combined.EocReached)
{ {
Console.WriteLine("battery has reached EOC"); Console.WriteLine("battery has reached EOC");
File.AppendAllTextAsync(Config.LogSalimaxLog, String.Join(Environment.NewLine, UnixTime.Now + "battery has reached EOC")); File.AppendAllTextAsync(Config.LogSalimaxLog,
String.Join(Environment.NewLine,
UnixTime.Now + "battery has reached EOC"));
return UnixTime.Now; return UnixTime.Now;
} }
}
else
{
Console.WriteLine("No battery Detected");
}
return statusRecord.SalimaxConfig.LastEoc; return statusRecord.SalimaxConfig.LastEoc;
} }

View File

@ -17,7 +17,7 @@ public record StatusRecord
public EmuMeterStatus? GridMeterStatus { get; init; } public EmuMeterStatus? GridMeterStatus { get; init; }
public SaliMaxRelayStatus? SaliMaxRelayStatus { get; init; } public SaliMaxRelayStatus? SaliMaxRelayStatus { get; init; }
public AmptStatus? AmptStatus { get; init; } public AmptCommunicationUnitStatus? AmptStatus { get; init; }
public EmuMeterStatus? AcInToAcOutMeterStatus { get; init; } public EmuMeterStatus? AcInToAcOutMeterStatus { get; init; }
public SalimaxConfig SalimaxConfig { get; init; } = null!; public SalimaxConfig SalimaxConfig { get; init; } = null!;
} }

View File

@ -1,26 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.Devices.AMPT;
using InnovEnergy.Lib.StatusApi;
namespace InnovEnergy.App.SaliMax.Log;
public static class Ampt
{
public static JsonObject? Log(this AmptStatus? s)
{
if (s is null)
return null;
// TODO return one AMPT device to sum all the other
return DeviceType
.PvOnDc
.CreateDevice("AMPT")
.AddProp("Current 1", s.Devices[0].Dc.Current)
.AddProp("Current 2", s.Devices[1].Dc.Current)
.AddProp("Voltage 1", s.Devices[0].Dc.Voltage)
.AddProp("Voltage 2", s.Devices[1].Dc.Voltage)
.AddProp("Power 1", s.Devices[0].Dc.Current * s.Devices[0].Dc.Voltage)
.AddProp("Power 2", s.Devices[1].Dc.Current * s.Devices[1].Dc.Voltage);
}
}

View File

@ -1,26 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.Devices.Battery48TL;
using InnovEnergy.Lib.StatusApi;
namespace InnovEnergy.App.SaliMax.Log;
public static class Battery48Tl
{
public static JsonObject? Log(this Battery48TLStatus? s)
{
if (s is null)
return null;
return DeviceType
.Battery
.CreateDevice("48TL Battery")
.AddDc48Connection(s.Dc.Current.Round3(),s.Dc.Voltage.Round3())
.AddAlarms(s.Alarms)
.AddWarnings(s.Warnings)
.AddProp("Soc", s.Soc.Round3())
.AddProp("HeaterOn", s.HeaterOn)
.AddProp("EocReached", s.EocReached)
.AddProp("BatteryCold", s.BatteryCold)
.AddProp("Temperature", s.Temperature);
}
}

View File

@ -1,46 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.Devices.EmuMeter;
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx;
using static InnovEnergy.App.SaliMax.Log.JsonUtil;
namespace InnovEnergy.App.SaliMax.Log;
public static class EmuMeter
{
public static JsonObject? Log(this EmuMeterStatus? s, DeviceType type, String serialNb)
{
if (s is null)
return null;
//
var l1 = CreateAcPhase(s.Ac.L1.Current, s.Ac.L1.Voltage, ACos(s.Ac.L1.PowerFactor));
var l2 = CreateAcPhase(s.Ac.L2.Current, s.Ac.L2.Voltage, ACos(s.Ac.L2.PowerFactor));
var l3 = CreateAcPhase(s.Ac.L3.Current, s.Ac.L3.Voltage, ACos(s.Ac.L3.PowerFactor));
var ac = new JsonObject
{
["L1"] = l1,
["L2"] = l2,
["L3"] = l3,
["Frequency"] = s.Ac.Frequency
};
var status = new JsonObject
{
["Ac"] = ac,
};
return new JsonObject { [$"EmuMeter {serialNb}"] = status };
}
private static IEnumerable<JsonObject> GetAcPhases(this EmuMeterStatus s)
{
yield return CreateAcPhase(s.Ac.L1.Current.Round3(),s.Ac.L1.Voltage.Round3(),s.Ac.L1.PowerFactor.Apply(ACos).Round3());
yield return CreateAcPhase(s.Ac.L2.Current.Round3(),s.Ac.L2.Voltage.Round3(),s.Ac.L2.PowerFactor.Apply(ACos).Round3());
yield return CreateAcPhase(s.Ac.L3.Current.Round3(),s.Ac.L3.Voltage.Round3(),s.Ac.L3.PowerFactor.Apply(ACos).Round3());
}
}

View File

@ -1,118 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.StatusApi;
namespace InnovEnergy.App.SaliMax.Log;
public static class JsonUtil
{
public static JsonObject CreateDevice(this DeviceType deviceType, String name)
{
return new JsonObject
{
{ "Name", name },
{ "Type", deviceType.ToString() }
};
}
public static JsonObject AddAcConnection(this JsonObject json, Decimal frequency, IEnumerable<JsonNode> acPhases)
{
return json.AddAcConnection(frequency, acPhases.ToArray());
}
public static JsonObject AddAcConnection(this JsonObject json, Decimal frequency, params JsonNode[] acPhases)
{
return json
.AddProp("Ac", new JsonArray(acPhases))
.AddProp("Frequency", frequency);
}
public static JsonObject AddAlarms<T>(this JsonObject json, IEnumerable<T> alarms)
{
return json.AddProp("Alarms", alarms.ToJsonArray());
}
public static JsonObject AddWarnings<T>(this JsonObject json, IEnumerable<T> warnings)
{
return json.AddProp("Warnings", warnings.ToJsonArray());
}
public static JsonObject AddProp(this JsonObject json, String key, JsonNode? value)
{
json.Add(key, value);
return json;
}
public static JsonObject AddDcConnection(this JsonObject json, Decimal current, Decimal voltage)
{
return json.AddProp("Dc", CreateDcPhase(current, voltage));
}
public static JsonObject AddDc48Connection(this JsonObject json, Decimal current, Decimal voltage)
{
return json.AddProp("Dc48", CreateDcPhase(current, voltage));
}
public static JsonObject CreateAcPhase(Decimal current, Decimal voltage, Decimal phi)
{
return new JsonObject
{
["Current"] = current,
["Voltage"] = voltage,
["Phi" ] = phi,
};
}
public static JsonObject CreateDcPhase(Decimal current, Decimal voltage)
{
return new JsonObject
{
["Current"] = current ,
["Voltage"] = voltage ,
["Power"] = current * voltage,
};
}
public static Decimal Round3(this Decimal val)
{
return Decimal.Round(val, 3);
}
public static Decimal Round0(this Decimal val)
{
return Decimal.Round(val, 0);
}
public static JsonObject CreateBus(String left, String top, String bottom, String right, String name)
{
return new JsonObject
{
["Name"] = name,
["Left"] = left,
["Top"] = top,
["Bottom"] = bottom,
["Right"] = right
};
}
public static String Port(DeviceType dt, BusPort bp, Boolean display = true)
{
return $"{Enum.GetName(dt)}:{Enum.GetName(bp)}:{(display ? "show" : "hide")}";
}
public static JsonArray ToJsonArray<T>(this IEnumerable<T> things)
{
var jsonValues = things
.Select(t => t!.ToString())
.Select(t => JsonValue.Create(t))
.OfType<JsonNode>()
.ToArray();
return new JsonArray(jsonValues);
}
}

View File

@ -1,93 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.App.SaliMax.Controller;
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.Time.Unix;
namespace InnovEnergy.App.SaliMax.Log;
public static class Salimax
{
public static JsonObject ToLog(this StatusRecord s, UnixTime timestamp)
{
return new JsonObject
{
{ "TimeStamp", timestamp.ToString()! },
{ "Devices", CreateDevices(s) }
};
}
private static JsonArray CreateDevices(StatusRecord s)
{
var devices = GetDevices(s).Where(d => d != null).ToArray();
return new JsonArray(devices);
}
private static IEnumerable<JsonNode?> GetDevices(StatusRecord s)
{
yield return s.InverterStatus.Log();
yield return s.DcDcStatus.Log("3214");
yield return s.GridMeterStatus.Log(DeviceType.Grid , "123");
yield return s.AcInToAcOutMeterStatus.Log(DeviceType.AcInToAcOut, "123");
yield return s.AmptStatus.Log();
yield return s.BatteriesStatus![0].Log();
yield return s.BatteriesStatus[1].Log();
}
private static JsonArray CreateTopology()
{
var acInBusJson = JsonUtil.CreateBus
(
name: "AcIn",
left: JsonUtil.Port(DeviceType.Grid, BusPort.Ac),
top: JsonUtil.Port(DeviceType.PvOnAcIn, BusPort.Ac),
bottom: JsonUtil.Port(DeviceType.Load, BusPort.Infer),
right: JsonUtil.Port(DeviceType.AcInToAcOut, BusPort.Ac, false)
);
var acOutBusJson = JsonUtil.CreateBus
(
name: "AcOut",
left: JsonUtil.Port(DeviceType.AcInToAcOut, BusPort.Ac, false),
top: JsonUtil.Port(DeviceType.PvOnAcOut, BusPort.Ac),
bottom: JsonUtil.Port(DeviceType.CriticalLoad, BusPort.Infer),
right: JsonUtil.Port(DeviceType.Inverter, BusPort.Ac)
);
var inverterJson = JsonUtil.CreateBus
(
name: "Inverter",
left: JsonUtil.Port(DeviceType.Inverter, BusPort.Ac),
top: JsonUtil.Port(DeviceType.None, BusPort.None),
bottom: JsonUtil.Port(DeviceType.Losses, BusPort.Infer),
right: JsonUtil.Port(DeviceType.Inverter, BusPort.Dc)
);
var dcBusJson = JsonUtil.CreateBus
(
name: "Dc",
left: JsonUtil.Port(DeviceType.Inverter, BusPort.Dc),
top: JsonUtil.Port(DeviceType.PvOnDc, BusPort.Dc),
bottom: JsonUtil.Port(DeviceType.DcLoad, BusPort.Infer),
right: JsonUtil.Port(DeviceType.DcDc, BusPort.Dc)
);
var dcDcJson = JsonUtil.CreateBus
(
name: "DcDc",
left: JsonUtil.Port(DeviceType.DcDc, BusPort.Dc),
top: JsonUtil.Port(DeviceType.None, BusPort.None),
bottom: JsonUtil.Port(DeviceType.Losses, BusPort.Infer),
right: JsonUtil.Port(DeviceType.Battery, BusPort.Dc)
);
return new JsonArray(acInBusJson, acOutBusJson, inverterJson, dcBusJson, dcDcJson);
}
public static JsonObject TopologyToLog(UnixTime timestamp)
{
return new JsonObject
{
{ "Topology", CreateTopology() }
};
}
}

View File

@ -1,65 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx;
using static InnovEnergy.App.SaliMax.Log.JsonUtil;
namespace InnovEnergy.App.SaliMax.Log;
public static class TruConvertAc
{
public static JsonObject? Log(this TruConvertAcStatus? s)
{
if (s is null)
return null;
var dcPower = s.Ac.ActivePower;
var dcVoltage = s.ActualDcLinkVoltageLowerHalfExt + s.ActualDcLinkVoltageUpperHalfExt;
var dcCurrent = dcVoltage != 0m
? dcPower / dcVoltage
: 0m;
// TODO: acos quadrant
// TODO: total AC power
var l1 = CreateAcPhase(s.Ac.L1.Current.Round3(), s.Ac.L1.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
var l2 = CreateAcPhase(s.Ac.L2.Current.Round3(), s.Ac.L2.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
var l3 = CreateAcPhase(s.Ac.L3.Current.Round3(), s.Ac.L3.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
var ac = new JsonObject
{
["L1"] = l1,
["L2"] = l2,
["L3"] = l3,
["Frequency"] = s.Ac.Frequency
};
var dc = CreateDcPhase(dcCurrent, dcVoltage);
var status = new JsonObject
{
["Ac"] = ac ,
["Dc"] = dc ,
["Warnings"] = s.Warnings.ToJsonArray() ,
["Alarms"] = s.Alarms.ToJsonArray() ,
};
return new JsonObject { [$"TruConvertAc {s.SerialNumber}"] = status };
}
private static IEnumerable<JsonObject> GetAcPhases(this TruConvertAcStatus s)
{
// Math.Acos return "NaN" if the cos phi < -1 or > 1
// Decimal.Acos throw an exception
yield return JsonUtil.CreateAcPhase(s.Ac.L1.Current.Round3(), s.Ac.L1.Voltage.Round3(),
s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
yield return JsonUtil.CreateAcPhase(s.Ac.L2.Current.Round3(), s.Ac.L2.Voltage.Round3(),
s.Ac.L2.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
yield return JsonUtil.CreateAcPhase(s.Ac.L3.Current.Round3(), s.Ac.L3.Voltage.Round3(),
s.Ac.L3.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3());
}
}

View File

@ -1,32 +0,0 @@
using System.Text.Json.Nodes;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
using static InnovEnergy.App.SaliMax.Log.JsonUtil;
namespace InnovEnergy.App.SaliMax.Log;
using JO = JsonObject;
public static class TruConvertDc
{
// TODO: remove serialNb arg, embed TruConvertDcStatus
public static JsonObject? Log(this TruConvertDcStatus? s, String serialNb)
{
if (s is null)
return null;
var dcCurrent = s.Dc.Current;
return new JO
{
{
$"TruConvertDc {serialNb}", new JO
{
{ "Dc" , CreateDcPhase(dcCurrent, s.Dc.Voltage) },
{ "Dc48" , CreateDcPhase(s.BatteryCurrent, s.BatteryVoltage) },
{ "Warnings", s.Warnings.ToJsonArray() },
{ "Alarms" , s.Alarms.ToJsonArray() },
}
}
};
}
}

View File

@ -4,7 +4,6 @@ using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Flurl.Http; using Flurl.Http;
using InnovEnergy.App.SaliMax.Controller; using InnovEnergy.App.SaliMax.Controller;
using InnovEnergy.App.SaliMax.Log;
using InnovEnergy.App.SaliMax.SaliMaxRelays; using InnovEnergy.App.SaliMax.SaliMaxRelays;
using InnovEnergy.App.SaliMax.SystemConfig; using InnovEnergy.App.SaliMax.SystemConfig;
using InnovEnergy.Lib.Devices.AMPT; using InnovEnergy.Lib.Devices.AMPT;
@ -109,7 +108,6 @@ internal static class Program
{ {
InverterStatus = inverterDevice.ReadStatus(), InverterStatus = inverterDevice.ReadStatus(),
DcDcStatus = dcDcDevice.ReadStatus(), DcDcStatus = dcDcDevice.ReadStatus(),
BatteriesStatus = combinedBatteryStatus, BatteriesStatus = combinedBatteryStatus,
AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(), AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(),
GridMeterStatus = gridMeterDevice.ReadStatus(), GridMeterStatus = gridMeterDevice.ReadStatus(),
@ -123,10 +121,6 @@ internal static class Program
var startTime = UnixTime.Now; var startTime = UnixTime.Now;
const Int32 delayTime = 10; const Int32 delayTime = 10;
await UploadTopology(s3Config, Salimax.TopologyToLog(startTime), startTime);
DebugWriteTopology(startTime);
Console.WriteLine("press ctrl-C to stop"); Console.WriteLine("press ctrl-C to stop");
@ -167,21 +161,6 @@ internal static class Program
// WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO // WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO
} }
// [Conditional("RELEASE")]
private static JsonObject ReleaseWriteTopology(UnixTime timestamp)
{
var topologyJson = Salimax.TopologyToLog(timestamp);
// WriteToFile(topologyJson, "/home/debian/DataSaliMax/topology" + timestamp); // this is was for beaglebone
return topologyJson;
}
[Conditional("DEBUG")]
private static void DebugWriteTopology(UnixTime timestamp)
{
var topologyJson = Salimax.TopologyToLog(timestamp);
WriteToFile(topologyJson, "/home/atef/JsonData/topology" + timestamp);
}
[Conditional("DEBUG")] [Conditional("DEBUG")]
private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp) private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp)

View File

@ -1,42 +1,67 @@
#undef BatteriesAllowed #define BatteriesAllowed
using InnovEnergy.App.SaliMax.Controller; using InnovEnergy.App.SaliMax.Controller;
using InnovEnergy.App.SaliMax.Log;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Units;
namespace InnovEnergy.App.SaliMax; namespace InnovEnergy.App.SaliMax;
public static class Topology public static class Topology
{ {
public static void Print(StatusRecord s) private static String Separator(Decimal power)
{ {
const String chargingSeparator = ">>>>>>>>>>"; const String chargingSeparator = ">>>>>>>>>>";
const String dischargingSeparator = "<<<<<<<<<"; const String dischargingSeparator = "<<<<<<<<<";
return power > 0 ? chargingSeparator : dischargingSeparator;
}
public static Decimal Round3(this Decimal d)
{
return d.RoundToSignificantDigits(3);
}
public static void Print(StatusRecord s)
{
const Int32 height = 25; const Int32 height = 25;
var calculatedActivePwr = - s.InverterStatus!.Ac.ActivePower;
var measuredActivePwr = (s.InverterStatus.SumActivePowerL1 + s.InverterStatus.SumActivePowerL2 +
s.InverterStatus.SumActivePowerL3) * -1;
var pwr = s.InverterStatus!.Ac.ActivePower; measuredActivePwr.WriteLine(" : measured Sum of Active Pwr ");
var setValueCosPhi = s.InverterStatus.CosPhiSetValue;
var setValueApparentPower = s.InverterStatus.ApparentPowerSetValue;
#if AmptAvailable
var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt
var loadPower = Utils.Round3((s.GridMeterStatus!.ActivePowerL123 + pwr)); // it's a + because the pwr is inverted #else
var pvPower = 0;
var gridSeparator = s.GridMeterStatus!.ActivePowerL123 > 0 ? chargingSeparator : dischargingSeparator;
var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator;
var dcSeparator = -s.DcDcStatus!.Dc.Power > 0 ? chargingSeparator : dischargingSeparator;
#if BatteriesAllowed
var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator;
var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator;
#endif #endif
var criticalLoadPower = (s.AcInToAcOutMeterStatus!.Ac.ActivePower.Value).Round3();
var dcTotalPower = -s.DcDcStatus!.TotalDcPower;
var gridSeparator = Separator(s.GridMeterStatus!.Ac.ActivePower);
var inverterSeparator = Separator(measuredActivePwr);
var dcSeparator = Separator(dcTotalPower);
var something = measuredActivePwr + criticalLoadPower;
var gridLoadPower = (s.GridMeterStatus!.Ac.ActivePower - something).Value.Round3();
////////////////// Grid ////////////////////// ////////////////// Grid //////////////////////
var boxGrid = AsciiArt.CreateBox var boxGrid = AsciiArt.CreateBox
( (
"Grid", "Grid",
s.GridMeterStatus.Ac.L1.Voltage.V(), s.GridMeterStatus.Ac.L1.Voltage.Value.V(),
s.GridMeterStatus.Ac.L2.Voltage.V(), s.GridMeterStatus.Ac.L2.Voltage.Value.V(),
s.GridMeterStatus.Ac.L3.Voltage.V() s.GridMeterStatus.Ac.L3.Voltage.Value.V()
).AlignCenterVertical(height); ).AlignCenterVertical(height);
var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator) var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.Ac.ActivePower, gridSeparator)
.AlignCenterVertical(height); .AlignCenterVertical(height);
@ -44,9 +69,9 @@ public static class Topology
var boxAcBus = AsciiArt.CreateBox var boxAcBus = AsciiArt.CreateBox
( (
"AC Bus", "AC Bus",
s.InverterStatus.Ac.L1.Voltage.V(), s.InverterStatus.Ac.L1.Voltage.Value.V(),
s.InverterStatus.Ac.L2.Voltage.V(), s.InverterStatus.Ac.L2.Voltage.Value.V(),
s.InverterStatus.Ac.L3.Voltage.V() s.InverterStatus.Ac.L3.Voltage.Value.V()
); );
var boxLoad = AsciiArt.CreateBox var boxLoad = AsciiArt.CreateBox
@ -56,9 +81,9 @@ public static class Topology
"" ""
); );
var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, loadPower), height); var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, gridLoadPower), height);
var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator)
.AlignCenterVertical(height); .AlignCenterVertical(height);
//////////////////// Inverter ///////////////////////// //////////////////// Inverter /////////////////////////
@ -69,7 +94,7 @@ public static class Topology
"" ""
).AlignCenterVertical(height); ).AlignCenterVertical(height);
var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) var inverterArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator)
.AlignCenterVertical(height); .AlignCenterVertical(height);
@ -77,70 +102,89 @@ public static class Topology
var dcBusBox = AsciiArt.CreateBox var dcBusBox = AsciiArt.CreateBox
( (
"DC Bus", "DC Bus",
(s.InverterStatus.ActualDcLinkVoltageLowerHalfExt + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt).V(), (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt.Value + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt.Value).V(),
"" ""
); );
var pvBox = AsciiArt.CreateBox var pvBox = AsciiArt.CreateBox
( (
"MPPT", "MPPT",
((s.AmptStatus!.Devices[0].Dc.Voltage + s.AmptStatus!.Devices[1].Dc.Voltage) / 2).Round0().V(), ((s.AmptStatus!.Devices[0].Strings[0].Voltage.Value + s.AmptStatus!.Devices[0].Strings[1].Voltage.Value) / 2).V(),
"" ""
); );
var pvRect = StringUtils.AlignTop(CreateRect(pvBox, dcBusBox, pvPower), height); var pvRect = StringUtils.AlignTop(CreateRect(pvBox, dcBusBox, pvPower), height);
var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Dc.Power, dcSeparator) var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Left.Power, dcSeparator)
.AlignCenterVertical(height); .AlignCenterVertical(height);
//////////////////// Dc/Dc ///////////////////////// //////////////////// Dc/Dc /////////////////////////
var dcBox = AsciiArt.CreateBox
(
"Dc/Dc",
s.DcDcStatus.BatteryVoltage.V(),
""
).AlignCenterVertical(height);
#if BatteriesAllowed
var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power.Round0(), battery1Separator);
var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power.Round0(), battery2Separator);
#else
var dcArrow1 ="";
var dcArrow2 = "";
var dcArrowRect = StringUtils.AlignCenterVertical(CreateRect(dcArrow1, dcArrow2), height);
#endif
var dcBox = AsciiArt.CreateBox( "Dc/Dc", s.DcDcStatus.Right.Voltage.Value.V(), "").AlignCenterVertical(height);
#if BatteriesAllowed var topology = "";
if (s.BatteriesStatus != null)
{
var numBatteries = s.BatteriesStatus.Children.Count;
// Create an array of battery arrows using LINQ
var dcArrows = s
.BatteriesStatus.Children
.Select(b => AsciiArt.CreateHorizontalArrow(b.Dc.Power, Separator(b.Dc.Power)))
.ToArray();
// Create a rectangle from the array of arrows and align it vertically
var dcArrowRect = CreateRect(dcArrows).AlignCenterVertical(height);
//////////////////// Batteries ///////////////////////// //////////////////// Batteries /////////////////////////
var battery1Box = AsciiArt.CreateBox
var batteryBox = new String[numBatteries];
for (var i = 0; i < numBatteries; i++)
{
if (s.BatteriesStatus.Children[i] != null)
{
batteryBox[i] = AsciiArt.CreateBox
( (
"Battery 1", "Battery " + (i+1),
s.BatteriesStatus[0].Voltage.V(), s.BatteriesStatus.Children[i].Dc.Voltage .Value.V(),
s.BatteriesStatus[0].Soc.Percent(), s.BatteriesStatus.Children[i].Soc .Value.Percent(),
s.BatteriesStatus[0].Temperature.Celsius() s.BatteriesStatus.Children[i].Temperature .Value.Celsius(),
s.BatteriesStatus.Children[i].Dc.Current .Value.A(),
s.BatteriesStatus.Children[i].TotalCurrent.Value.A()
); );
}
var battery2Box = AsciiArt.CreateBox else
{
batteryBox[i] = AsciiArt.CreateBox
( (
"Battery 2", "Battery " + (i+1),
s.BatteriesStatus[1].Voltage.V(), "not detected"
s.BatteriesStatus[1].Soc.Percent(),
s.BatteriesStatus[1].Temperature.Celsius()
); );
}
}
var batteryRect = CreateRect(battery1Box, battery2Box).AlignCenterVertical(height); var batteryRect = CreateRect(batteryBox).AlignCenterVertical(height);
var avgBatteryBox = AsciiArt.CreateBox
var avgBatteryBox = "";
if (s.BatteriesStatus.Combined != null)
{
avgBatteryBox = AsciiArt.CreateBox
( (
"Batteries", "Batteries",
s.AvgBatteriesStatus!.Voltage.V(), s.BatteriesStatus.Combined.CellsVoltage,
s.AvgBatteriesStatus.Soc.Percent(), s.BatteriesStatus.Combined.Soc,
s.AvgBatteriesStatus.Temperature.Celsius() s.BatteriesStatus.Combined.Temperature,
s.BatteriesStatus.Combined.Dc.Current,
s.BatteriesStatus.Combined.Alarms.Count > 0 ? String.Join(Environment.NewLine, s.BatteriesStatus.Combined.Alarms) : "No Alarm"
).AlignCenterVertical(height); ).AlignCenterVertical(height);
}
var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") topology = boxGrid.SideBySideWith(gridAcBusArrow, "")
.SideBySideWith(loadRect, "") .SideBySideWith(loadRect, "")
.SideBySideWith(acBusInvertArrow, "") .SideBySideWith(acBusInvertArrow, "")
.SideBySideWith(inverterBox, "") .SideBySideWith(inverterBox, "")
@ -151,8 +195,11 @@ public static class Topology
.SideBySideWith(dcArrowRect, "") .SideBySideWith(dcArrowRect, "")
.SideBySideWith(batteryRect, "") .SideBySideWith(batteryRect, "")
.SideBySideWith(avgBatteryBox, "")+ "\n"; .SideBySideWith(avgBatteryBox, "")+ "\n";
#else
var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") }
else
{
topology = boxGrid.SideBySideWith(gridAcBusArrow, "")
.SideBySideWith(loadRect, "") .SideBySideWith(loadRect, "")
.SideBySideWith(acBusInvertArrow, "") .SideBySideWith(acBusInvertArrow, "")
.SideBySideWith(inverterBox, "") .SideBySideWith(inverterBox, "")
@ -160,7 +207,8 @@ public static class Topology
.SideBySideWith(pvRect, "") .SideBySideWith(pvRect, "")
.SideBySideWith(dcBusArrow, "") .SideBySideWith(dcBusArrow, "")
.SideBySideWith(dcBox, "") + "\n"; .SideBySideWith(dcBox, "") + "\n";
#endif }
Console.WriteLine(topology); Console.WriteLine(topology);
} }
@ -183,4 +231,11 @@ public static class Topology
return rect; return rect;
} }
private static String CreateRect(String[] boxes)
{
var maxWidth = boxes.Max(l => l.Width());
var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines();
return rect;
}
} }

View File

@ -1,11 +0,0 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.SaliMax;
public static class Utils
{
public static Decimal Round3(this Decimal d)
{
return DecimalUtils.RoundToSignificantDigits(d, 3);
}
}

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
host=debian@10.2.1.87 host=ie-entwicklung@10.2.3.104
tunnel() { tunnel() {
name=$1 name=$1
@ -22,11 +22,11 @@ tunnel() {
echo "" echo ""
tunnel "Trumpf Inverter (http) " 192.168.1.2 80 8001 tunnel "Trumpf Inverter (http) " 192.168.1.2 80 7001
tunnel "Trumpf DCDC (http) " 192.168.1.3 80 8002 tunnel "Trumpf DCDC (http) " 192.168.1.3 80 7002
tunnel "Emu Meter (http) " 192.168.1.241 80 8003 tunnel "Emu Meter (http) " 192.168.1.241 80 7003
tunnel "ADAM (http) " 192.168.1.242 80 8004 tunnel "ADAM (http) " 192.168.1.242 80 7004
tunnel "AMPT (http) " 192.168.1.249 8080 8005 tunnel "AMPT (http) " 192.168.1.249 8080 7005
tunnel "Trumpf Inverter (modbus)" 192.168.1.2 502 5001 tunnel "Trumpf Inverter (modbus)" 192.168.1.2 502 5001
tunnel "Trumpf DCDC (modbus) " 192.168.1.3 502 5002 tunnel "Trumpf DCDC (modbus) " 192.168.1.3 502 5002

View File

@ -22,10 +22,13 @@ public record Battery48TLStatus : BatteryStatus
public IReadOnlyList<String> Warnings { get; init; } = Array.Empty<String>(); public IReadOnlyList<String> Warnings { get; init; } = Array.Empty<String>();
public IReadOnlyList<String> Alarms { get; init; } = Array.Empty<String>(); public IReadOnlyList<String> Alarms { get; init; } = Array.Empty<String>();
public Boolean EocReached { get; init; }
public Boolean ConnectedToDc { get; init; } public Boolean ConnectedToDc { get; init; }
public Boolean Heating { get; init; } public Boolean Heating { get; init; }
public TemperatureState TemperatureState { get; init; } // cold | operating temperature | overheated public TemperatureState TemperatureState { get; init; } // cold | operating temperature | overheated
public Current TotalCurrent { get; init; }
// TODO: strings // TODO: strings

View File

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text;
using InnovEnergy.Lib.Protocols.Modbus.Conversions; using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Units.Composite; using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
@ -17,9 +18,11 @@ public static class ModbusParser
var soc = data.ParseSoc(); var soc = data.ParseSoc();
var eoc = greenLed is On // var eoc = greenLed is On
&& amberLed is Off // && amberLed is Off
&& blueLed is Off; // && blueLed is Off;
var eoc = data.ParseEocReached();
var maxSoc = eoc ? 100m : 99.9m; var maxSoc = eoc ? 100m : 99.9m;
@ -48,6 +51,8 @@ public static class ModbusParser
MaxChargingPower = data.CalcMaxChargePower(), MaxChargingPower = data.CalcMaxChargePower(),
MaxDischargingPower = data.CalcMaxDischargePower(), MaxDischargingPower = data.CalcMaxDischargePower(),
CellsVoltage = data.ParseDecimal(register: 1000, scaleFactor: 0.01m), CellsVoltage = data.ParseDecimal(register: 1000, scaleFactor: 0.01m),
TotalCurrent = data.ReadTotalCurrent(),
EocReached = eoc
}; };
} }
@ -72,6 +77,19 @@ public static class ModbusParser
return data.ParseDecimal(register: 1002, scaleFactor: 0.01m); return data.ParseDecimal(register: 1002, scaleFactor: 0.01m);
} }
internal static Decimal ReadTotalCurrent(this ModbusRegisters data)
{
try
{
return ParseDecimal(data, register: 1063, scaleFactor: 0.01m, offset: -100);
}
catch (Exception e)
{
Console.WriteLine(e + " Read Total current fail ");
throw;
}
}
internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit) internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit)
{ {
var x = bit / 16; var x = bit / 16;
@ -96,6 +114,21 @@ public static class ModbusParser
}; };
} }
internal static String ParseString(this ModbusRegisters data, Int32 register, Int16 count)
{
return Enumerable
.Range(register, count)
.Select(i => data[i])
.Select(BitConverter.GetBytes)
.Select(Encoding.ASCII.GetString)
.Aggregate("", (a, b) => a + b[1] + b[0]); // endian swap
}
internal static Boolean ParseEocReached(this ModbusRegisters data)
{
var s = ParseString(data, 1061, 2);
return "EOC_" == s;
}
internal static Decimal ParseSoc(this ModbusRegisters data) internal static Decimal ParseSoc(this ModbusRegisters data)
{ {

View File

@ -15,6 +15,7 @@ public static class Units
public static Angle Rad (this Decimal value) => value; public static Angle Rad (this Decimal value) => value;
public static Temperature Celsius(this Decimal value) => value; public static Temperature Celsius(this Decimal value) => value;
public static Energy KWh (this Decimal value) => value; public static Energy KWh (this Decimal value) => value;
public static Percent Percent(this Decimal value) => value;
} }
public static class Prefixes public static class Prefixes