This commit is contained in:
Kim 2023-03-09 09:06:02 +01:00
commit 683ef02704
140 changed files with 1789 additions and 908 deletions

View File

@ -1,13 +1,14 @@
using System.Net;
using System.Text;
using Innovenergy.Backend.Database;
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Model.Relations;
using Innovenergy.Backend.Utils;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.Model;
using InnovEnergy.App.Backend.Model.Relations;
using InnovEnergy.App.Backend.Utils;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc;
using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor;
namespace Innovenergy.Backend.Controllers;
namespace InnovEnergy.App.Backend.Controllers;
[ApiController]
[Route("api/")]
@ -183,6 +184,25 @@ public class Controller
.ToList(); // important!
}
[Returns<TreeNode[]>] // assuming swagger knows about arrays but not lists (JSON)
[Returns(HttpStatusCode.Unauthorized)]
[HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")]
public Object GetAllFoldersAndInstallations()
{
var caller = GetCaller();
if (caller == null)
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
using var db = Db.Connect();
var folders = db.GetAllAccessibleFolders(caller) as IEnumerable<TreeNode>;
var installations = db.GetAllAccessibleInstallations(caller);
return folders
.Concat(installations)
.ToList(); // important!
}
private static Folder PopulateChildren(Db db, Folder folder, HashSet<Int64>? hs = null)
{
// TODO: remove cycle detector

View File

@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
namespace Innovenergy.Backend.Controllers;
namespace InnovEnergy.App.Backend.Controllers;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public record Credentials(String Username, String Password);

View File

@ -1,7 +1,7 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace Innovenergy.Backend.Controllers;
namespace InnovEnergy.App.Backend.Controllers;
public class ReturnsAttribute : ProducesResponseTypeAttribute
{

View File

@ -1,11 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Model.Relations;
using Innovenergy.Backend.Utils;
using InnovEnergy.App.Backend.Model;
using InnovEnergy.App.Backend.Model.Relations;
using InnovEnergy.App.Backend.Utils;
using InnovEnergy.Lib.Utils;
using SQLite;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db : IDisposable
{
@ -89,21 +89,23 @@ public partial class Db : IDisposable
public IEnumerable<Installation> 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<Folder> 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
}
@ -113,7 +115,8 @@ public partial class Db : IDisposable
.Where(r => r.UserId == user.Id)
.Select(r => r.InstallationId)
.Select(GetInstallationById)
.NotNull();
.NotNull()
.Do(i => i.ParentId = 0); // hide inaccessible parents from calling user
}
public IEnumerable<Folder> GetDirectlyAccessibleFolders(User user)
@ -122,7 +125,8 @@ public partial class Db : IDisposable
.Where(r => r.UserId == user.Id)
.Select(r => r.FolderId)
.Select(GetFolderById)
.NotNull();
.NotNull()
.Do(i => i.ParentId = 0); // hide inaccessible parents from calling user;
}
public Result AddToAccessibleInstallations(Int64 userId, Int64 updatedInstallationId)

View File

@ -1,6 +1,6 @@
using Innovenergy.Backend.Model.Relations;
using InnovEnergy.App.Backend.Model.Relations;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -1,9 +1,9 @@
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Utils;
using InnovEnergy.App.Backend.Model;
using InnovEnergy.App.Backend.Utils;
using InnovEnergy.Lib.Utils;
using SQLite;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -1,8 +1,8 @@
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Utils;
using InnovEnergy.App.Backend.Model;
using InnovEnergy.App.Backend.Utils;
using SQLite;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -3,12 +3,11 @@ using System.Net.Http.Headers;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Flurl.Http;
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Utils;
using InnovEnergy.App.Backend.Model;
using InnovEnergy.App.Backend.Utils;
using InnovEnergy.Lib.Utils;
using SQLite;
using ResponseExtensions = Flurl.Http.ResponseExtensions;
@ -16,7 +15,7 @@ using ResponseExtensions = Flurl.Http.ResponseExtensions;
#pragma warning disable CS0472
#pragma warning disable CS8602
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -1,7 +1,7 @@
using Innovenergy.Backend.Model.Relations;
using InnovEnergy.App.Backend.Model.Relations;
using SQLite;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -1,7 +1,7 @@
using Innovenergy.Backend.Model.Relations;
using InnovEnergy.App.Backend.Model.Relations;
using SQLite;
namespace Innovenergy.Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
{

View File

@ -1,7 +1,7 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Innovenergy.Backend;
namespace InnovEnergy.App.Backend;
/// <summary>
/// This is for convenient testing! Todo throw me out?

View File

@ -1,4 +1,4 @@
namespace Innovenergy.Backend.Model;
namespace InnovEnergy.App.Backend.Model;
public class Folder : TreeNode
{

View File

@ -1,4 +1,4 @@
namespace Innovenergy.Backend.Model;
namespace InnovEnergy.App.Backend.Model;
public class Installation : TreeNode

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using SQLite;
namespace Innovenergy.Backend.Model.Relations;
namespace InnovEnergy.App.Backend.Model.Relations;
public abstract class Relation<L,R>
{

View File

@ -1,6 +1,6 @@
using SQLite;
namespace Innovenergy.Backend.Model.Relations;
namespace InnovEnergy.App.Backend.Model.Relations;
public class Session : Relation<String, Int64>
{

View File

@ -1,6 +1,6 @@
using SQLite;
namespace Innovenergy.Backend.Model.Relations;
namespace InnovEnergy.App.Backend.Model.Relations;
internal class User2Folder : Relation<Int64, Int64>
{

View File

@ -1,6 +1,6 @@
using SQLite;
namespace Innovenergy.Backend.Model.Relations;
namespace InnovEnergy.App.Backend.Model.Relations;
internal class User2Installation : Relation<Int64, Int64>
{

View File

@ -1,12 +1,13 @@
using System.Diagnostics.CodeAnalysis;
namespace Innovenergy.Backend.Model;
namespace InnovEnergy.App.Backend.Model;
public abstract partial class TreeNode
{
// Note: Only consider Id, but not ParentId for TreeNode equality checks
protected Boolean Equals(TreeNode other)
{
return Id == other.Id && ParentId == other.ParentId;
return Id == other.Id;
}
public override Boolean Equals(Object? obj)
@ -17,8 +18,5 @@ public abstract partial class TreeNode
}
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override Int32 GetHashCode()
{
return HashCode.Combine(Id, ParentId);
}
public override Int32 GetHashCode() => Id.GetHashCode();
}

View File

@ -1,6 +1,6 @@
using SQLite;
namespace Innovenergy.Backend.Model;
namespace InnovEnergy.App.Backend.Model;
public abstract partial class TreeNode
{

View File

@ -1,6 +1,6 @@
using SQLite;
namespace Innovenergy.Backend.Model;
namespace InnovEnergy.App.Backend.Model;
public class User : TreeNode
{

View File

@ -1,7 +1,7 @@
using Innovenergy.Backend.Database;
using InnovEnergy.App.Backend.Database;
using Microsoft.OpenApi.Models;
namespace Innovenergy.Backend;
namespace InnovEnergy.App.Backend;
public static class Program
{

View File

@ -1,6 +1,6 @@
using System.Security.Cryptography;
namespace Innovenergy.Backend.Utils;
namespace InnovEnergy.App.Backend.Utils;
public static class Crypto
{

View File

@ -1,4 +1,4 @@
namespace Innovenergy.Backend.Utils;
namespace InnovEnergy.App.Backend.Utils;
public class Result
{

View File

@ -20,32 +20,32 @@ public static class Config
public static readonly IReadOnlyList<Signal> Signals = new Signal[]
{
new(s => s.CurrentL1, "/Ac/L1/Current", "0.0 A"),
new(s => s.CurrentL2, "/Ac/L2/Current", "0.0 A"),
new(s => s.CurrentL3, "/Ac/L3/Current", "0.0 A"),
new(s => s.CurrentL123, "/Ac/Current", "0.0 A"),
new(s => s.Ac.L1.Current, "/Ac/L1/Current", "0.0 A"),
new(s => s.Ac.L2.Current, "/Ac/L2/Current", "0.0 A"),
new(s => s.Ac.L3.Current, "/Ac/L3/Current", "0.0 A"),
new(s => s.Ac.L1.Current + s.Ac.L2.Current + s.Ac.L3.Current, "/Ac/Current", "0.0 A"),
new(s => s.VoltageL1N, "/Ac/L1/Voltage", "0.0 A"),
new(s => s.VoltageL2N, "/Ac/L2/Voltage", "0.0 A"),
new(s => s.VoltageL3N, "/Ac/L3/Voltage", "0.0 A"),
new(s => (s.VoltageL1N + s.VoltageL2N + s.VoltageL3N) / 3.0m, "/Ac/Voltage", "0.0 A"),
new(s => s.Ac.L1.Voltage, "/Ac/L1/Voltage", "0.0 A"),
new(s => s.Ac.L2.Voltage, "/Ac/L2/Voltage", "0.0 A"),
new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"),
new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3.0m, "/Ac/Voltage", "0.0 A"),
new(s => s.ActivePowerL1, "/Ac/L1/Power", "0 W"),
new(s => s.ActivePowerL2, "/Ac/L2/Power", "0 W"),
new(s => s.ActivePowerL3, "/Ac/L3/Power", "0 W"),
new(s => s.ActivePowerL123, "/Ac/Power", "0 W"),
new(s => s.Ac.L1.ActivePower, "/Ac/L1/Power", "0 W"),
new(s => s.Ac.L2.ActivePower, "/Ac/L2/Power", "0 W"),
new(s => s.Ac.L3.ActivePower, "/Ac/L3/Power", "0 W"),
new(s => s.Ac.ActivePower, "/Ac/Power", "0 W"),
new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"),
new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"),
new(s => s.EnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
new(s => s.EnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
new(s => s.EnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
// new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
};
public static VeProperties DefaultProperties => new VeProperties

View File

@ -19,7 +19,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>InnovEnergy.App.$(AssemblyName)</RootNamespace>
</PropertyGroup>
</Project>

View File

@ -6,9 +6,6 @@
<ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="RemoteSupportConsole.csproj.DotSettings" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />

View File

@ -6,6 +6,6 @@ public static class Utils
{
public static Decimal Round3(this Decimal d)
{
return DecimalUtils.RoundToSignificantFigures(d, 3);
return DecimalUtils.RoundToSignificantDigits(d, 3);
}
}

View File

@ -9,7 +9,7 @@
<TargetFramework>net6.0</TargetFramework>
<InvariantGlobalization>true</InvariantGlobalization>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<RootNamespace>$(Company).$(MSBuildProjectDirectory.Replace($(SolutionDir), "").Replace("src/", "").Replace("/","."))</RootNamespace>
<RootNamespace>$(Company).$(MSBuildProjectDirectory.Replace($(SolutionDir), "").Replace("src/", "").Replace("/",".").Replace("\","."))</RootNamespace>
<Authors>$(Company) Team</Authors>
</PropertyGroup>

View File

@ -4,6 +4,7 @@
<s:Boolean x:Key="/Default/CodeEditing/SuppressNullableWarningFix/Enabled/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=AMPT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=arctan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=backfill/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=beaglebone/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CCGX/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,5 +1,5 @@
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
namespace InnovEnergy.Lib.Devices.AMPT;
@ -11,5 +11,5 @@ public record AmptDeviceStatus
UInt32 Timestamp, // The UTC timestamp of the measurements
Decimal ProductionToday, // converted to kW in AmptCU class
IReadOnlyList<DcConnection> Strings
): Mppt(Dc, Strings)
): MpptStatus(Dc, Strings)
{}

View File

@ -1,7 +1,5 @@
using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.StatusApi.Connections;
namespace InnovEnergy.Lib.Devices.Battery48TL;
@ -33,18 +31,11 @@ public class Battery48TlDevice
public Battery48TLStatus? ReadStatus() //Already try catch is implemented
{
if (Modbus is null) // TODO : remove fake
{
Console.WriteLine("Battery is null");
return null;
}
// Console.WriteLine("Reading Battery Data");
try
{
var registers = Modbus.ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters);
return TryReadStatus(registers);
return Modbus
.ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters)
.ParseBatteryStatus();
}
catch (Exception e)
{
@ -53,92 +44,4 @@ public class Battery48TlDevice
return null;
}
}
private Battery48TLStatus? TryReadStatus(ModbusRegisters data)
{
var soc = data.ParseDecimal(register: 1054, scaleFactor: 0.1m);
var eocReached = data.ParseEocReached();
var warnings = new List<String>();
if (data.ParseBool(1006, 1)) warnings.Add("TaM1: BMS temperature high");
if (data.ParseBool(1006, 4)) warnings.Add("TbM1: Battery temperature high");
if (data.ParseBool(1006, 6)) warnings.Add("VBm1: Bus voltage low");
if (data.ParseBool(1006, 8)) warnings.Add("VBM1: Bus voltage high");
if (data.ParseBool(1006, 10)) warnings.Add("IDM1: Discharge current high");
if (data.ParseBool(1006, 24)) warnings.Add("vsM1: String voltage high");
if (data.ParseBool(1006, 26)) warnings.Add("iCM1: Charge current high");
if (data.ParseBool(1006, 28)) warnings.Add("iDM1: Discharge current high");
if (data.ParseBool(1006, 30)) warnings.Add("MID1: String voltages unbalanced");
if (data.ParseBool(1006, 32)) warnings.Add("BLPW: Not enough charging power on bus");
if (data.ParseBool(1006, 35)) warnings.Add("Ah_W: String SOC low");
if (data.ParseBool(1006, 38)) warnings.Add("MPMM: Midpoint wiring problem");
if (data.ParseBool(1006, 39)) warnings.Add("TCMM:");
if (data.ParseBool(1006, 40)) warnings.Add("TCdi: Temperature difference between strings high");
if (data.ParseBool(1006, 41)) warnings.Add("WMTO:");
if (data.ParseBool(1006, 44)) warnings.Add("bit44:");
if (data.ParseBool(1006, 46)) warnings.Add("CELL1:");
var alarms = new List<String>();
if (data.ParseBool(1010, 0)) alarms.Add("Tam : BMS temperature too low");
if (data.ParseBool(1010, 2)) alarms.Add("TaM2 : BMS temperature too high");
if (data.ParseBool(1010, 3)) alarms.Add("Tbm : Battery temperature too low");
if (data.ParseBool(1010, 5)) alarms.Add("TbM2 : Battery temperature too high");
if (data.ParseBool(1010, 7)) alarms.Add("VBm2 : Bus voltage too low");
if (data.ParseBool(1010, 9)) alarms.Add("VBM2 : Bus voltage too high");
if (data.ParseBool(1010, 11)) alarms.Add("IDM2 : Discharge current too high");
if (data.ParseBool(1010, 12)) alarms.Add("ISOB : Electrical insulation failure");
if (data.ParseBool(1010, 13)) alarms.Add("MSWE : Main switch failure");
if (data.ParseBool(1010, 14)) alarms.Add("FUSE : Main fuse blown");
if (data.ParseBool(1010, 15)) alarms.Add("HTRE : Battery failed to warm up");
if (data.ParseBool(1010, 16)) alarms.Add("TCPE : Temperature sensor failure");
if (data.ParseBool(1010, 17)) alarms.Add("STRE :");
if (data.ParseBool(1010, 18)) alarms.Add("CME : Current sensor failure");
if (data.ParseBool(1010, 19)) alarms.Add("HWFL : BMS hardware failure");
if (data.ParseBool(1010, 20)) alarms.Add("HWEM : Hardware protection tripped");
if (data.ParseBool(1010, 21)) alarms.Add("ThM : Heatsink temperature too high");
if (data.ParseBool(1010, 22)) alarms.Add("vsm1 : String voltage too low");
if (data.ParseBool(1010, 23)) alarms.Add("vsm2 : Low string voltage failure");
if (data.ParseBool(1010, 25)) alarms.Add("vsM2 : String voltage too high");
if (data.ParseBool(1010, 27)) alarms.Add("iCM2 : Charge current too high");
if (data.ParseBool(1010, 29)) alarms.Add("iDM2 : Discharge current too high");
if (data.ParseBool(1010, 31)) alarms.Add("MID2 : String voltage unbalance too high");
if (data.ParseBool(1010, 33)) alarms.Add("CCBF : Internal charger hardware failure");
if (data.ParseBool(1010, 34)) alarms.Add("AhFL :");
if (data.ParseBool(1010, 36)) alarms.Add("TbCM :");
if (data.ParseBool(1010, 37)) alarms.Add("BRNF :");
if (data.ParseBool(1010, 42)) alarms.Add("HTFS : If Heaters Fuse Blown");
if (data.ParseBool(1010, 43)) alarms.Add("DATA : Parameters out of range");
if (data.ParseBool(1010, 45)) alarms.Add("CELL2:");
return new Battery48TLStatus(
Dc: new DcConnection
(
Voltage : data.ReadVoltage(),
Current : data.ReadCurrent()),
Soc : !eocReached && soc >= 100m ? 99.9m : soc,
Temperature : data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400),
BusVoltage : data.ParseDecimal(register: 1002, scaleFactor: 0.01m),
GreenLed : data.ParseLedState(register: 1005, led: LedColor.Green),
AmberLed : data.ParseLedState(register: 1006, led: LedColor.Amber),
BlueLed : data.ParseLedState(register: 1005, led: LedColor.Blue),
RedLed : data.ParseLedState(register: 1005, led: LedColor.Red),
Warnings : warnings,
Alarms : alarms,
MainSwitchClosed : data.ParseBool(baseRegister: 1014, bit: 0),
AlarmOutActive : data.ParseBool(baseRegister: 1014, bit: 1),
InternalFanActive : data.ParseBool(baseRegister: 1014, bit: 2),
VoltMeasurementAllowed: data.ParseBool(baseRegister: 1014, bit: 3),
AuxRelay : data.ParseBool(baseRegister: 1014, bit: 4),
RemoteState : data.ParseBool(baseRegister: 1014, bit: 5),
HeaterOn : data.ParseBool(baseRegister: 1014, bit: 6),
EocReached : eocReached,
BatteryCold : data.ParseBatteryCold(),
MaxChargingPower : data.CalcMaxChargePower(),
MaxDischargingPower : data.CalcMaxDischargePower()
);
}
}

View File

@ -1,39 +1,50 @@
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL;
using T = Battery48TLStatus;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public record Battery48TLStatus
(
DcConnection Dc,
Decimal Soc,
Decimal Temperature,
//Decimal Current,
//Decimal Voltage,
Decimal BusVoltage,
LedState GreenLed,
LedState AmberLed,
LedState BlueLed,
LedState RedLed,
IReadOnlyList<String> Warnings,
IReadOnlyList<String> Alarms,
Boolean MainSwitchClosed,
Boolean AlarmOutActive,
Boolean InternalFanActive,
Boolean VoltMeasurementAllowed,
Boolean AuxRelay,
Boolean RemoteState,
Boolean HeaterOn,
Boolean EocReached,
Boolean BatteryCold,
Decimal MaxChargingPower,
Decimal MaxDischargingPower
)
: Battery(Dc, Soc, Temperature)
public record Battery48TLStatus : BatteryStatus
{
public Voltage CellsVoltage { get; init; }
public Power MaxChargingPower { get; init; }
public Power MaxDischargingPower { get; init; }
public State GreenLed { get; init; }
public State AmberLed { get; init; }
public State BlueLed { get; init; }
public State RedLed { get; init; }
public State Warnings { get; init; }
public State Alarms { get; init; }
public State MainSwitchState { get; init; } // connected to bus | disconnected from bus
public State HeaterState { get; init; } // heating | not heating
public State EocState { get; init; } // EOC reached | EOC not reached
public State TemperatureState { get; init; } // cold | operating temperature | overheated
public static T operator |(T left, T right) => OpParallel(left, right);
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
// TODO: strings
// TODO
// public State LimitedBy { get; init; }
// TODO
// public Boolean AlarmOutActive { get; init; }
// public Boolean InternalFanActive { get; init; }
// public Boolean VoltMeasurementAllowed { get; init; }
// public Boolean AuxRelay { get; init; }
// public Boolean RemoteState { get; init; }
}

View File

@ -1,167 +0,0 @@
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL;
public static class BatteryDataParser
{
public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0)
{
var value = data[register].ConvertTo<Int32>(); // widen to 32bit signed
if (value >= 0x8000)
value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&!
return (Decimal)(value + offset) * scaleFactor;
}
internal static Decimal ReadCurrent(this ModbusRegisters data)
{
return ParseDecimal(data, register: 1001, scaleFactor: 0.01m, offset: -10000);
}
internal static Decimal ReadVoltage(this ModbusRegisters data)
{
return ParseDecimal(data, register: 1000, scaleFactor: 0.01m);
}
internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit)
{
var x = bit / 16;
var y = bit % 16;
var value = (UInt32)data[baseRegister + x];
return (value & (1 << y)) > 0;
}
internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led)
{
var lo = ParseBool(data, register, (led.ConvertTo<Int16>() * 2).ConvertTo<Int16>());
var hi = ParseBool(data, register, (led.ConvertTo<Int16>() * 2 + 1).ConvertTo<Int16>());
if (hi)
{
if (lo)
{
return LedState.BlinkingFast;
}
else
{
return LedState.BlinkingSlow;
}
}
else
{
if (lo)
{
return LedState.On;
}
else
{
return LedState.Off;
}
}
}
internal static String ParseRegisters(this ModbusRegisters data, Int32 register, Int16 count)
{
var container = "";
var start = register;
var end = register + count;
for (var i = start; i < end; i++)
{
var binary = Convert.ToString(data[register], 2);
container += binary.PadLeft(16, '0');
}
return container;
}
internal static Boolean ParseEocReached(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) == LedState.On &&
ParseLedState(data, 1005, LedColor.Amber) == LedState.Off &&
ParseLedState(data, 1005, LedColor.Blue) == LedState.Off;
}
internal static Boolean ParseBatteryCold(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) >= LedState.BlinkingSlow &&
ParseLedState(data, 1005, LedColor.Blue) >= LedState.BlinkingSlow;
}
private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt)
{
var dv = vLimit - v;
var di = dv / rInt;
var pLimit = vLimit * (i + di);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt)
{
var di = iLimit - i;
var dv = di * rInt;
var pLimit = iLimit * (v + dv);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint)
{
// const Int32 holdZone = 300;
// const Int32 maxAllowedTemp = 315;
var kp = 0.05m;
var error = setpoint - power;
var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m));
return controlOutput;
// var a = holdZone - maxAllowedTemp;
// var b = -a * maxAllowedTemp;
}
internal static Decimal CalcMaxChargePower(this ModbusRegisters data)
{
var v = ReadVoltage(data);
var i = ReadCurrent(data);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax)
};
var pLimit = pLimits.Min();
return Math.Max(pLimit, 0);
}
internal static Decimal CalcMaxDischargePower(this ModbusRegisters data)
{
var v = ReadVoltage(data);
var i = ReadCurrent(data);
var t = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax),
// CalcPowerLimitImposedByTempLimit(t,315,300)
};
var pLimit = pLimits.Max();
return Math.Min(pLimit, 0);
}
}

View File

@ -0,0 +1,272 @@
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL;
public static class ModbusParser
{
internal static Battery48TLStatus ParseBatteryStatus(this ModbusRegisters data)
{
return new Battery48TLStatus
{
Dc = data.ParseDcBus(),
Alarms = data.ParseAlarms().ToList(),
Warnings = data.ParseWarnings().ToList(),
Soc = data.ParseSoc(),
Temperature = data.ParseTemperature(),
GreenLed = data.ParseGreenLed(),
AmberLed = data.ParseAmberLed(),
BlueLed = data.ParseBlueLed(),
RedLed = data.ParseRedLed(),
MainSwitchState = data.ParseMainSwitchState(),
HeaterState = data.ParseHeaterState(),
EocState = data.ParseEocState(),
TemperatureState = data.ParseTemperatureState(),
MaxChargingPower = data.CalcMaxChargePower(),
MaxDischargingPower = data.CalcMaxDischargePower(),
CellsVoltage = data.ParseCellsVoltage(),
};
}
public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0)
{
var value = data[register].ConvertTo<Int32>(); // widen to 32bit signed
if (value >= 0x8000)
value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&!
return (Decimal)(value + offset) * scaleFactor;
}
internal static Decimal ParseCurrent(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1001, scaleFactor: 0.01m, offset: -10000);
}
internal static Decimal ParseCellsVoltage(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1000, scaleFactor: 0.01m);
}
internal static Decimal ParseBusVoltage(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1002, scaleFactor: 0.01m);
}
internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit)
{
var x = bit / 16;
var y = bit % 16;
var value = (UInt32)data[baseRegister + x];
return (value & (1 << y)) > 0;
}
internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led)
{
var lo = data.ParseBool(register, (led.ConvertTo<Int16>() * 2 ).ConvertTo<Int16>());
var hi = data.ParseBool(register, (led.ConvertTo<Int16>() * 2 + 1).ConvertTo<Int16>());
return (hi, lo) switch
{
(false, false) => LedState.Off,
(false, true) => LedState.On,
(true, false) => LedState.BlinkingSlow,
(true, true) => LedState.BlinkingFast,
};
}
private static Boolean ParseEocReached(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) == LedState.On &&
ParseLedState(data, 1005, LedColor.Amber) == LedState.Off &&
ParseLedState(data, 1005, LedColor.Blue) == LedState.Off;
}
internal static State ParseTemperatureState(this ModbusRegisters data)
{
return data.ParseBatteryCold() ? "cold" : "operating temperature"; // TODO: overheated,
}
internal static Decimal ParseTemperature(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
}
internal static Decimal ParseSoc(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1054, scaleFactor: 0.1m);
}
internal static State ParseEocState(this ModbusRegisters data)
{
return data.ParseEocReached() ? "EOC reached" : "EOC not reached";
}
internal static State ParseHeaterState(this ModbusRegisters data)
{
return data.ParseBool(baseRegister: 1014, bit: 6) ? "heating" : "not heating";
}
internal static State ParseMainSwitchState(this ModbusRegisters data)
{
return data.ParseBool(baseRegister: 1014, bit: 0) ? "connected to bus" : "disconnected from bus";
}
internal static Boolean ParseBatteryCold(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) >= LedState.BlinkingSlow &&
ParseLedState(data, 1005, LedColor.Blue) >= LedState.BlinkingSlow;
}
private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt)
{
var dv = vLimit - v;
var di = dv / rInt;
var pLimit = vLimit * (i + di);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt)
{
var di = iLimit - i;
var dv = di * rInt;
var pLimit = iLimit * (v + dv);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint)
{
// const Int32 holdZone = 300;
// const Int32 maxAllowedTemp = 315;
var kp = 0.05m;
var error = setpoint - power;
var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m));
return controlOutput;
// var a = holdZone - maxAllowedTemp;
// var b = -a * maxAllowedTemp;
}
internal static Decimal CalcMaxChargePower(this ModbusRegisters data)
{
var v = ParseCellsVoltage(data);
var i = ParseCurrent(data);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax)
};
var pLimit = pLimits.Min();
return Math.Max(pLimit, 0);
}
internal static DcBus ParseDcBus(this ModbusRegisters data)
{
return new()
{
Current = data.ParseCurrent(),
Voltage = data.ParseBusVoltage(),
};
}
internal static Decimal CalcMaxDischargePower(this ModbusRegisters data)
{
var v = ParseCellsVoltage(data);
var i = ParseCurrent(data);
var t = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax),
// CalcPowerLimitImposedByTempLimit(t,315,300)
};
var pLimit = pLimits.Max();
return Math.Min(pLimit, 0);
}
internal static LedState ParseGreenLed(this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Green);
internal static LedState ParseAmberLed(this ModbusRegisters data) => data.ParseLedState(register: 1006, led: LedColor.Amber);
internal static LedState ParseBlueLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Blue);
internal static LedState ParseRedLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Red);
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static IEnumerable<String> ParseAlarms(this ModbusRegisters data)
{
if (data.ParseBool(1010, 0)) yield return "Tam : BMS temperature too low";
if (data.ParseBool(1010, 2)) yield return "TaM2 : BMS temperature too high";
if (data.ParseBool(1010, 3)) yield return "Tbm : Battery temperature too low";
if (data.ParseBool(1010, 5)) yield return "TbM2 : Battery temperature too high";
if (data.ParseBool(1010, 7)) yield return "VBm2 : Bus voltage too low";
if (data.ParseBool(1010, 9)) yield return "VBM2 : Bus voltage too high";
if (data.ParseBool(1010, 11)) yield return "IDM2 : Discharge current too high";
if (data.ParseBool(1010, 12)) yield return "ISOB : Electrical insulation failure";
if (data.ParseBool(1010, 13)) yield return "MSWE : Main switch failure";
if (data.ParseBool(1010, 14)) yield return "FUSE : Main fuse blown";
if (data.ParseBool(1010, 15)) yield return "HTRE : Battery failed to warm up";
if (data.ParseBool(1010, 16)) yield return "TCPE : Temperature sensor failure";
if (data.ParseBool(1010, 17)) yield return "STRE :";
if (data.ParseBool(1010, 18)) yield return "CME : Current sensor failure";
if (data.ParseBool(1010, 19)) yield return "HWFL : BMS hardware failure";
if (data.ParseBool(1010, 20)) yield return "HWEM : Hardware protection tripped";
if (data.ParseBool(1010, 21)) yield return "ThM : Heatsink temperature too high";
if (data.ParseBool(1010, 22)) yield return "vsm1 : String voltage too low";
if (data.ParseBool(1010, 23)) yield return "vsm2 : Low string voltage failure";
if (data.ParseBool(1010, 25)) yield return "vsM2 : String voltage too high";
if (data.ParseBool(1010, 27)) yield return "iCM2 : Charge current too high";
if (data.ParseBool(1010, 29)) yield return "iDM2 : Discharge current too high";
if (data.ParseBool(1010, 31)) yield return "MID2 : String voltage unbalance too high";
if (data.ParseBool(1010, 33)) yield return "CCBF : Internal charger hardware failure";
if (data.ParseBool(1010, 34)) yield return "AhFL :";
if (data.ParseBool(1010, 36)) yield return "TbCM :";
if (data.ParseBool(1010, 37)) yield return "BRNF :";
if (data.ParseBool(1010, 42)) yield return "HTFS : If Heaters Fuse Blown";
if (data.ParseBool(1010, 43)) yield return "DATA : Parameters out of range";
if (data.ParseBool(1010, 45)) yield return "CELL2:";
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static IEnumerable<String> ParseWarnings(this ModbusRegisters data)
{
if (data.ParseBool(1006, 1)) yield return "TaM1: BMS temperature high";
if (data.ParseBool(1006, 4)) yield return "TbM1: Battery temperature high";
if (data.ParseBool(1006, 6)) yield return "VBm1: Bus voltage low";
if (data.ParseBool(1006, 8)) yield return "VBM1: Bus voltage high";
if (data.ParseBool(1006, 10)) yield return "IDM1: Discharge current high";
if (data.ParseBool(1006, 24)) yield return "vsM1: String voltage high";
if (data.ParseBool(1006, 26)) yield return "iCM1: Charge current high";
if (data.ParseBool(1006, 28)) yield return "iDM1: Discharge current high";
if (data.ParseBool(1006, 30)) yield return "MID1: String voltages unbalanced";
if (data.ParseBool(1006, 32)) yield return "BLPW: Not enough charging power on bus";
if (data.ParseBool(1006, 35)) yield return "Ah_W: String SOC low";
if (data.ParseBool(1006, 38)) yield return "MPMM: Midpoint wiring problem";
if (data.ParseBool(1006, 39)) yield return "TCMM:";
if (data.ParseBool(1006, 40)) yield return "TCdi: Temperature difference between strings high";
if (data.ParseBool(1006, 41)) yield return "WMTO:";
if (data.ParseBool(1006, 44)) yield return "bit44:";
if (data.ParseBool(1006, 46)) yield return "CELL1:";
}
}

View File

@ -1,8 +1,6 @@
using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Phases;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Units.Composite;
using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Devices.EmuMeter;
@ -29,91 +27,71 @@ public class EmuMeterDevice
return null;
}
}
private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos);
//private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos);
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 activePowerL123 = powerCurrent[0];
// var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s();
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;
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 ThreePhaseAcConnection
(
new AcPhase(
voltageL1N,
currentL1,
GetPhi(powerFactorL1)
),
{
Ac = new Ac3Bus
{
Frequency = frequency,
L1 = l1,
L2 = l2,
L3 = l3
}
};
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
);
}
}

View File

@ -1,26 +1,10 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
using InnovEnergy.Lib.StatusApi;
namespace InnovEnergy.Lib.Devices.EmuMeter;
public record EmuMeterStatus
(
ThreePhaseAcConnection Ac,
Decimal ActivePowerL123,
Decimal ReactivePowerL123,
Decimal ApparentPowerL123,
Decimal CurrentL123,
Decimal VoltageL1L2,
Decimal VoltageL2L3,
Decimal VoltageL3L1,
Decimal EnergyImportL123,
Decimal EnergyImportL1,
Decimal EnergyImportL2,
Decimal EnergyImportL3,
Decimal EnergyExportL123,
Decimal EnergyExportL1,
Decimal EnergyExportL2,
Decimal EnergyExportL3
):GridMeter(Ac)
{}
public record EmuMeterStatus : PowerMeterStatus
{
// TODO: additional Measurements, device id
}

Binary file not shown.

View File

@ -2,8 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Phases;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx;
using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters;
@ -216,7 +215,7 @@ public class TruConvertAcDevice
return new TruConvertAcStatus
(
Ac: new ThreePhaseAcConnection
Ac: new Ac3Bus
(
new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)),
new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)),

View File

@ -1,6 +1,6 @@
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
@ -9,7 +9,7 @@ using WarningMessages = IReadOnlyList<WarningMessage>;
public record TruConvertAcStatus
(
ThreePhaseAcConnection Ac,
Ac3Bus Ac,
DcConnection Dc,
String SerialNumber,
MainState MainState,

View File

@ -1,6 +1,7 @@
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
@ -10,18 +11,22 @@ using DcCurrentLimitStates = IReadOnlyList<DcCurrentLimitState>;
public record TruConvertDcStatus
(
DcConnection Dc,
MainState MainState,
UInt16 NumberOfConnectedSlaves,
UInt16 NumberOfConnectedSubSlaves,
Decimal BatteryVoltage,
Decimal BatteryCurrent,
Decimal TotalDcPower,
DcCurrentLimitStates StatusOfCurrentLimiting,
DcBus DcLeft,
DcBus DcRight,
State MainState,
Power TotalDcPower, // TODO: necessary?
State StatusOfCurrentLimiting,
Decimal OverloadCapacity,
Decimal DcDcInletTemperature,
AlarmMessages Alarms,
WarningMessages Warnings,
Boolean PowerOperation
):DcDevice(Dc)
{}
Temperature DcDcInletTemperature,
State Alarms,
State Warnings,
State PowerOperation
// UInt16 NumberOfConnectedSlaves, // TODO: necessary?
// UInt16 NumberOfConnectedSubSlaves, // TODO: necessary?
) :
DcDcConverterStatus(DcLeft, DcRight)
{
public static TruConvertDcStatus operator |(TruConvertDcStatus left, TruConvertDcStatus right) => OpParallel(left, right);
private static readonly Func<TruConvertDcStatus, TruConvertDcStatus, TruConvertDcStatus> OpParallel = Operators.Op<TruConvertDcStatus>("|");
}

View File

@ -0,0 +1,10 @@
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
[Flags]
public enum Endianness
{
LittleEndian32BitIntegers = 0,
LittleEndian32Floats = 0,
BigEndian32BitIntegers = 1,
BigEndian32Floats = 2,
}

View File

@ -1,6 +1,6 @@
using System.Threading.Channels;
using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using static InnovEnergy.Lib.Protocols.Modbus.Clients.Endianness;
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
@ -13,6 +13,7 @@ public abstract class ModbusClient
{
protected ModbusConnection Connection { get; }
protected Byte SlaveId { get; }
protected Endianness Endianness { get; }
// TODO: add additional functions: coils...
@ -40,11 +41,11 @@ public abstract class ModbusClient
return WriteRegisters(writeAddress, (IReadOnlyList<UInt16>)values);
}
protected ModbusClient(ModbusConnection connection, Byte slaveId)
protected ModbusClient(ModbusConnection connection, Byte slaveId, Endianness endianness = LittleEndian32BitIntegers | LittleEndian32Floats)
{
Connection = connection;
SlaveId = slaveId;
Endianness = endianness;
}
public void CloseConnection() => Connection.Close();

View File

@ -4,13 +4,13 @@ using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
using InnovEnergy.Lib.Protocols.Modbus.Tcp;
using static InnovEnergy.Lib.Protocols.Modbus.Clients.Endianness;
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
using UInt16s = IReadOnlyList<UInt16>;
using Coils = IReadOnlyList<Boolean>;
public class ModbusTcpClient : ModbusClient
{
public const UInt16 DefaultPort = 502;
@ -20,7 +20,7 @@ public class ModbusTcpClient : ModbusClient
private UInt16 NextId() => unchecked(++_Id);
public ModbusTcpClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId)
public ModbusTcpClient(ModbusConnection connection, Byte slaveId, Endianness endianness = LittleEndian32BitIntegers | LittleEndian32Floats) : base(connection, slaveId, endianness)
{
}

View File

@ -1,4 +1,5 @@
using System.Collections;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Protocols.Modbus.Conversions;
@ -8,7 +9,7 @@ public partial class ModbusRegisters
{
var offset = index - StartRegister;
var byteArray = BitConverter.GetBytes(Registers[offset]).Reverse().ToArray();
var byteArray = Registers[offset].Apply(BitConverter.GetBytes).Reverse().ToArray();
var bitArray = new BitArray(byteArray);
return bitArray.Get(bitIndex);
@ -18,7 +19,7 @@ public partial class ModbusRegisters
{
var offset = index - StartRegister;
var byteArray = BitConverter.GetBytes(Registers[offset]).Reverse().ToArray();
var byteArray = Registers[offset].Apply(BitConverter.GetBytes).Reverse().ToArray();
var bitArray = new BitArray(byteArray);
bitArray.Set(bitIndex, value);

View File

@ -9,7 +9,7 @@ public partial class ModbusRegisters
var bytearray = BitConverter.GetBytes(value).Reverse().ToArray();
var value32 = BitConverter.ToUInt32(bytearray);
Registers[index - StartRegister] = (UInt16)(value32 >> 16);
Registers[index - StartRegister ] = (UInt16)(value32 >> 16);
Registers[index - StartRegister + 1] = (UInt16)(value32 & 0xFFFF);
}

View File

@ -9,7 +9,7 @@ public static class Accessors
public static MbWord WordAt (this ArraySegment<Byte> data, Byte i) => new MbWord(data, i);
public static MbAddress AddressAt(this ArraySegment<Byte> data, Byte i) => new MbAddress(data, i);
public static MbWords WordsAt(this ArraySegment<Byte> data, Byte i) => new MbWords(data, i);
public static MbRegisters RegistersAt(this ArraySegment<Byte> data, Byte i) => new MbRegisters(data, i);
public static MbBits BitsAt (this ArraySegment<Byte> data, Byte i) => new MbBits(data, i);

View File

@ -3,30 +3,30 @@ using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
public readonly struct MbWords : IReadOnlyList<UInt16>
public readonly struct MbRegisters : IReadOnlyList<UInt16>
{
private readonly ArraySegment<Byte> _Data;
internal MbWords(ArraySegment<Byte> data, Byte startIndex) : this(data, startIndex, CountWords(data, startIndex))
internal MbRegisters(ArraySegment<Byte> data, Byte startIndex) : this(data, startIndex, CountWords(data, startIndex))
{ }
internal MbRegisters(ArraySegment<Byte> data, Byte startIndex, UInt16 wordCount) : this(data.Array!, startIndex, wordCount)
{
}
internal MbRegisters(Byte[] data, Byte startIndex, UInt16 wordCount)
{
_Data = new ArraySegment<Byte>(data, startIndex, wordCount * 2);
}
private static UInt16 CountWords(ArraySegment<Byte> data, Byte startIndex)
{
var wordCount = (data.Count - startIndex) / 2;
return wordCount.ConvertTo<UInt16>();
}
internal MbWords(ArraySegment<Byte> data, Byte startIndex, UInt16 wordCount) : this(data.Array!, startIndex, wordCount)
{
}
internal MbWords(Byte[] data, Byte startIndex, UInt16 wordCount)
{
_Data = new ArraySegment<Byte>(data, startIndex, wordCount * 2);
}
internal IReadOnlyCollection<UInt16> Set(IReadOnlyCollection<UInt16> values)
{
if (values.Count != _Data.Count / 2)

View File

@ -16,7 +16,7 @@ internal class ReadWriteRegistersCommandFrame : ModbusFrame
public MbAddress WriteAddress => Data.AddressAt(6);
public MbWord NbToWrite => Data.WordAt(8);
public MbByte ByteCount => Data.ByteAt(10);
public MbWords RegistersToWrite => Data.WordsAt(11);
public MbRegisters RegistersToWrite => Data.RegistersAt(11);
public Int32 ExpectedResponseSize => ReadWriteRegistersResponseFrame.ExpectedSize(NbToRead);

View File

@ -14,7 +14,7 @@ internal class WriteRegistersCommandFrame : ModbusFrame
public MbAddress WriteAddress => Data.AddressAt(2);
public MbWord NbOfRegisters => Data.WordAt(4);
public MbByte ByteCount => Data.ByteAt(6);
public MbWords RegistersToWrite => Data.WordsAt(7);
public MbRegisters RegistersToWrite => Data.RegistersAt(7);
public Int32 ExpectedResponseSize => WriteRegistersResponseFrame.ExpectedSize();

View File

@ -11,7 +11,7 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2);
public MbWords RegistersRead => Data.WordsAt(3);
public MbRegisters RegistersRead => Data.RegistersAt(3);
public ReadHoldingRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))

View File

@ -12,7 +12,7 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2);
public MbWords RegistersRead => Data.WordsAt(3);
public MbRegisters RegistersRead => Data.RegistersAt(3);
public ReadInputRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))
{

View File

@ -12,7 +12,7 @@ internal class ReadWriteRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2);
public MbWords RegistersRead => Data.WordsAt(3);
public MbRegisters RegistersRead => Data.RegistersAt(3);
public ReadWriteRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))
{

View File

@ -1,7 +1,17 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record BatteryStatus(DcPhase Dc) : DeviceStatus, IDcConnection;
using T = BatteryStatus;
[OpParallel]
public partial record BatteryStatus : DeviceStatus, IDcConnection
{
public DcBus Dc { get; init; }
public Percent Soc { get; init; }
public Temperature Temperature { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = BatteryStatus;
[GeneratedCode("generate.sh", "1")]
public partial record BatteryStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IAc1Connection
{
Ac1Phase Ac { get; }
Ac1Bus Ac { get; }
}

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IAc3Connection
{
Ac1Phase Ac3 { get; }
Ac3Bus Ac { get; }
}

View File

@ -5,5 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IDcConnection
{
DcPhase Dc { get; }
DcBus Dc { get; }
}

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IPvConnection
{
IReadOnlyList<DcPhase> Strings { get; }
IReadOnlyList<DcBus> Strings { get; }
}

View File

@ -1,8 +1,14 @@
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record DcDcConverterStatus(DcPhase Left, DcPhase Right) : DeviceStatus;
[OpParallel]
public partial record DcDcConverterStatus : DeviceStatus
{
public DcBus Left { get; init; }
public DcBus Right { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = DcDcConverterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record DcDcConverterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -5,8 +5,48 @@ namespace InnovEnergy.Lib.StatusApi;
public abstract record DeviceStatus
{
public String DeviceType => GetType()
.Generate(t => t.BaseType!)
.Unfold(t => t.BaseType)
.First(t => t.IsAbstract)
.Name
.Replace("Status", "");
}
// public static class Program
// {
// public static void Main(string[] args)
// {
// var x = new ThreePhasePvInverterStatus
// {
// Ac = new()
// {
// Frequency = 50,
// L1 = new()
// {
// Current = 10,
// Voltage = 10,
// Phi = 0,
// },
// L2 = new()
// {
// Current = 52,
// Voltage = 220,
// Phi = Angle.Pi / 2,
// },
// L3 = new()
// {
// Current = 158,
// Voltage = 454,
// Phi = Angle.Pi / 3,
// },
// },
// Strings = new DcBus[]
// {
// new() { Current = 10, Voltage = 22 },
// new() { Current = 12, Voltage = 33 },
// }
// };
//
// var s = x.ToJson();
// }
// }

View File

@ -0,0 +1,5 @@
namespace InnovEnergy.Lib.StatusApi.Generator;
[AttributeUsage(AttributeTargets.Class)]
internal class OpParallelAttribute : Attribute
{}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = Template;
[GeneratedCode("generate.sh", "1")]
public partial record Template
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
scriptDir=$( dirname -- "$0"; )
cd "$scriptDir/.." || exit
for path in $(grep -e '\[OpParallel\]' -l *.cs)
do
file=$(basename -- "$path")
class="${file%.*}"
echo "generating $file"
sed "s/Template/$class/g" "./Generator/Template.txt" > "./$class.generated.cs"
done

View File

@ -1,8 +1,14 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public record MpptStatus(DcPhase Dc, IReadOnlyList<DcPhase> Strings) : IDcConnection, IPvConnection;
[OpParallel]
public partial record MpptStatus : IDcConnection, IPvConnection
{
public DcBus Dc { get; init; }
public IReadOnlyList<DcBus> Strings { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = MpptStatus;
[GeneratedCode("generate.sh", "1")]
public partial record MpptStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -1,6 +1,11 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record PowerMeterStatus(Ac1Phase Ac3) : DeviceStatus, IAc3Connection;
[OpParallel]
public partial record PowerMeterStatus : DeviceStatus, IAc3Connection
{
public Ac3Bus Ac { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = PowerMeterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record PowerMeterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -1,9 +1,15 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record SinglePhaseInverterStatus(Ac1Phase Ac, DcPhase Dc) :
[OpParallel]
public partial record SinglePhaseInverterStatus :
DeviceStatus,
IAc1Connection,
IDcConnection;
IDcConnection
{
public Ac1Bus Ac { get; init; }
public DcBus Dc { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = SinglePhaseInverterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record SinglePhaseInverterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -1,9 +1,15 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record SinglePhasePvInverterStatus(Ac1Phase Ac, IReadOnlyList<DcPhase> Strings) :
[OpParallel]
public partial record SinglePhasePvInverterStatus :
DeviceStatus,
IAc1Connection,
IPvConnection;
IPvConnection
{
public Ac1Bus Ac { get; init; }
public IReadOnlyList<DcBus> Strings { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = SinglePhasePvInverterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record SinglePhasePvInverterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -1,11 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.Lib.props" />
<!-- <Import Project="../../App/InnovEnergy.App.props" />-->
<ItemGroup>
<ProjectReference Include="../Protocols/Modbus/Modbus.csproj" />
<ProjectReference Include="..\Units\Units.csproj" />
<ProjectReference Include="../Units/Units.csproj" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="./Generator/generate.sh" />
</Target>
</Project>

View File

@ -1,10 +1,16 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record ThreePhaseInverterStatus(Ac1Phase Ac3, DcPhase Dc) :
[OpParallel]
public partial record ThreePhaseInverterStatus :
DeviceStatus,
IAc3Connection,
IDcConnection;
IDcConnection
{
public Ac3Bus Ac { get; init; }
public DcBus Dc { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = ThreePhaseInverterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record ThreePhaseInverterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

@ -1,9 +1,15 @@
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Generator;
using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.StatusApi;
public abstract record ThreePhasePvInverterStatus(Ac1Phase Ac3, IReadOnlyList<DcPhase> Strings) :
[OpParallel]
public partial record ThreePhasePvInverterStatus :
DeviceStatus,
IAc3Connection,
IPvConnection;
IPvConnection
{
public Ac3Bus Ac { get; init; }
public IReadOnlyList<DcBus> Strings { get; init; }
}

View File

@ -0,0 +1,16 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
using System.CodeDom.Compiler;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.StatusApi;
using T = ThreePhasePvInverterStatus;
[GeneratedCode("generate.sh", "1")]
public partial record ThreePhasePvInverterStatus
{
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
public static T operator |(T left, T right) => OpParallel(left, right);
}

View File

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

View File

@ -6,7 +6,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Utils\Utils.csproj" />
<ProjectReference Include="../Utils/Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -2,15 +2,19 @@
#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 = Angle;
[JsonConverter(typeof(AngleConverter))]
public readonly partial struct Angle
{
public Decimal Value { get; }
public override String ToString() => Value + Unit;
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
@ -18,25 +22,24 @@ public readonly partial struct Angle
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);
// addition
// parallel
#if Sum
public static T operator +(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T left, T right) => new T(left.Value - right.Value);
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);
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)
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m)
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct Angle
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class AngleConverter : JsonConverter<Angle>
{
public override Angle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Angle(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Angle value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -2,15 +2,19 @@
#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 = ApparentPower;
[JsonConverter(typeof(ApparentPowerConverter))]
public readonly partial struct ApparentPower
{
public Decimal Value { get; }
public override String ToString() => Value + Unit;
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
@ -18,25 +22,24 @@ public readonly partial struct ApparentPower
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);
// addition
// parallel
#if Sum
public static T operator +(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T left, T right) => new T(left.Value - right.Value);
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);
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)
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m)
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct ApparentPower
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class ApparentPowerConverter : JsonConverter<ApparentPower>
{
public override ApparentPower Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ApparentPower(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, ApparentPower value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.Lib.Units.Composite;
public record Ac1Bus : AcPhase
{
public Frequency Frequency { get; init; }
[SuppressMessage("ReSharper", "RedundantCast")]
public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right)
{
var f = left.Frequency | right.Frequency;
var p = (AcPhase)left | (AcPhase)right;
return new Ac1Bus
{
Frequency = f,
Current = p.Current,
Voltage = p.Voltage,
Phi = p.Phi
};
}
}

View File

@ -1,26 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.Lib.Units.Composite;
public record Ac1Phase
(
Voltage Voltage,
Current Current,
Angle Phi,
Frequency Frequency
)
: AcPhase(Voltage, Current, Phi)
{
[SuppressMessage("ReSharper", "RedundantCast")]
public static Ac1Phase operator +(Ac1Phase left, Ac1Phase right)
{
var f = (left.Frequency + right.Frequency) / 2m; // TODO: check that l & r approximately equal
var acPhase = (AcPhase)left + (AcPhase)right;
return new Ac1Phase(acPhase.Voltage, acPhase.Current, acPhase.Phi, f);
}
}

View File

@ -0,0 +1,20 @@
using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite;
public record Ac3Bus
{
public AcPhase L1 { get; init; }
public AcPhase L2 { get; init; }
public AcPhase L3 { get; init; }
public Frequency Frequency { get; init; }
public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower;
public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower;
public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower;
public Angle Phi => ATan2(ReactivePower, ActivePower);
public static Ac3Bus operator |(Ac3Bus left, Ac3Bus right) => OpParallel(left, right);
private static readonly Func<Ac3Bus, Ac3Bus, Ac3Bus> OpParallel = "|".CreateBinaryOpForProps<Ac3Bus>();
}

View File

@ -1,25 +0,0 @@
using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite;
public record Ac3Phase(AcPhase L1, AcPhase L2, AcPhase L3, Frequency Frequency)
{
public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower;
public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower;
public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower;
public Angle Phi => ATan2(ReactivePower, ActivePower);
public static Ac3Phase operator +(Ac3Phase left, Ac3Phase right)
{
var f = (left.Frequency + right.Frequency) / 2m; // TODO: check that l & r approximately equal
var l1 = left.L1 + right.L1;
var l2 = left.L2 + right.L2;
var l3 = left.L3 + right.L3;
return new Ac3Phase(l1, l2, l3, f);
}
}

View File

@ -3,29 +3,35 @@ using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite;
public record AcPhase : Phase
public record AcPhase : IBus
{
public AcPhase(Voltage voltage, Current current, Angle phi) : base(voltage, current)
private readonly Voltage _Voltage;
public Voltage Voltage
{
if (voltage < 0) throw new ArgumentException("RMS value cannot be negative", nameof(voltage));
if (current < 0) throw new ArgumentException("RMS value cannot be negative", nameof(current));
Phi = phi;
get => _Voltage;
init => _Voltage = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative");
}
public Angle Phi { get; }
private readonly Current _Current;
public Current Current
{
get => _Current;
init => _Current = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative");
}
public Angle Phi { get; init; }
public ApparentPower ApparentPower => Voltage.Value * Current.Value ;
public Power ActivePower => ApparentPower.Value * PowerFactor;
public Power ActivePower => ApparentPower.Value * PowerFactor.Value;
public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi);
public Decimal PowerFactor => Cos(Phi);
public Number PowerFactor => Cos(Phi);
public static AcPhase operator +(AcPhase left, AcPhase right)
public static AcPhase operator |(AcPhase left, AcPhase right)
{
// the Voltages of two phases are expected to be in phase and equal
var v = (left.Voltage + right.Voltage) / 2m; // TODO: check that l & r approximately equal
var v = left.Voltage | right.Voltage;
// currents (RMS) can be different and out of phase
// https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/
@ -34,21 +40,27 @@ public record AcPhase : Phase
// left(t) = ILeft sin(ωt)
// right(t) = IRight sin(ωt + φ).
// sum(t) = left(t) + right(t) = ISum sin(ωt + ψ).
// THEN
// THEN
// ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ).
// C = IRight * sin(φ) / sin(ψ).
// in this calc left(t) has zero phase shift.
// in this calculation left(t) has zero phase shift.
// we can shift both waves by -left.Phi, so
// φ := right.phi - left.phi
var phi = right.Phi - left.Phi;
var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi));
var iSum = right.Current * Sin(phi) / Sin(phiSum);
return new AcPhase(v, iSum, phiSum);
return new AcPhase
{
Voltage = v,
Current = iSum,
Phi = phiSum
};
}
}

View File

@ -0,0 +1,14 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units.Composite;
public record DcBus : IBus
{
public Voltage Voltage { get; init; }
public Current Current { get; init; }
public Power Power => Current * Voltage;
public static DcBus operator |(DcBus left, DcBus right) => OpParallel(left, right);
private static readonly Func<DcBus, DcBus, DcBus> OpParallel = "|".CreateBinaryOpForProps<DcBus>();
}

View File

@ -1,15 +0,0 @@
namespace InnovEnergy.Lib.Units.Composite;
public record DcPhase(Voltage Voltage, Current Current) : Phase(Voltage, Current)
{
public Power Power => Current * Voltage;
public static DcPhase operator +(DcPhase left, DcPhase right)
{
var v = (left.Voltage + right.Voltage) / 2m;
var i = left.Current + right.Current;
return new DcPhase(v, i);
}
}

View File

@ -0,0 +1,11 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.Lib.Units.Composite;
[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
public interface IBus
{
public Voltage Voltage { get; }
public Current Current { get; }
}

View File

@ -1,7 +0,0 @@
namespace InnovEnergy.Lib.Units.Composite;
public abstract record Phase
(
Voltage Voltage,
Current Current
);

View File

@ -2,15 +2,19 @@
#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 = Current;
[JsonConverter(typeof(CurrentConverter))]
public readonly partial struct Current
{
public Decimal Value { get; }
public override String ToString() => Value + Unit;
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
@ -18,25 +22,24 @@ public readonly partial struct Current
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);
// addition
// parallel
#if Sum
public static T operator +(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T left, T right) => new T(left.Value - right.Value);
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);
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)
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m)
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct Current
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class CurrentConverter : JsonConverter<Current>
{
public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Current(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -0,0 +1,17 @@
using InnovEnergy.Lib.Time.Unix;
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 * 1000m / (Decimal) timeSpan.TotalHours ;
public static Power operator /(Energy energy, UnixTimeSpan timeSpan) => energy.Value * 3_600_000m / timeSpan.Ticks;
}

View File

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

View File

@ -2,15 +2,19 @@
#define Equal
using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units;
using T = Frequency;
[JsonConverter(typeof(FrequencyConverter))]
public readonly partial struct Frequency
{
public Decimal Value { get; }
public override String ToString() => Value + Unit;
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
@ -18,25 +22,24 @@ public readonly partial struct Frequency
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);
// addition
// parallel
#if Sum
public static T operator +(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T left, T right) => new T(left.Value - right.Value);
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);
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)
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m)
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct Frequency
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class FrequencyConverter : JsonConverter<Frequency>
{
public override Frequency Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Frequency(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Frequency value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,16 +1,20 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
#define Type
#define AggregationType
using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units;
using T = Template;
[JsonConverter(typeof(TemplateConverter))]
public readonly partial struct Template
{
public Decimal Value { get; }
public override String ToString() => Value + Unit;
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
@ -18,25 +22,24 @@ public readonly partial struct Template
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);
// addition
// parallel
#if Sum
public static T operator +(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T left, T right) => new T(left.Value - right.Value);
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);
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)
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m)
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct Template
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class TemplateConverter : JsonConverter<Template>
{
public override Template Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Template(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Template value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

Some files were not shown because too many files have changed in this diff Show More