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; namespace LFP_Manager.Threads { public sealed class CsRs232Thread124050 : IDisposable { #region ENUMS private enum CommandType { NoCommand = 0, WriteParam = 1, WriteCoil = 2, WriteRegister = 3, ReadRegister = 4 } private enum AddressChannel { Channel0 = 0, Channel1 = 1, Channel2 = 2, Channel3 = 3 } #endregion #region CONSTANTS private static class Constants { public const int BUFFER_SIZE = 512; public const int DEFAULT_BAUD_RATE = 115200; public const int MAX_TIMEOUT_COUNT = 5; public const int DEFAULT_DATA_BITS = 8; public const int POLLING_INTERVAL = 100; public const int ERROR_RETRY_DELAY = 1000; } #endregion #region PRIVATE FIELDS private readonly object _lockObject = new object(); private readonly CommConfig _config; private DeviceSystemData _systemData; private readonly CancellationTokenSource _cancellationTokenSource; private volatile bool _isRunning; private volatile bool _isDisposed; private SerialPort _serialPort; private Thread _commThread; private readonly byte[] _receiveBuffer; private readonly byte[] _readBuffer; private int _bufferStart; private int _bufferEnd; private ushort _readPosition; private bool _bufferStarted; private readonly TUartTxBuff _uartTxBuff; private int _systemId; private bool _uartPolling; private bool _autoUartTx; private AddressChannel _currentChannel; private ushort _requestRegAddr; private ushort _requestRegLen; private bool _responseFlag; private int _responseType; private CommandType _currentCommand; private int _timeoutCount; private ushort _writeRegAddr; private short _writeCoilRegData; private byte[] _writeRegData; #endregion #region EVENTS public event UartDataUpdate OnUpdate; public event UartDataRecv OnDataRecv; public event UartDataPrint OnPrint; #endregion #region CONSTRUCTORS public CsRs232Thread124050(int systemId, CommConfig config, ref DeviceSystemData systemData) { if (config == null) throw new ArgumentNullException(nameof(config)); _systemId = systemId; _config = config; _systemData = systemData; _cancellationTokenSource = new CancellationTokenSource(); _receiveBuffer = new byte[Constants.BUFFER_SIZE]; _readBuffer = new byte[Constants.BUFFER_SIZE]; _uartTxBuff = new TUartTxBuff(); _autoUartTx = true; InitializeSerialPort(); } private void InitializeSerialPort() { try { _serialPort = new SerialPort(); _serialPort.BaudRate = Constants.DEFAULT_BAUD_RATE; _serialPort.DataBits = Constants.DEFAULT_DATA_BITS; _serialPort.StopBits = StopBits.One; _serialPort.Parity = Parity.None; _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; _serialPort.DataReceived += SerialPort_DataReceived; _serialPort.ErrorReceived += SerialPort_ErrorReceived; _serialPort.PinChanged += SerialPort_PinChanged; } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Failed to initialize serial port: {0}", ex.Message)); } throw; } } #endregion #region PUBLIC METHODS public bool Start(ref CommConfig config, int systemId, bool polling) { ThrowIfDisposed(); lock (_lockObject) { if (_isRunning) return false; _systemId = systemId; _uartPolling = polling; try { if (OpenPort(config.UartPort)) { _isRunning = true; _commThread = new Thread(CommThreadProcess); _commThread.IsBackground = true; _commThread.Name = "UART_Communication_Thread"; _commThread.Priority = ThreadPriority.AboveNormal; _commThread.Start(); return true; } } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Failed to start UART thread: {0}", ex.Message)); } } return false; } } public void Stop() { if (_isDisposed) return; lock (_lockObject) { _isRunning = false; if (_cancellationTokenSource != null) { _cancellationTokenSource.Cancel(); } ClosePort(); } } public void Dispose() { if (_isDisposed) return; Stop(); lock (_lockObject) { if (_cancellationTokenSource != null) { _cancellationTokenSource.Dispose(); } if (_serialPort != null) { _serialPort.Dispose(); } _isDisposed = true; } GC.SuppressFinalize(this); } public void SetPolling(bool flag, int systemId, ref DeviceSystemData systemData) { ThrowIfDisposed(); lock (_lockObject) { _systemId = systemId; _uartPolling = flag; _systemData = systemData; _systemData.mNo = systemId; } } public void SetAutoTx(bool autoTx) { ThrowIfDisposed(); lock (_lockObject) { _autoUartTx = autoTx; } } public void SetWriteCoilReg(ushort writeAddr, short writeData) { ThrowIfDisposed(); lock (_lockObject) { _currentCommand = CommandType.WriteCoil; _writeRegAddr = writeAddr; _writeCoilRegData = writeData; Thread.Sleep(500); } } public void SetReadReg(ushort readAddr, ushort readLen, bool replyFlag) { ThrowIfDisposed(); lock (_lockObject) { _currentCommand = CommandType.ReadRegister; _requestRegAddr = readAddr; _requestRegLen = readLen; _responseFlag = replyFlag; } } public void SetWriteReg(ushort writeAddr, short[] writeData) { ThrowIfDisposed(); if (writeData == null) throw new ArgumentNullException(nameof(writeData)); lock (_lockObject) { _currentCommand = CommandType.WriteParam; _writeRegAddr = writeAddr; TUartTRxData uartTRxData = new TUartTRxData(); uartTRxData.type = (int)CommandType.WriteParam; uartTRxData.data = csSerialCommFunction.MakeWriteRegisterData((ushort)_systemId, writeAddr, writeData); uartTRxData.length = uartTRxData.data.Length; if (_uartTxBuff != null) { _uartTxBuff.PutBuff(uartTRxData); } } } #endregion #region PRIVATE METHODS private void ThrowIfDisposed() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); } private bool OpenPort(string portName) { try { if (string.IsNullOrEmpty(portName)) throw new ArgumentException("Port name cannot be null or empty", "portName"); if (_serialPort.IsOpen) _serialPort.Close(); _serialPort.PortName = portName; _serialPort.Open(); return true; } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Failed to open port {0}: {1}", portName, ex.Message)); } return false; } } private void ClosePort() { try { if (_serialPort != null && _serialPort.IsOpen) _serialPort.Close(); } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Error closing port: {0}", ex.Message)); } } } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (!_serialPort.IsOpen) return; int bytesToRead = _serialPort.BytesToRead; if (bytesToRead == 0) return; byte[] buffer = new byte[bytesToRead]; int bytesRead = _serialPort.Read(buffer, 0, bytesToRead); lock (_lockObject) { for (int i = 0; i < bytesRead; i++) { AddToBuffer(buffer[i]); } } } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Error in data receive: {0}", ex.Message)); } } } private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e) { if (OnPrint != null) { OnPrint(this, string.Format("Serial port error: {0}", e.EventType)); } } private void SerialPort_PinChanged(object sender, SerialPinChangedEventArgs e) { if (OnPrint != null) { OnPrint(this, string.Format("Serial pin changed: {0}", e.EventType)); } } private void AddToBuffer(byte data) { _receiveBuffer[_bufferStart++] = data; _bufferStart %= Constants.BUFFER_SIZE; if (_bufferStart == _bufferEnd) { _bufferEnd = (_bufferEnd + 1) % Constants.BUFFER_SIZE; } } private int GetFromBuffer() { if (_bufferStart == _bufferEnd) return 0; int result = 0x0100 + _receiveBuffer[_bufferEnd++]; _bufferEnd %= Constants.BUFFER_SIZE; return result; } private void FlushBuffer() { _bufferStart = _bufferEnd = 0; } private void ProcessReceivedData() { if (_readPosition >= Constants.BUFFER_SIZE) { ResetReceiveState(); return; } if (!_bufferStarted) { int data = GetFromBuffer(); if ((data & 0xFF) == _systemId || (data & 0xFF) == 0x7F) { _readPosition = 0; _readBuffer[_readPosition++] = (byte)(data & 0xFF); _bufferStarted = true; } return; } int receivedByte = GetFromBuffer(); if ((receivedByte & 0x0100) == 0) return; _readBuffer[_readPosition++] = (byte)(receivedByte & 0xFF); ProcessModbusResponse(); } private void ProcessModbusResponse() { int result = csSerialCommFunction.ModbusPacketFromSlaveCheck(_readBuffer, _readPosition); switch (result) { case 0: // Need more data return; case 1: // Valid packet if (OnPrint != null) { OnPrint(this, csLog.trx_data_print(_readBuffer, _readPosition, 1)); } _timeoutCount = 0; ProcessValidResponse(); break; case 2: // Firmware update packet if (OnPrint != null) { OnPrint(this, csLog.trx_data_print(_readBuffer, _readPosition, 1)); } _timeoutCount = 0; ProcessFirmwareResponse(); break; case -1: // Error packet ResetReceiveState(); break; } } private void ProcessValidResponse() { try { int moduleId = _readBuffer[0]; if (moduleId > 0) moduleId--; _systemData.CommFail = false; _systemData.ShelfCommFail = false; if (_responseType == 0) { short[] resultCode = csSerialCommFunction124050.SerialRxProcess(_readBuffer, _requestRegAddr, _readPosition, ref _systemData); if (OnUpdate != null) { OnUpdate(this, ref _systemData); } } else { _responseType = 0; if (OnDataRecv != null) { byte[] responseData = new byte[_readPosition]; Array.Copy(_readBuffer, responseData, _readPosition); OnDataRecv(responseData); } } } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Error processing valid response: {0}", ex.Message)); } } finally { ResetReceiveState(); } } private void ProcessFirmwareResponse() { try { if (OnDataRecv != null) { byte[] responseData = new byte[_readPosition]; Array.Copy(_readBuffer, responseData, _readPosition); OnDataRecv(responseData); } } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Error processing firmware response: {0}", ex.Message)); } } finally { ResetReceiveState(); } } private void ResetReceiveState() { _bufferStarted = false; _readPosition = 0; _responseFlag = false; } private void CommThreadProcess() { while (_isRunning && !_cancellationTokenSource.Token.IsCancellationRequested) { try { ProcessCommunication(); Thread.Sleep(Constants.POLLING_INTERVAL); } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Communication thread error: {0}", ex.Message)); } Thread.Sleep(Constants.ERROR_RETRY_DELAY); } } } private void ProcessCommunication() { if (_serialPort == null || !_serialPort.IsOpen) return; bool writeMode = false; byte[] txData = PrepareTransmitData(ref writeMode); if (txData != null) { SendData(txData); if (_responseFlag) { ProcessReceivedData(); } } } private byte[] PrepareTransmitData(ref bool writeMode) { byte[] txData = null; _responseType = 0; if (_uartTxBuff != null && _uartTxBuff.CheckBuff()) { TUartTRxData txBuffer = _uartTxBuff.GetBuff(); if (txBuffer != null) { txData = txBuffer.data; writeMode = true; _responseFlag = true; _responseType = txBuffer.type; } } else if (_currentCommand == CommandType.WriteCoil) { txData = csSerialCommFunction.MakeWriteCoilData((ushort)_systemId, _writeRegAddr, _writeCoilRegData); _currentCommand = CommandType.NoCommand; _responseFlag = true; } else if (_currentCommand == CommandType.ReadRegister) { txData = csSerialCommFunction.MakeReadRegisterData((ushort)_systemId, csSerialCommFunction.READ_HOLDING_REG, _requestRegAddr, _requestRegLen); _currentCommand = CommandType.NoCommand; _responseFlag = true; } else if (_uartPolling && _autoUartTx) { txData = PreparePollingData(); } return txData; } private byte[] PreparePollingData() { ushort command; switch (_currentChannel) { case AddressChannel.Channel0: _currentChannel = AddressChannel.Channel1; _requestRegAddr = 0x0000; _requestRegLen = 0x0040; command = csSerialCommFunction.READ_HOLDING_REG; _responseFlag = true; break; case AddressChannel.Channel1: _currentChannel = AddressChannel.Channel2; _requestRegAddr = 0x0040; _requestRegLen = 0x0040; command = csSerialCommFunction.READ_HOLDING_REG; _responseFlag = true; break; case AddressChannel.Channel2: _currentChannel = AddressChannel.Channel3; _requestRegAddr = 0x0080; _requestRegLen = 0x0040; command = csSerialCommFunction.READ_HOLDING_REG; _responseFlag = true; break; case AddressChannel.Channel3: if (_config.ModuleQty > 1) { _systemId++; if (_systemId > _config.ModuleQty) { _systemId = 1; } } _currentChannel = AddressChannel.Channel0; command = csSerialCommFunction.NO_CMD; _responseFlag = false; break; default: _currentChannel = AddressChannel.Channel0; _requestRegAddr = 0x0000; _requestRegLen = 0x0040; command = csSerialCommFunction.READ_HOLDING_REG; break; } return command == csSerialCommFunction.NO_CMD ? null : csSerialCommFunction.MakeReadRegisterData((ushort)_systemId, command, _requestRegAddr, _requestRegLen); } private void SendData(byte[] data) { try { if (data == null) throw new ArgumentNullException("data"); FlushBuffer(); _serialPort.Write(data, 0, data.Length); if (OnPrint != null) { OnPrint(this, csLog.trx_data_print(data, data.Length, 0)); } } catch (Exception ex) { if (OnPrint != null) { OnPrint(this, string.Format("Send data error: {0}", ex.Message)); } throw; } } #endregion } }