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

View File

@ -1,13 +1,14 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using Innovenergy.Backend.Database; using InnovEnergy.App.Backend.Database;
using Innovenergy.Backend.Model; using InnovEnergy.App.Backend.Model;
using Innovenergy.Backend.Model.Relations; using InnovEnergy.App.Backend.Model.Relations;
using Innovenergy.Backend.Utils; using InnovEnergy.App.Backend.Utils;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor; using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor;
namespace Innovenergy.Backend.Controllers; namespace InnovEnergy.App.Backend.Controllers;
[ApiController] [ApiController]
[Route("api/")] [Route("api/")]
@ -173,7 +174,7 @@ public class Controller
using var db = Db.Connect(); using var db = Db.Connect();
var folders = db var folders = db
.GetDirectlyAccessibleFolders(caller) // ReSharper disable once AccessToDisposedClosure .GetDirectlyAccessibleFolders(caller) // ReSharper disable once AccessToDisposedClosure
.Select(f => PopulateChildren(db, f)); .Select(f => PopulateChildren(db, f));
var installations = db.GetDirectlyAccessibleInstallations(caller); var installations = db.GetDirectlyAccessibleInstallations(caller);
@ -183,6 +184,25 @@ public class Controller
.ToList(); // important! .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) private static Folder PopulateChildren(Db db, Folder folder, HashSet<Int64>? hs = null)
{ {
// TODO: remove cycle detector // TODO: remove cycle detector

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<InvariantGlobalization>true</InvariantGlobalization> <InvariantGlobalization>true</InvariantGlobalization>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings> <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> <Authors>$(Company) Team</Authors>
</PropertyGroup> </PropertyGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.StatusApi.Phases;
using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx; using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Devices.EmuMeter; namespace InnovEnergy.Lib.Devices.EmuMeter;
@ -29,91 +27,71 @@ public class EmuMeterDevice
return null; 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() private EmuMeterStatus TryReadStatus()
{ {
// Console.WriteLine("Reading Emu Meter Data"); // 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 powerCurrent = Modbus.ReadHoldingRegisters(9000, 108).ToDecimals(); // TODO "ModbusRegisters"
var voltageFreq = Modbus.ReadHoldingRegisters(9200, 112).ToDecimals(); // To check with Ivo var voltageFreq = Modbus.ReadHoldingRegisters(9200, 112).ToDecimals(); // To check with Ivo
var energyTotal = Modbus.ReadHoldingRegisters(6000, 24) .ToUInt64s();
var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s(); // var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s();
var activePowerL123 = powerCurrent[0];
var activePowerL1 = powerCurrent[1]; var activePowerL1 = powerCurrent[1];
var activePowerL2 = powerCurrent[2]; var activePowerL2 = powerCurrent[2];
var activePowerL3 = powerCurrent[3]; var activePowerL3 = powerCurrent[3];
var reactivePowerL123 = powerCurrent[5];
var reactivePowerL1 = powerCurrent[6]; var reactivePowerL1 = powerCurrent[6];
var reactivePowerL2 = powerCurrent[7]; var reactivePowerL2 = powerCurrent[7];
var reactivePowerL3 = powerCurrent[8]; 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 currentL1 = powerCurrent[51];
var currentL2 = powerCurrent[52]; var currentL2 = powerCurrent[52];
var currentL3 = powerCurrent[53]; var currentL3 = powerCurrent[53];
var voltageL1N = voltageFreq[0]; var voltageL1N = voltageFreq[0];
var voltageL2N = voltageFreq[1]; var voltageL2N = voltageFreq[1];
var voltageL3N = voltageFreq[2]; 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 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 l1 = new AcPhase
var energyExportL1 = energyPhases[20 / 4] / 1000.0m; {
var energyImportL2 = energyPhases[40 / 4] / 1000.0m; Current = currentL1,
var energyExportL2 = energyPhases[60 / 4] / 1000.0m; Voltage = voltageL1N,
var energyImportL3 = energyPhases[80 / 4] / 1000.0m; Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works
var energyExportL3 = energyPhases[100 / 4] / 1000.0m; };
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 return new EmuMeterStatus
( {
Ac: new ThreePhaseAcConnection Ac = new Ac3Bus
( {
new AcPhase( Frequency = frequency,
voltageL1N, L1 = l1,
currentL1, L2 = l2,
GetPhi(powerFactorL1) L3 = l3
), }
};
new AcPhase(
voltageL2N,
currentL2,
GetPhi(powerFactorL2)
),
new AcPhase(
voltageL3N,
currentL3,
GetPhi(powerFactorL3)
),
frequency
),
activePowerL123,
reactivePowerL123,
apparentPowerL123,
currentL123,
voltageL1L2,
voltageL2L3,
voltageL3L1,
energyImportL123,
energyImportL1,
energyImportL2,
energyImportL3,
energyExportL123,
energyExportL1,
energyExportL2,
energyExportL3
);
} }
} }

View File

@ -1,26 +1,10 @@
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Devices;
namespace InnovEnergy.Lib.Devices.EmuMeter; namespace InnovEnergy.Lib.Devices.EmuMeter;
public record EmuMeterStatus public record EmuMeterStatus : PowerMeterStatus
( {
ThreePhaseAcConnection Ac, // TODO: additional Measurements, device id
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)
{}

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using System.Collections; using System.Collections;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Protocols.Modbus.Conversions; namespace InnovEnergy.Lib.Protocols.Modbus.Conversions;
@ -8,7 +9,7 @@ public partial class ModbusRegisters
{ {
var offset = index - StartRegister; 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); var bitArray = new BitArray(byteArray);
return bitArray.Get(bitIndex); return bitArray.Get(bitIndex);
@ -18,7 +19,7 @@ public partial class ModbusRegisters
{ {
var offset = index - StartRegister; 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); var bitArray = new BitArray(byteArray);
bitArray.Set(bitIndex, value); bitArray.Set(bitIndex, value);

View File

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

View File

@ -9,8 +9,8 @@ public static class Accessors
public static MbWord WordAt (this ArraySegment<Byte> data, Byte i) => new MbWord(data, i); 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 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); public static MbBits BitsAt (this ArraySegment<Byte> data, Byte i) => new MbBits(data, i);
public static MbByte<T> ByteAt<T>(this ArraySegment<Byte> data,Byte i) where T : struct, IConvertible => new MbByte<T>(data, i); public static MbByte<T> ByteAt<T>(this ArraySegment<Byte> data,Byte i) where T : struct, IConvertible => new MbByte<T>(data, i);

View File

@ -3,30 +3,30 @@ using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; 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; 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) private static UInt16 CountWords(ArraySegment<Byte> data, Byte startIndex)
{ {
var wordCount = (data.Count - startIndex) / 2; var wordCount = (data.Count - startIndex) / 2;
return wordCount.ConvertTo<UInt16>(); 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) internal IReadOnlyCollection<UInt16> Set(IReadOnlyCollection<UInt16> values)
{ {
if (values.Count != _Data.Count / 2) if (values.Count != _Data.Count / 2)

View File

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

View File

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

View File

@ -11,7 +11,7 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3; internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2); 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)) public ReadHoldingRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))

View File

@ -12,7 +12,7 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3; internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2); 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)) public ReadInputRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))
{ {

View File

@ -12,7 +12,7 @@ internal class ReadWriteRegistersResponseFrame : ModbusFrame
internal new const Int32 MinSize = 3; internal new const Int32 MinSize = 3;
public MbByte ByteCount => Data.ByteAt(2); 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)) public ReadWriteRegistersResponseFrame(Byte slave, UInt16s registersRead) : base(ExpectedSize(registersRead.Count))
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ public readonly partial struct UnixTimeSpan
public static UnixTimeSpan operator /(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks / b); public static UnixTimeSpan operator /(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks / b);
public static UnixTimeSpan operator /(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks / (UInt32)b); public static UnixTimeSpan operator /(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks / (UInt32)b);
public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks; public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks;
public static UnixTimeSpan operator %(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks % b); public static UnixTimeSpan operator %(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks % b);
public static UnixTimeSpan operator %(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks % (UInt32)b); public static UnixTimeSpan operator %(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks % (UInt32)b);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,52 +3,64 @@ using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite; 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)); get => _Voltage;
if (current < 0) throw new ArgumentException("RMS value cannot be negative", nameof(current)); init => _Voltage = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative");
Phi = phi;
} }
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 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 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 // 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 // currents (RMS) can be different and out of phase
// https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/ // https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/
// IF // IF
// left(t) = ILeft sin(ωt) // left(t) = ILeft sin(ωt)
// right(t) = IRight sin(ωt + φ). // right(t) = IRight sin(ωt + φ).
// sum(t) = left(t) + right(t) = ISum sin(ωt + ψ). // sum(t) = left(t) + right(t) = ISum sin(ωt + ψ).
// THEN
// THEN
// ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ). // ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ).
// C = IRight * sin(φ) / sin(ψ). // 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 // we can shift both waves by -left.Phi, so
// φ := right.phi - left.phi // φ := right.phi - left.phi
var phi = right.Phi - left.Phi; var phi = right.Phi - left.Phi;
var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi)); var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi));
var iSum = right.Current * Sin(phi) / Sin(phiSum); var iSum = right.Current * Sin(phi) / Sin(phiSum);
return new AcPhase(v, iSum, phiSum); return new AcPhase
{
Voltage = v,
Current = iSum,
Phi = phiSum
};
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,17 @@
using InnovEnergy.Lib.Time.Unix;
using InnovEnergy.Lib.Units.Generator;
namespace InnovEnergy.Lib.Units;
[Sum]
public readonly partial struct Energy
{
public static String Unit => "kWh";
public static String Symbol => "E";
public Energy(Decimal value) => Value = value;
public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value * 1000m / (Decimal) timeSpan.TotalHours ;
public static Power operator /(Energy energy, UnixTimeSpan timeSpan) => energy.Value * 3_600_000m / timeSpan.Ticks;
}

View File

@ -0,0 +1,97 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
#define Sum
using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units;
using T = Energy;
[JsonConverter(typeof(EnergyConverter))]
public readonly partial struct Energy
{
public Decimal Value { get; }
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value);
public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value);
public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar);
// parallel
#if Sum
public static T operator |(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T t) => new T(-t.Value);
#elif Mean
public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m);
#elif Equal
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
const Decimal maxRelativeError = 0.05m;
if (relativeError > maxRelativeError)
throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" +
$"Difference > {maxRelativeError * 100}% detected\n" +
$"{nameof(left)} : {left}\n" +
$"{nameof(right)}: {right}");
return new T((left.Value + right.Value) / 2m);
}
#endif
// compare
public static Boolean operator ==(T left, T right) => left.Value == right.Value;
public static Boolean operator !=(T left, T right) => left.Value != right.Value;
public static Boolean operator > (T left, T right) => left.Value > right.Value;
public static Boolean operator < (T left, T right) => left.Value < right.Value;
public static Boolean operator >=(T left, T right) => left.Value >= right.Value;
public static Boolean operator <=(T left, T right) => left.Value <= right.Value;
// conversion
public static implicit operator T(Decimal d) => new T(d);
public static implicit operator T(Double d) => new T((Decimal)d);
public static implicit operator T(Int32 i) => new T(i);
public static implicit operator Decimal(T t) => t.Value;
// equality
public Boolean Equals(T other) => Value == other.Value;
public override Boolean Equals(Object? obj) => obj is T other && Equals(other);
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class EnergyConverter : JsonConverter<Energy>
{
public override Energy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Energy(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Energy value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

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

View File

@ -1,16 +1,20 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
#define Type #define AggregationType
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
using T = Template; using T = Template;
[JsonConverter(typeof(TemplateConverter))]
public readonly partial struct Template public readonly partial struct Template
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // 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(scalar * t.Value);
public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar);
// addition // parallel
#if Sum #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); public static T operator -(T t) => new T(-t.Value);
#elif Mean #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 #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)); var d = Max(Abs(left.Value), Abs(right.Value));
if (d != 0m) if (d == 0m)
return new T(0m); return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d; var relativeError = Abs(left.Value - right.Value) / d;
@ -76,3 +79,19 @@ public readonly partial struct Template
public override Int32 GetHashCode() => Value.GetHashCode(); 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