From 9d60bb5fd11ec3153af67cd87af2d38436f06e19 Mon Sep 17 00:00:00 2001 From: quowingwang Date: Mon, 26 Jan 2026 19:43:14 +0800 Subject: [PATCH] =?UTF-8?q?PLC=E9=87=87=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PLC/Model/PlcProductionData.cs | 2 +- .../PLC/Service/PlcHostedService.cs | 1 - RIZO.Admin.WebApi/PLC/Service/PlcService.cs | 181 ++++++++++++++---- RIZO.Admin.WebApi/appsettings.json | 4 +- 4 files changed, 149 insertions(+), 39 deletions(-) diff --git a/RIZO.Admin.WebApi/PLC/Model/PlcProductionData.cs b/RIZO.Admin.WebApi/PLC/Model/PlcProductionData.cs index 4e1d870..6e84fc7 100644 --- a/RIZO.Admin.WebApi/PLC/Model/PlcProductionData.cs +++ b/RIZO.Admin.WebApi/PLC/Model/PlcProductionData.cs @@ -420,7 +420,7 @@ namespace RIZO.Admin.WebApi.PLC.Model /// /// 芯片 SN(PWM 条码) /// - [SugarColumn(ColumnName = "Screw7TightenTime")] + [SugarColumn(ColumnName = "ChipSN")] public string ChipSN { get; set; } /// diff --git a/RIZO.Admin.WebApi/PLC/Service/PlcHostedService.cs b/RIZO.Admin.WebApi/PLC/Service/PlcHostedService.cs index 9b96d96..a4953d2 100644 --- a/RIZO.Admin.WebApi/PLC/Service/PlcHostedService.cs +++ b/RIZO.Admin.WebApi/PLC/Service/PlcHostedService.cs @@ -123,7 +123,6 @@ namespace RIZO.Admin.WebApi.PLC.Service Rack = (short)it.Rack, // 空值兜底 Slot = (short)it.Slot // 空值兜底 }) - .Where(c => !string.IsNullOrWhiteSpace(c.Ip)) .ToList(); } catch (Exception ex) diff --git a/RIZO.Admin.WebApi/PLC/Service/PlcService.cs b/RIZO.Admin.WebApi/PLC/Service/PlcService.cs index dc5ad46..341a0f7 100644 --- a/RIZO.Admin.WebApi/PLC/Service/PlcService.cs +++ b/RIZO.Admin.WebApi/PLC/Service/PlcService.cs @@ -32,6 +32,7 @@ namespace RIZO.Admin.WebApi.PLC.Service private PlcProductionDataService _plcProductionDataService = new PlcProductionDataService(); private PlantWorkstationService _plantWorkstationService = new PlantWorkstationService(); + private PlcOperationResultService _plcOperationResultService = new PlcOperationResultService(); // 先在类中添加2个核心优化字段(支撑高频访问) private readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(15, 50); // 限制20并发(适配50台PLC) @@ -41,15 +42,15 @@ namespace RIZO.Admin.WebApi.PLC.Service //MES返回PLC请求映射 private readonly Dictionary _mesIntReturnMap = new() { - { "设备使能", "DB1050.DBW0" }, // Int - { "工位开始查询结果", "DB1050.DBW2000" }, // Int - { "保存结果", "DB1050.DBW2002" }, // Int + { "设备使能", "DB1101.DBW0" }, // Int + { "工位开始查询结果", "DB1101.DBW2000" }, // Int + { "保存结果", "DB1101.DBW2002" }, // Int }; private readonly Dictionary _mesStringReturnMap = new() { - { "产品型号", ("DB1001.DBB1016", 14) }, // String[14] - { "订单下发", ("DB1001.DBB1032", 50) }, // String[50] + { "产品型号", ("DB1101.DBB1016", 14) }, // String[14] + { "订单下发", ("DB1101.DBB1032", 50) }, // String[50] }; // OP020-2 专属地址映射(合盖工位,DB1001) @@ -403,17 +404,9 @@ namespace RIZO.Admin.WebApi.PLC.Service else { plc = CreatePlcClient(cpuType, ip, rack, slot); - try - { - await OpenPlcConnectionAsync(plc); - if (!plc.IsConnected) return (false, null, $"{plcName}连接失败(含2次重试)"); - _plcConnPool.TryAdd(poolKey, (plc, DateTime.Now)); - } - catch - { - ReleasePlcConnection(plc); - return (false, null, $"{plcName}连接失败(含2次重试)"); - } + await OpenPlcConnectionAsync(plc); + if (!plc.IsConnected) return (false, null, $"{plcName}连接失败"); + _plcConnPool.TryAdd(poolKey, (plc, DateTime.Now)); } // 4. 多工位请求状态读取(精简分支,保留原逻辑,移除冗余注释) @@ -508,13 +501,12 @@ namespace RIZO.Admin.WebApi.PLC.Service ? $"{plcName}生产数据读取成功(复用连接)" : $"{plcName}生产数据读取成功(新建连接)"; WritePlcSaveRequestResult(plc, ip, plcName, prodData, "1"); - + //保存成功,到PLC过站表过站状态 + RecordPlcOperationResult(plcName, prodData); return (true, prodData, successMsg); } catch (Exception ex) { - // 精细化异常日志,保留堆栈 - Console.WriteLine($"{plcName}({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}"); return (false, null, $"{plcName}生产数据读取失败:{ex.Message}"); } finally @@ -529,7 +521,6 @@ namespace RIZO.Admin.WebApi.PLC.Service } catch (Exception ex) { - Console.WriteLine($"{plcName}({ip})释放PLC连接失败:{ex.Message}"); } finally { @@ -539,7 +530,6 @@ namespace RIZO.Admin.WebApi.PLC.Service } } - #region 工位专属读取方法 /// /// 读取OP020-2数据(合盖工位 @@ -752,8 +742,8 @@ namespace RIZO.Admin.WebApi.PLC.Service var taskOnlineStatus = ReadPlcIntAsync(plc, _op070_1IntMap["设备在线状态"]); var taskByPass = ReadPlcIntAsync(plc, _op070_1IntMap["ByPass"]); var taskProduceModel = ReadPlcIntAsync(plc, _op070_1IntMap["生产模式"]); - var taskQueryReq = ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]); - var taskSaveReq = ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]); + //var taskQueryReq = ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]); + //var taskSaveReq = ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]); var taskTrayNo = ReadPlcIntAsync(plc, _op070_1IntMap["托盘号"]); var taskCameraResult = ReadPlcIntAsync(plc, _op070_1IntMap["相机结果"]); var taskStationResult = ReadPlcIntAsync(plc, _op070_1IntMap["站位结果"]); @@ -767,7 +757,7 @@ namespace RIZO.Admin.WebApi.PLC.Service taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2, // Int任务 taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel, - taskQueryReq, taskSaveReq, taskTrayNo, taskCameraResult, taskStationResult, + taskTrayNo, taskCameraResult, taskStationResult, // Real任务 taskCycleTime); @@ -785,8 +775,6 @@ namespace RIZO.Admin.WebApi.PLC.Service int onlineStatus = await taskOnlineStatus; int byPass = await taskByPass; int produceModel = await taskProduceModel; - int queryReq = await taskQueryReq; - int saveReq = await taskSaveReq; int trayNo = await taskTrayNo; int cameraResult = await taskCameraResult; int stationResult = await taskStationResult; @@ -879,8 +867,6 @@ namespace RIZO.Admin.WebApi.PLC.Service var taskOnlineStatus = ReadPlcIntAsync(plc, _op075IntMap["设备在线状态"]); var taskByPass = ReadPlcIntAsync(plc, _op075IntMap["ByPass"]); var taskProduceModel = ReadPlcIntAsync(plc, _op075IntMap["生产模式"]); - var taskQueryReq = ReadPlcIntAsync(plc, _op075IntMap["查询请求"]); - var taskSaveReq = ReadPlcIntAsync(plc, _op075IntMap["保存请求"]); var taskTrayNo = ReadPlcIntAsync(plc, _op075IntMap["托盘号"]); var taskStationResult = ReadPlcIntAsync(plc, _op075IntMap["站位结果"]); @@ -893,7 +879,7 @@ namespace RIZO.Admin.WebApi.PLC.Service taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2, taskChipSN, // Int任务 taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel, - taskQueryReq, taskSaveReq, taskTrayNo, taskStationResult, + taskTrayNo, taskStationResult, // Real任务 taskCycleTime); @@ -912,8 +898,6 @@ namespace RIZO.Admin.WebApi.PLC.Service int onlineStatus = await taskOnlineStatus; int byPass = await taskByPass; int produceModel = await taskProduceModel; - int queryReq = await taskQueryReq; - int saveReq = await taskSaveReq; int trayNo = await taskTrayNo; int stationResult = await taskStationResult; @@ -2134,21 +2118,85 @@ namespace RIZO.Admin.WebApi.PLC.Service } } - // 提取写PLC返回值的通用方法,统一处理逻辑和日志 + // 提取写PLC返回值的通用方法,统一处理逻辑和日志 private void WritePlcSaveRequestResult(Plc plc, string ip, string plcName, PlcProductionData prodData, string saveResult) { try { WritePlcValue(plc, _mesIntReturnMap["设备使能"], "1"); - //WritePlcValue(plc, _mesIntReturnMap["工位开始查询结果"], "1"); + WritePlcValue(plc, _mesIntReturnMap["工位开始查询结果"], "1"); WritePlcValue(plc, _mesIntReturnMap["保存结果"], saveResult); - WritePlcValue(plc, "产品型号", prodData.ProductModel); - WritePlcValue(plc, "订单下发", "");//?? + if (prodData.ProductModel != null && prodData.ProductModel.Length > 0) + { + WritePlcString(plc, _mesStringReturnMap["产品型号"].Addr, _mesStringReturnMap["产品型号"].Len, prodData.ProductModel); + } + else + { + WritePlcString(plc, _mesStringReturnMap["产品型号"].Addr, _mesStringReturnMap["产品型号"].Len, prodData.ProductModel); + } + WritePlcString(plc, _mesStringReturnMap["订单下发"].Addr, _mesStringReturnMap["订单下发"].Len, "cpxhtest"); } catch (Exception ex) {; } } + + //记录PLC过站状态 + private void RecordPlcOperationResult(string plcName, PlcProductionData prodData) + { + if (string.IsNullOrWhiteSpace(plcName)) + { + throw new ArgumentNullException(nameof(plcName), "PLC/工位名称不能为空"); + } + if (prodData == null) + { + throw new ArgumentNullException(nameof(prodData), "PLC生产数据实体不能为空"); + } + string strSN = prodData.SN2?.Trim(); + if (string.IsNullOrWhiteSpace(strSN)) + { + return; + } + int result = string.Equals(prodData.QualificationFlag, "1", StringComparison.OrdinalIgnoreCase) ? 1 : 0; + + try + { + var existingRecord = _plcOperationResultService.Queryable() + .Where(it => it.Sn == strSN && it.Workstationcode == plcName).ToList().FirstOrDefault(); + + if (existingRecord != null) + { + // 情况1:记录存在 + if (existingRecord.Result == result) + { + return; + } + else + { + // 情况2:记录存在但结果不一致,执行修改操作 + existingRecord.Result = result; + existingRecord.UpdatedBy = "PLC"; + existingRecord.UpdatedTime = DateTime.Now; + _plcOperationResultService.Update(existingRecord); + } + } + else + { + // 情况3:记录不存在,执行新增操作 + PlcOperationResult porNew = new PlcOperationResult(); + porNew.Sn = strSN; + porNew.Workstationcode = plcName; + porNew.Result = result; + porNew.CreatedBy = "PLC"; + porNew.CreatedTime = DateTime.Now; + _plcOperationResultService.Insert(porNew); + } + } + catch (Exception ex) + { + } + } + /// /// 异步复位上传请求(独立方法,非阻塞) /// @@ -2180,5 +2228,68 @@ namespace RIZO.Admin.WebApi.PLC.Service Console.WriteLine($"({ip})写保存请求复位失败:{ex.Message}"); } } + + /// + /// 向PLC写入字符串(字节数组形式) + /// + /// PLC客户端 + /// 字符串起始地址(如DB1101.DBB1016) + /// PLC定义的字符串长度(如14) + /// 要写入的字符串 + private void WritePlcString(Plc plc, string startAddress, int strLength, string value) + { + // 1. 解析起始地址(提取DB块、起始字节) + if (!ParsePlcAddress(startAddress, out var dataType, out int dbNumber, out int startByte)) + { + throw new ArgumentException($"无效的PLC字符串起始地址:{startAddress}"); + } + + // 2. 处理字符串:转字节数组,长度不足补0,超长截断 + byte[] strBytes = Encoding.ASCII.GetBytes(value ?? ""); + byte[] plcBytes = new byte[strLength]; // PLC字符串固定长度 + + // 复制字符串字节,超长则截断 + int copyLength = Math.Min(strBytes.Length, strLength); + Array.Copy(strBytes, 0, plcBytes, 0, copyLength); + // 剩余字节填充0(清空原有内容) + if (copyLength < strLength) + { + Array.Clear(plcBytes, copyLength, strLength - copyLength); + } + + // 3. 写入PLC(批量写入字节数组) + plc.WriteBytes(dataType, dbNumber, startByte, plcBytes); + } + + /// + /// PLC地址(如DB1101.DBB1016) + /// 数据类型(DB/Input等) + /// DB块编号(如1101) + /// 起始字节(如1016) + /// 是否解析成功 + private bool ParsePlcAddress(string address, out DataType dataType, out int dbNumber, out int startByte) + { + dataType = DataType.DataBlock; // 默认DB块 + dbNumber = 0; + startByte = 0; + + try + { + // 匹配格式:DB1101.DBB1016 + var match = System.Text.RegularExpressions.Regex.Match(address, @"DB(\d+)\.DBB(\d+)"); + if (match.Success) + { + dbNumber = int.Parse(match.Groups[1].Value); + startByte = int.Parse(match.Groups[2].Value); + return true; + } + + return false; + } + catch (Exception ex) + { + return false; + } + } } } \ No newline at end of file diff --git a/RIZO.Admin.WebApi/appsettings.json b/RIZO.Admin.WebApi/appsettings.json index a5a379e..534c11a 100644 --- a/RIZO.Admin.WebApi/appsettings.json +++ b/RIZO.Admin.WebApi/appsettings.json @@ -97,8 +97,8 @@ "uniappPath": "D:\\Work" //h5前端代码存储路径 }, "GlobalConfig": { - "ConnectTimeout": 5000, // 连接超时(毫秒) - "ReadWriteTimeout": 5000 // 读写超时(毫秒) + "ConnectTimeout": 1000, // 连接超时(毫秒) + "ReadWriteTimeout": 1000 // 读写超时(毫秒) }, "PlcPollingSettings": { "MaxConcurrentPerPlc": 1,