Release v1.1.1.1: Anti-Theft buttons update, BMS history chg_wait column, and wait for charging status display

This commit is contained in:
2026-09-22 15:17:18 +09:00
parent df038ae86b
commit 33fee50a10
26 changed files with 1570 additions and 2643 deletions

View File

@@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Collections.Concurrent;
using System.IO.Ports;
using System.Threading;
using System.Windows.Forms;
using LFP_Manager.DataStructure;
@@ -12,6 +12,7 @@ namespace LFP_Manager.Threads
public delegate void UartDataUpdate(object sender, ref DeviceSystemData aSystemData);
public delegate void UartDataRecv(byte[] data);
public delegate void UartDataPrint(object sender, string msg);
public delegate void UartPacketLogEvent(DateTime timestamp, bool isTx, byte[] packet, string description);
class csUartThread
{
@@ -28,23 +29,14 @@ namespace LFP_Manager.Threads
private bool AutoUartTx = true;
private int addr_ch = 0;
private ushort RequestRegAddr = 0x0000; //Byul Init 0x0000
private ushort RequestRegLen = 50;
private bool rFlag = false;
private int wFlag = 0;
private ushort ReadRegAddr = 0x0000; //Byul Init 0x0000
private ushort ReadRegLen = 0x0000; //Byul Init 0x0000
private ushort WriteRegAddr = 0x0000; //Byul Init 0x0000
private short WriteCoilRegData = 0;
private byte[] WriteRegData;
private TUartTxBuff uartTxBuff = null;
// Thread-Safe TX Queue
private readonly ConcurrentQueue<TUartTxItem> txQueue = new ConcurrentQueue<TUartTxItem>();
public event UartDataUpdate OnUpdate = null;
public event UartDataRecv OnDataRecv = null;
public event UartDataPrint OnPrint = null;
public event UartPacketLogEvent OnPacketLog = null;
#endregion
@@ -56,8 +48,6 @@ namespace LFP_Manager.Threads
Config = aConfig;
SystemData = aSystemData;
uartTxBuff = new TUartTxBuff();
serialComm = new Thread(uartCommThread);
}
@@ -130,13 +120,10 @@ namespace LFP_Manager.Threads
private void sErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
{
//
//csMakeDataFunction.SetData()
}
private void sPinChanged(object sender, System.IO.Ports.SerialPinChangedEventArgs e)
{
//
}
private bool Open(string cPort, int cBaudrate)
@@ -174,7 +161,7 @@ namespace LFP_Manager.Threads
#endregion
#region PUBLIC FUNCTION
#region PUBLIC TX FUNCTIONS (QUEUE BASED)
public void SetPolling(bool flag, int sId, ref DeviceSystemData aSystemData)
{
@@ -189,52 +176,66 @@ namespace LFP_Manager.Threads
AutoUartTx = autoTx;
}
public void SetWriteCoilReg(ushort WriteAddr, short WriteData)
/// <summary>
/// 공통 TX 큐 삽입 메서드
/// </summary>
public void EnqueueTx(TUartTxItem item)
{
wFlag = 2;
WriteRegAddr = WriteAddr;
WriteCoilRegData = WriteData;
for (int i = 0; i < 500; i += 10)
if (item != null)
{
Thread.Sleep(10);
Application.DoEvents();
txQueue.Enqueue(item);
}
}
/// <summary>
/// 원시 패킷 전송 (FW 업데이트 등)
/// </summary>
public void SendRawPacket(byte[] rawData, bool expectReply = true)
{
if (rawData == null || rawData.Length == 0) return;
TUartTxItem item = new TUartTxItem(UartTxType.Raw, SystemId, 0, (ushort)rawData.Length, rawData, expectReply, Config.RecvWaitTime);
EnqueueTx(item);
}
/// <summary>
/// 코일 레지스터 쓰기 (TX 큐 삽입)
/// </summary>
public void SetWriteCoilReg(ushort WriteAddr, short WriteData)
{
byte[] data = csSerialCommFunction.MakeWriteCoilData((ushort)SystemId, WriteAddr, WriteData);
TUartTxItem item = new TUartTxItem(UartTxType.WriteCoil, SystemId, WriteAddr, 1, data, true, Config.RecvWaitTime);
EnqueueTx(item);
}
/// <summary>
/// 레지스터 쓰기 (TX 큐 삽입)
/// </summary>
public void SetWriteReg(ushort WriteAddr, byte[] WriteData, bool ReplyFlag)
{
wFlag = 1;
WriteRegAddr = WriteAddr;
TUartTRxData uartTRxData = new TUartTRxData();
uartTRxData.type = wFlag;
//uartTRxData.data = csSerialCommFunction.MakeWriteRegisterData((ushort)SystemId, WriteRegAddr, WriteData);
uartTRxData.data = WriteData;
uartTRxData.length = uartTRxData.data.Length;
uartTxBuff?.PutBuff(uartTRxData);
if (WriteData == null) return;
TUartTxItem item = new TUartTxItem(UartTxType.WriteReg, SystemId, WriteAddr, (ushort)WriteData.Length, WriteData, ReplyFlag, Config.RecvWaitTime);
EnqueueTx(item);
}
/// <summary>
/// 레지스터 읽기 (TX 큐 삽입)
/// </summary>
public void SetReadReg(ushort ReadAddr, ushort ReadLen, bool ReplyFlag)
{
wFlag = 4;
ReadRegAddr = ReadAddr;
ReadRegLen = ReadLen;
byte[] data = csSerialCommFunction.MakeReadRegisterData((ushort)SystemId, csSerialCommFunction.READ_HOLDING_REG, ReadAddr, ReadLen);
TUartTxItem item = new TUartTxItem(UartTxType.ReadReg, SystemId, ReadAddr, ReadLen, data, ReplyFlag, Config.RecvWaitTime);
EnqueueTx(item);
}
/// <summary>
/// 파라미터 레지스터 쓰기 (TX 큐 삽입)
/// </summary>
public void SetParam(ushort WriteAddr, short[] WriteData)
{
wFlag = 1;
WriteRegAddr = WriteAddr;
TUartTRxData uartTRxData = new TUartTRxData();
uartTRxData.type = wFlag;
uartTRxData.data = csSerialCommFunction.MakeWriteRegisterData((ushort)SystemId, WriteRegAddr, WriteData);
uartTRxData.length = uartTRxData.data.Length;
uartTxBuff?.PutBuff(uartTRxData);
if (WriteData == null) return;
byte[] data = csSerialCommFunction.MakeWriteRegisterData((ushort)SystemId, WriteAddr, WriteData);
TUartTxItem item = new TUartTxItem(UartTxType.WriteParam, SystemId, WriteAddr, (ushort)WriteData.Length, data, true, Config.RecvWaitTime);
EnqueueTx(item);
}
public void SendProcessFromApp(int sId, int mode, int index, int flag, ref DeviceParamData aParam, ref DeviceCalibration aCalib)
@@ -315,13 +316,8 @@ namespace LFP_Manager.Threads
break;
case csConstData.UART_PARAM_LIST.DEFAULT_PARAM: // Default Parameter
int i = 0;
Forms.fmxWait WaitForm = new Forms.fmxWait();
WaitForm.StartPosition = FormStartPosition.CenterScreen;
try
{
//WaitForm.Show();
data = new short[1];
i = csConstData.UART_PARAM_LIST.CELL_UNDER_VOLTAGE;
@@ -385,10 +381,6 @@ namespace LFP_Manager.Threads
catch (Exception)
{
}
finally
{
//WaitForm?.Close();
}
break;
case 15: // Capcity change
@@ -413,16 +405,11 @@ namespace LFP_Manager.Threads
case 18:
data = new short[1];
i = csConstData.UART_CALIB_ADDR_LIST.ANTI_THEFT;
//data[0] = (short)aCalib.AntiTheft.Comm; SetParam((ushort)(i + 0), data);
data[0] = (short)aCalib.AntiTheft.GyroScope; SetParam((ushort)(i + 1), data);
break;
case 19:
data = new short[1];
i = csConstData.UART_CALIB_ADDR_LIST.ANTI_THEFT;
//data[0] = (short)0; SetParam((ushort)(i + 0), data);
data[0] = (short)0; SetParam((ushort)(i + 1), data);
//data[0] = (short)aCalib.AntiTheft.Comm; SetParam((ushort)(i + 0), data);
data[0] = (short)aCalib.AntiTheft.GyroScope; SetParam((ushort)(i + 1), data);
break;
default:
@@ -471,176 +458,84 @@ namespace LFP_Manager.Threads
#endregion
#region TX BUFFERING
#region POLLING GENERATOR
private byte[] MakeWriteRegData(ushort DevID, ushort WriteAddr, ushort WriteLen, ref ushort[] WriteData)
/// <summary>
/// 다음 주기 Polling 패킷 아이템 생성
/// </summary>
private TUartTxItem CreateNextPollingItem()
{
byte[] wData = null;
ushort reqAddr = 0x0000;
ushort reqLen = 50;
ushort sCmd = csSerialCommFunction.READ_HOLDING_REG;
if (WriteLen > 0)
switch (addr_ch)
{
}
return wData;
}
private string MakeCheckSum(byte[] sData, int len)
{
string result = "";
int checksum = 0;
for (int i = 0; i < len; i++)
{
checksum += sData[i + 1];
}
checksum = ~checksum + 1;
result = String.Format("{0:X2}{1:X2}", (byte)(checksum >> 8), (byte)checksum);
return result;
}
private byte[] MakeTxDataVision(ref bool wData)
{
byte[] sData = null;
if ((uartTxBuff != null) && (uartTxBuff.CheckBuff()))
{
TUartTRxData sBuff = uartTxBuff.GetBuff();
if (sBuff != null)
{
sData = sBuff.data;
wData = true;
rFlag = true;
}
}
else if (wFlag == 2)
{
sData = csSerialCommFunction.MakeWriteCoilData((ushort)SystemId, WriteRegAddr, WriteCoilRegData);
if (wFlag != 0) wFlag = 0;
rFlag = true;
}
else if (wFlag == 3)
{
sData = WriteRegData;
if (wFlag != 0) wFlag = 0;
rFlag = true;
}
else if (wFlag == 4)
{
sData = csSerialCommFunction.MakeReadRegisterData((ushort)SystemId, csSerialCommFunction.READ_HOLDING_REG, ReadRegAddr, ReadRegLen);
if (wFlag != 0) wFlag = 0;
rFlag = true;
}
else
{
if (UartPolling && AutoUartTx)
{
ushort sCmd;
switch (addr_ch)
{
case 0:
addr_ch = 1;
RequestRegAddr = 0x0000; //Byul Init 0x0000
RequestRegLen = 0x0031;
sCmd = csSerialCommFunction.READ_HOLDING_REG;
rFlag = true;
break;
case 1:
addr_ch = 4;
RequestRegAddr = 58;
RequestRegLen = 138-58;
sCmd = csSerialCommFunction.READ_HOLDING_REG;
rFlag = true;
break;
case 2:
addr_ch = 3;
RequestRegAddr = 0x3078;
RequestRegLen = 8;
sCmd = csSerialCommFunction.READ_COIL_STATUS;
rFlag = true;
break;
case 3:
addr_ch = 4;
RequestRegAddr = 0x502E;
RequestRegLen = 1;
sCmd = csSerialCommFunction.READ_COIL_STATUS;
rFlag = true;
break;
case 4:
if (Config.ModuleQty > 1)
{
SystemId++;
if (SystemId > Config.ModuleQty)
{
SystemId = 1;
}
}
addr_ch = 0;
sCmd = csSerialCommFunction.NO_CMD;
rFlag = false;
break;
default:
addr_ch = 0;
RequestRegAddr = 0x0000; //Byul Init 0x0000
RequestRegLen = 100;
sCmd = csSerialCommFunction.READ_HOLDING_REG;
break;
}
if (sCmd == csSerialCommFunction.NO_CMD)
{
sData = null;
}
else
{
sData = csSerialCommFunction.MakeReadRegisterData((ushort)SystemId, sCmd, RequestRegAddr, RequestRegLen);
}
}
}
return sData;
}
private byte ReqCmd = 0x42;
private byte RequstedCmd;
private byte[] TxProcess()
{
byte[] result = null;
switch (ReqCmd)
{
case 0x42: // Get the number of PACK
csSerialCommFunction.MakeTxData(0x01, ReqCmd, 2);
RequstedCmd = ReqCmd;
ReqCmd = 0x46;
case 0:
addr_ch = 1;
reqAddr = 0x0000;
reqLen = 0x0031; // 49
sCmd = csSerialCommFunction.READ_HOLDING_REG;
break;
case 0x46: // Ger the analog information
csSerialCommFunction.MakeTxData(0x01, ReqCmd, 2);
RequstedCmd = ReqCmd;
ReqCmd = 0x46;
case 1:
addr_ch = 4;
reqAddr = 58;
reqLen = 139 - 58; // 81 (Registers 58 ~ 138)
sCmd = csSerialCommFunction.READ_HOLDING_REG;
break;
case 0x90: // Ger warning information
csSerialCommFunction.MakeTxData(0x01, ReqCmd, 2);
RequstedCmd = ReqCmd;
ReqCmd = 0x46;
case 2:
addr_ch = 3;
reqAddr = 0x3078;
reqLen = 8;
sCmd = csSerialCommFunction.READ_COIL_STATUS;
break;
case 3:
addr_ch = 4;
reqAddr = 0x502E;
reqLen = 1;
sCmd = csSerialCommFunction.READ_COIL_STATUS;
break;
case 4:
if (Config.ModuleQty > 1)
{
SystemId++;
if (SystemId > Config.ModuleQty)
{
SystemId = 1;
}
}
addr_ch = 0;
sCmd = csSerialCommFunction.NO_CMD;
break;
default:
ReqCmd = 0x42;
addr_ch = 0;
reqAddr = 0x0000;
reqLen = 100;
sCmd = csSerialCommFunction.READ_HOLDING_REG;
break;
}
return result;
if (sCmd == csSerialCommFunction.NO_CMD)
{
return null;
}
byte[] sData = csSerialCommFunction.MakeReadRegisterData((ushort)SystemId, sCmd, reqAddr, reqLen);
if (sData == null || sData.Length == 0)
{
return null;
}
return new TUartTxItem(UartTxType.Polling, SystemId, reqAddr, reqLen, sData, true, Config.RecvWaitTime);
}
#endregion
#region COMM THREAD
#region COMM THREAD (QUEUE BASED PIPELINE)
private readonly byte[] ReadBuf = new byte[BUFFER_SIZE];
ushort rPosition = 0;
bool BuffStart = false;
private ushort rPosition = 0;
private bool BuffStart = false;
private void RecvPacketInit()
{
@@ -650,153 +545,180 @@ namespace LFP_Manager.Threads
private void uartCommThread()
{
int RecvTimeout = Config.RecvWaitTime;
int getData = 0;
byte cData = 0;
int TimeOutCount = 0;
StartSend:
while (SerialPortThreadEnd == false)
{
if ((sPort == null) || (sPort.IsOpen == false)) continue;
//if (UartPolling)
if (true)
if ((sPort == null) || (sPort.IsOpen == false))
{
bool wMode = false;
byte[] txData = MakeTxDataVision(ref wMode);
if (sPort == null) return;
if (txData != null)
Thread.Sleep(50);
continue;
}
// 1. 큐가 비어있고 Polling이 활성화되어 있을 때 다음 Polling 패킷을 TX 큐에 삽입
if (txQueue.IsEmpty && UartPolling && AutoUartTx)
{
TUartTxItem pollItem = CreateNextPollingItem();
if (pollItem != null)
{
FlushBuff();
sPort.Write(txData, 0, txData.Length);
txQueue.Enqueue(pollItem);
}
if (rFlag == true)
else
{
int rTimeOut = RecvTimeout;
RecvPacketInit();
Thread.Sleep(10);
continue;
}
}
while (rTimeOut > 0)
// 2. TX 큐에서 패킷 1개 꺼내기
if (!txQueue.TryDequeue(out TUartTxItem currentTx) || currentTx == null || currentTx.Data == null || currentTx.Data.Length == 0)
{
Thread.Sleep(20);
continue;
}
// 3. SerialPort로 패킷 전송
try
{
FlushBuff();
sPort.Write(currentTx.Data, 0, currentTx.Data.Length);
OnPacketLog?.Invoke(DateTime.Now, true, currentTx.Data, GetTxDescription(currentTx));
}
catch (Exception)
{
Thread.Sleep(50);
continue;
}
// 4. 응답 대기 및 수신 처리
if (currentTx.ExpectReply)
{
int rTimeOut = currentTx.TimeoutMs > 0 ? currentTx.TimeoutMs : Config.RecvWaitTime;
RecvPacketInit();
bool packetHandled = false;
while (rTimeOut > 0 && SerialPortThreadEnd == false)
{
if (((getData = GetBuff()) & 0x0100) != 0x0000)
{
if (((getData = GetBuff()) & 0x0100) != 0x0000)
rTimeOut = currentTx.TimeoutMs > 0 ? currentTx.TimeoutMs : Config.RecvWaitTime;
cData = (byte)(getData & 0x00FF);
if (rPosition >= BUFFER_SIZE) RecvPacketInit();
if (BuffStart == false)
{
rTimeOut = RecvTimeout;
cData = (byte)(getData & 0x00FF);
if (rPosition >= BUFFER_SIZE) RecvPacketInit();
if (BuffStart == false)
{
if ((cData == (byte)SystemId) || (cData == (byte)0x7F))
{
rPosition = 0;
ReadBuf[rPosition++] = cData;
BuffStart = true;
}
}
else
if ((cData == (byte)currentTx.SystemId) || (cData == (byte)0x7F) || (cData == (byte)SystemId))
{
rPosition = 0;
ReadBuf[rPosition++] = cData;
switch (csSerialCommFunction.ModbusPacketFromSlaveCheck(ReadBuf, rPosition))
{
case 0: // Need more data
break;
case 1: // Packet OK, no error
TimeOutCount = 0;
int mID = ReadBuf[0];
if (mID > 0) mID--;
SystemData.CommFail = false;
SystemData.ShelfCommFail = false;
if (wMode == false)
{
short[] result_code = csSerialCommFunction.SerialRxProcess(ReadBuf, RequestRegAddr, rPosition, ref SystemData);
OnUpdate?.Invoke(this, ref SystemData);
}
else
{
wMode = false;
WriteRegAddr = 0;
if (OnDataRecv != null)
{
byte[] adata = new byte[rPosition + 1];
for (int j = 0; j < adata.Length; j++) { adata[j] = ReadBuf[j]; }
OnDataRecv(adata);
}
}
rFlag = false;
Thread.Sleep(5);
goto StartSend;
case 2: // Fw Update Packet OK
TimeOutCount = 0;
rFlag = false;
if (OnDataRecv != null)
{
byte[] adata = new byte[rPosition + 1];
for (int j = 0; j < adata.Length; j++)
{
adata[j] = ReadBuf[j];
}
OnDataRecv(adata);
}
goto StartSend;
case -1: // Packet error
RecvPacketInit();
Thread.Sleep(100);
goto StartSend;
default:
break;
}
BuffStart = true;
}
}
else
{
Thread.Sleep(1);
if (rTimeOut > 0)
ReadBuf[rPosition++] = cData;
int checkResult = csSerialCommFunction.ModbusPacketFromSlaveCheck(ReadBuf, rPosition);
if (checkResult == 0)
{
rTimeOut--;
// Need more data
}
else if (checkResult == 1)
{
// Packet OK
TimeOutCount = 0;
SystemData.CommFail = false;
SystemData.ShelfCommFail = false;
byte[] rxPacket = new byte[rPosition];
Array.Copy(ReadBuf, rxPacket, rPosition);
OnPacketLog?.Invoke(DateTime.Now, false, rxPacket, GetRxDescription(rxPacket, currentTx));
if (currentTx.TxType == UartTxType.Polling)
{
csSerialCommFunction.SerialRxProcess(ReadBuf, currentTx.RegAddr, rPosition, ref SystemData);
OnUpdate?.Invoke(this, ref SystemData);
}
else
{
if (OnDataRecv != null)
{
byte[] adata = new byte[rPosition + 1];
for (int j = 0; j < adata.Length; j++) { adata[j] = ReadBuf[j]; }
OnDataRecv(adata);
}
}
packetHandled = true;
Thread.Sleep(5);
break;
}
else if (checkResult == 2)
{
// Fw Update Packet OK
TimeOutCount = 0;
byte[] rxPacket = new byte[rPosition];
Array.Copy(ReadBuf, rxPacket, rPosition);
OnPacketLog?.Invoke(DateTime.Now, false, rxPacket, "Fw Update Response OK");
if (OnDataRecv != null)
{
byte[] adata = new byte[rPosition + 1];
for (int j = 0; j < adata.Length; j++) { adata[j] = ReadBuf[j]; }
OnDataRecv(adata);
}
packetHandled = true;
Thread.Sleep(5);
break;
}
else if (checkResult == -1)
{
// Packet Error
byte[] rxPacket = new byte[rPosition];
Array.Copy(ReadBuf, rxPacket, rPosition);
OnPacketLog?.Invoke(DateTime.Now, false, rxPacket, "Packet CRC/Format Error");
RecvPacketInit();
Thread.Sleep(20);
packetHandled = true;
break;
}
}
}
if (rPosition > 0)
{
Thread.Sleep(1);
}
else
{
WriteRegAddr = 0;
TimeOutCount++;
if (TimeOutCount >= 5)
Thread.Sleep(1);
rTimeOut--;
}
}
// 타임아웃 처리
if (!packetHandled)
{
TimeOutCount++;
if (TimeOutCount >= 5)
{
TimeOutCount = 5;
if (SystemData.ShelfCommFail == false)
{
TimeOutCount = 5;
//SystemData[0].CommFail = true;
if (SystemData.ShelfCommFail == false)
{
SystemData.ShelfCommFail = true;
DataInit();
}
SystemData.ShelfCommFail = true;
DataInit();
}
Thread.Sleep(100);
}
OnUpdate?.Invoke(this, ref SystemData);
Thread.Sleep(1);
Thread.Sleep(50);
}
else
{
Thread.Sleep(100);
} /* if (rFlag == true) */
rPosition = 0;
}
else
{
Thread.Sleep(100);
// No reply expected
Thread.Sleep(10);
}
}
}
@@ -850,5 +772,62 @@ namespace LFP_Manager.Threads
}
#endregion
#region PACKET LOG HELPERS
private string GetTxDescription(TUartTxItem item)
{
if (item == null) return "Unknown TX";
switch (item.TxType)
{
case UartTxType.Polling:
return string.Format("Polling (ID:{0}, Addr:0x{1:X4}, Len:{2})", item.SystemId, item.RegAddr, item.RegLen);
case UartTxType.WriteParam:
return string.Format("Write Param (ID:{0}, Addr:0x{1:X4})", item.SystemId, item.RegAddr);
case UartTxType.WriteCoil:
return string.Format("Write Coil (ID:{0}, Addr:0x{1:X4})", item.SystemId, item.RegAddr);
case UartTxType.WriteReg:
return string.Format("Write Reg (ID:{0}, Addr:0x{1:X4}, Len:{2})", item.SystemId, item.RegAddr, item.RegLen);
case UartTxType.ReadReg:
return string.Format("Read Reg (ID:{0}, Addr:0x{1:X4}, Len:{2})", item.SystemId, item.RegAddr, item.RegLen);
case UartTxType.FwUpdate:
return string.Format("FW Update (ID:{0}, Len:{1})", item.SystemId, item.Data != null ? item.Data.Length : 0);
default:
return string.Format("RAW TX (ID:{0}, Len:{1})", item.SystemId, item.Data != null ? item.Data.Length : 0);
}
}
private string GetRxDescription(byte[] rxPacket, TUartTxItem item)
{
if (rxPacket == null || rxPacket.Length < 3) return "Unknown RX";
byte devId = rxPacket[0];
byte funcCode = rxPacket[1];
if (funcCode == 0x03)
{
byte byteCount = rxPacket[2];
if (item != null && item.TxType == UartTxType.Polling)
{
return string.Format("Polling Response (ID:{0}, Bytes:{1})", devId, byteCount);
}
return string.Format("Read Holding Reg Response (ID:{0}, Bytes:{1})", devId, byteCount);
}
else if (funcCode == 0x05)
{
return string.Format("Write Coil Response (ID:{0})", devId);
}
else if (funcCode == 0x06 || funcCode == 0x10)
{
return string.Format("Write Reg Response (ID:{0})", devId);
}
else if (funcCode > 0x80)
{
byte errCode = rxPacket.Length > 2 ? rxPacket[2] : (byte)0;
return string.Format("Modbus Error Response (ID:{0}, Func:0x{1:X2}, ErrCode:0x{2:X2})", devId, funcCode, errCode);
}
return string.Format("RX Data (ID:{0}, Func:0x{1:X2}, Len:{2})", devId, funcCode, rxPacket.Length);
}
#endregion
}
}