Merge remote-tracking branch 'origin/main'

This commit is contained in:
Yinyin Liu 2025-01-21 11:52:28 +01:00
commit 1048ed0a7f
13 changed files with 1108 additions and 4 deletions

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>InnovEnergy.App.DeligreenBatteryCommunication</RootNamespace>
</PropertyGroup>
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<ProjectReference Include="../../Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.39" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@ -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)
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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; }
}

View File

@ -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
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../InnovEnergy.Lib.props" />
<PropertyGroup>
<RootNamespace>InnovEnergy.Lib.Devices.BatteryDeligreen</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Folder Include="Doc\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Units\Units.csproj" />
</ItemGroup>
</Project>

View File

@ -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<Byte>(); // 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<Byte[]> 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;
}
}

View File

@ -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()

View File

@ -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()

View File

@ -0,0 +1,6 @@
namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
public class TelecommandFrameParser
{
}

View File

@ -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<Double, Double> 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);
}
}