초기 커밋.

This commit is contained in:
2025-12-17 12:40:51 +09:00
parent e8d195c03e
commit 368acb1aa8
184 changed files with 95393 additions and 0 deletions

View File

@@ -0,0 +1,562 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LFP_Manager.DataStructure;
using LFP_Manager.Utils;
using LFP_Manager.Controls;
namespace LFP_Manager.Function
{
/// <summary>
/// BMS 장치와의 시리얼 통신을 위한 개선된 클래스
/// Modbus RTU 프로토콜 및 커스텀 프로토콜 지원
/// </summary>
public class CsRs232CommFunction124050
{
#region Constants - Modbus Function Codes
public const byte READ_COIL_STATUS = 0x01;
public const byte READ_HOLDING_REG = 0x03;
public const byte READ_INPUT_REG = 0x04;
public const byte FORCE_SINGLE_COIL = 0x05;
public const byte PRESET_SINGLE_REG = 0x06;
public const byte WRITE_COIL_REG = 0x0F;
public const byte PRESET_MULTI_REG = 0x10;
public const byte ERROR_REG = 0x90;
public const byte FW_FLASH_ERASE_CMD = 0x43;
public const byte FW_FLASH_WRITE_CMD = 0x31;
public const byte NO_CMD = 0xFF;
#endregion
#region Constants - Register Addresses
private static class RegisterAddress
{
// Basic Data
public const int PACK_VOLTAGE = 0;
public const int PACK_CURRENT = 1;
public const int CELL_VOLTAGE_START = 2;
public const int CELL_VOLTAGE_END = 17;
public const int EXT1_TEMPERATURE = 18;
public const int EXT2_TEMPERATURE = 19;
public const int REMAINING_CAPACITY = 21;
public const int MAX_CHARGE_CURRENT = 22;
public const int STATE_OF_HEALTH = 23;
public const int STATE_OF_CHARGE = 24;
// Status Registers
public const int OPERATING_STATUS = 25;
public const int WARNING_STATUS = 26;
public const int PROTECTION_STATUS = 27;
public const int ERROR_CODE = 28;
public const int CYCLE_COUNT_MSB = 29;
public const int CYCLE_COUNT_LSB = 30;
// Temperature Registers (Packed)
public const int CELL_TEMP_START = 32;
public const int CELL_TEMP_END = 35;
// Device Info
public const int CELL_QTY = 36;
public const int DESIGNED_CAPACITY = 37;
public const int CELL_BALANCE_STATUS = 38;
public const int DATETIME_MSB = 45;
public const int DATETIME_LSB = 46;
public const int SPECIAL_ALARM = 49;
// Protection Parameters
public const int LOW_SOC_WARNING = 58;
public const int CELL_UV_WARNING = 61;
public const int CELL_UV_TRIP = 62;
public const int CELL_UV_RELEASE = 63;
public const int SYS_UV_WARNING = 64;
public const int SYS_UV_TRIP = 65;
public const int SYS_UV_RELEASE = 66;
public const int CELL_OV_WARNING = 67;
public const int CELL_OV_TRIP = 68;
public const int CELL_OV_RELEASE = 69;
public const int SYS_OV_WARNING = 70;
public const int SYS_OV_TRIP = 71;
public const int SYS_OV_RELEASE = 72;
// Current Protection
public const int CHA_OC_TIMES = 76;
public const int DCH_OC_TIMES = 77;
public const int CHA_OC_RELEASE_TIME = 78;
public const int DCH_OC_RELEASE_TIME = 79;
public const int CHA_OC_TRIP1 = 80;
public const int DCH_OC_TRIP1 = 81;
public const int SHORT_CIRCUIT = 82;
public const int CHA_OC_TRIP2 = 83;
public const int DCH_OC_TRIP2 = 84;
public const int CHA_OC_DELAY1 = 85;
public const int CHA_OC_DELAY2 = 86;
public const int DCH_OC_DELAY1 = 87;
public const int DCH_OC_DELAY2 = 88;
// Temperature Protection
public const int CHA_LOW_TEMP_WARNING = 90;
public const int CHA_LOW_TEMP_TRIP = 91;
public const int CHA_LOW_TEMP_RELEASE = 92;
public const int CHA_HIGH_TEMP_WARNING = 93;
public const int CHA_HIGH_TEMP_TRIP = 94;
public const int CHA_HIGH_TEMP_RELEASE = 95;
public const int DCH_LOW_TEMP_WARNING = 96;
public const int DCH_LOW_TEMP_TRIP = 97;
public const int DCH_LOW_TEMP_RELEASE = 98;
public const int DCH_HIGH_TEMP_WARNING = 99;
public const int DCH_HIGH_TEMP_TRIP = 100;
public const int DCH_HIGH_TEMP_RELEASE = 101;
// Device Information
public const int MODEL_NAME_START = 105;
public const int MODEL_NAME_END = 116;
public const int FW_VERSION_START = 117;
public const int FW_VERSION_END = 119;
public const int SERIAL_NUMBER_START = 120;
public const int SERIAL_NUMBER_END = 127;
// Extended Cell Voltages
public const int EXT_CELL_VOLTAGE_START = 138;
public const int EXT_CELL_VOLTAGE_END = 160;
// Manufacturing Date
public const int MANU_DATE_START = 163;
public const int MANU_DATE_END = 166;
}
#endregion
#region Delegates and Events
public delegate void LogEventHandler(string message, LogLevel level);
public static event LogEventHandler OnLog;
public enum LogLevel
{
Info,
Warning,
Error
}
#endregion
#region Modbus Frame Construction
/// <summary>
/// Modbus 읽기 요청 프레임 생성
/// </summary>
public static byte[] CreateReadRegisterFrame(byte deviceId, byte functionCode, ushort startAddress, ushort quantity)
{
if (quantity == 0 || quantity > 125)
throw new ArgumentException("Invalid register quantity");
var frame = new byte[8];
frame[0] = deviceId;
frame[1] = functionCode;
frame[2] = (byte)(startAddress >> 8);
frame[3] = (byte)(startAddress & 0xFF);
frame[4] = (byte)(quantity >> 8);
frame[5] = (byte)(quantity & 0xFF);
var crc = csUtils.CalculateCRC(frame, 6);
frame[6] = crc[0];
frame[7] = crc[1];
return frame;
}
/// <summary>
/// Modbus 다중 레지스터 쓰기 요청 프레임 생성
/// </summary>
public static byte[] CreateWriteMultipleRegistersFrame(byte deviceId, ushort startAddress, short[] values)
{
if (values == null || values.Length == 0 || values.Length > 123)
throw new ArgumentException("Invalid values array");
var frame = new byte[9 + (values.Length * 2)];
int index = 0;
frame[index++] = deviceId;
frame[index++] = PRESET_MULTI_REG;
frame[index++] = (byte)(startAddress >> 8);
frame[index++] = (byte)(startAddress & 0xFF);
frame[index++] = (byte)(values.Length >> 8);
frame[index++] = (byte)(values.Length & 0xFF);
frame[index++] = (byte)(values.Length * 2);
foreach (var value in values)
{
frame[index++] = (byte)(value >> 8);
frame[index++] = (byte)(value & 0xFF);
}
var crc = csUtils.CalculateCRC(frame, index);
frame[index++] = crc[0];
frame[index++] = crc[1];
return frame;
}
/// <summary>
/// 단일 코일 쓰기 요청 프레임 생성
/// </summary>
public static byte[] CreateWriteCoilFrame(byte deviceId, ushort coilAddress, bool value)
{
var frame = new byte[8];
frame[0] = deviceId;
frame[1] = FORCE_SINGLE_COIL;
frame[2] = (byte)(coilAddress >> 8);
frame[3] = (byte)(coilAddress & 0xFF);
frame[4] = value ? (byte)0xFF : (byte)0x00;
frame[5] = 0x00;
var crc = csUtils.CalculateCRC(frame, 6);
frame[6] = crc[0];
frame[7] = crc[1];
return frame;
}
#endregion
#region Frame Validation
/// <summary>
/// Modbus 응답 프레임 검증
/// </summary>
public static ValidationResult ValidateModbusResponse(byte[] data, int length)
{
if (data == null || length < 3)
return new ValidationResult(false, "Insufficient data length");
try
{
byte functionCode = data[1];
int expectedLength = CalculateExpectedLength(functionCode, data, length);
if (length < expectedLength)
return new ValidationResult(false, "Incomplete frame");
var calculatedCrc = csUtils.CalculateCRC(data, length - 2);
var receivedCrc = new byte[] { data[length - 2], data[length - 1] };
if (calculatedCrc[0] != receivedCrc[0] || calculatedCrc[1] != receivedCrc[1])
return new ValidationResult(false, "CRC mismatch");
return new ValidationResult(true, "Valid frame");
}
catch (Exception ex)
{
OnLog?.Invoke($"Frame validation error: {ex.Message}", LogLevel.Error);
return new ValidationResult(false, ex.Message);
}
}
private static int CalculateExpectedLength(byte functionCode, byte[] data, int length)
{
switch (functionCode)
{
case READ_COIL_STATUS:
case READ_HOLDING_REG:
case READ_INPUT_REG:
return length >= 3 ? data[2] + 5 : 5;
case PRESET_MULTI_REG:
case FORCE_SINGLE_COIL:
case PRESET_SINGLE_REG:
return 8;
case ERROR_REG:
return 5;
case FW_FLASH_ERASE_CMD:
case FW_FLASH_WRITE_CMD:
return 5;
default:
return length;
}
}
#endregion
#region Data Processing
/// <summary>
/// Modbus 응답 데이터를 시스템 데이터로 변환
/// </summary>
public static ProcessingResult ProcessModbusResponse(byte[] data, ushort startAddress, ushort length, ref DeviceSystemData systemData)
{
if (data == null || systemData == null)
return new ProcessingResult(false, "Invalid input parameters");
try
{
byte functionCode = data[1];
switch (functionCode)
{
case READ_COIL_STATUS:
return ProcessCoilResponse(data, startAddress, length, ref systemData);
case READ_HOLDING_REG:
return ProcessHoldingRegisterResponse(data, startAddress, length, ref systemData);
case READ_INPUT_REG:
return ProcessInputRegisterResponse(data, startAddress, length, ref systemData);
case ERROR_REG:
return ProcessErrorResponse(data, ref systemData);
default:
return new ProcessingResult(false, $"Unsupported function code: {functionCode:X2}");
}
}
catch (Exception ex)
{
OnLog?.Invoke($"Data processing error: {ex.Message}", LogLevel.Error);
return new ProcessingResult(false, ex.Message);
}
}
private static ProcessingResult ProcessHoldingRegisterResponse(byte[] data, ushort startAddress, ushort length, ref DeviceSystemData systemData)
{
int byteCount = data[2];
int registerCount = byteCount / 2;
int dataIndex = 3;
var processors = GetRegisterProcessors();
for (int i = 0; i < registerCount; i++)
{
ushort registerAddress = (ushort)(startAddress + i);
short registerValue = (short)((data[dataIndex] << 8) | data[dataIndex + 1]);
if (processors.TryGetValue(registerAddress, out var processor))
{
processor.Invoke(registerValue, systemData);
}
dataIndex += 2;
}
// 계산된 값들 업데이트
UpdateCalculatedValues(ref systemData);
return new ProcessingResult(true, "Processing completed successfully");
}
private static ProcessingResult ProcessCoilResponse(byte[] data, ushort startAddress, ushort length, ref DeviceSystemData systemData)
{
// 코일 데이터 처리 로직
return new ProcessingResult(true, "Coil processing completed");
}
private static ProcessingResult ProcessInputRegisterResponse(byte[] data, ushort startAddress, ushort length, ref DeviceSystemData systemData)
{
// 입력 레지스터 데이터 처리 로직
return new ProcessingResult(true, "Input register processing completed");
}
private static ProcessingResult ProcessErrorResponse(byte[] data, ref DeviceSystemData systemData)
{
return new ProcessingResult(false, $"Device error: {data[2]:X2}");
}
#endregion
#region Register Processing
private static Dictionary<ushort, Action<short, DeviceSystemData>> GetRegisterProcessors()
{
return new Dictionary<ushort, Action<short, DeviceSystemData>>
{
[RegisterAddress.PACK_VOLTAGE] = (value, data) => data.ValueData.voltageOfPack = (short)(value / 10),
[RegisterAddress.PACK_CURRENT] = (value, data) => data.ValueData.current = (short)(value / 10),
[RegisterAddress.REMAINING_CAPACITY] = (value, data) => data.ValueData.remainingCapacity = value,
[RegisterAddress.STATE_OF_HEALTH] = (value, data) => data.ValueData.stateOfHealth = (short)(value * 10),
[RegisterAddress.STATE_OF_CHARGE] = (value, data) => data.ValueData.rSOC = (short)(value * 10),
[RegisterAddress.OPERATING_STATUS] = (value, data) => data.StatusData.status = value,
[RegisterAddress.WARNING_STATUS] = (value, data) => data.StatusData.warning = ConvertWarningData(value),
[RegisterAddress.PROTECTION_STATUS] = (value, data) => data.StatusData.protection = ConvertProtectionData(value),
[RegisterAddress.CELL_QTY] = (value, data) => data.recv_cellQty = value,
[RegisterAddress.DESIGNED_CAPACITY] = (value, data) => data.ValueData.designedCapacity = value,
// 추가 레지스터 처리기들...
};
}
private static void ProcessCellVoltages(ushort address, short value, ref DeviceSystemData systemData)
{
if (address >= RegisterAddress.CELL_VOLTAGE_START && address <= RegisterAddress.CELL_VOLTAGE_END)
{
int cellIndex = address - RegisterAddress.CELL_VOLTAGE_START;
if (cellIndex < systemData.ValueData.CellVoltage.Length)
{
systemData.ValueData.CellVoltage[cellIndex] = (ushort)value;
}
}
else if (address >= RegisterAddress.EXT_CELL_VOLTAGE_START && address <= RegisterAddress.EXT_CELL_VOLTAGE_END)
{
int cellIndex = address - RegisterAddress.EXT_CELL_VOLTAGE_START + 16;
if (cellIndex < systemData.ValueData.CellVoltage.Length)
{
systemData.ValueData.CellVoltage[cellIndex] = (ushort)value;
}
}
}
private static void ProcessCellTemperatures(ushort address, short value, ref DeviceSystemData systemData)
{
if (address >= RegisterAddress.CELL_TEMP_START && address <= RegisterAddress.CELL_TEMP_END)
{
int tempIndex = (address - RegisterAddress.CELL_TEMP_START) * 2;
if (tempIndex + 1 < systemData.ValueData.CellTemperature.Length)
{
systemData.ValueData.CellTemperature[tempIndex] = (short)(((value >> 8) & 0xFF) * 10);
systemData.ValueData.CellTemperature[tempIndex + 1] = (short)((value & 0xFF) * 10);
}
}
}
#endregion
#region Data Conversion
private static short ConvertWarningData(short rawData)
{
short result = 0;
bool[] alarmBits = ConvertToBitArray(rawData);
if (alarmBits[0]) result |= (1 << 2); // Pack OV
if (alarmBits[1]) result |= (1 << 4); // Cell OV
if (alarmBits[2]) result |= (1 << 3); // Pack UV
if (alarmBits[3]) result |= (1 << 5); // Cell UV
if (alarmBits[4]) result |= (1 << 6); // Charging OC
if (alarmBits[5]) result |= (1 << 7); // Discharging OC
if (alarmBits[8]) result |= (1 << 0); // Charging Over Temperature
if (alarmBits[9]) result |= (1 << 0); // Discharging Over Temperature
if (alarmBits[10]) result |= (1 << 1); // Charging Under Temperature
if (alarmBits[11]) result |= (1 << 1); // Discharging Under Temperature
if (alarmBits[12]) result |= (1 << 11); // SOC Low
return result;
}
private static short ConvertProtectionData(short rawData)
{
short result = 0;
bool[] alarmBits = ConvertToBitArray(rawData);
if (alarmBits[0]) result |= (1 << 2); // Pack OV
if (alarmBits[1]) result |= (1 << 4); // Cell OV
if (alarmBits[2]) result |= (1 << 3); // Pack UV
if (alarmBits[3]) result |= (1 << 5); // Cell UV
if (alarmBits[4]) result |= (1 << 6); // Charging OC
if (alarmBits[5]) result |= (1 << 7); // Discharging OC
if (alarmBits[8]) result |= (1 << 0); // Charging Over Temperature
if (alarmBits[9]) result |= (1 << 0); // Discharging Over Temperature
if (alarmBits[10]) result |= (1 << 1); // Charging Under Temperature
if (alarmBits[11]) result |= (1 << 1); // Discharging Under Temperature
if (alarmBits[13]) result |= (1 << 9); // Short Circuit Protection
return result;
}
private static bool[] ConvertToBitArray(short value)
{
var bits = new bool[16];
for (int i = 0; i < 16; i++)
{
bits[i] = ((value >> i) & 1) == 1;
}
return bits;
}
#endregion
#region Calculated Values
private static void UpdateCalculatedValues(ref DeviceSystemData systemData)
{
CalculateCellVoltageStatistics(ref systemData);
CalculateTemperatureStatistics(ref systemData);
csMakeDataFunction.MakeAlarm(ref systemData);
}
private static void CalculateCellVoltageStatistics(ref DeviceSystemData systemData)
{
if (systemData.cellQty <= 0) return;
int max = 0, min = int.MaxValue, sum = 0;
int maxIndex = 0, minIndex = 0;
for (int i = 0; i < systemData.cellQty; i++)
{
if (i >= systemData.ValueData.CellVoltage.Length) break;
int cellVoltage = systemData.ValueData.CellVoltage[i];
sum += cellVoltage;
if (cellVoltage > max)
{
max = cellVoltage;
maxIndex = i;
}
if (cellVoltage < min)
{
min = cellVoltage;
minIndex = i;
}
}
systemData.AvgData.avgCellVoltage = (short)(sum / systemData.cellQty);
systemData.AvgData.maxCellVoltage = (short)max;
systemData.AvgData.maxCellNum = (short)(maxIndex + 1);
systemData.AvgData.minCellVoltage = (short)min;
systemData.AvgData.minCellNum = (short)(minIndex + 1);
systemData.AvgData.diffCellVoltage = (short)(max - min);
}
private static void CalculateTemperatureStatistics(ref DeviceSystemData systemData)
{
if (systemData.tempQty <= 0) return;
int max = int.MinValue, min = int.MaxValue, sum = 0;
int maxIndex = 0, minIndex = 0;
for (int i = 0; i < systemData.tempQty; i++)
{
if (i >= systemData.ValueData.CellTemperature.Length) break;
int temperature = systemData.ValueData.CellTemperature[i];
sum += temperature;
if (temperature > max)
{
max = temperature;
maxIndex = i;
}
if (temperature < min)
{
min = temperature;
minIndex = i;
}
}
systemData.AvgData.avgTemp = (short)(sum / systemData.tempQty);
systemData.AvgData.maxTemp = (short)max;
systemData.AvgData.maxTempNum = (short)(maxIndex + 1);
systemData.AvgData.minTemp = (short)min;
systemData.AvgData.minTempNum = (short)(minIndex + 1);
systemData.AvgData.diffTemp = (short)(max - min);
}
#endregion
#region Result Classes
public class ValidationResult
{
public bool IsValid { get; }
public string Message { get; }
public ValidationResult(bool isValid, string message)
{
IsValid = isValid;
Message = message;
}
}
public class ProcessingResult
{
public bool Success { get; }
public string Message { get; }
public ProcessingResult(bool success, string message)
{
Success = success;
Message = message;
}
}
#endregion
}
}