133 lines
7.6 KiB
C#
133 lines
7.6 KiB
C#
using InnovEnergy.Lib.Units;
|
|
using InnovEnergy.Lib.Units.Power;
|
|
|
|
namespace InnovEnergy.Lib.Devices.PLVario2Meter;
|
|
|
|
public class PlVarioMeterRecord
|
|
{
|
|
private readonly PlVarioMeterRegisters _raw;
|
|
|
|
public PlVarioMeterRecord(PlVarioMeterRegisters raw)
|
|
{
|
|
_raw = raw ?? throw new ArgumentNullException(nameof(raw));
|
|
}
|
|
|
|
|
|
static ushort SwapBytes16(ushort x)
|
|
=> (ushort)((x << 8) | (x >> 8));
|
|
|
|
static float DecodeFloat_BADC(ushort w0, ushort w1)
|
|
{
|
|
// Build bytes: [B A D C] = swap bytes in each word, and use W1 then W0
|
|
ushort w0s = SwapBytes16(w0); // F1AA
|
|
ushort w1s = SwapBytes16(w1); // 4247
|
|
uint raw = ((uint)w1s << 16) | w0s; // 0x4247F1AA
|
|
return BitConverter.Int32BitsToSingle(unchecked((int)raw));
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Decoders (DCBA) from raw UInt16 pairs
|
|
// -------------------------------------------------------------------------
|
|
private static uint DecodeUInt32DCBA(ushort lowWord, ushort highWord)
|
|
=> ((uint)highWord << 16) | (uint)lowWord;
|
|
|
|
private static float DecodeFloatDCBA(ushort lowWord, ushort highWord)
|
|
{
|
|
uint raw = ((uint)highWord << 16) | (uint)lowWord;
|
|
return BitConverter.Int32BitsToSingle(unchecked((int)raw));
|
|
}
|
|
|
|
private static Double F(ushort w0, ushort w1) => DecodeFloat_BADC(w0, w1);
|
|
private static UInt32 U32(ushort w0, ushort w1) => DecodeUInt32DCBA(w0, w1);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Time
|
|
// -------------------------------------------------------------------------
|
|
/// <summary>Timestamp in seconds (as provided by the device).</summary>
|
|
public uint TimestampSeconds => U32(_raw.Timestamp_W0, _raw.Timestamp_W1);
|
|
|
|
/// <summary>Timestamp interpreted as Unix epoch seconds (UTC).</summary>
|
|
public DateTimeOffset TimestampUtc => DateTimeOffset.FromUnixTimeSeconds(TimestampSeconds);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Voltages (phase-neutral and line-line)
|
|
// -------------------------------------------------------------------------
|
|
public VoltageRms VoltageU1 => new(F(_raw.VoltageU1_W0, _raw.VoltageU1_W1)); // U-1
|
|
public VoltageRms VoltageU2 => new(F(_raw.VoltageU2_W0, _raw.VoltageU2_W1)); // U-2
|
|
public VoltageRms VoltageU3 => new(F(_raw.VoltageU3_W0, _raw.VoltageU3_W1)); // U-3
|
|
|
|
// Friendly aliases (match your naming style)
|
|
public VoltageRms GridAbLineVoltage => (VoltageRms)new(F(_raw.VoltageUL1_W0, _raw.VoltageUL1_W1));
|
|
public VoltageRms GridBcLineVoltage => (VoltageRms)new(F(_raw.VoltageUL2_W0, _raw.VoltageUL2_W1));
|
|
public VoltageRms GridCaLineVoltage => (VoltageRms)new(F(_raw.VoltageUL3_W0, _raw.VoltageUL3_W1));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Frequency
|
|
// -------------------------------------------------------------------------
|
|
public Double Frequency => (F(_raw.Frequency_W0, _raw.Frequency_W1));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Currents
|
|
// -------------------------------------------------------------------------
|
|
public CurrentRms CurrentI1 => new(F(_raw.CurrentI1_W0, _raw.CurrentI1_W1));
|
|
public CurrentRms CurrentI2 => new(F(_raw.CurrentI2_W0, _raw.CurrentI2_W1));
|
|
public CurrentRms CurrentI3 => new(F(_raw.CurrentI3_W0, _raw.CurrentI3_W1));
|
|
public CurrentRms CurrentI4 => new(F(_raw.CurrentI4_W0, _raw.CurrentI4_W1)); // optional channel
|
|
public CurrentRms CurrentTotal => new(F(_raw.CurrentTotal_W0, _raw.CurrentTotal_W1));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Active Power (kW)
|
|
// -------------------------------------------------------------------------
|
|
public ActivePower ActivePowerL1 => new(F(_raw.ActivePowerP1_W0, _raw.ActivePowerP1_W1) * 1000);
|
|
public ActivePower ActivePowerL2 => new(F(_raw.ActivePowerP2_W0, _raw.ActivePowerP2_W1) * 1000);
|
|
public ActivePower ActivePowerL3 => new(F(_raw.ActivePowerP3_W0, _raw.ActivePowerP3_W1) * 1000);
|
|
public ActivePower ActivePowerCh4 => new(F(_raw.ActivePowerP4_W0, _raw.ActivePowerP4_W1) * 1000); // optional channel
|
|
public ActivePower ActivePowerTotal => new(F(_raw.ActivePowerTotal_W0, _raw.ActivePowerTotal_W1) * 1000);
|
|
|
|
// If you later map import/export separately, you can redefine these.
|
|
// For now, the table provides only total P (signed), so we expose it as "GridPower".
|
|
public ActivePower GridPower => ActivePowerTotal;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Reactive Power (kVAr)
|
|
// -------------------------------------------------------------------------
|
|
public ReactivePower ReactivePowerL1 => new(F(_raw.ReactivePowerQ1_W0, _raw.ReactivePowerQ1_W1));
|
|
public ReactivePower ReactivePowerL2 => new(F(_raw.ReactivePowerQ2_W0, _raw.ReactivePowerQ2_W1));
|
|
public ReactivePower ReactivePowerL3 => new(F(_raw.ReactivePowerQ3_W0, _raw.ReactivePowerQ3_W1));
|
|
public ReactivePower ReactivePowerCh4 => new(F(_raw.ReactivePowerQ4_W0, _raw.ReactivePowerQ4_W1));
|
|
public ReactivePower ReactivePowerTotal => new(F(_raw.ReactivePowerTotal_W0, _raw.ReactivePowerTotal_W1));
|
|
|
|
// Friendly alias
|
|
public ReactivePower GridReactivePower => ReactivePowerTotal;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Apparent Power (kVA)
|
|
// -------------------------------------------------------------------------
|
|
public ApparentPower ApparentPowerL1 => new(F(_raw.ApparentPowerS1_W0, _raw.ApparentPowerS1_W1));
|
|
public ApparentPower ApparentPowerL2 => new(F(_raw.ApparentPowerS2_W0, _raw.ApparentPowerS2_W1));
|
|
public ApparentPower ApparentPowerL3 => new(F(_raw.ApparentPowerS3_W0, _raw.ApparentPowerS3_W1));
|
|
public ApparentPower ApparentPowerCh4 => new(F(_raw.ApparentPowerS4_W0, _raw.ApparentPowerS4_W1));
|
|
public ApparentPower ApparentPowerTotal => new(F(_raw.ApparentPowerTotal_W0, _raw.ApparentPowerTotal_W1));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Power Factor
|
|
// -------------------------------------------------------------------------
|
|
public Double PowerFactorL1 => (F(_raw.PowerFactorPF1_W0, _raw.PowerFactorPF1_W1));
|
|
public Double PowerFactorL2 => (F(_raw.PowerFactorPF2_W0, _raw.PowerFactorPF2_W1));
|
|
public Double PowerFactorL3 => (F(_raw.PowerFactorPF3_W0, _raw.PowerFactorPF3_W1));
|
|
public Double PowerFactorCh4 => (F(_raw.PowerFactorPF4_W0, _raw.PowerFactorPF4_W1));
|
|
public Double PowerFactorTotal => (F(_raw.PowerFactorTotal_W0, _raw.PowerFactorTotal_W1));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Repeated totals (table shows duplicates at offsets 66..74)
|
|
// Keep them exposed in case your installation populates them differently.
|
|
// -------------------------------------------------------------------------
|
|
public CurrentRms CurrentTotalRepeat => new(F(_raw.CurrentTotalRepeat_W0, _raw.CurrentTotalRepeat_W1));
|
|
public ActivePower ActivePowerTotalRepeat => new(F(_raw.ActivePowerTotalRepeat_W0, _raw.ActivePowerTotalRepeat_W1));
|
|
public ReactivePower ReactivePowerTotalRepeat => new(F(_raw.ReactivePowerTotalRepeat_W0, _raw.ReactivePowerTotalRepeat_W1));
|
|
public ApparentPower ApparentPowerTotalRepeat => new(F(_raw.ApparentPowerTotalRepeat_W0, _raw.ApparentPowerTotalRepeat_W1));
|
|
public Double PowerFactorTotalRepeat => (F(_raw.PowerFactorTotalRepeat_W0, _raw.PowerFactorTotalRepeat_W1));
|
|
|
|
} |