Files
2026-03-12 14:50:47 +01:00

309 lines
9.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Threading;
using FluentNHibernate.Conventions;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger;
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
{
public class SerialDriver : IDisposable, ISerialDriver
{
readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(SerialDriver));
public string ErrorMessage { get; private set; }
private List<byte> SerialPortReadBuffer = new List<byte>();
private SerialPort _serialPort;
private readonly List<byte> _binMessages = new List<byte>();
private bool _isReading;
// Stored configuration (used by Builder)
private readonly string _portName;
private readonly int _baudRate;
private readonly int _dataBits;
private readonly Parity _parity;
private readonly StopBits _stopBits;
private readonly int _readTimeout;
private readonly int _writeTimeout;
private readonly ManualResetEvent _responseReceived = new ManualResetEvent(false);
#region Constructors
// Default constructor (legacy support)
public SerialDriver()
{
_serialPort = new SerialPort();
}
// Builder constructor
internal SerialDriver(
string portName,
int baudRate,
int dataBits,
Parity parity,
StopBits stopBits,
int readTimeout,
int writeTimeout)
{
_portName = portName;
_baudRate = baudRate;
_dataBits = dataBits;
_parity = parity;
_stopBits = stopBits;
_readTimeout = readTimeout;
_writeTimeout = writeTimeout;
}
#endregion
#region Open / Close
// Builder-based open
public bool Open()
{
return OpenConnection(
_portName,
_baudRate,
_dataBits,
_parity,
_stopBits,
_readTimeout,
_writeTimeout
);
}
// Legacy API (unchanged)
public bool OpenConnection(
string comPort,
int baudrate,
int dataBits,
Parity parity,
StopBits stopbits,
int readTimeout = 1000,
int writeTimeout = 1000)
{
lock (this)
{
CloseConnection();
try
{
ErrorMessage = string.Empty;
_serialPort = new SerialPort(comPort, baudrate, parity, dataBits, stopbits)
{
ReadTimeout = readTimeout,
WriteTimeout = writeTimeout
};
_serialPort.DataReceived += DataReceivedHandler;
_serialPort.Open();
}
catch (Exception ex)
{
ErrorMessage = $"COM error: Open failed {comPort}. {ex.Message}";
return false;
}
if (!_serialPort.IsOpen)
{
ErrorMessage = $"COM error: Can't open {comPort}.";
return false;
}
log.Debug("SerialDriver opened successfully for port: " + comPort);
}
return true;
}
public void CloseConnection()
{
if (_serialPort != null)
{
_serialPort.DataReceived -= DataReceivedHandler;
if (_serialPort.IsOpen)
_serialPort.Close();
_serialPort.Dispose();
_serialPort = null;
}
}
public bool IsOpen() => _serialPort?.IsOpen == true;
#endregion
#region Send / Receive
public bool SendMessage(byte[] sendDataBytes, int length, int readTimeout = 1000, int writeTimeout = 1000)
{
if (!IsOpen()) return false;
if (sendDataBytes.Length == 0) return true;
try
{
PrepareReading();
_serialPort.WriteTimeout = writeTimeout;
_serialPort.ReadTimeout = readTimeout;
_serialPort.Write(sendDataBytes, 0, length);
_isReading = true;
var stopwatch = Stopwatch.StartNew();
while (_isReading)
{
if (stopwatch.ElapsedMilliseconds > readTimeout)
{
ErrorMessage = "COM error: Receive timeout";
return false;
}
}
}
catch (Exception ex)
{
ErrorMessage = $"COM error: Transmit failed {_serialPort.PortName}. {ex.Message}";
return false;
}
return true;
}
private void PrepareReading()
{
_serialPort.DiscardInBuffer();
_binMessages.Clear();
_responseReceived.Reset();
SerialPortReadBuffer.Clear();
_isReading = true;
}
public byte[] GetRawData()
{
return _binMessages.ToArray();
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
lock (this)
{
if (_serialPort == null || !_serialPort.IsOpen) return;
try
{
//Thread.Sleep(5);
if (!SerialPortReadBuffer.IsEmpty())
{
SerialPortReadBuffer.Clear();
}
int iWordCounter = 0;
bool isStart = false;
bool isQuestion = false;
int iLength = 0;
while (true)//_serialPort.BytesToRead > 0
{
byte readByte = (byte)_serialPort.ReadByte();
//I have START
if (readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.Start)
{
iWordCounter++;
isStart = true;
}
// I have QUESTION
if (readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.Question)
{
iWordCounter++;
isQuestion = true;
}
//I count length from start
if (iWordCounter > 0)
iWordCounter++;
if (iWordCounter > 0)
{
//Store byte to data
SerialPortReadBuffer.Add(readByte);
}
// we have length
if (iLength == 0 && isStart && SerialPortReadBuffer.Count > 2 )
{
iLength = (int)SerialPortReadBuffer[2];
}
//If we have enough bytes
if (isStart && iLength > 0
&& (SerialPortReadBuffer.Count >= iLength ||
readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.End
)
)
{
break;
}
//if we read END
if (isQuestion && readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.End)
{
break;
}
}
if (SerialPortReadBuffer.Count > 0)
{
_binMessages.AddRange(SerialPortReadBuffer.ToArray());
_responseReceived.Set();
}
}
catch (TimeoutException te)
{
// Ignore shutdown race conditions
}
finally
{
_isReading = false;
}
}
}
public byte[] SendAndWait(byte[] data, int timeoutMs)
{
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
log.Debug("SendAndWait() - TX: " + HexFormatter.ToHex(data));
PrepareReading();
_serialPort.Write(data, 0, data.Length);
if (!_responseReceived.WaitOne(timeoutMs))
{
log.Error("SendAndWait() - Response timeout! Details: " +
" SerialPortReadBuffer: " + HexFormatter.ToHex(SerialPortReadBuffer.ToArray()) +
" _binMessages" + HexFormatter.ToHex(_binMessages.ToArray()) +
"_responseReceived: " + _responseReceived.WaitOne(0)
);
ErrorMessage = "COM error: response timeout";
return null;
}
return GetRawData();
}
#endregion
public void Dispose()
{
CloseConnection();
}
public override string ToString()
{
return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen +")";
}
}
}