Merge branch 'main' of https://git.innov.energy/Innovenergy/git_trunk
This commit is contained in:
commit
683ef02704
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -1,7 +1,7 @@
|
|||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Innovenergy.Backend.Controllers;
|
||||
namespace InnovEnergy.App.Backend.Controllers;
|
||||
|
||||
public class ReturnsAttribute : ProducesResponseTypeAttribute
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Innovenergy.Backend.Model;
|
||||
namespace InnovEnergy.App.Backend.Model;
|
||||
|
||||
public class Folder : TreeNode
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Innovenergy.Backend.Model;
|
||||
namespace InnovEnergy.App.Backend.Model;
|
||||
|
||||
|
||||
public class Installation : TreeNode
|
||||
|
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using SQLite;
|
||||
|
||||
namespace Innovenergy.Backend.Model.Relations;
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
public class Session : Relation<String, Int64>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using SQLite;
|
||||
|
||||
namespace Innovenergy.Backend.Model.Relations;
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
internal class User2Folder : Relation<Int64, Int64>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using SQLite;
|
||||
|
||||
namespace Innovenergy.Backend.Model.Relations;
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
internal class User2Installation : Relation<Int64, Int64>
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using SQLite;
|
||||
|
||||
namespace Innovenergy.Backend.Model;
|
||||
namespace InnovEnergy.App.Backend.Model;
|
||||
|
||||
public abstract partial class TreeNode
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using SQLite;
|
||||
|
||||
namespace Innovenergy.Backend.Model;
|
||||
namespace InnovEnergy.App.Backend.Model;
|
||||
|
||||
public class User : TreeNode
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
namespace Innovenergy.Backend.Utils;
|
||||
namespace InnovEnergy.App.Backend.Utils;
|
||||
|
||||
public static class Crypto
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Innovenergy.Backend.Utils;
|
||||
namespace InnovEnergy.App.Backend.Utils;
|
||||
|
||||
public class Result
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>InnovEnergy.App.$(AssemblyName)</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
{}
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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:";
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>("|");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||
|
||||
[Flags]
|
||||
public enum Endianness
|
||||
{
|
||||
LittleEndian32BitIntegers = 0,
|
||||
LittleEndian32Floats = 0,
|
||||
BigEndian32BitIntegers = 1,
|
||||
BigEndian32Floats = 2,
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IAc1Connection
|
||||
{
|
||||
Ac1Phase Ac { get; }
|
||||
Ac1Bus Ac { get; }
|
||||
}
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IAc3Connection
|
||||
{
|
||||
Ac1Phase Ac3 { get; }
|
||||
Ac3Bus Ac { get; }
|
||||
}
|
|
@ -5,5 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IDcConnection
|
||||
{
|
||||
DcPhase Dc { get; }
|
||||
DcBus Dc { get; }
|
||||
}
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IPvConnection
|
||||
{
|
||||
IReadOnlyList<DcPhase> Strings { get; }
|
||||
IReadOnlyList<DcBus> Strings { get; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,5 @@
|
|||
namespace InnovEnergy.Lib.StatusApi.Generator;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class OpParallelAttribute : Attribute
|
||||
{}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||
<ProjectReference Include="../Utils/Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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>();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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>();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace InnovEnergy.Lib.Units.Composite;
|
||||
|
||||
public abstract record Phase
|
||||
(
|
||||
Voltage Voltage,
|
||||
Current Current
|
||||
);
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue