diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln
index 10c541c54..24a874d54 100644
--- a/csharp/InnovEnergy.sln
+++ b/csharp/InnovEnergy.sln
@@ -93,6 +93,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SofarInverter", "Lib\Device
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchneiderMeterDriver", "App\SchneiderMeterDriver\SchneiderMeterDriver.csproj", "{2E7E7657-3A53-4B62-8927-FE9A082B81DE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery250UP", "Lib\Devices\Battery250UP\Battery250UP.csproj", "{F2967439-A590-4D5E-9208-1B973C83AA1C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -244,6 +246,10 @@ Global
{2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2967439-A590-4D5E-9208-1B973C83AA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2967439-A590-4D5E-9208-1B973C83AA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2967439-A590-4D5E-9208-1B973C83AA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2967439-A590-4D5E-9208-1B973C83AA1C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
@@ -286,5 +292,6 @@ Global
{6B98449D-BF75-415A-8893-E49518F9307D} = {145597B4-3E30-45E6-9F72-4DD43194539A}
{2C7F3D89-402B-43CB-988E-8D2D853BEF44} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
{2E7E7657-3A53-4B62-8927-FE9A082B81DE} = {145597B4-3E30-45E6-9F72-4DD43194539A}
+ {F2967439-A590-4D5E-9208-1B973C83AA1C} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
EndGlobalSection
EndGlobal
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UP.csproj b/csharp/Lib/Devices/Battery250UP/Battery250UP.csproj
new file mode 100644
index 000000000..a9d8288d4
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UP.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+ InnovEnergy.Lib.Devices.Battery250UP
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UPDevices.cs b/csharp/Lib/Devices/Battery250UP/Battery250UPDevices.cs
new file mode 100644
index 000000000..68f7e5cf6
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UPDevices.cs
@@ -0,0 +1,35 @@
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up;
+
+public class Battery250UpDevices
+{
+ private readonly IReadOnlyList _Devices;
+
+ public Battery250UpDevices(IReadOnlyList devices) => _Devices = devices;
+
+ public Battery250UpRecords? Read()
+ {
+ var records = _Devices
+ .Select(TryRead)
+ .NotNull()
+ .ToList();
+
+ return Battery250UpRecords.FromBatteries(records);
+ }
+
+ private static Battery250UpRecord? TryRead(Battery250UpDevice d)
+ {
+ try
+ {
+ return d.Read();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to read Battery node {d.SlaveId}\n{e.Message}");
+ // TODO: log
+
+ return null;
+ }
+ }
+}
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Api.cs b/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Api.cs
new file mode 100644
index 000000000..28a713dbc
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Api.cs
@@ -0,0 +1,259 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Units.Power;
+using static InnovEnergy.Lib.Devices.Battery250Up.DataTypes.LedState;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up;
+
+using Strings = IReadOnlyList;
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+[SuppressMessage("ReSharper", "ConvertToAutoProperty")]
+public partial class Battery250UpRecord
+{
+ public Dc_ Dc => new Dc_(this);
+ public Leds_ Leds => new Leds_(this);
+ public Temperatures_ Temperatures => new Temperatures_(this);
+ public StringActive_ BatteryStrings => new StringActive_(this);
+ public IoStatus_ IoStatus => new IoStatus_(this);
+
+ public Boolean Eoc => ParseEocReached();//Leds is { Green: On, Amber: Off, Blue : Off }; // ParseEocReached(); //
+
+ public UInt16 IoStates => _IoStates;
+ public UInt16 LimpBitMap => _LimpBitMap;
+
+ public String BatteryState => ParseBatteryState();
+
+ public String SerialNumber => $"{_SerialNum1:X4}{_SerialNum2:X4}{_SerialNum3:X4}{_SerialNum4:X4}".TrimEnd('0');
+
+ public String FwVersion => _FwVersion.ToString("X4");
+
+ public Strings Warnings => ParseWarnings().OrderBy(w => w).ToList();
+ public Strings Alarms => ParseAlarms() .OrderBy(w => w).ToList();
+
+ public Percent Soc => _Soc;
+ public Double SOCAh => _SOCAh;
+
+ public Current BusCurrent => _BusCurrent;
+
+ public Current CellsCurrent => _CellsCurrent;
+
+ public Current HeatingCurrent => _BusCurrent - _CellsCurrent;
+ public DcPower HeatingPower => HeatingCurrent * Dc.Voltage;
+
+ // Time since TOC is a counter from the last moment when the battery reached EOC
+ // When The battery is full charged (reached EOC) the Time Since TOC is set to 0
+ public TimeSpan TimeSinceTOC => TimeSpan.FromMinutes(_TimeSinceToc);
+
+ public Boolean CalibrationChargeRequested => TimeSinceTOC > TimeSpan.FromDays(7); // From AF0A (Fw Version) Each 14 days , But Max and Peter asked for 7 days.
+
+ public readonly struct Leds_
+ {
+ public LedState Blue => Self.ParseLed(LedColor.Blue);
+ public LedState Red => Self.ParseLed(LedColor.Red);
+ public LedState Green => Self.ParseLed(LedColor.Green);
+ public LedState Amber => Self.ParseLed(LedColor.Amber);
+
+ private Battery250UpRecord Self { get; }
+ internal Leds_(Battery250UpRecord self) => this.Self = self;
+
+ }
+
+ public readonly struct StringActive_
+ {
+ public Boolean String1Active => (Self._LimpBitMap & 1) == 0;
+ public Boolean String2Active => (Self._LimpBitMap & 2) == 0;
+ public Boolean String3Active => (Self._LimpBitMap & 4) == 0;
+ public Boolean String4Active => (Self._LimpBitMap & 8) == 0;
+ public Boolean String5Active => (Self._LimpBitMap & 16) == 0;
+
+ internal StringActive_(Battery250UpRecord self) => Self = self;
+ private Battery250UpRecord Self { get; }
+ }
+
+ public readonly struct IoStatus_
+ {
+ public Boolean ConnectedToDcBus => ((Self._IoStates >> 0) & 1) == 0;
+ public Boolean AlarmOutActive => ((Self._IoStates >> 1) & 1) == 0;
+ public Boolean InternalFanActive => ((Self._IoStates >> 2) & 1) == 1;
+ public Boolean VoltMeasurementAllowed => ((Self._IoStates >> 3) & 1) == 1;
+ public Boolean AuxRelayBus => ((Self._IoStates >> 4) & 1) == 0;
+ public Boolean RemoteStateActive => ((Self._IoStates >> 5) & 1) == 1;
+ public Boolean RiscActive => ((Self._IoStates >> 6) & 1) == 1;
+
+ internal IoStatus_(Battery250UpRecord self) => Self = self;
+ private Battery250UpRecord Self { get; }
+ }
+
+ public readonly struct Temperatures_
+ {
+ public Boolean Heating => (Self._IoStates & 64) != 0;
+ public Temperature Board => Self._TemperaturesBoard;
+ public Cells_ Cells => new Cells_(Self);
+
+ public TemperatureState State => Self.Leds switch
+ {
+ { Green: >= Blinking, Blue: >= Blinking } => TemperatureState.Cold,
+ _ => TemperatureState.Operation,
+ // TODO: overheated
+ };
+
+ internal Temperatures_(Battery250UpRecord self) => Self = self;
+ private Battery250UpRecord Self { get; }
+ }
+
+ public readonly struct Cells_
+ {
+ public Temperature Center => Self._TemperaturesCellsCenter;
+ public Temperature Left => Self._TemperaturesCellsLeft;
+ public Temperature Right => Self._TemperaturesCellsRight;
+ public Temperature Average => Self._TemperaturesCellsAverage;
+
+ internal Cells_(Battery250UpRecord self) => Self = self;
+ private Battery250UpRecord Self { get; }
+ }
+
+ public readonly struct Dc_
+ {
+ public Voltage Voltage => Self._CellsVoltage;
+ public Current Current => Self._CellsCurrent;
+ public ActivePower Power => Self._CellsVoltage * Self._CellsCurrent;
+
+ internal Dc_(Battery250UpRecord self) => Self = self;
+ private Battery250UpRecord Self { get; }
+ }
+
+
+ private String ParseBatteryState()
+ {
+ var s1 = Encoding.ASCII.GetString(BitConverter.GetBytes(_BatteryState1).Reverse().ToArray()); // endian swap
+ var s2 = Encoding.ASCII.GetString(BitConverter.GetBytes(_BatteryState2).Reverse().ToArray()); // endian swap
+
+ return s1 + s2;
+ }
+
+ private Boolean ParseEocReached()
+ {
+ return "EOC_" == ParseBatteryState();
+ }
+
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ private IEnumerable ParseAlarms()
+ {
+ Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0;
+
+ if (HasBit(0)) yield return "Tam : Ambient Temperature too low";
+ if (HasBit(2)) yield return "TaM2 : Ambient Temperature too high";
+ if (HasBit(3) ) yield return "Tbm : Battery temperature too low";
+ if (HasBit(5) ) yield return "TbM2 : Battery temperature too high";
+ if (HasBit(7) ) yield return "VBm2 : Bus voltage too low";
+ if (HasBit(9) ) yield return "VBM2 : Bus voltage too high";
+ if (HasBit(11)) yield return "IDM2 : Discharge current too high";
+ if (HasBit(12)) yield return "ISOB : Electrical insulation failure";
+ if (HasBit(13)) yield return "MSWE : Main switch failure";
+ if (HasBit(14)) yield return "FUSE : Main fuse blown";
+ if (HasBit(15)) yield return "HTRE : Battery failed to warm up";
+ if (HasBit(16)) yield return "TCPE : Temperature sensor failure";
+ if (HasBit(17)) yield return "STRE : Voltage measurement circuit fails";
+ if (HasBit(18)) yield return "CME : Current sensor failure";
+ if (HasBit(19)) yield return "HWFL : BMS hardware failure";
+ if (HasBit(20)) yield return "HWEM : Hardware protection tripped";
+ if (HasBit(21)) yield return "ThM : Heatsink temperature too high";
+ if (HasBit(23)) yield return "vsm2 : Low string voltage failure";
+ if (HasBit(25)) yield return "vsM2 : String voltage too high";
+ if (HasBit(27)) yield return "iCM2 : Charge current too high";
+ if (HasBit(29)) yield return "iDM2 : Discharge current too high";
+ if (HasBit(31)) yield return "MID2 : String voltage unbalance too high";
+ if (HasBit(42)) yield return "HTFS : Heater Fuse Blown";
+ if (HasBit(43)) yield return "DATA : Parameters out of range";
+ if (HasBit(45)) yield return "LMPA : Unbalance string voltages";
+ if (HasBit(46)) yield return "HEBT : Loss of heartbeat";
+ if (HasBit(48)) yield return "CURM : Battery charge requested after EDCH";
+ }
+
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ private IEnumerable ParseWarnings()
+ {
+ Boolean HasBit(Int16 bit) => (_WarningFlags & 1uL << bit) > 0;
+
+ if (HasBit(1) ) yield return "TaM1: BMS temperature high";
+ if (HasBit(4) ) yield return "TbM1: Battery temperature high";
+ if (HasBit(6) ) yield return "VBm1: Bus voltage low";
+ if (HasBit(8) ) yield return "VBM1: Bus voltage high";
+ if (HasBit(10)) yield return "IDM1: Discharge current high";
+ if (HasBit(22)) yield return "vsm1: String voltage too low";
+ if (HasBit(24)) yield return "vsM1: String voltage high";
+ if (HasBit(26)) yield return "iCM1: Charge current high";
+ if (HasBit(28)) yield return "iDM1: Discharge current high";
+ if (HasBit(30)) yield return "MID1: String voltages unbalanced";
+ if (HasBit(32)) yield return "BLPW: Not enough charging power on bus";
+ if (HasBit(33)) yield return "CCBF : Internal charger hardware failure";
+ if (HasBit(35)) yield return "Ah_W: String SOC low";
+ if (HasBit(38)) yield return "MPMM: Midpoint wiring problem";
+ if (HasBit(40)) yield return "TCdi: Temperature difference between strings high";
+ if (HasBit(44)) yield return "LMPW : String voltages unbalance warning";
+ if (HasBit(47)) yield return "TOCW : Top of Charge requested";
+ if (HasBit(49)) yield return "BUSL : Bus lower than string";
+ }
+
+
+ private Double CalcPowerLimitImposedByVoltageLimit(Double vLimit, Double rInt)
+ {
+ var v = Dc.Voltage;
+ var i = Dc.Current;
+
+ var dv = vLimit - v;
+ var di = dv / rInt;
+
+ return vLimit * (i + di);
+ }
+
+ private Double CalcPowerLimitImposedByCurrentLimit(Double iLimit, Double rInt)
+ {
+ var v = Dc.Voltage;
+ var i = Dc.Current;
+
+ var di = iLimit - i;
+ var dv = di * rInt;
+
+ return iLimit * (v + dv);
+ }
+
+ public DcPower MaxChargePower
+ {
+ get
+ {
+ var pLimits = new[]
+ {
+ CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMin),
+ CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMax),
+ CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMin),
+ CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMax)
+ };
+
+ var pLimit = pLimits.Min();
+
+ return Math.Max(pLimit, 0);
+ }
+ }
+
+ public DcPower MaxDischargePower
+ {
+ get
+ {
+ var pLimits = new[]
+ {
+ CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMin),
+ CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMax),
+ CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMin),
+ CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMax),
+ };
+
+ var pLimit = pLimits.Max();
+
+ return Math.Min(pLimit, 0);
+ }
+ }
+}
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Modbus.cs b/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Modbus.cs
new file mode 100644
index 000000000..0c4eeb80b
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UPRecord.Modbus.cs
@@ -0,0 +1,57 @@
+using System.Diagnostics.CodeAnalysis;
+using InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up;
+#pragma warning disable CS0169, CS0649
+
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+[SuppressMessage("ReSharper", "UnusedMember.Global")]
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
+[BigEndian]
+public partial class Battery250UpRecord
+{
+ [InputRegister(1004)] private UInt16 _LedStates;
+ [InputRegister(1005)] private UInt64 _WarningFlags;
+ [InputRegister(1009)] private UInt64 _AlarmFlags;
+ [InputRegister(1013)] private UInt16 _IoStates;
+
+ [InputRegister(999, Scale = 0.01)] private Double _CellsVoltage;
+ [InputRegister(1001, Scale = 0.01)] private Double _BusVoltage;
+ [InputRegister(1000, Scale = 0.01, Offset = -10000)] private Double _CellsCurrent;
+ [InputRegister(1002, Scale = 0.1, Offset = -10000)] private Double _SOCAh;
+
+
+ [InputRegister(1062, Scale = 0.01, Offset = -10000)] private Double _BusCurrent;
+
+ [InputRegister(1053, Scale = 0.1)] private Double _Soc;
+ [InputRegister(1052)] private UInt16 _TimeSinceToc;
+
+ [InputRegister(1014, Scale = 0.1, Offset = -400)] private Double _TemperaturesBoard;
+ [InputRegister(1015, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsCenter;
+ [InputRegister(1016, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsLeft;
+ [InputRegister(1017, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsRight;
+ [InputRegister(1003, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsAverage;
+
+ [InputRegister(1054)] private UInt16 _FwVersion;
+ [InputRegister(1055)] private UInt16 _SerialNum1;
+ [InputRegister(1056)] private UInt16 _SerialNum2;
+ [InputRegister(1057)] private UInt16 _SerialNum3;
+ [InputRegister(1058)] private UInt16 _SerialNum4;
+ [InputRegister(1059)] private UInt16 _LimpBitMap;
+ [InputRegister(1060)] private UInt16 _BatteryState1;
+ [InputRegister(1061)] private UInt16 _BatteryState2;
+
+ //[InputRegister(1063)] private UInt16 _TotalBatteryCycle;
+
+
+ private LedState ParseLed(LedColor led) => (LedState)((_LedStates >> (Int32)led) & 3);
+
+ // public Decimal CellsVoltage { get; init; }
+ //
+ // public Decimal MaxChargingPower { get; init; }
+ // public Decimal MaxDischargingPower { get; init; }
+}
+
+
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UPRecords.cs b/csharp/Lib/Devices/Battery250UP/Battery250UPRecords.cs
new file mode 100644
index 000000000..f8d986ef2
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UPRecords.cs
@@ -0,0 +1,50 @@
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Units.Composite;
+using InnovEnergy.Lib.Units.Power;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up;
+
+public class Battery250UpRecords
+{
+ public required DcBus Dc { get; init; }
+ public required Boolean Eoc { get; init; }
+ public required IReadOnlyList Warnings { get; init; }
+ public required IReadOnlyList Alarms { get; init; }
+ public required Percent Soc { get; init; }
+ public required Double SocAh { get; init; }
+ public required Percent CurrentMinSoc { get; init; }
+ public required Temperature Temperature { get; init; }
+ public required DcPower HeatingPower { get; init; }
+ public required TimeSpan TimeSinceToc { get; init; }
+ public required Boolean CalibrationChargeRequested { get; init; }
+
+ public required IReadOnlyList Devices { get; init; }
+
+ public static Battery250UpRecords? FromBatteries(IReadOnlyList? records)
+ {
+ if (records is null || records.Count == 0)
+ return null;
+
+ return new Battery250UpRecords
+ {
+ Devices = records,
+ Eoc = records.All(r => r.Eoc),
+ Warnings = records.SelectMany(r => r.Warnings).Distinct().ToList(),
+ Alarms = records.SelectMany(r => r.Alarms).Distinct().ToList(),
+ Soc = records.Average(r => r.Soc.Value),
+ SocAh = records.Average(r => r.SOCAh),
+ CurrentMinSoc = records.Min(r => r.Soc.Value),
+ Temperature = records.Average(b => b.Temperatures.Cells.Average.Value),
+ HeatingPower = records.Sum(b => b.HeatingPower),
+ TimeSinceToc = records.Max(r => r.TimeSinceTOC),
+ CalibrationChargeRequested = records.Any(r => r.CalibrationChargeRequested), // we changed this to Any instead of All, it's mean we wait only one battery to reach the 7 days
+
+ Dc = new()
+ {
+ Voltage = records.Average(r => r.Dc.Voltage),
+ Current = records.Sum(r => r.Dc.Current),
+ }
+ };
+ }
+
+}
diff --git a/csharp/Lib/Devices/Battery250UP/Battery250UpDevice.cs b/csharp/Lib/Devices/Battery250UP/Battery250UpDevice.cs
new file mode 100644
index 000000000..8586f9356
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/Battery250UpDevice.cs
@@ -0,0 +1,48 @@
+using System.IO.Ports;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+using InnovEnergy.Lib.Protocols.Modbus.Clients;
+using InnovEnergy.Lib.Protocols.Modbus.Slaves;
+using InnovEnergy.Lib.Utils;
+using static System.IO.Ports.Parity;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up;
+
+public class Battery250UpDevice : ModbusDevice
+{
+ public const Parity Parity = Odd;
+ public const Int32 StopBits = 1;
+ public const Int32 BaudRate = 115200;
+ public const Int32 DataBits = 8;
+
+ public Byte SlaveId { get; }
+
+ public Battery250UpDevice(String tty, Byte slaveId, SshHost host) : this
+ (
+ channel: new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits),
+ slaveId
+ )
+ {}
+
+ public Battery250UpDevice(String tty, Byte slaveId, String? host = null) : this
+ (
+ channel: host switch
+ {
+ null => new SerialPortChannel ( tty, BaudRate, Parity, DataBits, StopBits),
+ _ => new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits)
+ },
+ slaveId
+ )
+ {}
+
+ public Battery250UpDevice(Channel channel, Byte slaveId) : this
+ (
+ client: new ModbusRtuClient(channel, slaveId)
+ )
+ {}
+
+ public Battery250UpDevice(ModbusClient client): base(client)
+ {
+ SlaveId = client.SlaveId;
+ }
+
+}
diff --git a/csharp/Lib/Devices/Battery250UP/DataTypes/CellTemperatures.cs b/csharp/Lib/Devices/Battery250UP/DataTypes/CellTemperatures.cs
new file mode 100644
index 000000000..b01db23c4
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/DataTypes/CellTemperatures.cs
@@ -0,0 +1,11 @@
+using InnovEnergy.Lib.Units;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+
+public readonly struct CellTemperatures
+{
+ public Temperature Center { get; internal init; }
+ public Temperature Left { get; internal init; }
+ public Temperature Right { get; internal init; }
+}
+
diff --git a/csharp/Lib/Devices/Battery250UP/DataTypes/Constants.cs b/csharp/Lib/Devices/Battery250UP/DataTypes/Constants.cs
new file mode 100644
index 000000000..6fcf4c089
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/DataTypes/Constants.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO.Ports;
+using static System.IO.Ports.Parity;
+
+namespace InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+public static class Constants
+{
+ public const Int32 BaseAddress = 1000;
+ public const Int32 NoOfRegisters = 56;
+
+ public const Parity Parity = Odd;
+ public const Int32 StopBits = 1;
+ public const Int32 BaudRate = 115200;
+ public const Int32 DataBits = 8;
+ public static TimeSpan Timeout { get; } = TimeSpan.FromMilliseconds(100);
+
+ public const Double VMax = 59.0;
+ public const Double VMin = 42.0;
+ public const Double AhPerString = 40.0;
+
+ private const Double RStringMin = 0.125;
+ private const Double RStringMax = 0.250;
+ private const Double IMaxPerString = 20.0;
+ private const UInt16 NumberOfStrings = 5;
+
+ public const Double RIntMin = RStringMin / NumberOfStrings;
+ public const Double RIntMax = RStringMax / NumberOfStrings;
+ public const Double IMax = NumberOfStrings * IMaxPerString;
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/Battery250UP/DataTypes/LedColor.cs b/csharp/Lib/Devices/Battery250UP/DataTypes/LedColor.cs
new file mode 100644
index 000000000..a6904219e
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/DataTypes/LedColor.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+
+public enum LedColor
+{
+ Green = 0, // don't change: numbers are important
+ Amber = 2,
+ Blue = 4,
+ Red = 6,
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/Battery250UP/DataTypes/LedState.cs b/csharp/Lib/Devices/Battery250UP/DataTypes/LedState.cs
new file mode 100644
index 000000000..7fe83297c
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/DataTypes/LedState.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+
+public enum LedState
+{
+ Off = 0,
+ On = 1,
+ Blinking = 2,
+ BlinkingFast = 3
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/Battery250UP/DataTypes/Leds.cs b/csharp/Lib/Devices/Battery250UP/DataTypes/Leds.cs
new file mode 100644
index 000000000..1ca1c850b
--- /dev/null
+++ b/csharp/Lib/Devices/Battery250UP/DataTypes/Leds.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.Lib.Devices.Battery250Up.DataTypes;
+
+public readonly struct Leds
+{
+ public LedState Blue { get; internal init; }
+ public LedState Green { get; internal init; }
+ public LedState Amber { get; internal init; }
+ public LedState Red { get; internal init; }
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/Battery250UP/Doc/FZSONICK UP_Protocol_Manual - solar_application_rev00.pdf b/csharp/Lib/Devices/Battery250UP/Doc/FZSONICK UP_Protocol_Manual - solar_application_rev00.pdf
new file mode 100644
index 000000000..c4896c72c
Binary files /dev/null and b/csharp/Lib/Devices/Battery250UP/Doc/FZSONICK UP_Protocol_Manual - solar_application_rev00.pdf differ