619 lines
21 KiB
C#
619 lines
21 KiB
C#
using System;
|
|
|
|
using System.Threading;
|
|
using System.IO.Ports;
|
|
using System.Windows.Forms;
|
|
|
|
using LFP_Manager.DataStructure;
|
|
using LFP_Manager.Function;
|
|
using LFP_Manager.Utils;
|
|
using DevExpress.XtraGauges.Core.Model;
|
|
using System.Collections.Concurrent;
|
|
using System.Drawing.Printing;
|
|
using System.ComponentModel;
|
|
|
|
namespace LFP_Manager.Threads
|
|
{
|
|
class CsUartThreadSB
|
|
{
|
|
#region DELEGATE
|
|
// 표준 이벤트 패턴
|
|
public sealed class ModuleDataEventArgs : EventArgs
|
|
{
|
|
public CsDeviceData.DeviceModuleData[] Modules { get; }
|
|
public ModuleDataEventArgs(CsDeviceData.DeviceModuleData[] modules) => Modules = modules;
|
|
}
|
|
#endregion
|
|
|
|
#region CONSTANTS
|
|
private const int RX_PACKET_BUFFER_SIZE = 512;
|
|
#endregion
|
|
|
|
#region VARIABLES
|
|
|
|
private readonly CommConfig Config;
|
|
private CsDeviceData.DeviceModuleData[] ModuleData;
|
|
private Thread _serialComm = null;
|
|
private SerialPort sPort = null;
|
|
private CancellationTokenSource _cts;
|
|
private volatile bool _serialPortThreadEnd = true;
|
|
|
|
private readonly object _startStopLock = new object();
|
|
|
|
private readonly ISynchronizeInvoke _ui; // 보통 Form 또는 Control
|
|
|
|
public int ModuleID = 1;
|
|
private bool UartPolling = false;
|
|
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 int rFlag2 = 0;
|
|
|
|
private ushort ExtReqRegAddr = 0x0000;
|
|
|
|
private ushort WriteRegAddr = 0x0000; //Byul Init 0x0000
|
|
private short WriteCoilRegData = 0;
|
|
private byte[] WriteRegData;
|
|
private short WriteParamRegData;
|
|
|
|
private readonly ConcurrentQueue<byte> _rxQ = new ConcurrentQueue<byte>();
|
|
|
|
private TUartTxBuff UartTxBuff;
|
|
|
|
// 이벤트
|
|
public event EventHandler<ModuleDataEventArgs> OnUpdate;
|
|
public event EventHandler<DataRecvEventArgs> OnDataRecv;
|
|
public event EventHandler<PrintEventArgs> OnPrint;
|
|
|
|
#endregion
|
|
|
|
#region CONSTRUCTORS
|
|
|
|
public CsUartThreadSB(int mId,
|
|
CommConfig aConfig,
|
|
ISynchronizeInvoke uiInvoker,
|
|
CsDeviceData.DeviceModuleData[] aModuleData)
|
|
{
|
|
ModuleID = mId;
|
|
Config = aConfig;
|
|
ModuleData = aModuleData;
|
|
|
|
UartTxBuff = new TUartTxBuff();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
public bool Start(ref CommConfig aConfig, int mID, bool aPolling)
|
|
{
|
|
if (aConfig == null) throw new ArgumentNullException(nameof(aConfig));
|
|
|
|
string modelname = csConstData.UART_MODEL[aConfig.UartModelIndex];
|
|
int mQty = aConfig.ModuleQty;
|
|
|
|
if (ModuleData == null || ModuleData.Length < mQty)
|
|
throw new InvalidOperationException($"ModuleData length({ModuleData?.Length ?? 0}) < ModuleQty({mQty})");
|
|
|
|
for (int i = 0; i < mQty; i++)
|
|
{
|
|
ModuleData[i].mNo = i + 1;
|
|
//ModuleData[i].Information.ModelName = modelname;
|
|
}
|
|
|
|
ModuleID = mID;
|
|
UartPolling = aPolling;
|
|
|
|
if (!Open(aConfig.UartPort, aConfig.UartBaudrate))
|
|
return false;
|
|
|
|
// 이중 시작 방지
|
|
if (_serialComm != null && _serialComm.IsAlive)
|
|
return true;
|
|
|
|
_serialPortThreadEnd = false;
|
|
_cts = new CancellationTokenSource();
|
|
|
|
_serialComm = new Thread(() => uartCommThreadSB(_cts.Token))
|
|
{
|
|
IsBackground = true,
|
|
Name = "UartCommThreadDelta"
|
|
};
|
|
_serialComm.Start();
|
|
return true;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
_serialPortThreadEnd = true;
|
|
var cts = _cts;
|
|
_cts = null;
|
|
if (cts != null && !cts.IsCancellationRequested)
|
|
cts.Cancel();
|
|
|
|
var t = _serialComm;
|
|
_serialComm = null;
|
|
if (t != null && t.IsAlive)
|
|
{
|
|
try { t.Join(500); } catch { /* timeout 시 로그만 */ }
|
|
}
|
|
|
|
Close(); // SerialPort 닫기 및 핸들러 해제
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SAFETY RAISE HELPER
|
|
private void RaiseOnUpdate(CsDeviceData.DeviceModuleData[] snapshot)
|
|
{
|
|
var handler = OnUpdate;
|
|
if (handler == null) return;
|
|
|
|
var args = new ModuleDataEventArgs(snapshot);
|
|
|
|
if (_ui != null && _ui.InvokeRequired)
|
|
{
|
|
try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
|
|
catch { /* 폼 종료 중 등 예외 무시(로그만) */ }
|
|
}
|
|
else
|
|
{
|
|
handler(this, args);
|
|
}
|
|
}
|
|
|
|
private void RaiseOnDataRecv(byte[] data)
|
|
{
|
|
var handler = OnDataRecv;
|
|
if (handler == null) return;
|
|
|
|
var args = new DataRecvEventArgs(data);
|
|
|
|
if (_ui != null && _ui.InvokeRequired)
|
|
{
|
|
try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
handler(this, args);
|
|
}
|
|
}
|
|
|
|
private void RaiseOnPrint(int id, string msg)
|
|
{
|
|
var handler = OnPrint;
|
|
if (handler == null) return;
|
|
|
|
var args = new PrintEventArgs(id, msg);
|
|
|
|
if (_ui != null && _ui.InvokeRequired)
|
|
{
|
|
try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
handler(this, args);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region COMMPORT CONTROLS
|
|
|
|
private void sDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var sp = (SerialPort)sender;
|
|
int available = sp.BytesToRead;
|
|
if (available <= 0) return;
|
|
|
|
byte[] tmp = new byte[available];
|
|
int read = sp.Read(tmp, 0, tmp.Length);
|
|
for (int i = 0; i < read; i++)
|
|
_rxQ.Enqueue(tmp[i]);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
/* 필요시 로그 */
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
sPort = new SerialPort();
|
|
sPort.PortName = cPort;
|
|
sPort.BaudRate = cBaudrate;
|
|
sPort.DataReceived += sDataReceived;
|
|
sPort.ErrorReceived += sErrorReceived;
|
|
sPort.PinChanged += sPinChanged;
|
|
|
|
try
|
|
{
|
|
sPort.Open();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Exception("Error Open - " + ex.Message);
|
|
}
|
|
|
|
return sPort.IsOpen;
|
|
}
|
|
|
|
private void Close()
|
|
{
|
|
if (sPort != null)
|
|
{
|
|
try
|
|
{
|
|
sPort.DataReceived -= sDataReceived;
|
|
sPort.ErrorReceived -= sErrorReceived;
|
|
sPort.PinChanged -= sPinChanged;
|
|
if (sPort.IsOpen) sPort.Close();
|
|
}
|
|
finally
|
|
{
|
|
sPort.Dispose();
|
|
sPort = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PUBLIC FUNCTION
|
|
|
|
public void SetPolling(bool flag, int mID, ref CsDeviceData.DeviceModuleData[] aModuleData)
|
|
{
|
|
ModuleID = mID;
|
|
UartPolling = flag;
|
|
ModuleData = aModuleData;
|
|
ModuleData[0].mNo = ModuleID;
|
|
}
|
|
|
|
public void SetAutoTx(bool autoTx)
|
|
{
|
|
AutoUartTx = autoTx;
|
|
}
|
|
|
|
public void SetWriteReg(ushort WriteAddr, byte[] WriteData, bool ReplyFlag, int type)
|
|
{
|
|
WriteRegAddr = WriteAddr;
|
|
|
|
TUartTRxData uartTRxData = new TUartTRxData();
|
|
uartTRxData.type = type;
|
|
uartTRxData.data = WriteData;
|
|
uartTRxData.length = uartTRxData.data.Length;
|
|
|
|
UartTxBuff?.PutBuff(uartTRxData);
|
|
}
|
|
|
|
public void SetParam(ushort WriteAddr, short WriteData)
|
|
{
|
|
short[] wData = new short[1];
|
|
wData[0] = WriteData;
|
|
|
|
byte[] sData = CsSerialCommFunctionDelta.MakeWriteRegisterData((ushort)ModuleID, WriteAddr, wData);
|
|
|
|
if (sData != null)
|
|
{
|
|
TUartTRxData aData = new TUartTRxData();
|
|
aData.length = sData.Length;
|
|
aData.data = sData;
|
|
|
|
UartTxBuff.PutBuff(aData);
|
|
}
|
|
}
|
|
|
|
public void SetReadWriteParam(ushort WriteAddr, short WriteData)
|
|
{
|
|
byte[] sData = CsSerialCommFunctionDelta.MakeReadWriteRegisterData((ushort)ModuleID, WriteAddr, WriteData);
|
|
|
|
if (sData != null)
|
|
{
|
|
TUartTRxData aData = new TUartTRxData();
|
|
aData.length = sData.Length;
|
|
aData.data = sData;
|
|
|
|
ExtReqRegAddr = WriteAddr;
|
|
|
|
UartTxBuff.PutBuff(aData);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region RX BUFFERING
|
|
|
|
private int GetBuff()
|
|
{
|
|
return _rxQ.TryDequeue(out var b) ? (0x0100 | b) : 0;
|
|
}
|
|
|
|
private void FlushBuff()
|
|
{
|
|
while (_rxQ.TryDequeue(out _)) { }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TX BUFFERING
|
|
|
|
private byte[] MakeTxDataDelta(bool wData)
|
|
{
|
|
byte[] sData = null;
|
|
|
|
if ((UartTxBuff != null) && (UartTxBuff.CheckBuff()))
|
|
{
|
|
TUartTRxData sBuff = UartTxBuff.GetBuff();
|
|
if (sBuff != null)
|
|
{
|
|
sData = sBuff.data;
|
|
wData = true;
|
|
rFlag = true;
|
|
rFlag2 = sBuff.type;
|
|
|
|
RequestRegAddr = ExtReqRegAddr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AutoUartTx && UartPolling)
|
|
{
|
|
ushort sCmd;
|
|
switch (addr_ch)
|
|
{
|
|
case 0: // Battery Status Data
|
|
addr_ch = 3;
|
|
RequestRegAddr = 0x0FFF; //Byul Init 0x0000
|
|
RequestRegLen = 0x27;
|
|
sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG; // Command 0x04
|
|
rFlag = true;
|
|
break;
|
|
case 1: // Gyroscope Data
|
|
addr_ch = 2;
|
|
RequestRegAddr = 0x4000;
|
|
RequestRegLen = 7;
|
|
sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG; // Command 0x17
|
|
rFlag = true;
|
|
break;
|
|
case 2:
|
|
addr_ch = 3;
|
|
RequestRegAddr = 0x3000;
|
|
RequestRegLen = 3;
|
|
sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG; // Command 0x19
|
|
rFlag = true;
|
|
break;
|
|
case 3:
|
|
addr_ch = 4;
|
|
RequestRegAddr = 0x0000;
|
|
RequestRegLen = 1;
|
|
sCmd = CsSerialCommFunctionDelta.READ_DEV_ID; // Command 0x2B
|
|
rFlag = true;
|
|
break;
|
|
case 4:
|
|
if (Config.ModuleQty > 1)
|
|
{
|
|
ModuleID++;
|
|
if (ModuleID > Config.ModuleQty)
|
|
{
|
|
ModuleID = 1;
|
|
}
|
|
}
|
|
addr_ch = 0;
|
|
sCmd = CsSerialCommFunctionDelta.NO_CMD;
|
|
rFlag = false;
|
|
break;
|
|
default:
|
|
addr_ch = 0;
|
|
RequestRegAddr = 0x0FFF; //Byul Init 0x0000
|
|
RequestRegLen = 27;
|
|
sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG; // Command 0x04
|
|
rFlag = true;
|
|
break;
|
|
}
|
|
if (sCmd == CsSerialCommFunctionDelta.NO_CMD)
|
|
{
|
|
sData = null;
|
|
}
|
|
else if (sCmd == CsSerialCommFunctionDelta.READ_DEV_ID)
|
|
{
|
|
sData = CsSerialCommFunctionDelta.MakeReadDevIdRegReqData((ushort)ModuleID, sCmd, RequestRegAddr);
|
|
}
|
|
else
|
|
{
|
|
sData = CsSerialCommFunctionDelta.MakeReadRegisterData((ushort)ModuleID, sCmd, RequestRegAddr, RequestRegLen);
|
|
}
|
|
}
|
|
}
|
|
return sData;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region COMM THREAD
|
|
|
|
private readonly byte[] ReadBuf = new byte[RX_PACKET_BUFFER_SIZE];
|
|
ushort rPosition = 0;
|
|
bool BuffStart = false;
|
|
|
|
private void RecvPacketInit()
|
|
{
|
|
BuffStart = false;
|
|
rPosition = 0;
|
|
}
|
|
|
|
private void uartCommThreadSB(CancellationToken token)
|
|
{
|
|
int RecvTimeout = Config.RecvWaitTime;
|
|
int COMM_TIMEOUT_MAX_COUNT = (Config.CommFailMaxCount > 100) ? 5 : Config.CommFailMaxCount; // 5초 동안 수신 없으면 Fail
|
|
DateTime[] LastReceiveTime = new DateTime[csConstData.SystemInfo.MAX_MODULE_SIZE];
|
|
int[] RecvTimeoutCount = new int[csConstData.SystemInfo.MAX_MODULE_SIZE];
|
|
|
|
// ★ 마지막 수신 시간 초기화 (현재 시각 기준)
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < LastReceiveTime.Length; i++)
|
|
LastReceiveTime[i] = now;
|
|
|
|
StartSend:
|
|
while (!token.IsCancellationRequested && !_serialPortThreadEnd)
|
|
{
|
|
if (sPort == null || !sPort.IsOpen) { Thread.Sleep(50); continue; }
|
|
|
|
FlushBuff();
|
|
|
|
byte[] txData = MakeTxDataDelta(false);
|
|
if (txData != null)
|
|
{
|
|
try
|
|
{
|
|
sPort.Write(txData, 0, txData.Length);
|
|
RaiseOnPrint(txData[0], csLog.trx_data_print(txData, txData.Length, 0));
|
|
}
|
|
catch
|
|
{
|
|
/* 로그 */
|
|
RaiseOnPrint(ModuleID, "UART Thread - TX Buff Error !");
|
|
}
|
|
}
|
|
|
|
if (rFlag)
|
|
{
|
|
var deadline = DateTime.UtcNow.AddMilliseconds(RecvTimeout);
|
|
RecvPacketInit();
|
|
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
int gd = GetBuff();
|
|
if ((gd & 0x0100) == 0) { Thread.Sleep(1); continue; }
|
|
|
|
byte cData = (byte)(gd & 0xFF);
|
|
|
|
if (!BuffStart)
|
|
{
|
|
if (cData == (byte)ModuleID || cData == 0x7F)
|
|
{
|
|
rPosition = 0;
|
|
ReadBuf[rPosition++] = cData;
|
|
BuffStart = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rPosition >= ReadBuf.Length) { RecvPacketInit(); continue; }
|
|
ReadBuf[rPosition++] = cData;
|
|
|
|
int chk = CsSerialCommFunctionDelta.ModbusPacketFromSlaveCheck(ReadBuf, rPosition);
|
|
if (chk == 0) continue;
|
|
|
|
RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));
|
|
|
|
if (chk == 1)
|
|
{
|
|
int mID = ReadBuf[0];
|
|
if (mID > 0) mID--;
|
|
if (mID >= 0 && mID < ModuleData.Length)
|
|
{
|
|
// 마지막 정상 업데이트 시간 기록
|
|
LastReceiveTime[mID] = DateTime.UtcNow;
|
|
RecvTimeoutCount[mID] = 0;
|
|
|
|
ModuleData[mID].mNo = mID + 1;
|
|
ModuleData[mID].ShelfCommFail = false;
|
|
var rc = CsSerialCommFunctionDelta.SerialRxProcess(
|
|
ReadBuf, RequestRegAddr, (ushort)rPosition, ref ModuleData[mID]);
|
|
}
|
|
|
|
var snapshot1 = ModuleData; // 필요 시 DeepCopy
|
|
RaiseOnUpdate(snapshot1);
|
|
Thread.Sleep(5);
|
|
goto StartSend;
|
|
}
|
|
else if (chk == 2)
|
|
{
|
|
rFlag = false;
|
|
RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));
|
|
|
|
byte[] adata = new byte[rPosition + 1];
|
|
for (int j = 0; j < adata.Length; j++)
|
|
{
|
|
adata[j] = ReadBuf[j];
|
|
}
|
|
RaiseOnDataRecv(adata);
|
|
|
|
goto StartSend;
|
|
}
|
|
else // -1
|
|
{
|
|
RecvPacketInit();
|
|
Thread.Sleep(50);
|
|
goto StartSend;
|
|
}
|
|
}
|
|
}
|
|
|
|
// timeout
|
|
if (rPosition > 0)
|
|
{
|
|
RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));
|
|
}
|
|
else
|
|
{
|
|
int idx = csUtils.MathEx.Clamp(ModuleID - 1, 0, csConstData.SystemInfo.MAX_MODULE_SIZE - 1);
|
|
|
|
RecvTimeoutCount[idx]++;
|
|
// 아직 한 번도 정상 수신한 적 없는 경우 MinValue일 수 있으므로,
|
|
// 첫 타임아웃에서는 기준 시간을 현재로 초기화해 준다.
|
|
if (LastReceiveTime[idx] == DateTime.MinValue)
|
|
LastReceiveTime[idx] = DateTime.UtcNow;
|
|
|
|
// 시간 차이 계산
|
|
var elapsed = (DateTime.UtcNow - LastReceiveTime[idx]).TotalMilliseconds;
|
|
|
|
if (RecvTimeoutCount[idx] >= COMM_TIMEOUT_MAX_COUNT)
|
|
//if (elapsed >= COMM_TIMEOUT_MS)
|
|
{
|
|
RecvTimeoutCount[idx] = 5;
|
|
// Fail 처리
|
|
if (!ModuleData[idx].ShelfCommFail)
|
|
{
|
|
ModuleData[idx].Reset();
|
|
ModuleData[idx].mNo = ModuleID;
|
|
ModuleData[idx].ShelfCommFail = true;
|
|
}
|
|
}
|
|
Thread.Sleep(100);
|
|
}
|
|
var snapshot2 = ModuleData; // 필요 시 DeepCopy
|
|
RaiseOnUpdate(snapshot2);
|
|
}
|
|
else
|
|
{
|
|
Thread.Sleep(10);
|
|
} /* if (rFlag == true) */
|
|
rPosition = 0;
|
|
Thread.Sleep(100);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|