diff --git a/csharp/App/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj b/csharp/App/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj
new file mode 100644
index 000000000..6ab0149e5
--- /dev/null
+++ b/csharp/App/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj
@@ -0,0 +1,19 @@
+
+
+
+ InnovEnergy.App.DeligreenBatteryCommunication
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App/DeligreenBatteryCommunication/Program.cs b/csharp/App/DeligreenBatteryCommunication/Program.cs
new file mode 100644
index 000000000..9d73e76e2
--- /dev/null
+++ b/csharp/App/DeligreenBatteryCommunication/Program.cs
@@ -0,0 +1,46 @@
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+
+internal static class Program
+{
+ private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
+
+ // private static readonly Channel? BatteriesChannel;
+
+ private const String Port = "/dev/ttyUSB0";
+
+
+ static Program()
+ {
+ Console.WriteLine("Hello, Deligreen World!");
+
+ // BatteriesChannel = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits);
+
+ }
+
+ public static async Task Main(string[] args)
+ {
+ Console.WriteLine("Starting Battery Communication");
+
+ var batteryDevices = new BatteryDeligreenDevice(Port);
+
+ while (true)
+ {
+ try
+ {
+ Console.WriteLine("***************************** New Frame *********************************");
+ Console.WriteLine($"First Reading Timestamp: {DateTime.Now:HH:mm:ss.fff}");
+ // Read telemetry data asynchronously
+ await batteryDevices.ReadTelemetryData();
+ Console.WriteLine($"Last Timestamp: {DateTime.Now:HH:mm:ss.fff}");
+ // Wait for 2 seconds before the next reading
+ await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
+ }
+ catch (Exception e)
+ {
+ // Handle exception and print the error
+ Console.WriteLine(e);
+ await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/App/DeligreenBatteryCommunication/deploy.sh b/csharp/App/DeligreenBatteryCommunication/deploy.sh
new file mode 100755
index 000000000..05983dda8
--- /dev/null
+++ b/csharp/App/DeligreenBatteryCommunication/deploy.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+set -e
+
+echo -e "\n============================ Build ============================\n"
+
+dotnet publish \
+ ./DeligreenBatteryCommunication.csproj \
+ -p:PublishTrimmed=false \
+ -c Release \
+ -r linux-x64
+
+echo -e "\n============================ Deploy ============================\n"
+
+rsync -v \
+ --exclude '*.pdb' \
+ ./bin/Release/$dotnet_version/linux-x64/publish/* \
+ $username@"$salimax_ip":~/salimax
\ No newline at end of file
diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln
index baaec091f..a334ae2c0 100644
--- a/csharp/InnovEnergy.sln
+++ b/csharp/InnovEnergy.sln
@@ -93,6 +93,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchneiderMeterDriver", "App
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery250UP", "Lib\Devices\Battery250UP\Battery250UP.csproj", "{F2967439-A590-4D5E-9208-1B973C83AA1C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatteryDeligreen", "Lib\Devices\BatteryDeligreen\BatteryDeligreen.csproj", "{1045AC74-D4D8-4581-AAE3-575DF26060E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeligreenBatteryCommunication", "App\DeligreenBatteryCommunication\DeligreenBatteryCommunication.csproj", "{11ED6871-5B7D-462F-8710-B5D85DEC464A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -244,6 +248,14 @@ Global
{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
+ {1045AC74-D4D8-4581-AAE3-575DF26060E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1045AC74-D4D8-4581-AAE3-575DF26060E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1045AC74-D4D8-4581-AAE3-575DF26060E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1045AC74-D4D8-4581-AAE3-575DF26060E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11ED6871-5B7D-462F-8710-B5D85DEC464A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11ED6871-5B7D-462F-8710-B5D85DEC464A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11ED6871-5B7D-462F-8710-B5D85DEC464A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11ED6871-5B7D-462F-8710-B5D85DEC464A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
@@ -286,5 +298,7 @@ Global
{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}
+ {1045AC74-D4D8-4581-AAE3-575DF26060E6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
+ {11ED6871-5B7D-462F-8710-B5D85DEC464A} = {145597B4-3E30-45E6-9F72-4DD43194539A}
EndGlobalSection
EndGlobal
diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs
index 5312ba815..b4c2c8aa8 100644
--- a/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs
+++ b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs
@@ -48,10 +48,6 @@ public partial class Battery48TlRecord
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/BatteryDeligreen/AlarmMessage.cs b/csharp/Lib/Devices/BatteryDeligreen/AlarmMessage.cs
new file mode 100644
index 000000000..74fca0bc2
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/AlarmMessage.cs
@@ -0,0 +1,30 @@
+namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
+
+public class AlarmMessage
+{
+ // Enum for Alarm Event 1
+ public enum AlarmEvent1
+ {
+ VoltageSensorFault,
+ TemperatureSensorFault,
+ CurrentSensorFault,
+ KeySwitchFault,
+ CellVoltageDropoutFault,
+ ChargeSwitchFault,
+ DischargeSwitchFault,
+ CurrentLimitSwitchFault
+ }
+
+ // Enum for Alarm Event 2
+ public enum AlarmEvent2
+ {
+ MonomerHighVoltageAlarm,
+ MonomerOvervoltageProtection,
+ MonomerLowVoltageAlarm,
+ MonomerUnderVoltageProtection,
+ HighVoltageAlarmForTotalVoltage,
+ OvervoltageProtectionForTotalVoltage,
+ LowVoltageAlarmForTotalVoltage,
+ UnderVoltageProtectionForTotalVoltage
+ }
+}
diff --git a/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj b/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj
new file mode 100644
index 000000000..03f0a38e1
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ InnovEnergy.Lib.Devices.BatteryDeligreen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreenDevice.cs b/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreenDevice.cs
new file mode 100644
index 000000000..2b590d796
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/BatteryDeligreenDevice.cs
@@ -0,0 +1,178 @@
+namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
+
+using System;
+using System.IO.Ports;
+
+
+public class BatteryDeligreenDevice
+{
+ private const Parity Parity = System.IO.Ports.Parity.None;
+ private const StopBits StopBits = System.IO.Ports.StopBits.One;
+ private const Int32 BaudRate = 19200;
+ private const Int32 DataBits = 8;
+
+ private readonly SerialPort _serialPort;
+
+ // Constructor for local serial port connection
+ public BatteryDeligreenDevice(String tty)
+ {
+ _serialPort = new SerialPort(tty, BaudRate, Parity, DataBits, StopBits)
+ {
+ ReadTimeout = 1000, // 1 second timeout for reads
+ WriteTimeout = 1000 // 1 second timeout for writes
+ };
+
+ try
+ {
+ // Open the serial port
+ _serialPort.Open();
+ Console.WriteLine("Serial port opened successfully.");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+
+ }
+
+ // Method to send data to the device
+ private void Write(String hexCommand)
+ {
+ try
+ {
+ // Convert hex string to byte array
+ byte[] commandBytes = HexStringToByteArray(hexCommand);
+
+ // Send the command
+ _serialPort.Write(commandBytes, 0, commandBytes.Length);
+ Console.WriteLine("Command sent successfully.");
+ }
+ catch (TimeoutException)
+ {
+ Console.WriteLine("Write operation timed out.");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error during write operation: {e.Message}");
+ throw;
+ }
+ }
+
+ // Method to read data from the device
+ private Byte[] Read(Int32 bufferSize)
+ {
+ try
+ {
+ // Read data from the serial port
+ var buffer = new Byte[bufferSize];
+ var bytesRead = _serialPort.Read(buffer, 0, bufferSize);
+
+ Console.WriteLine($"Read {bytesRead} bytes from the device.");
+
+ // Return only the received bytes
+ var responseData = new Byte[bytesRead];
+ Array.Copy(buffer, responseData, bytesRead);
+ return responseData;
+ }
+ catch (TimeoutException)
+ {
+ Console.WriteLine("Read operation timed out.");
+ return Array.Empty(); // Return empty array on timeout
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error during read operation: {e.Message}");
+ throw;
+ }
+ }
+
+ private static String BytesToHexString(byte[] byteArray)
+ {
+ return BitConverter.ToString(byteArray).Replace("-", "").ToUpper();
+ }
+
+ // Helper method to convert a hex string to a byte array
+ private static Byte[] HexStringToByteArray(string hex)
+ {
+ if (string.IsNullOrWhiteSpace(hex) || hex.Length % 2 != 0)
+ throw new ArgumentException("Invalid hex string.");
+
+ byte[] bytes = new byte[hex.Length / 2];
+ for (var i = 0; i < hex.Length; i += 2)
+ {
+ bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
+ }
+ return bytes;
+ }
+
+ // Dispose method to release the serial port
+ public void Dispose()
+ {
+ if (_serialPort.IsOpen)
+ {
+ _serialPort.Close();
+ Console.WriteLine("Serial port closed.");
+ }
+ }
+
+ // Read telemetry data from the connected device
+ public async Task ReadTelemetryData()
+ {
+ const String frameToSend = "7E3230303034363432453030323030464433370D"; // Example custom frame
+
+ try
+ {
+ // Write the frame to the channel (send it to the device)
+ await Task.Run(() => Write(frameToSend));
+
+ // Read the response from the channel (assuming max response size)
+ var responseBytes = await Task.Run(() => Read(1024)); // Assuming Read can be executed asynchronously
+
+ // Convert the byte array to a hexadecimal string
+ var responseHex = BytesToHexString(responseBytes);
+
+ new TelemetryFrameParser().ParsingTelemetryFrame(responseHex);
+
+ // Parse the ASCII response (you can implement any custom parsing logic)
+ var responseData = ParseAsciiResponse(responseBytes.ToArray());
+
+ return responseData;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error during telemetry data retrieval: {ex.Message}");
+ throw;
+ }
+ }
+
+ public Byte[] ReadTelecomandData()
+ {
+ const String frameToSend = "7E3230303034363434453030323030464433350D"; // Example custom frame
+
+ // Write the frame to the channel (send it to the device)
+ Write(frameToSend);
+
+ // Read the response from the channel (assuming max response size)
+ var responseBytes = Read(1024); // Adjust this size if needed
+
+ // Parse the ASCII response (you can implement any custom parsing logic)
+ var responseData = ParseAsciiResponse(responseBytes.ToArray());
+
+ return responseData;
+ }
+
+ // Helper method to parse the ASCII response (you can add any parsing logic here)
+ private static byte[] ParseAsciiResponse(byte[] responseBytes)
+ {
+ Console.WriteLine($"Last Timestamp: {DateTime.Now:HH:mm:ss.fff}");
+ // Convert the byte array to a hex string for display
+ var hexResponse = BitConverter.ToString(responseBytes).Replace("-", " ");
+ //Console.WriteLine($"Response (Hex): {hexResponse}");
+ // Implement custom parsing logic if necessary based on the protocol's frame
+ // For now, we return the raw response bytes
+ return responseBytes;
+ }
+}
+
+
+
diff --git a/csharp/Lib/Devices/BatteryDeligreen/Doc/BMS communication home energy storage protocol V2.0.pdf b/csharp/Lib/Devices/BatteryDeligreen/Doc/BMS communication home energy storage protocol V2.0.pdf
new file mode 100644
index 000000000..4f88b4a16
Binary files /dev/null and b/csharp/Lib/Devices/BatteryDeligreen/Doc/BMS communication home energy storage protocol V2.0.pdf differ
diff --git a/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve Alarm.py b/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve Alarm.py
new file mode 100644
index 000000000..8c60b65ca
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve Alarm.py
@@ -0,0 +1,132 @@
+import serial
+
+def parse_start_code(frame):
+ soi = frame[0:2]
+ if soi == "~":
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid start identifier! ({soi})")
+
+def parse_version_code(frame):
+ ver = frame[2:6]
+ return f"Protocol Version V{ver[0]}.{ver[1]}"
+
+def parse_address_code(frame):
+ adr = frame[6:10]
+ if 0 <= int(adr) <= 15:
+ return adr
+ else:
+ raise ValueError(f"Invalid address: {adr} (out of range 0-15)")
+
+def parse_device_code(frame):
+ cid1 = frame[10:14]
+ return bms.CID1_DEVICE_CODES.get(cid1, "Unknown!")
+
+def parse_function_code(frame):
+ cid2 = frame[14:18]
+ if cid2 in bms.CID2_COMMAND_CODES:
+ return f"Command -> {bms.CID2_COMMAND_CODES.get(cid2)}"
+ elif cid2 in bms.CID2_RETURN_CODES:
+ return f"Return -> {bms.CID2_RETURN_CODES.get(cid2)}"
+ else:
+ return f"Unknown CID2: {cid2}"
+
+def parse_lchksum(length_code):
+ # implements chapter 3.2.2 of the Protocol Specification
+ lchksum = int(length_code[0], 16)
+ # Compute lchksum
+ d11d10d09d08 = int(length_code[1])
+ d07d06d05d04 = int(length_code[2])
+ d03d0ld01d00 = int(length_code[3])
+ sum = d11d10d09d08 + d07d06d05d04 + d03d0ld01d00
+ remainder = sum % 16
+ inverted = ~remainder & 0xF
+ computed_lchksum = (inverted + 1) & 0xF
+ if computed_lchksum == lchksum:
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid LCHKSUM: {lchksum} (computed: {computed_lchksum})")
+
+
+def parse_lenid(length_code):
+ # implements chapter 3.2.1 of the Protocol Specification
+ d11d10d09d08 = int(length_code[1])
+ d07d06d05d04 = int(length_code[2])
+ d03d0ld01d00 = int(length_code[3])
+ lenid = d11d10d09d08 << 8 | d07d06d05d04 << 4 | d03d0ld01d00
+ return lenid>>1
+
+def parse_length_code(frame):
+ # implements chapter 3.2 of the Protocol Specification
+ length_code = frame[18:26]
+ lchksum = parse_lchksum(length_code)
+ lenid = parse_lenid(length_code)
+ return { "LCHKSUM": lchksum, "LENID": lenid }
+
+def parse_info(frame):
+ cid2 = frame[14:18]
+ lenid = parse_lenid(frame[18:26])
+ info = frame[26:26+lenid*2]
+
+ if cid2 == '00' and lenid == 49:
+ return parse_telecommand_return(info)
+ elif cid2 == '00' and lenid == 75:
+ return parse_telemetry_return(info)
+ else:
+ return info
+
+def parse_telecommand_return(info_raw, info={}, index=0):
+
+ info["DATA FLAG"] = info_raw[index:index+4]
+ index += 4
+
+ info["COMMAND GROUP"] = info_raw[index:index+4]
+ index += 4
+
+
+def parse_modbus_ascii_frame(frame, parsed_data = {}):
+ frame = bytes.fromhex(frame).decode('ascii')
+ parsed_data["SOI"] = parse_start_code(frame)
+ parsed_data["VER"] = parse_version_code(frame)
+ parsed_data["ADR"] = parse_address_code(frame)
+ parsed_data["CID1"] = parse_device_code(frame)
+ parsed_data["CID2"] = parse_function_code(frame)
+ parsed_data["LENGTH"] = parse_length_code(frame)
+ parsed_data["INFO"] = parse_info(frame)
+ parsed_data["CHKSUM"] = parse_checksum(frame)
+ parsed_data["EOI"] = parse_end_code(frame)
+ return parsed_data
+
+def send_command():
+
+ # Define the serial port and baud rate
+ port = 'COM9' # Replace with your actual port
+ baudrate = 19200 # Replace with the correct baud rate for your BMS
+
+ # Create the serial connection
+ try:
+ with serial.Serial(port, baudrate, timeout=1) as ser:
+ # Convert the hex string to bytes
+ command = bytes.fromhex("7E3230303034363434453030323030464433350D")
+
+ # Send the command
+ ser.write(command)
+ print("Command sent successfully.")
+
+ # Wait for and read the response
+ response = ser.read(200) # Adjust the number of bytes to read as needed
+ if response:
+ hex_response = response.hex()
+ print("Response received:", hex_response)
+ # Process the response to check details
+ check_starting_byte_and_extract_details(hex_response)
+ else:
+ print("No response received.")
+ except serial.SerialException as e:
+ print(f"Error opening serial port: {e}")
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+
+
+if __name__ == "__main__":
+ send_command()
diff --git a/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve data.py b/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve data.py
new file mode 100644
index 000000000..617e6f724
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/Doc/retrieve data.py
@@ -0,0 +1,480 @@
+import serial
+import csv
+
+TELECOMMAND_FILE_PATH = "Telecommand_Return_Record.csv"
+
+# Table 3
+CID1_DEVICE_CODES = {
+ "46": "Lithium iron phosphate battery BMS",
+}
+
+# Table 4
+CID2_COMMAND_CODES = {
+ "42": "Acquisition of telemetering information",
+ "44": "Acquisition of telecommand information",
+ "45": "Telecontrol command",
+ "47": "Acquisition of teleregulation information",
+ "49": "Setting of teleregulation information",
+ "4F": "Acquisition of the communication protocol version number",
+ "51": "Acquisition of device vendor information",
+ "4B": "Acquisition of historical data",
+ "4D": "Acquisition time",
+ "4E": "Synchronization time",
+ "A0": "Production calibration",
+ "A1": "Production setting",
+ "A2": "Regular recording"
+}
+
+# Table 5
+CID2_RETURN_CODES = {
+ "00": "Normal",
+ "01": "VER error",
+ "02": "CHKSUM error",
+ "03": "LCHKSUM error",
+ "04": "CID2 invalid",
+ "05": "Command format error",
+ "06": "Data invalid (parameter setting)",
+ "07": "No data (history)",
+ "E1": "CID1 invalid",
+ "E2": "Command execution failure",
+ "E3": "Device fault",
+ "E4": "Invalid permissions"
+ }
+
+
+# Table 12
+BYTE_ALARM_CODES = {
+ "00": "Normal, no alarm",
+ "01": "Alarm that analog quantity reaches the lower limit",
+ "02": "Alarm that analog quantity reaches the upper limit",
+ "F0": "Other alarms"
+}
+
+# Table 13
+BIT_ALARM_CODES = {
+ "Alarm event 1": (
+ "Voltage sensor fault",
+ "Temperature sensor fault",
+ "Current sensor fault",
+ "Key switch fault",
+ "Cell voltage dropout fault",
+ "Charge switch fault",
+ "Discharge switch fault",
+ "Current limit switch fault"
+ ),
+ "Alarm event 2": (
+ "Monomer high voltage alarm",
+ "Monomer overvoltage protection",
+ "Monomer low voltage alarm",
+ "Monomer under voltage protection",
+ "High voltage alarm for total voltage",
+ "Overvoltage protection for total voltage",
+ "Low voltage alarm for total voltage",
+ "Under voltage protection for total voltage"
+ ),
+ "Alarm event 3": (
+ "Charge high temperature alarm",
+ "Charge over temperature protection",
+ "Charge low temperature alarm",
+ "Charge under temperature protection",
+ "Discharge high temperature alarm",
+ "Discharge over temperature protection",
+ "Discharge low temperature alarm",
+ "Discharge under temperature protection"
+ ),
+ "Alarm event 4": (
+ "Environment high temperature alarm",
+ "Environment over temperature protection",
+ "Environment low temperature alarm",
+ "Environment under temperature protection",
+ "Power over temperature protection",
+ "Power high temperature alarm",
+ "Cell low temperature heating",
+ "Reservation bit"
+ ),
+ "Alarm event 5": (
+ "Charge over current alarm",
+ "Charge over current protection",
+ "Discharge over current alarm",
+ "Discharge over current protection",
+ "Transient over current protection",
+ "Output short circuit protection",
+ "Transient over current lockout",
+ "Output short circuit lockout"
+ ),
+ "Alarm event 6": (
+ "Charge high voltage protection",
+ "Intermittent recharge waiting",
+ "Residual capacity alarm",
+ "Residual capacity protection",
+ "Cell low voltage charging prohibition",
+ "Output reverse polarity protection",
+ "Output connection fault",
+ "Inside bit"
+ ),
+ "On-off state": (
+ "Discharge switch state",
+ "Charge switch state",
+ "Current limit switch state",
+ "Heating switch state",
+ "Reservation bit",
+ "Reservation bit",
+ "Reservation bit",
+ "Reservation bit"
+ ),
+ "Equilibrium state 1": (
+ "Cell 01 equilibrium",
+ "Cell 02 equilibrium",
+ "Cell 03 equilibrium",
+ "Cell 04 equilibrium",
+ "Cell 05 equilibrium",
+ "Cell 06 equilibrium",
+ "Cell 07 equilibrium",
+ "Cell 08 equilibrium"
+ ),
+ "Equilibrium state 2": (
+ "Cell 09 equilibrium",
+ "Cell 10 equilibrium",
+ "Cell 11 equilibrium",
+ "Cell 12 equilibrium",
+ "Cell 13 equilibrium",
+ "Cell 14 equilibrium",
+ "Cell 15 equilibrium",
+ "Cell 16 equilibrium"
+ ),
+ "System state": (
+ "Discharge",
+ "Charge",
+ "Floating charge",
+ "Reservation bit",
+ "Standby",
+ "Shutdown",
+ "Reservation bit",
+ "Reservation bit"
+ ),
+ "Disconnection state 1": (
+ "Cell 01 disconnection",
+ "Cell 02 disconnection",
+ "Cell 03 disconnection",
+ "Cell 04 disconnection",
+ "Cell 05 disconnection",
+ "Cell 06 disconnection",
+ "Cell 07 disconnection",
+ "Cell 08 disconnection"
+ ),
+ "Disconnection state 2": (
+ "Cell 09 disconnection",
+ "Cell 10 disconnection",
+ "Cell 11 disconnection",
+ "Cell 12 disconnection",
+ "Cell 13 disconnection",
+ "Cell 14 disconnection",
+ "Cell 15 disconnection",
+ "Cell 16 disconnection"
+ ),
+ "Alarm event 7": (
+ "Inside bit",
+ "Inside bit",
+ "Inside bit",
+ "Inside bit",
+ "Automatic charging waiting",
+ "Manual charging waiting",
+ "Inside bit",
+ "Inside bit"
+ ),
+ "Alarm event 3": (
+ "EEP storage fault",
+ "RTC error",
+ "Voltage calibration not performed",
+ "Current calibration not performed",
+ "Zero calibration not performed",
+ "Inside bit",
+ "Inside bit",
+ "Inside bit"
+ ),
+}
+
+
+def parse_start_code(frame):
+ soi = frame[0:1]
+ if soi == "~":
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid start identifier! ({soi})")
+
+def parse_version_code(frame):
+ ver = frame[1:3]
+ return f"Protocol Version V{ver[0]}.{ver[1]}"
+
+def parse_address_code(frame):
+ adr = frame[3:5]
+ if 0 <= int(adr) <= 15:
+ return adr
+ else:
+ raise ValueError(f"Invalid address: {adr} (out of range 0-15)")
+
+def parse_device_code(frame):
+ cid1 = frame[5:7]
+ return CID1_DEVICE_CODES.get(cid1, "Unknown!")
+
+def parse_function_code(frame):
+ cid2 = frame[7:9]
+ if cid2 in CID2_COMMAND_CODES:
+ return f"Command -> {CID2_COMMAND_CODES.get(cid2)}"
+ elif cid2 in CID2_RETURN_CODES:
+ return f"Return -> {CID2_RETURN_CODES.get(cid2)}"
+ else:
+ return f"Unknown CID2: {cid2}"
+
+def parse_lchksum(length_code):
+ # implements chapter 3.2.2 of the Protocol Specification
+ lchksum = int(length_code[0], 16)
+ # Compute lchksum
+ d11d10d09d08 = int(length_code[1])
+ d07d06d05d04 = int(length_code[2])
+ d03d0ld01d00 = int(length_code[3])
+ sum = d11d10d09d08 + d07d06d05d04 + d03d0ld01d00
+ remainder = sum % 16
+ inverted = ~remainder & 0xF
+ computed_lchksum = (inverted + 1) & 0xF
+ if computed_lchksum == lchksum:
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid LCHKSUM: {lchksum} (computed: {computed_lchksum})")
+
+def parse_lenid(length_code):
+ # implements chapter 3.2.1 of the Protocol Specification
+ d11d10d09d08 = int(length_code[1])
+ d07d06d05d04 = int(length_code[2])
+ d03d0ld01d00 = int(length_code[3])
+ lenid = d11d10d09d08 << 8 | d07d06d05d04 << 4 | d03d0ld01d00
+ return lenid >> 1
+
+def parse_length_code(frame):
+ # implements chapter 3.2 of the Protocol Specification
+ length_code = frame[9:13]
+ lchksum = parse_lchksum(length_code)
+ lenid = parse_lenid(length_code)
+ return { "LCHKSUM": lchksum, "LENID": lenid }
+
+def parse_info(frame):
+ cid2 = frame[7:9]
+ lenid = parse_lenid(frame[9:13])
+ info = frame[13:13+lenid*2]
+
+ if cid2 == '00' and lenid == 49:
+ return parse_telecommand_return(info)
+ elif cid2 == '00' and lenid == 75:
+ return parse_telemetry_return(info)
+ else:
+ return info
+
+def parse_telecommand_return(info_raw, info={}, index=0):
+
+ info["DATA FLAG"] = info_raw[index:index+2]
+ index += 2
+
+ info["COMMAND GROUP"] = info_raw[index:index+2]
+ index += 2
+
+ num_of_cells = int(info_raw[index:index+2], 16)
+ info["Number of cells"] = num_of_cells
+ index += 2
+
+ for cell in range(info["Number of cells"]):
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info[f"Cell {cell +1} alarm"] = alarm
+ index += 2
+
+ num_of_temperatures = int(info_raw[index:index+2], 16)
+ info["Number of temperatures"] = num_of_temperatures
+ index += 2
+
+ for sensor in range(4):
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info[f"Cell temperature alarm {sensor}"] = alarm
+ index += 2
+
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info["Environment temperature alarm"] = alarm
+ index += 2
+
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info["Power temperature alarm 1"] = alarm
+ index += 2
+
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info["Charge/discharge current alarm"] = alarm
+ index += 2
+
+ alarm = BYTE_ALARM_CODES.get(info_raw[index:index+2])
+ info["Total battery voltage alarm"] = alarm
+ index += 2
+
+ num_custom = int(info_raw[index:index+2], 16)
+ info["Number of custom alarms"] = num_custom
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 1"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 2"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 3"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 4"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 5"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 6"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["On-off state"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Equilibrium state 1"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Equilibrium state 2"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["System state"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Disconnection state 1"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Disconnection state 2"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 7"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Alarm event 8"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 1"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 2"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 3"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 4"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 5"] = alarm
+ index += 2
+
+ alarm = info_raw[index:index+2]
+ info["Reservation extention 6"] = alarm
+ index += 2
+
+ save_dict_to_csv(TELECOMMAND_FILE_PATH, info)
+ return f"Telecommand Return Data saved in ./{TELECOMMAND_FILE_PATH}"
+
+
+def save_dict_to_csv(file_path, data):
+ with open(file_path, mode='a+', newline='') as csvfile:
+ csvfile.seek(0)
+ has_header = csvfile.read(1) != ""
+ csvfile.seek(0, 2)
+ writer = csv.DictWriter(csvfile, fieldnames=data.keys())
+ if not has_header:
+ writer.writeheader()
+ writer.writerow(data)
+
+def parse_checksum(frame):
+ """implements section 3.3 of the Protocol Specification"""
+ chksum = int(frame[-6:-1], 16)
+ data = frame[1:-5]
+ # Compute chksum
+ ascii_sum = sum(ord(char) for char in data)
+ remainder = ascii_sum % 65536
+ inverted = ~remainder & 0xFFFF
+ computed_chksum = (inverted + 1) & 0xFFFF
+ # Compare with CHKSUM in frame
+ if computed_chksum == chksum:
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid CHKSUM: {chksum} (computed: {computed_chksum})")
+
+def parse_end_code(frame):
+ eoi = frame[-1]
+ if eoi == "\r":
+ return "ok!"
+ else:
+ raise ValueError(f"Invalid end identifier! ({eoi})")
+
+def parse_modbus_ascii_frame(frame, parsed_data = {}):
+ frame = bytes.fromhex(frame).decode('ascii')
+ parsed_data["SOI"] = parse_start_code(frame)
+ parsed_data["VER"] = parse_version_code(frame)
+ parsed_data["ADR"] = parse_address_code(frame)
+ parsed_data["CID1"] = parse_device_code(frame)
+ parsed_data["CID2"] = parse_function_code(frame)
+ parsed_data["LENGTH"] = parse_length_code(frame)
+ parsed_data["INFO"] = parse_info(frame)
+ parsed_data["CHKSUM"] = parse_checksum(frame)
+ parsed_data["EOI"] = parse_end_code(frame)
+ return parsed_data
+
+def send_command():
+
+ # Define the serial port and baud rate
+ port = 'COM9' # Replace with your actual port
+ baudrate = 19200 # Replace with the correct baud rate for your BMS
+
+ # Create the serial connection
+ try:
+ with serial.Serial(port, baudrate, timeout=1) as ser:
+ # Convert the hex string to bytes
+ command = bytes.fromhex("7E3230303034363434453030323030464433350D")
+
+ # Send the command
+ ser.write(command)
+ print("Command sent successfully.")
+
+ # Wait for and read the response
+ response = ser.read(200) # Adjust the number of bytes to read as needed
+ if response:
+ hex_response = response.hex()
+ print("Response received:", hex_response)
+ # Process the response to check details
+ parsed_result = parse_modbus_ascii_frame(hex_response)
+ for key, value in parsed_result.items():
+ print(f"{key}: {value}")
+ else:
+ print("No response received.")
+ except serial.SerialException as e:
+ print(f"Error opening serial port: {e}")
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+
+if __name__ == "__main__":
+ send_command()
diff --git a/csharp/Lib/Devices/BatteryDeligreen/TelecommandFrameParser.cs b/csharp/Lib/Devices/BatteryDeligreen/TelecommandFrameParser.cs
new file mode 100644
index 000000000..f50bf70ec
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/TelecommandFrameParser.cs
@@ -0,0 +1,6 @@
+namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
+
+public class TelecommandFrameParser
+{
+
+}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/BatteryDeligreen/TelemetryFrameParser.cs b/csharp/Lib/Devices/BatteryDeligreen/TelemetryFrameParser.cs
new file mode 100644
index 000000000..8045c4dc8
--- /dev/null
+++ b/csharp/Lib/Devices/BatteryDeligreen/TelemetryFrameParser.cs
@@ -0,0 +1,160 @@
+using System.Globalization;
+
+namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
+
+public class TelemetryFrameParser
+{
+ private static Int32 _currentIndex;
+ private const Int32 FrameLenght = 286;
+
+ public void ParsingTelemetryFrame(String response)
+ {
+
+ _currentIndex = 0; // Reset currentIndex to the start
+
+ if (string.IsNullOrEmpty(response) || response.Length < FrameLenght)
+ {
+ Console.WriteLine("Response is too short to contain valid data.");
+ return;
+ }
+
+ // Check starting byte
+ string startingByte = response.Substring(_currentIndex, 2).ToUpper();
+ if (startingByte == "7E")
+ {
+ Console.WriteLine($"Starting byte: {startingByte} (Hex)");
+ }
+ else
+ {
+ Console.WriteLine($"Incorrect starting byte: {startingByte}");
+ return;
+ }
+ _currentIndex += 2;
+
+ // Extract firmware version
+ var versionBytes = response.Substring(_currentIndex, 4);
+ try
+ {
+ String versionAscii = HexToAscii(versionBytes);
+ Console.WriteLine($"Firmware version: {versionBytes} (Hex), ASCII: {versionAscii}");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to decode firmware version from bytes: {versionBytes}");
+ return;
+ }
+ _currentIndex += 4;
+
+ // Extract and parse other fields
+ ParseAndPrintHexField(response, "Device Address", 4);
+ ParseAndPrintHexField(response, "Device Code (CID1)", 4);
+ ParseAndPrintHexField(response, "Function Code", 4);
+ ParseAndPrintHexField(response, "Length Code", 8);
+ ParseAndPrintHexField(response, "Data Flag", 4);
+ ParseAndPrintHexField(response, "Command Group", 4);
+ ParseAndPrintHexField(response, "Number of Cells", 4);
+
+ // Process voltages for all 16 cells
+ for (var i = 0; i < 16; i++)
+ {
+ String cellVoltageBytes = response.Substring(_currentIndex, 8);
+ try
+ {
+ var cellVoltageAscii = HexToAscii(cellVoltageBytes);
+ var cellVoltageDecimal = HexToDecimal(cellVoltageAscii) / 1000.0; // cell voltage are divided 1000
+ Console.WriteLine($"Voltage of Cell {i + 1}: {cellVoltageBytes} (Hex), ASCII: {cellVoltageAscii}, Voltage: {cellVoltageDecimal:F3} V");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to decode Voltage of Cell {i + 1} from bytes: {cellVoltageBytes}");
+ }
+ _currentIndex += 8;
+ }
+
+ // Parse other fields
+ ParseAndPrintHexField(response, "Number of Temperature Sensors", 4);
+
+ // Parse cell temperatures
+ for (var i = 1; i <= 4; i++)
+ {
+ ParseAndPrintTemperatureField(response, $"Cell Temperature {i}");
+ }
+
+ // Parse other temperature and battery information
+ ParseAndPrintTemperatureField(response, "Environment Temperature");
+ ParseAndPrintTemperatureField(response, "Power Temperature");
+ ParseAndPrintField(response, "Charge/Discharge Current", 8, value => value / 100.0, "A");
+ ParseAndPrintField(response, "Total Battery Voltage", 8, value => value / 100.0, "V");
+ ParseAndPrintField(response, "Residual Capacity", 8, value => value / 100.0, "Ah");
+ ParseAndPrintHexField(response, "Custom Number", 4);
+ ParseAndPrintField(response, "Battery Capacity", 8, value => value / 100.0, "Ah");
+ ParseAndPrintField(response, "SOC", 8, value => value / 10.0, "%");
+ ParseAndPrintField(response, "Rated Capacity", 8, value => value / 100.0, "Ah");
+ ParseAndPrintHexField(response, "Number of Cycles", 8);
+ ParseAndPrintField(response, "SOH", 8, value => value / 10.0, "%");
+ ParseAndPrintField(response, "Bus Voltage", 8, value => value / 100.0, "V");
+ }
+
+ private static void ParseAndPrintHexField(String response, String fieldName, int length)
+ {
+ var hexBytes = response.Substring(_currentIndex, length);
+ try
+ {
+ var asciiValue = HexToAscii(hexBytes);
+ var decimalValue = int.Parse(asciiValue, NumberStyles.HexNumber);
+ Console.WriteLine($"{fieldName}: {hexBytes} (Hex), ASCII: {asciiValue}, Decimal: {decimalValue}");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to decode {fieldName} from bytes: {hexBytes}");
+ }
+ _currentIndex += length;
+ }
+
+ private static void ParseAndPrintTemperatureField(String response, String fieldName)
+ {
+ var tempBytes = response.Substring(_currentIndex, 8);
+ try
+ {
+ var tempAscii = HexToAscii(tempBytes);
+ var tempDecimal = (HexToDecimal(tempAscii) - 2731) / 10.0;
+ Console.WriteLine($"{fieldName}: {tempBytes} (Hex), ASCII: {tempAscii}, Temperature: {tempDecimal:F2} °C");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to decode {fieldName} from bytes: {tempBytes}");
+ }
+ _currentIndex += 8;
+ }
+
+ private static void ParseAndPrintField(String response, String fieldName, Int32 length, Func conversion, String unit)
+ {
+ var fieldBytes = response.Substring(_currentIndex, length);
+ try
+ {
+ var fieldAscii = HexToAscii(fieldBytes);
+ var fieldDecimal = conversion(HexToDecimal(fieldAscii));
+ Console.WriteLine($"{fieldName}: {fieldBytes} (Hex), ASCII: {fieldAscii}, {fieldName}: {fieldDecimal:F3} {unit}");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to decode {fieldName} from bytes: {fieldBytes}");
+ }
+ _currentIndex += length;
+ }
+
+ private static String HexToAscii(String hex)
+ {
+ var bytes = new Byte[hex.Length / 2];
+ for (var i = 0; i < hex.Length; i += 2)
+ {
+ bytes[i / 2] = byte.Parse(hex.Substring(i, 2), NumberStyles.HexNumber);
+ }
+ return System.Text.Encoding.ASCII.GetString(bytes);
+ }
+
+ private static double HexToDecimal(String hex)
+ {
+ return int.Parse(hex, NumberStyles.HexNumber);
+ }
+}
\ No newline at end of file