Files
WC-CZGKJ/SCADA/Core/BarcodeScanner.cs
2026-05-11 11:09:04 +08:00

324 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
namespace MesWork
{
/// <summary>
/// 单把扫码枪的串口通信封装
/// 包含数据接收(缓冲+空闲提交)、防抖、断线自动重连
/// </summary>
public class BarcodeScanner : IDisposable
{
// ── 基本属性 ──
/// <summary>关联工位号OP10/OP20</summary>
public string OpName { get; }
/// <summary>当前COM口</summary>
public string ComPort { get; private set; }
/// <summary>是否已连接</summary>
public bool IsConnected
{
get { try { return _serial != null && _serial.IsOpen; } catch { return false; } }
}
/// <summary>最近一次扫到的条码</summary>
public string LastBarcode { get; private set; } = "";
/// <summary>最近一次扫码时间</summary>
public DateTime? LastScanTime { get; private set; }
// ── 事件 ──
/// <summary>扫码完成事件opName, barcode</summary>
public event Action<string, string> BarcodeReceived;
/// <summary>日志事件message由外部接入系统日志</summary>
public event Action<string> 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;
}
/// <summary>
/// 连接指定COM口
/// </summary>
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;
}
}
/// <summary>
/// 断开连接
/// </summary>
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 { }
}
/// <summary>
/// 测试当前COM口是否可连接
/// </summary>
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);
}
/// <summary>
/// 手动发送条码(模拟扫码触发,走完整业务流程)
/// </summary>
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();
}
}
}