using FATrace.Com; using HslCommunication; using HslCommunication.Profinet.Keyence; using NLog; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FATrace.OEMApp.Services { public class PLCDataService { /// /// 日志 /// private Logger Logger { get; set; } = LogManager.GetCurrentClassLogger(); public PLCDataService() { PLCLinkInitial(); StartPlcScan(); PadCodeAddress= ConfigHelper.GetStringOrDefault("PDAScanCode", "D1050"); } /// /// PAD地址 /// private string PadCodeAddress { get; set; } /// /// 扫码事件 /// public event EventHandler ScanCodeEventHandler; /// /// PLC连接状态事件 /// public event EventHandler PlcConnectedEventHandler; /// /// 基恩士通信组件 /// private KeyenceMcNet KeyencePlcMcNet { get; set; } = null; private bool _PlcConnected; /// /// PLC连接状态 /// public bool PlcConnected { get { return _PlcConnected; } set { if (_PlcConnected != value) { if (value) { if (PlcConnectedEventHandler != null) PlcConnectedEventHandler.Invoke(this, "OK"); } else { if (PlcConnectedEventHandler != null) PlcConnectedEventHandler.Invoke(this, "NG"); } _PlcConnected = value; } } } private string _ScanCode = string.Empty; /// /// 扫描结果 /// public string ScanCode { get { return _ScanCode; } set { if (_ScanCode != value) { if (!string.IsNullOrEmpty(value)) { if (ScanCodeEventHandler != null) ScanCodeEventHandler.Invoke(this, value); } else { Logger.Error($"PLC条码数据为空: 数据改变但是数据为空"); } _ScanCode = value; } } } /// /// 扫描结果 /// private OperateResult OperateResultPDAScanCode { get; set; } // 扫描控制 private CancellationTokenSource? _plcScanCts; private Task? _plcScanTask; private volatile bool _isScanning = false; public bool IsScanning => _isScanning; private bool _disposed = false; /// /// PLC通信初始化 /// /// /// private void PLCLinkInitial() { try { // 读取 PLC 配置(大小写敏感,采用公共 ConfigHelper 并提供默认值) string PLCIP = ConfigHelper.GetStringOrDefault("PLCIP", "192.0.1.10"); int Port = ConfigHelper.GetIntOrDefault("PLCPort", 5000); //PLC通信的连接 KeyencePlcMcNet = new KeyenceMcNet(); KeyencePlcMcNet.IpAddress = PLCIP; KeyencePlcMcNet.Port = Port; KeyencePlcMcNet.ConnectClose(); KeyencePlcMcNet.ConnectTimeOut = 3000; // 连接3秒超时 OperateResult connect = KeyencePlcMcNet.ConnectServer(); if (connect.IsSuccess)//初始连接状态的显示判断 { Console.WriteLine($"PLC-连接 OK"); PlcConnected = true; } else { MessageBox.Show(connect.Message + Environment.NewLine + "ErrorCode: " + connect.ErrorCode); PlcConnected = false; } } catch (Exception ex) { Logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString())); //insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null); PlcConnected = false; } } /// /// 启动PLC扫描。若已在扫描则忽略重复启动。 /// /// 扫描周期,若为 null 则读取 App.config 的 PLCScan(毫秒),默认600ms /// 可选:外部取消令牌,用于联动取消 public void StartPlcScan(TimeSpan? scanPeriod = null, CancellationToken externalToken = default) { if (_isScanning) return; var period = scanPeriod ?? ConfigHelper.GetTimeSpanOrDefault("PLCScan", TimeSpan.FromMilliseconds(600)); _plcScanCts = CancellationTokenSource.CreateLinkedTokenSource(externalToken); var token = _plcScanCts.Token; _isScanning = true; _plcScanTask = Task.Run(() => PlcScanLoopAsync(period, token), token); } /// /// 停止PLC扫描,等待后台任务结束。 /// public async Task StopPlcScanAsync() { try { if (_plcScanCts != null) { _plcScanCts.Cancel(); } if (_plcScanTask != null) { try { await _plcScanTask.ConfigureAwait(false); } catch { /* ignore */ } } } finally { _isScanning = false; _plcScanTask = null; _plcScanCts?.Dispose(); _plcScanCts = null; } } /// /// 扫描循环:按周期轮询PLC寄存器并更新模型。 /// - 读取配置项 Addr_WeightPhotoEnable(默认 M100)为布尔量 /// - 读失败时尝试重连一次 /// private async Task PlcScanLoopAsync(TimeSpan period, CancellationToken token) { string addrWeight = ConfigHelper.GetStringOrDefault("Addr_WeightPhotoEnable", "M100"); bool? lastWeight = null; while (!token.IsCancellationRequested) { try { if (KeyencePlcMcNet == null) { PLCLinkInitial(); } //内包扫码 OperateResultPDAScanCode = KeyencePlcMcNet!.ReadString(PadCodeAddress, 40); if (OperateResultPDAScanCode.IsSuccess) { //ScanCode = RevData(OperateResultPDAScanCode.Content).Replace("\r", "").Replace("\n", "").Replace("\0", "").Trim(); ScanCode = (OperateResultPDAScanCode.Content).Replace("\r", "").Replace("\n", "").Replace("\0", "").Trim(); PlcConnected = true; } else { PlcConnected = false; } } catch (OperationCanceledException) { break; } catch (Exception ex) { Logger.Error($"PLC 扫描异常: {ex}"); } finally { try { await Task.Delay(period, token).ConfigureAwait(false); } catch (OperationCanceledException) { // 不允许从 finally 中 break/return, // 这里吞掉异常,循环将在下一次 while 判断时依据 token 自动退出 } } } _isScanning = false; } /// /// 尝试重连 PLC(快速一次) /// private async Task TryReconnectAsync(CancellationToken token) { try { if (KeyencePlcMcNet == null) { PLCLinkInitial(); return; } KeyencePlcMcNet.ConnectClose(); await Task.Delay(100, token).ConfigureAwait(false); KeyencePlcMcNet.ConnectTimeOut = 3000; var ret = KeyencePlcMcNet.ConnectServer(); if (!ret.IsSuccess) { Logger.Warn($"PLC重连失败: {ret.Message} (Code: {ret.ErrorCode})"); PlcConnected = false; } else { PlcConnected = true; } } catch (Exception ex) { Logger.Error($"PLC重连异常: {ex.Message}"); PlcConnected = false; } } /// /// 释放资源,确保扫描停止并断开PLC连接。 /// public void Dispose() { if (_disposed) return; _disposed = true; try { // 停止扫描 try { StopPlcScanAsync().GetAwaiter().GetResult(); } catch { } // 解除事件订阅(当前未订阅自定义事件,保留占位以便后续扩展) // try { /* no event to unsubscribe */ } catch { } // 关闭PLC连接 try { KeyencePlcMcNet?.ConnectClose(); } catch { } } finally { KeyencePlcMcNet = null; } } /// /// 把字符串对调 /// public string RevData(string Code) { // 需求:PLC 字符串按“字”(2字节)进行高低字节对调,导致 ABCDEF -> BADCFE // 纠正方式:按两个字符一组交换顺序,奇数长度保留最后一个字符 if (string.IsNullOrEmpty(Code)) return string.Empty; // 去掉读取中可能包含的 '\0' 空字符 var src = Code.Replace("\0", string.Empty); var sb = new System.Text.StringBuilder(src.Length); for (int i = 0; i < src.Length; i += 2) { if (i + 1 < src.Length) { sb.Append(src[i + 1]); sb.Append(src[i]); } else { // 奇数长度,最后一个字符原样保留 //sb.Append((char)0x0); sb.Append('\0'); sb.Append(src[i]); } } return sb.ToString(); } } }