using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
namespace MesWork
{
///
/// 单把扫码枪的串口通信封装
/// 包含数据接收(缓冲+空闲提交)、防抖、断线自动重连
///
public class BarcodeScanner : IDisposable
{
// ── 基本属性 ──
/// 关联工位号(OP10/OP20)
public string OpName { get; }
/// 当前COM口
public string ComPort { get; private set; }
/// 是否已连接
public bool IsConnected
{
get { try { return _serial != null && _serial.IsOpen; } catch { return false; } }
}
/// 最近一次扫到的条码
public string LastBarcode { get; private set; } = "";
/// 最近一次扫码时间
public DateTime? LastScanTime { get; private set; }
// ── 事件 ──
/// 扫码完成事件(opName, barcode)
public event Action BarcodeReceived;
/// 日志事件(message),由外部接入系统日志
public event Action Log;
// ── 私有字段 ──
private SerialPort _serial;
private readonly int _baudRate;
private readonly object _bufferLock = new object();
private readonly StringBuilder _buffer = new StringBuilder(256);
private System.Windows.Forms.Timer _flushTimer;
private const int FlushIntervalMs = 60;
// 防抖
private string _lastDebounceCode = "";
private DateTime _lastDebounceTime = DateTime.MinValue;
private const int DebounceMs = 500;
// 断线重连
private System.Windows.Forms.Timer _reconnectTimer;
private volatile bool _reconnecting = false;
private bool _disposed = false;
public BarcodeScanner(string opName, int baudRate = 9600)
{
OpName = opName;
_baudRate = baudRate;
}
///
/// 连接指定COM口
///
public bool Connect(string comPort)
{
if (string.IsNullOrWhiteSpace(comPort) || comPort.Length < 3)
return false;
try
{
Disconnect();
ComPort = comPort;
_serial = new SerialPort
{
PortName = comPort,
BaudRate = _baudRate,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 1000,
DtrEnable = true
};
_serial.DataReceived += Serial_DataReceived;
_serial.Open();
// 空闲提交定时器
if (_flushTimer == null)
{
_flushTimer = new System.Windows.Forms.Timer { Interval = FlushIntervalMs };
_flushTimer.Tick += FlushTimer_Tick;
}
// 断线重连定时器(每3秒检测)
if (_reconnectTimer == null)
{
_reconnectTimer = new System.Windows.Forms.Timer { Interval = 3000 };
_reconnectTimer.Tick += ReconnectTimer_Tick;
}
_reconnectTimer.Start();
Log?.Invoke($"[{OpName}] 扫码枪已连接 ({comPort})");
return true;
}
catch (Exception ex)
{
Log?.Invoke($"[{OpName}] 扫码枪连接失败 ({comPort}):{ex.Message}");
return false;
}
}
///
/// 断开连接
///
public void Disconnect()
{
try
{
_reconnectTimer?.Stop();
_flushTimer?.Stop();
if (_serial != null)
{
_serial.DataReceived -= Serial_DataReceived;
if (_serial.IsOpen) _serial.Close();
_serial.Dispose();
_serial = null;
}
}
catch { }
}
///
/// 测试当前COM口是否可连接
///
public bool TestConnection(string comPort)
{
try
{
using (var sp = new SerialPort(comPort, _baudRate))
{
sp.Open();
sp.Close();
return true;
}
}
catch { return false; }
}
// ── 串口数据接收 ──
private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (_serial == null || !_serial.IsOpen) return;
int bytesToRead = _serial.BytesToRead;
if (bytesToRead <= 0) return;
byte[] buf = new byte[bytesToRead];
_serial.Read(buf, 0, bytesToRead);
string chunk = Encoding.UTF8.GetString(buf, 0, bytesToRead);
lock (_bufferLock)
{
_buffer.Append(chunk);
string acc = _buffer.ToString();
// 按换行符(\r \n)拆分完整条码
int startIndex = 0;
for (int i = 0; i < acc.Length; i++)
{
if (acc[i] == '\r' || acc[i] == '\n')
{
int len = i - startIndex;
if (len > 0)
{
string line = acc.Substring(startIndex, len).Trim();
if (!string.IsNullOrEmpty(line))
SubmitBarcode(line);
}
startIndex = i + 1;
}
}
if (startIndex >= acc.Length)
_buffer.Clear();
else if (startIndex > 0)
{
_buffer.Clear();
_buffer.Append(acc.Substring(startIndex));
}
// 无换行 → 启动空闲定时器
if (_buffer.Length > 0)
{
try
{
_flushTimer?.Stop();
_flushTimer?.Start();
}
catch { }
}
}
}
catch (Exception ex)
{
if (ex is System.IO.IOException || ex is InvalidOperationException)
{
Log?.Invoke($"[{OpName}] 扫码枪通信异常,尝试重连:{ex.Message}");
TryReopen();
}
}
finally
{
try { _serial?.DiscardInBuffer(); } catch { }
}
}
// 空闲超时提交缓冲区
private void FlushTimer_Tick(object sender, EventArgs e)
{
_flushTimer.Stop();
string toSend;
lock (_bufferLock)
{
toSend = _buffer.ToString().Trim();
_buffer.Clear();
}
if (!string.IsNullOrEmpty(toSend))
SubmitBarcode(toSend);
}
// 提交条码(含防抖)
private void SubmitBarcode(string barcode)
{
barcode = barcode.Replace("\r", "").Replace("\n", "").Replace("\0", "").Trim();
if (string.IsNullOrEmpty(barcode)) return;
// 防抖
var now = DateTime.Now;
if (barcode == _lastDebounceCode && (now - _lastDebounceTime).TotalMilliseconds < DebounceMs)
return;
_lastDebounceCode = barcode;
_lastDebounceTime = now;
// 更新状态
LastBarcode = barcode;
LastScanTime = now;
// 触发事件
BarcodeReceived?.Invoke(OpName, barcode);
}
///
/// 手动发送条码(模拟扫码触发,走完整业务流程)
///
public void ManualSend(string barcode)
{
if (!string.IsNullOrWhiteSpace(barcode))
SubmitBarcode(barcode.Trim());
}
// ── 断线重连 ──
private void ReconnectTimer_Tick(object sender, EventArgs e)
{
if (_reconnecting || string.IsNullOrWhiteSpace(ComPort)) return;
if (IsConnected) return;
_reconnecting = true;
Task.Run(() =>
{
try { TryReopen(); }
catch { }
finally { _reconnecting = false; }
});
}
private void TryReopen()
{
try
{
string[] ports = SerialPort.GetPortNames();
bool exists = Array.Exists(ports, p => string.Equals(p, ComPort, StringComparison.OrdinalIgnoreCase));
if (!exists) return;
if (_serial != null)
{
try { if (_serial.IsOpen) _serial.Close(); } catch { }
try { _serial.Dispose(); } catch { }
}
_serial = new SerialPort
{
PortName = ComPort,
BaudRate = _baudRate,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 1000,
DtrEnable = true
};
_serial.DataReceived += Serial_DataReceived;
_serial.Open();
Log?.Invoke($"[{OpName}] 扫码枪自动重连成功 ({ComPort})");
}
catch (Exception ex)
{
Log?.Invoke($"[{OpName}] 扫码枪自动重连失败 ({ComPort}):{ex.Message}");
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Disconnect();
_flushTimer?.Dispose();
_reconnectTimer?.Dispose();
}
}
}