using FATrace.Model; using FATrace.WPLApp.Models; using HslCommunication; using HslCommunication.Profinet.Keyence; using Prism.Mvvm; using System.Collections.Concurrent; using System.Windows; using ComConfigHelper = FATrace.Com.ConfigHelper; using Prism.Events; using FATrace.WPLApp.Events; namespace FATrace.WPLApp.Services { /// /// 数据服务 /// 产线PLC数据交互的服务 /// public class DataServices : BindableBase, IDisposable { public DataServices(ILogService logService, IFreeSql freeSql, IEventAggregator eventAggregator, SysRunService sysRunService) { LogService = logService; FreeSql = freeSql; EventAggregator = eventAggregator; SysRunService = sysRunService; LineSglModel = new LineSglModel(); LineSglModel.WeightScanCodeHandle += LineSglModel_WeightScanCodeHandle; LineSglModel.BoxSprayCodeReqHandle += LineSglModel_BoxSprayCodeReqHandle; LineSglModel.BoxScanCodeReqHandle += LineSglModel_BoxScanCodeReqHandle; PLCLinkInitial(); StartPlcScan(); //var DD= RevData("DYG05030013,20250923,802,3,01,0001,"); } /// ///外箱喷码请求 /// /// /// private void LineSglModel_BoxSprayCodeReqHandle(object? sender, string e) { //首先复位PLC信号 KeyencePlcMcNet!.Write("D1100", (Int16)0); //获取箱子喷码的数据+A var ReqData = FreeSql.Select() .OrderBy(a => a.Id) .Limit(1) .ToList(); if (ReqData.Count > 0) { var CodeItem = ReqData.FirstOrDefault(); KeyencePlcMcNet.Write("D1150", RevData(CodeItem!.Code + ",A")); Console.WriteLine($"外箱喷码:{CodeItem.Code}-发送OK"); //这里需要删除数据吗?还是等扫码后删除,会不会过来新的箱子 } else { //不存在请求的数据,报错 KeyencePlcMcNet.Write("D1102", (Int16)1); Console.WriteLine($"外箱喷码:不存在请求的数据"); } Task.Run(async () => { await Task.Delay(1000).ConfigureAwait(false); KeyencePlcMcNet!.Write("D1102", (Int16)0); }); } /// /// 箱子扫描码请求 /// /// /// /// private void LineSglModel_BoxScanCodeReqHandle(object? sender, string e) { //首先复位PLC信号 KeyencePlcMcNet!.Write("D1200", (Int16)0); if (!string.IsNullOrEmpty(BoxScanCode)) { //var IsExist = FreeSql.Select().Where(a => a.BoxCode == BoxScanCode); var IsExist = FreeSql.Select().Where(a => a.BoxCode!.Contains(BoxScanCode));// 内包和外包一样的条码测试用 ??????????????? if (IsExist.Count() > 0) { //存在条码数据 OK,返回PLC结果 KeyencePlcMcNet!.Write("D1210", (Int16)1); Console.WriteLine($"外箱扫描码:{BoxScanCode}-存在找到"); //// 加入队列 //EnqueueMessage(WeightScanCode); LogService.Info($"外箱扫描码:{BoxScanCode}"); //删除临时的队列条码数据 var DeleteResult = FreeSql.Delete() .Where(p => BoxScanCode.Contains(p.Code!)) .ExecuteAffrows(); if (DeleteResult > 0) { Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功"); LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功"); } else { Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败"); LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败"); } var Result = FreeSql.Update() .Set(p => p.OutTime, DateTime.Now) .Where(p => p.BoxCode == BoxScanCode + ",A")//外箱二维码就是外箱扫描码 .ExecuteAffrows(); if (Result > 0) { Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间成功"); LogService.Info($"外箱扫描码:{WeightScanCode}更新时间成功"); } else { Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间失败"); LogService.Warn($"外箱扫描码:{WeightScanCode}更新时间失败"); } } else { //不存在条码数据 NG,返回PLC结果报警 KeyencePlcMcNet!.Write("D1210", (Int16)2); Console.WriteLine($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中"); LogService.Warn($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中"); } } else { Console.WriteLine($"外箱扫描码为空"); LogService.Warn("外箱扫描码为空"); } Task.Run(async () => { await Task.Delay(1000).ConfigureAwait(false); KeyencePlcMcNet!.Write("D1210", (Int16)0); KeyencePlcMcNet.Write("D1250", new Int16[30]); }); try { EventAggregator?.GetEvent()?.Publish(true); } catch { } } /// /// 称重扫描码请求数据 /// /// /// /// private void LineSglModel_WeightScanCodeHandle(object? sender, string e) { //首先复位PLC信号 KeyencePlcMcNet!.Write("D1000", (Int16)0); if (!string.IsNullOrEmpty(WeightScanCode)) { var IsExist = FreeSql.Select().Where(a => a.InBagCode == WeightScanCode); if (IsExist.Count() > 0) { //存在条码数据 OK,返回PLC结果 KeyencePlcMcNet!.Write("D1010", (Int16)1); Console.WriteLine($"称重扫描码:{WeightScanCode}"); //// 加入队列 //EnqueueMessage(WeightScanCode); LogService.Info($"称重扫描码:{WeightScanCode}"); var TempData = FreeSql.Insert(new LineTempCode() { Code = WeightScanCode }).ExecuteAffrows(); var Result = FreeSql.Update() .Set(p => p.WeightScanTime, DateTime.Now) .Where(p => p.InBagCode == WeightScanCode)//内箱二维码就是称重扫描码 .ExecuteAffrows(); if (Result > 0) { Console.WriteLine($"称重扫描码:{WeightScanCode}更新时间成功"); LogService.Info($"称重扫描码:{WeightScanCode}更新时间成功"); } else { Console.WriteLine($"称重扫描码:{WeightScanCode}更新时间失败"); LogService.Warn($"称重扫描码:{WeightScanCode}更新时间失败"); } } else { //不存在条码数据 NG,返回PLC结果报警 KeyencePlcMcNet!.Write("D1010", (Int16)2); Console.WriteLine($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中"); LogService.Warn($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中"); } Task.Run(async () => { await Task.Delay(1000).ConfigureAwait(false); KeyencePlcMcNet!.Write("D1010", (Int16)0); KeyencePlcMcNet.Write("D1050", new Int16[30]); }); } else { Console.WriteLine($"称重扫描码为空"); LogService.Warn("称重扫描码为空"); } } public ILogService LogService { get; } public IFreeSql FreeSql { get; } public IEventAggregator EventAggregator { get; } public SysRunService SysRunService { get; } private bool _plcConnected; public bool PlcConnected { get { return _plcConnected; } private set { _plcConnected = value; RaisePropertyChanged(); } } /// /// 基恩士通信组件 /// private KeyenceMcNet KeyencePlcMcNet { get; set; } = null; private string _WeightScanCode; /// /// 称重扫描码 /// public string WeightScanCode { get { return _WeightScanCode; } set { _WeightScanCode = value; RaisePropertyChanged(); } } private string _BoxScanCode; /// /// 外箱喷码扫码数据 /// public string BoxScanCode { get { return _BoxScanCode; } set { _BoxScanCode = value; RaisePropertyChanged(); } } // 扫描控制 private CancellationTokenSource? _plcScanCts; private Task? _plcScanTask; private volatile bool _isScanning = false; public bool IsScanning => _isScanning; private bool _disposed = false; /// /// 简单字符串队列(FIFO) /// private readonly ConcurrentQueue _messageQueue = new(); /// /// 入队:将字符串加入 FIFO 队列(先进先出)。允许空字符串,不允许 null。 /// /// 要加入队列的字符串 /// 当 message 为 null 时抛出 public void EnqueueMessage(string message) { if (message is null) throw new ArgumentNullException(nameof(message)); _messageQueue.Enqueue(message); } /// /// 出队:尝试从 FIFO 队列取出一个字符串。 /// /// 输出参数:若成功则为取出的字符串;若失败(队列为空)则为 default /// 是否成功取出 public bool TryDequeueMessage(out string message) { return _messageQueue.TryDequeue(out message); } /// /// 产线信号模型 /// public LineSglModel LineSglModel { get; set; } /// /// PLC通信初始化 /// /// /// private void PLCLinkInitial() { try { // 读取 PLC 配置(大小写敏感,采用公共 ConfigHelper 并提供默认值) string PLCIP = ComConfigHelper.GetStringOrDefault("PLCIP", "192.0.1.10"); int Port = ComConfigHelper.GetIntOrDefault("PLCPort", 5000); try { SysRunService.PlcIp = PLCIP; SysRunService.PlcPort = Port; } catch { } //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; try { SysRunService.PlcLinkState = true; SysRunService.SysRunState.ComState = 1; SysRunService.SysRunState.ComMsg = "正常"; } catch { } } else { MessageBox.Show(connect.Message + Environment.NewLine + "ErrorCode: " + connect.ErrorCode); PlcConnected = false; try { SysRunService.PlcLinkState = false; SysRunService.SysRunState.ComState = 2; SysRunService.SysRunState.ComMsg = "连接失败"; } catch { } } } catch (Exception ex) { LogService.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; try { SysRunService.PlcLinkState = false; SysRunService.SysRunState.ComState = 2; SysRunService.SysRunState.ComMsg = "异常"; } catch { } } } /// /// 启动PLC扫描。若已在扫描则忽略重复启动。 /// /// 扫描周期,若为 null 则读取 App.config 的 PLCScan(毫秒),默认600ms /// 可选:外部取消令牌,用于联动取消 public void StartPlcScan(TimeSpan? scanPeriod = null, CancellationToken externalToken = default) { if (_isScanning) return; var period = scanPeriod ?? ComConfigHelper.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; } } /// /// 称重扫描条码OperateResult结果 /// private OperateResult OperateResultWeightScanCode { get; set; } /// /// 称重扫描条码OK信号 /// private OperateResult OperateResultWeightScanSgl { get; set; } /// /// 外箱喷码请求信号 /// private OperateResult OperateResultBoxSprayCodeReqSgl { get; set; } /// /// 外箱扫码条码OperateResult结果 /// private OperateResult OperateResultBoxScanCode { get; set; } /// /// 外箱扫码请求信号 /// private OperateResult OperateResultBoxScanSgl { get; set; } /// /// 扫描循环:按周期轮询PLC寄存器并更新模型。 /// - 读取配置项 Addr_WeightPhotoEnable(默认 M100)为布尔量 /// - 读失败时尝试重连一次 /// private async Task PlcScanLoopAsync(TimeSpan period, CancellationToken token) { string addrWeight = ComConfigHelper.GetStringOrDefault("Addr_WeightPhotoEnable", "M100"); bool? lastWeight = null; while (!token.IsCancellationRequested) { try { if (KeyencePlcMcNet == null) { PLCLinkInitial(); } //内包扫码 OperateResultWeightScanCode = KeyencePlcMcNet!.ReadString("D1050", 20); if (OperateResultWeightScanCode.IsSuccess) { WeightScanCode = RevData(OperateResultWeightScanCode.Content).Replace("\r", "").Replace("\n", ""); } OperateResultWeightScanSgl = KeyencePlcMcNet!.ReadInt16("D1000"); if (OperateResultWeightScanSgl.IsSuccess) { LineSglModel.WeightScanCodeEnable = OperateResultWeightScanSgl.Content; } //外箱喷码 OperateResultBoxSprayCodeReqSgl = KeyencePlcMcNet!.ReadInt16("D1100"); if (OperateResultBoxSprayCodeReqSgl.IsSuccess) { LineSglModel.BoxSprayCodeReqEnable = OperateResultBoxSprayCodeReqSgl.Content; } //外箱扫码 OperateResultBoxScanCode = KeyencePlcMcNet!.ReadString("D1250", 20); if (OperateResultBoxScanCode.IsSuccess) { BoxScanCode = RevData(OperateResultBoxScanCode.Content).Replace("\r", "").Replace("\n", ""); } OperateResultBoxScanSgl = KeyencePlcMcNet!.ReadInt16("D1200"); if (OperateResultBoxScanSgl.IsSuccess) { LineSglModel.BoxScanCodeEnable = OperateResultBoxScanSgl.Content; PlcConnected = true; try { SysRunService.PlcLinkState = true; SysRunService.SysRunState.ComState = 1; SysRunService.SysRunState.ComMsg = "正常"; } catch { } } else { PlcConnected = false; try { SysRunService.PlcLinkState = false; SysRunService.SysRunState.ComState = 2; SysRunService.SysRunState.ComMsg = "连接失败"; } catch { } } } catch (OperationCanceledException) { break; } catch (Exception ex) { LogService.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) { LogService.Warn($"PLC重连失败: {ret.Message} (Code: {ret.ErrorCode})"); PlcConnected = false; try { SysRunService.PlcLinkState = false; SysRunService.SysRunState.ComState = 2; SysRunService.SysRunState.ComMsg = "异常"; } catch { } } else { PlcConnected = true; try { SysRunService.PlcLinkState = true; SysRunService.SysRunState.ComState = 1; SysRunService.SysRunState.ComMsg = "正常"; } catch { } } } catch (Exception ex) { LogService.Error($"PLC重连异常: {ex.Message}"); PlcConnected = false; try { SysRunService.PlcLinkState = false; SysRunService.SysRunState.ComState = 2; SysRunService.SysRunState.ComMsg = "异常"; } catch { } } } /// /// 释放资源,确保扫描停止并断开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; } } /// /// 把字符串对调 /// private 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(src[i]); } } return sb.ToString(); } } }