725 lines
21 KiB
C#
725 lines
21 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.IO.Ports;
|
|
|
|
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
|
|
SafeRaiseOnPrint(csLog.trx_data_print(_readBuffer, _readPosition, 1));
|
|
//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
|
|
|
|
#region SAFE CALLS
|
|
private void SafeRaiseOnPrint(string msg)
|
|
{
|
|
var handler = OnPrint; // 복사
|
|
if (handler == null) return;
|
|
|
|
// 비차단 + 예외격리: 핸들러를 ThreadPool에서 실행
|
|
Task.Run(() =>
|
|
{
|
|
try { handler.Invoke(this, msg); }
|
|
catch (Exception ex) { /* 내부 로깅 */ }
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |