Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
1048ed0a7f
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Binary file not shown.
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,6 @@
|
|||
namespace InnovEnergy.Lib.Devices.BatteryDeligreen;
|
||||
|
||||
public class TelecommandFrameParser
|
||||
{
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue