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(); } } }