首次提交
This commit is contained in:
323
SCADA/Core/BarcodeScanner.cs
Normal file
323
SCADA/Core/BarcodeScanner.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user