diff --git a/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj b/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj
new file mode 100644
index 000000000..5a9ec8ba5
--- /dev/null
+++ b/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0
+ enable
+ enable
+ InnovEnergy.Lib.Devices.PLVario2Meter
+ preview
+
+
+
+
+
+
+
+
+
diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs
new file mode 100644
index 000000000..385180012
--- /dev/null
+++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs
@@ -0,0 +1,47 @@
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+using InnovEnergy.Lib.Protocols.Modbus.Clients;
+using InnovEnergy.Lib.Protocols.Modbus.Slaves;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.Lib.Devices.PLVario2Meter;
+
+public class PlVarioMeterDevice: ModbusDevice
+{
+ public PlVarioMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 2) : this(new TcpChannel(hostname, port), slaveId)
+ {
+ }
+
+ public PlVarioMeterDevice(Channel channel, Byte slaveId = 2) : base(new ModbusTcpClient(channel, slaveId))
+ {
+ }
+
+ public PlVarioMeterDevice(ModbusClient client) : base(client)
+ {
+ }
+
+ public new PlVarioMeterRegisters? Read()
+ {
+ try
+ {
+ return base.Read();
+ }
+ catch
+ {
+ "Failed to read data from PLVario-II meter".WriteLine();
+ return null;
+ }
+ }
+
+ public new void Write(PlVarioMeterRegisters registers)
+ {
+ try
+ {
+ base.Write(registers);
+ }
+ catch (Exception e)
+ {
+ // TODO: Log
+ Console.WriteLine(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs
new file mode 100644
index 000000000..393948719
--- /dev/null
+++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs
@@ -0,0 +1,133 @@
+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
+ // -------------------------------------------------------------------------
+ /// Timestamp in seconds (as provided by the device).
+ public uint TimestampSeconds => U32(_raw.Timestamp_W0, _raw.Timestamp_W1);
+
+ /// Timestamp interpreted as Unix epoch seconds (UTC).
+ 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));
+
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs
new file mode 100644
index 000000000..556ff1d71
--- /dev/null
+++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs
@@ -0,0 +1,168 @@
+using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
+using InnovEnergy.Lib.StatusApi.DeviceTypes;
+using InnovEnergy.Lib.Units.Composite;
+// ReSharper disable InconsistentNaming
+
+namespace InnovEnergy.Lib.Devices.PLVario2Meter;
+
+using Float32 = Single;
+
+// If your project already defines Float32 as an alias/struct, keep that.
+// Otherwise, you can just use float. Here we use float for simplicity.
+public class PlVarioMeterRegisters : IAc3Meter
+{
+ // -------------------------------------------------------------------------
+ // Timestamp (UINT32) -> 2 x UInt16
+ // Offset 0: Timestamp [s]
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1000, writable: false)] public UInt16 Timestamp_W0; // low word
+ [HoldingRegister(1001, writable: false)] public UInt16 Timestamp_W1; // high word
+
+ // -------------------------------------------------------------------------
+ // Voltages phase-neutral (FLOAT32) -> 2 x UInt16 each
+ // Offsets 2,4,6
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1002, writable: false)] public UInt16 VoltageU1_W0;
+ [HoldingRegister(1003, writable: false)] public UInt16 VoltageU1_W1;
+
+ [HoldingRegister(1004, writable: false)] public UInt16 VoltageU2_W0;
+ [HoldingRegister(1005, writable: false)] public UInt16 VoltageU2_W1;
+
+ [HoldingRegister(1006, writable: false)] public UInt16 VoltageU3_W0;
+ [HoldingRegister(1007, writable: false)] public UInt16 VoltageU3_W1;
+
+ // -------------------------------------------------------------------------
+ // Voltages line-line (FLOAT32) -> 2 x UInt16 each
+ // Offsets 8,10,12
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1008, writable: false)] public UInt16 VoltageUL1_W0;
+ [HoldingRegister(1009, writable: false)] public UInt16 VoltageUL1_W1;
+
+ [HoldingRegister(1010, writable: false)] public UInt16 VoltageUL2_W0;
+ [HoldingRegister(1011, writable: false)] public UInt16 VoltageUL2_W1;
+
+ [HoldingRegister(1012, writable: false)] public UInt16 VoltageUL3_W0;
+ [HoldingRegister(1013, writable: false)] public UInt16 VoltageUL3_W1;
+
+ // -------------------------------------------------------------------------
+ // Frequency (FLOAT32) -> 2 x UInt16
+ // Offset 14
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1014, writable: false)] public UInt16 Frequency_W0;
+ [HoldingRegister(1015, writable: false)] public UInt16 Frequency_W1;
+
+ // -------------------------------------------------------------------------
+ // Currents (FLOAT32) -> 2 x UInt16 each
+ // Offsets 16..24
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1016, writable: false)] public UInt16 CurrentI1_W0;
+ [HoldingRegister(1017, writable: false)] public UInt16 CurrentI1_W1;
+
+ [HoldingRegister(1018, writable: false)] public UInt16 CurrentI2_W0;
+ [HoldingRegister(1019, writable: false)] public UInt16 CurrentI2_W1;
+
+ [HoldingRegister(1020, writable: false)] public UInt16 CurrentI3_W0;
+ [HoldingRegister(1021, writable: false)] public UInt16 CurrentI3_W1;
+
+ [HoldingRegister(1022, writable: false)] public UInt16 CurrentI4_W0;
+ [HoldingRegister(1023, writable: false)] public UInt16 CurrentI4_W1;
+
+ [HoldingRegister(1024, writable: false)] public UInt16 CurrentTotal_W0;
+ [HoldingRegister(1025, writable: false)] public UInt16 CurrentTotal_W1;
+
+ // -------------------------------------------------------------------------
+ // Active power P (FLOAT32, kW) -> 2 x UInt16 each
+ // Offsets 26..34
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1026, writable: false)] public UInt16 ActivePowerP1_W0;
+ [HoldingRegister(1027, writable: false)] public UInt16 ActivePowerP1_W1;
+
+ [HoldingRegister(1028, writable: false)] public UInt16 ActivePowerP2_W0;
+ [HoldingRegister(1029, writable: false)] public UInt16 ActivePowerP2_W1;
+
+ [HoldingRegister(1030, writable: false)] public UInt16 ActivePowerP3_W0;
+ [HoldingRegister(1031, writable: false)] public UInt16 ActivePowerP3_W1;
+
+ [HoldingRegister(1032, writable: false)] public UInt16 ActivePowerP4_W0;
+ [HoldingRegister(1033, writable: false)] public UInt16 ActivePowerP4_W1;
+
+ [HoldingRegister(1034, writable: false)] public UInt16 ActivePowerTotal_W0;
+ [HoldingRegister(1035, writable: false)] public UInt16 ActivePowerTotal_W1;
+
+ // -------------------------------------------------------------------------
+ // Reactive power Q (FLOAT32, kVAr) -> 2 x UInt16 each
+ // Offsets 36..44
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1036, writable: false)] public UInt16 ReactivePowerQ1_W0;
+ [HoldingRegister(1037, writable: false)] public UInt16 ReactivePowerQ1_W1;
+
+ [HoldingRegister(1038, writable: false)] public UInt16 ReactivePowerQ2_W0;
+ [HoldingRegister(1039, writable: false)] public UInt16 ReactivePowerQ2_W1;
+
+ [HoldingRegister(1040, writable: false)] public UInt16 ReactivePowerQ3_W0;
+ [HoldingRegister(1041, writable: false)] public UInt16 ReactivePowerQ3_W1;
+
+ [HoldingRegister(1042, writable: false)] public UInt16 ReactivePowerQ4_W0;
+ [HoldingRegister(1043, writable: false)] public UInt16 ReactivePowerQ4_W1;
+
+ [HoldingRegister(1044, writable: false)] public UInt16 ReactivePowerTotal_W0;
+ [HoldingRegister(1045, writable: false)] public UInt16 ReactivePowerTotal_W1;
+
+ // -------------------------------------------------------------------------
+ // Apparent power S (FLOAT32, kVA) -> 2 x UInt16 each
+ // Offsets 46..54
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1046, writable: false)] public UInt16 ApparentPowerS1_W0;
+ [HoldingRegister(1047, writable: false)] public UInt16 ApparentPowerS1_W1;
+
+ [HoldingRegister(1048, writable: false)] public UInt16 ApparentPowerS2_W0;
+ [HoldingRegister(1049, writable: false)] public UInt16 ApparentPowerS2_W1;
+
+ [HoldingRegister(1050, writable: false)] public UInt16 ApparentPowerS3_W0;
+ [HoldingRegister(1051, writable: false)] public UInt16 ApparentPowerS3_W1;
+
+ [HoldingRegister(1052, writable: false)] public UInt16 ApparentPowerS4_W0;
+ [HoldingRegister(1053, writable: false)] public UInt16 ApparentPowerS4_W1;
+
+ [HoldingRegister(1054, writable: false)] public UInt16 ApparentPowerTotal_W0;
+ [HoldingRegister(1055, writable: false)] public UInt16 ApparentPowerTotal_W1;
+
+ // -------------------------------------------------------------------------
+ // Power factor PF (FLOAT32) -> 2 x UInt16 each
+ // Offsets 56..64
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1056, writable: false)] public UInt16 PowerFactorPF1_W0;
+ [HoldingRegister(1057, writable: false)] public UInt16 PowerFactorPF1_W1;
+
+ [HoldingRegister(1058, writable: false)] public UInt16 PowerFactorPF2_W0;
+ [HoldingRegister(1059, writable: false)] public UInt16 PowerFactorPF2_W1;
+
+ [HoldingRegister(1060, writable: false)] public UInt16 PowerFactorPF3_W0;
+ [HoldingRegister(1061, writable: false)] public UInt16 PowerFactorPF3_W1;
+
+ [HoldingRegister(1062, writable: false)] public UInt16 PowerFactorPF4_W0;
+ [HoldingRegister(1063, writable: false)] public UInt16 PowerFactorPF4_W1;
+
+ [HoldingRegister(1064, writable: false)] public UInt16 PowerFactorTotal_W0;
+ [HoldingRegister(1065, writable: false)] public UInt16 PowerFactorTotal_W1;
+
+ // -------------------------------------------------------------------------
+ // Repeated totals (as shown in the table) at offsets 66..74
+ // -------------------------------------------------------------------------
+ [HoldingRegister(1066, writable: false)] public UInt16 CurrentTotalRepeat_W0;
+ [HoldingRegister(1067, writable: false)] public UInt16 CurrentTotalRepeat_W1;
+
+ [HoldingRegister(1068, writable: false)] public UInt16 ActivePowerTotalRepeat_W0;
+ [HoldingRegister(1069, writable: false)] public UInt16 ActivePowerTotalRepeat_W1;
+
+ [HoldingRegister(1070, writable: false)] public UInt16 ReactivePowerTotalRepeat_W0;
+ [HoldingRegister(1071, writable: false)] public UInt16 ReactivePowerTotalRepeat_W1;
+
+ [HoldingRegister(1072, writable: false)] public UInt16 ApparentPowerTotalRepeat_W0;
+ [HoldingRegister(1073, writable: false)] public UInt16 ApparentPowerTotalRepeat_W1;
+
+ [HoldingRegister(1074, writable: false)] public UInt16 PowerFactorTotalRepeat_W0;
+ [HoldingRegister(1075, writable: false)] public UInt16 PowerFactorTotalRepeat_W1;
+
+ public Ac3Bus Ac { get; }
+}