From 7e58b78572fc041f41a7ca846796e8527630bc1b Mon Sep 17 00:00:00 2001 From: ig Date: Wed, 8 Mar 2023 14:06:39 +0100 Subject: [PATCH 1/5] add new Unit: Energy (kWh) --- csharp/Lib/Units/Energy.cs | 15 +++++ csharp/Lib/Units/Energy.generated.cs | 97 ++++++++++++++++++++++++++++ csharp/Lib/Units/Power.cs | 4 +- 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 csharp/Lib/Units/Energy.cs create mode 100644 csharp/Lib/Units/Energy.generated.cs diff --git a/csharp/Lib/Units/Energy.cs b/csharp/Lib/Units/Energy.cs new file mode 100644 index 000000000..0f50ba858 --- /dev/null +++ b/csharp/Lib/Units/Energy.cs @@ -0,0 +1,15 @@ +using InnovEnergy.Lib.Units.Generator; + + +namespace InnovEnergy.Lib.Units; + +[Sum] +public readonly partial struct Energy +{ + public static String Unit => "kWh"; + public static String Symbol => "E"; + + public Energy(Decimal value) => Value = value; + + public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value / (Decimal) timeSpan.TotalHours * 1000m; +} \ No newline at end of file diff --git a/csharp/Lib/Units/Energy.generated.cs b/csharp/Lib/Units/Energy.generated.cs new file mode 100644 index 000000000..90496238b --- /dev/null +++ b/csharp/Lib/Units/Energy.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Sum + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Energy; + +[JsonConverter(typeof(EnergyConverter))] +public readonly partial struct Energy +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class EnergyConverter : JsonConverter +{ + public override Energy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new Energy(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, Energy value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Power.cs b/csharp/Lib/Units/Power.cs index 00d8b01c1..eb58ffeb3 100644 --- a/csharp/Lib/Units/Power.cs +++ b/csharp/Lib/Units/Power.cs @@ -14,5 +14,7 @@ public readonly partial struct Power // P=UI public static Voltage operator /(Power power, Current current) => new Voltage(power.Value / current.Value); public static Current operator /(Power power, Voltage voltage) => new Current(power.Value / voltage.Value); - + + public static Energy operator *(Power power, TimeSpan timeSpan) => power.Value / 1000m * (Decimal) timeSpan.TotalHours; + public static Energy operator *(TimeSpan timeSpan, Power power) => power.Value / 1000m * (Decimal) timeSpan.TotalHours; } \ No newline at end of file From 2738fe5aab29ba8894c42de21c6031d9d1f354a8 Mon Sep 17 00:00:00 2001 From: ig Date: Wed, 8 Mar 2023 14:09:00 +0100 Subject: [PATCH 2/5] rename UnixTimeDelta => UnixTimeSpan --- .../Unix/{UnixTimeDelta.Compare.cs => UnixTimeSpan.Compare.cs} | 0 ...UnixTimeDelta.Constructors.cs => UnixTimeSpan.Constructors.cs} | 0 .../{UnixTimeDelta.Operators.cs => UnixTimeSpan.Operators.cs} | 0 .../{UnixTimeDelta.Overrides.cs => UnixTimeSpan.Overrides.cs} | 0 .../{UnixTimeDeltaExtensions.cs => UnixTimeSpanExtensions.cs} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename csharp/Lib/Time/Unix/{UnixTimeDelta.Compare.cs => UnixTimeSpan.Compare.cs} (100%) rename csharp/Lib/Time/Unix/{UnixTimeDelta.Constructors.cs => UnixTimeSpan.Constructors.cs} (100%) rename csharp/Lib/Time/Unix/{UnixTimeDelta.Operators.cs => UnixTimeSpan.Operators.cs} (100%) rename csharp/Lib/Time/Unix/{UnixTimeDelta.Overrides.cs => UnixTimeSpan.Overrides.cs} (100%) rename csharp/Lib/Time/Unix/{UnixTimeDeltaExtensions.cs => UnixTimeSpanExtensions.cs} (100%) diff --git a/csharp/Lib/Time/Unix/UnixTimeDelta.Compare.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Compare.cs similarity index 100% rename from csharp/Lib/Time/Unix/UnixTimeDelta.Compare.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Compare.cs diff --git a/csharp/Lib/Time/Unix/UnixTimeDelta.Constructors.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Constructors.cs similarity index 100% rename from csharp/Lib/Time/Unix/UnixTimeDelta.Constructors.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Constructors.cs diff --git a/csharp/Lib/Time/Unix/UnixTimeDelta.Operators.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs similarity index 100% rename from csharp/Lib/Time/Unix/UnixTimeDelta.Operators.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs diff --git a/csharp/Lib/Time/Unix/UnixTimeDelta.Overrides.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Overrides.cs similarity index 100% rename from csharp/Lib/Time/Unix/UnixTimeDelta.Overrides.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Overrides.cs diff --git a/csharp/Lib/Time/Unix/UnixTimeDeltaExtensions.cs b/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs similarity index 100% rename from csharp/Lib/Time/Unix/UnixTimeDeltaExtensions.cs rename to csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs From 1b9353b1db3808096b49ef60de1e02ac47bbd25f Mon Sep 17 00:00:00 2001 From: ig Date: Wed, 8 Mar 2023 14:17:39 +0100 Subject: [PATCH 3/5] support UnixTime in Units --- csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs | 2 +- csharp/Lib/Units/Energy.cs | 4 +++- csharp/Lib/Units/Power.cs | 4 ++++ csharp/Lib/Units/Units.csproj | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs index dc2805e2d..e59d3dfba 100644 --- a/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs @@ -20,7 +20,7 @@ public readonly partial struct UnixTimeSpan public static UnixTimeSpan operator /(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks / b); public static UnixTimeSpan operator /(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks / (UInt32)b); - public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks; + public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks; public static UnixTimeSpan operator %(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks % b); public static UnixTimeSpan operator %(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks % (UInt32)b); diff --git a/csharp/Lib/Units/Energy.cs b/csharp/Lib/Units/Energy.cs index 0f50ba858..006568b06 100644 --- a/csharp/Lib/Units/Energy.cs +++ b/csharp/Lib/Units/Energy.cs @@ -1,3 +1,4 @@ +using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Units.Generator; @@ -11,5 +12,6 @@ public readonly partial struct Energy public Energy(Decimal value) => Value = value; - public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value / (Decimal) timeSpan.TotalHours * 1000m; + public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value * 1000m / (Decimal) timeSpan.TotalHours ; + public static Power operator /(Energy energy, UnixTimeSpan timeSpan) => energy.Value * 3_600_000m / timeSpan.Ticks; } \ No newline at end of file diff --git a/csharp/Lib/Units/Power.cs b/csharp/Lib/Units/Power.cs index eb58ffeb3..de4a7f9ab 100644 --- a/csharp/Lib/Units/Power.cs +++ b/csharp/Lib/Units/Power.cs @@ -1,4 +1,5 @@ +using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; @@ -17,4 +18,7 @@ public readonly partial struct Power public static Energy operator *(Power power, TimeSpan timeSpan) => power.Value / 1000m * (Decimal) timeSpan.TotalHours; public static Energy operator *(TimeSpan timeSpan, Power power) => power.Value / 1000m * (Decimal) timeSpan.TotalHours; + + public static Energy operator *(Power power, UnixTimeSpan timeSpan) => power.Value * timeSpan.Ticks / 3_600_000; + public static Energy operator *(UnixTimeSpan timeSpan, Power power) => power.Value * timeSpan.Ticks / 3_600_000; } \ No newline at end of file diff --git a/csharp/Lib/Units/Units.csproj b/csharp/Lib/Units/Units.csproj index f750ccf47..422120d58 100644 --- a/csharp/Lib/Units/Units.csproj +++ b/csharp/Lib/Units/Units.csproj @@ -7,6 +7,7 @@ + From 2240d3ef4759e76f6b9b12aa29fe3425b2597f8a Mon Sep 17 00:00:00 2001 From: ig Date: Wed, 8 Mar 2023 14:22:57 +0100 Subject: [PATCH 4/5] update EmuMeter to new StatusApi --- csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs | 114 +++++------------- csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs | 2 +- 2 files changed, 33 insertions(+), 83 deletions(-) diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs index ef3ba19ec..f26117c3f 100644 --- a/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs @@ -1,9 +1,6 @@ -using DecimalMath; using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.Units.Composite; -using InnovEnergy.Lib.Utils; using static DecimalMath.DecimalEx; namespace InnovEnergy.Lib.Devices.EmuMeter; @@ -37,108 +34,61 @@ public class EmuMeterDevice private EmuMeterStatus TryReadStatus() { // Console.WriteLine("Reading Emu Meter Data"); + + + // TODO: get SerialNb, depends on Little/Big Endian support in Modbus Lib + // var registers = Modbus.ReadHoldingRegisters(5001, 4); + // var id = registers.GetInt32(5001); var powerCurrent = Modbus.ReadHoldingRegisters(9000, 108).ToDecimals(); // TODO "ModbusRegisters" var voltageFreq = Modbus.ReadHoldingRegisters(9200, 112).ToDecimals(); // To check with Ivo - var energyTotal = Modbus.ReadHoldingRegisters(6000, 24) .ToUInt64s(); - var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s(); + + // var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s(); - var activePowerL123 = powerCurrent[0]; var activePowerL1 = powerCurrent[1]; var activePowerL2 = powerCurrent[2]; var activePowerL3 = powerCurrent[3]; - var reactivePowerL123 = powerCurrent[5]; var reactivePowerL1 = powerCurrent[6]; var reactivePowerL2 = powerCurrent[7]; var reactivePowerL3 = powerCurrent[8]; - var apparentPowerL123 = powerCurrent[10]; - var apparentPowerL1 = powerCurrent[11]; - var apparentPowerL2 = powerCurrent[12]; - var apparentPowerL3 = powerCurrent[13]; - var currentL123 = powerCurrent[50]; + var currentL1 = powerCurrent[51]; var currentL2 = powerCurrent[52]; var currentL3 = powerCurrent[53]; + var voltageL1N = voltageFreq[0]; var voltageL2N = voltageFreq[1]; var voltageL3N = voltageFreq[2]; - var voltageL1L2 = voltageFreq[3]; - var voltageL2L3 = voltageFreq[4]; - var voltageL3L1 = voltageFreq[5]; - var powerFactorL1 = voltageFreq[50]; - var powerFactorL2 = voltageFreq[51]; - var powerFactorL3 = voltageFreq[52]; var frequency = voltageFreq[55]; - var energyImportL123 = energyTotal[0 / 4] / 1000.0m; - var energyExportL123 = energyTotal[20 / 4] / 1000.0m; - var energyImportL1 = energyPhases[0 / 4] / 1000.0m; - var energyExportL1 = energyPhases[20 / 4] / 1000.0m; - var energyImportL2 = energyPhases[40 / 4] / 1000.0m; - var energyExportL2 = energyPhases[60 / 4] / 1000.0m; - var energyImportL3 = energyPhases[80 / 4] / 1000.0m; - var energyExportL3 = energyPhases[100 / 4] / 1000.0m; - - // Ac: new Ac3Bus - // ( - // new AcPhase( - // voltageL1N, - // currentL1, - // GetPhi(powerFactorL1) - // ), - // - // new AcPhase( - // voltageL2N, - // currentL2, - // GetPhi(powerFactorL2) - // ), - // - // new AcPhase( - // voltageL3N, - // currentL3, - // GetPhi(powerFactorL3) - // ), - // frequency - // ), - // activePowerL123, - // reactivePowerL123, - // apparentPowerL123, - // currentL123, - // voltageL1L2, - // voltageL2L3, - // voltageL3L1, - // energyImportL123, - // energyImportL1, - // energyImportL2, - // energyImportL3, - // energyExportL123, - // energyExportL1, - // energyExportL2, - // energyExportL3 - // ); + + + var l1 = new AcPhase + { + Current = currentL1, + Voltage = voltageL1N, + Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works + }; + var l2 = new AcPhase + { + Current = currentL2, + Voltage = voltageL2N, + Phi = ATan2(reactivePowerL2, activePowerL2) + }; + var l3 = new AcPhase + { + Current = currentL3, + Voltage = voltageL3N, + Phi = ATan2(reactivePowerL3, activePowerL3) + }; return new EmuMeterStatus { Ac = new Ac3Bus { Frequency = frequency, - L1 = new AcPhase - { - Current = currentL1, - Voltage = voltageL1N, - Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works - }, - L2 = new AcPhase - { - Current = currentL2, - Voltage = voltageL2N, - Phi = ATan2(reactivePowerL2, activePowerL2) - }, - L3 = new AcPhase - { - Current = currentL3, - Voltage = voltageL3N, - Phi = ATan2(reactivePowerL3, activePowerL3) - } + L1 = l1, + L2 = l2, + L3 = l3 } }; diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs index c1c9eb26e..1290843af 100644 --- a/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs @@ -4,7 +4,7 @@ namespace InnovEnergy.Lib.Devices.EmuMeter; public record EmuMeterStatus : PowerMeterStatus { - // TODO add serial nb, (and other?) + // TODO: additional Measurements, device id } From ee14c0844be483025b9a3ded42587bfb42dcea8e Mon Sep 17 00:00:00 2001 From: ig Date: Wed, 8 Mar 2023 15:45:53 +0100 Subject: [PATCH 5/5] Fix duplicate Folders bug --- csharp/App/Backend/Database/Db.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 1625c128f..5c87cc9c8 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -89,19 +89,23 @@ public partial class Db : IDisposable public IEnumerable GetAllAccessibleInstallations(User user) { - var direct = GetDirectlyAccessibleInstallations(user).ToList(); + var direct = GetDirectlyAccessibleInstallations(user); var fromFolders = GetAllAccessibleFolders(user) - .SelectMany(GetChildInstallations) - .Except(direct); + .SelectMany(GetChildInstallations); - return direct.Concat(fromFolders); + return direct + .Concat(fromFolders) + .Distinct(); } - public IEnumerable GetAllAccessibleFolders(User user) { return GetDirectlyAccessibleFolders(user) - .SelectMany(GetDescendantFolders); + .SelectMany(GetDescendantFolders) + .Distinct(); + + // Distinct because the user might have direct access + // to a child folder of a folder he has already access to }