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 _rxQ = new ConcurrentQueue(); private TUartTxBuff UartTxBuff; // 이벤트 public event EventHandler OnUpdate; public event EventHandler OnDataRecv; public event EventHandler 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[] MakeWriteRegData(ushort DevID, ushort WriteAddr, ushort WriteLen, ref ushort[] WriteData) { byte[] wData = null; if (WriteLen > 0) { } 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[] 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_MS = Config.CommFailTime; // 5초 동안 수신 없으면 Fail DateTime[] LastReceiveTime = new DateTime[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; ModuleData[mID].CommFail = false; 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); // 아직 한 번도 정상 수신한 적 없는 경우 MinValue일 수 있으므로, // 첫 타임아웃에서는 기준 시간을 현재로 초기화해 준다. if (LastReceiveTime[idx] == DateTime.MinValue) LastReceiveTime[idx] = DateTime.UtcNow; // 시간 차이 계산 var elapsed = (DateTime.Now - LastReceiveTime[idx]).TotalMilliseconds; if (elapsed >= COMM_TIMEOUT_MS) { // Fail 처리 if (!ModuleData[idx].ShelfCommFail) { ModuleData[idx].Reset(); ModuleData[idx].ShelfCommFail = true; } ModuleData[idx].CommFail = true; 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 } }