Explorar o código

增加添加螺丝判断与记录

KWD hai 3 meses
pai
achega
29d78da336

+ 457 - 41
TeamAAS-VM/Core/Management.cs

@@ -7,6 +7,7 @@ using Prism.Events;
 using Prism.Ioc;
 using Prism.Mvvm;
 using Prism.Regions;
+using SqlSugar;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -19,6 +20,7 @@ using System.Security.Claims;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using System.Web;
 using System.Web.UI.WebControls;
 using System.Windows.Media;
 using Team.FFFeederService.Interfaces;
@@ -1201,6 +1203,7 @@ namespace TeamAAS_VP.Core
                 addressConfig.In_RequestPosition.Address,
                 addressConfig.In_QrCodePhotoRequest.Address,
                 addressConfig.In_ScrewTeachExe.Address,
+                addressConfig.In_Screw_checkExe.Address,
                 addressConfig.In_PickINC.Address,
                 addressConfig.In_Throw.Address,
                 addressConfig.RunMode.Address,
@@ -1208,6 +1211,7 @@ namespace TeamAAS_VP.Core
                 addressConfig.ManualMode.Address,
                 addressConfig.ClearMode.Address,
                 addressConfig.Fault.Address,
+                addressConfig.In_FzCameraPixExe.Address,
             };
             var systemconfig = _configService.GetSystemConfiguration();
             for (int i = 0; i < 10; i++)
@@ -1353,11 +1357,13 @@ namespace TeamAAS_VP.Core
         bool _LastDowmCameraExe = false;
         bool _LastScrewTeachExe = false;
         bool _LastUpCameraPickExe = false;
+        bool _LastScrewFuJianExe = false;
         bool _LastUpCameraPutExe = false;
         bool _LastUPCameracheckExe = false;
         bool _LastFixedCameraPickExe = false;
         bool _LastFixedCameraPutExe = false;
         bool _LastFixedCameracheckExe = false;
+        bool _LastFzCameracheckExe = false;
         bool _LastThrowExe = false;
         bool _LastRunModel = false;
         public bool _LastAutoRunning = false;
@@ -1372,6 +1378,7 @@ namespace TeamAAS_VP.Core
         Int16 _LastPickINC = 0;
         DateTime[] _LastMesCheckTime = new DateTime[10];
         Dictionary<string, string> mes_cc = new Dictionary<string, string>();
+        Dictionary<string, double> Point = new Dictionary<string, double>();
         string[] ProductMainSN { get; set; } = new string[10];//产品主sn
         string[] model_num { get; set; } = new string[10];//产品model_num
         string[] wo { get; set; } = new string[10];//产品wo
@@ -1797,6 +1804,90 @@ namespace TeamAAS_VP.Core
                         }
                     }
                 }
+                //下相机拍照螺丝复检
+                else if (addressConfig.In_Screw_checkExe.Address.Contains(tuple.nodeId))
+                {
+                    Point.Clear();
+                    if (tuple.value is bool state)
+                    {
+                        if (_LastScrewFuJianExe != state)
+                        {
+                            _LastScrewFuJianExe = state;
+                        }
+                        else
+                        {
+                            return;
+                        }
+
+                        if (state)
+                        {
+
+                            SendTaskMessage(Lang.触发相机拍照螺丝复检, MessageLevel.Debug);
+                            //相机的编号
+                            Int16 cameraIndex = plc.ReadNode<Int16>(addressConfig.In_DowmCameraNum.Address);
+                            SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info);
+
+                            // 获取流程
+                            var process = _productService.GetCurrentProductProcedureModelById(currentProduct.FixedUpCameraProcedureId);
+                            if (process == null)
+                            {
+                                SendTaskMessage(string.Format(Lang.固定向上相机视觉流程流程ID, currentProduct.FixedUpCameraProcedureId), MessageLevel.Alarm);
+                                await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2);
+                                return;
+                            }
+
+                            // 获取机器人当前坐标
+                            var robot = _robotService.GetAllRobots().FirstOrDefault();
+                            if (robot == null)
+                            {
+                                SendTaskMessage(Lang.未找到机器人无法执行上相机锁附组装拍照视觉任务, MessageLevel.Alarm);
+                                await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2);
+                                return;
+
+                            }
+                            var currentPosition = robot.GetRobotPos();
+
+                            // 执行视觉任务
+                            var _cts = new CancellationTokenSource();
+                            ImageParameters imgPara = new ImageParameters();
+                            imgPara.SNCode = productSn;
+                            var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint1(process, null, null, _cts.Token, cameraIndex, cameraIndex, null, imgPara);
+                            //string X, Y, U;
+                            if (visionResult.IsSucceed)
+                            {
+                                //披头偏差
+                                //float offsetX = (float)(visionResult.X - currentPosition.X);
+                                //float offsetY = (float)(visionResult.Y - currentPosition.Y);
+                                string[] keys = { "X", "Y", "U" };
+                                double[] values = { visionResult.X, visionResult.Y, visionResult.U };
+                                for (int i = 0; i < keys.Length; i++)
+                                {
+                                    Point.Add(keys[i], values[i]);
+                                }
+
+                                // 2. 生成CSV文件(替换成你的保存路径)
+                                string csvPath = @"D:\视觉检测数据\XYU结果.csv";
+                                // 调用生成方法(appendMode=true 每次拍照新增一行,不会覆盖历史数据)
+                                ExportDictionaryToCsv(Point, csvPath, appendMode: true);
+                                //DowmCameraResults[cameraIndex] = new PointF(offsetX, offsetY);
+                                //ExportDictionaryToCsv1(ProductMainSN[1], visionResult.X, visionResult.Y, visionResult.U);
+
+                                await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)1);
+                                return;
+                            }
+                            else
+                            {
+                                await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2);
+                                return;
+                            }
+                        }
+                        else
+                        {
+                            SendTaskMessage(Lang.触发下相机拍照信号已关闭, MessageLevel.Alarm);
+                            await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)0);
+                        }
+                    }
+                }
                 // 固定相机取料拍照
                 else if (addressConfig.In_FixedCameraPickExe.Address.Contains(tuple.nodeId))
                 {
@@ -1885,6 +1976,213 @@ namespace TeamAAS_VP.Core
                         }
                     }
                 }
+                // 翻转相机检测拍照
+                else if (addressConfig.In_FzCameraPixExe.Address.Contains(tuple.nodeId))
+                {
+                    if (tuple.value is bool state)
+                    {
+                        if (_LastFzCameracheckExe != state)
+                        {
+                            _LastFzCameracheckExe = state;
+                        }
+                        else
+                        {
+                            return;
+                        }
+                        if (state)
+                        {
+                            int i = 5;
+                            SendTaskMessage("触发翻转相机拍照", MessageLevel.Debug);
+                            var code = plc.ReadNode<string>(string.Format(addressConfig.In_Code.Address, 1));
+
+                            string resultphoto = "";
+                            //这里缺少从MES获取产品配方的内容,需要补充
+
+                            _LastMesCheckTime[5] = (await _mesService.GetServerTimeAsync(5, code, 2)).Now;
+                            var stationConfig2 = _configService.GetDeviceInfo(0);
+                            //LogHelper.WriteLogMes($"【mes开始QUERY】");
+                            //SendTaskMessage($"mes开始QUERY", MessageLevel.Info);
+                            //if (stationConfig2.EnableMES)
+                            //{
+                            //    (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2);
+                            //    if (isSuccess)
+                            //    {
+                            //        string ProductMainSN_Temp = "";
+                            //        LogHelper.WriteLogMes($"询问mes成功");
+                            //        SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug);
+                            //        string[] item = response.Split('\n');
+                            //        //获取过度件sn
+                            //        string snItem = item.FirstOrDefault(f => f.Contains("sn="));
+                            //        if (snItem != null)
+                            //        {
+                            //            ProductMainSN_Temp = snItem.Replace("sn=", "").Trim();
+
+                            //            SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info);
+                            //        }
+
+                            //        LogHelper.WriteLogMes($"询问mes成功");
+                            //        SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug);
+
+                            //        //20260214,螺丝机特别版
+                            //        (bool isSuccess2, string response2) = await _mesService.StationGetAsync(ProductMainSN_Temp, "", 2);//询问mes
+                            //        if (isSuccess2)
+                            //        {
+                            //            LogHelper.WriteLogMes($"询问mes成功");
+                            //            SendTaskMessage($"询问mes成功:{response2}", MessageLevel.Debug);
+
+                            //            string[] items = response2.Split('\n');
+                            //            //获取产品主sn和wo用于后续上传数据使用
+                            //            string snItem2 = items.FirstOrDefault(f => f.Contains("sn="));
+                            //            if (snItem2 != null)
+                            //            {
+                            //                var codesn = snItem2.Replace("sn=", "").Trim();
+                            //                //_productService.CurrentProductCode = codesn;
+                            //                SendTaskMessage($"产品主sn:{codesn}", MessageLevel.Info);
+                            //            }
+                            //            string woItem = item.FirstOrDefault(f => f.Contains("wo="));
+                            //            if (woItem != null)
+                            //            {
+                            //                wo[i] = woItem.Replace("wo=", "").Trim();
+                            //                SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info);
+                            //            }
+                            //            string modelItem = item.FirstOrDefault(f => f.Contains("model_num="));
+                            //            if (modelItem != null)
+                            //            {
+                            //                model_num[i] = modelItem.Replace("model_num=", "").Trim();
+                            //                SendTaskMessage($"model_num:{model_num[i]}", MessageLevel.Info);
+                            //            }
+                            //            string colorItem = item.FirstOrDefault(f => f.Contains("color="));
+                            //            if (colorItem != null)
+                            //            {
+                            //                color[i] = colorItem.Replace("color=", "").Trim();
+                            //                SendTaskMessage($"color:{color[i]}", MessageLevel.Info);
+                            //            }
+                            //            string ppidItem = item.FirstOrDefault(f => f.Contains("ppid="));
+                            //            if (ppidItem != null)
+                            //            {
+                            //                ppid[5] = ppidItem.Replace("ppid=", "").Trim();
+                            //                SendTaskMessage($"ppid:{ppid[5]}", MessageLevel.Info);
+                            //            }
+                            //            string revisionItem = item.FirstOrDefault(f => f.Contains("stage="));
+                            //            if (revisionItem != null)
+                            //            {
+                            //                revision[5] = revisionItem.Replace("stage=", "").Trim();
+                            //                SendTaskMessage($"stage:{revision[5]}", MessageLevel.Info);
+                            //            }
+                            //            LogHelper.WriteLogMes($"第二次询问mes成功");
+                            //            SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug);
+
+                            //        }
+                            //        else
+                            //        {
+                            //            LogHelper.WriteLogMes($"第二次询问mes失败");
+                            //            SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error);
+                            //            await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2);
+                            //            return;
+                            //        }
+                            //    }
+                            //    else
+                            //    {
+                            //        LogHelper.WriteLogMes($"询问mes失败");
+                            //        SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
+                            //        await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2);
+                            //        return;
+                            //    }
+                            //}
+                            //else
+                            //{
+                            //    SendTaskMessage($"mes未启用", MessageLevel.Info);
+                            //}
+
+                            Guid FzCameraProcedureId = currentProduct.FzCameraProcedureId;
+
+                            var FzCameraProcess = _productService.GetCurrentProductProcedureModelById(FzCameraProcedureId);
+                            if (FzCameraProcess == null)
+                            {
+                                SendTaskMessage($"未找到翻转相机视觉流程,流程ID: {FzCameraProcedureId}", MessageLevel.Alarm);
+                                await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2);
+                                return;
+                            }
+                            SendTaskMessage($"开始执行翻转相机视觉流程", MessageLevel.Debug);
+                            var _cts = new CancellationTokenSource();
+
+                            await _remoteCommandService.SetLightBeforePhoto(FzCameraProcess);
+                            var FzCameraResult = _remoteCommandService.ExecutePhotoGetDistance(FzCameraProcess, null);
+
+                            if (FzCameraResult)
+                            {
+                                SendTaskMessage("翻转相机拍照成功", MessageLevel.Debug);
+                                resultphoto = "Pass";
+                                await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)1);
+
+                            }
+                            else
+                            {
+                                SendTaskMessage("翻转相机拍照失败", MessageLevel.Alarm);
+                                resultphoto = "Fail";
+                                await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2);
+                            }
+                            if (stationConfig2.SaveCsv)
+                            {
+                                List<DataFormCtq> DFC = new List<DataFormCtq>();
+                                string saveppid = "NA";
+                                string names = currentProduct.Name;
+                                if (stationConfig2.SaveCsvPPID) saveppid = ppid[i];
+                                DateTime servertime = DateTime.Now;
+                                // Dictionary<string, double> adjacentDiffDict1 = GetAdjacentDiffDict(keyData1);
+                                //判断锁付路径字符串是否为空,如果不为空则保存,否则不保存
+                                if (!string.IsNullOrEmpty(stationConfig2.SaveCsvPath))
+                                {
+
+                                    DFC.Add(new DataFormCtq
+                                    {
+                                        PPID = saveppid,
+                                        PROCESS_NAME = stationConfig2.DataInfo.PROCESS_NAME,
+                                        PROCESS_START_TIME = _LastMesCheckTime[5].ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00",
+                                        CTQ_NAME = "FzCameraPhoto",
+                                        CTQ_VALUE = 0,
+                                        CTQ_LSL = 0,
+                                        CTQ_USL = 0,
+                                        PROCESS_END_TIME = servertime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00",
+                                        SUPPLIER_NAME = stationConfig2.DataInfo.SUPPLIER_NAME,
+                                        COMMODITY_TYPE = stationConfig2.DataInfo.COMMODITY_TYPE,
+                                        REVISION = stationConfig2.DataInfo.REVISION + "_" + revision[i],
+                                        WO = "wo",//
+                                        MFG_LOT = stationConfig2.DataInfo.MFG_LOT,//
+                                        MFG_ASSY_LINE = stationConfig2.DataInfo.MFG_ASSY_LINE,
+                                        PROCESS_OUTCOME = resultphoto,
+                                        PROCESS_MACHINE_ID = stationConfig2.DataInfo.PROCESS_MACHINE_ID,
+                                        CTQ_UNIT_OF_MEASURE = "mm",
+                                        ERROR_MESSAGE = "",
+                                        PD_ATTR_01 = _productService.CurrentProductCode,//
+                                        PD_ATTR_02 = "",
+                                        PD_ATTR_03 = "",
+                                        PD_ATTR_04 = code,
+                                        PD_ATTR_05 = "",
+                                    });
+                                    bool recordResult = RecordStationResult(0, DFC, _productService.CurrentProductCode, servertime.Subtract(_LastMesCheckTime[5]).TotalSeconds, _LastMesCheckTime[5], stationConfig2.SaveCsvPath);
+                                    if (recordResult)
+                                    {
+                                        SendTaskMessage($"翻转相机拍照产品记录保存成功", MessageLevel.Debug);
+                                    }
+                                    else
+                                    {
+                                        SendTaskMessage($"{stationConfig2.DeviceName}产品记录保存失败", MessageLevel.Error);
+                                    }
+                                }
+                                else
+                                {
+                                    SendTaskMessage($"{stationConfig2.DeviceName}工位未配置保存路径,无法保存产品记录", MessageLevel.Error);
+                                }
+                            }
+                        }
+                        else
+                        {
+                            SendTaskMessage($"触发翻转相机检测拍照信号已关闭", MessageLevel.Alarm);
+                            await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)0);
+                        }
+                    }
+                }
                 // 开始锁付
                 else if (addressConfig.In_WorkStart.Address.Contains(tuple.nodeId))
                 {
@@ -2130,26 +2428,29 @@ namespace TeamAAS_VP.Core
                                 }
                                 //验证当前锁附点位与设置锁附点位的差值是否超限,超限则报警并停止流程
                                 //目前验证已OK 缺少切换按钮
-                                //try
-                                //{
-                                //    var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1);
-                                //    //判断实际偏差与设置的点位差数是否偏差过大
-                                //    float XOffsetPoint = Math.Abs(PositionPoint.X_Position - (TempPoint.X_Position + TempPoint.X_Offset));
-                                //    float YOffsetPoint = Math.Abs(PositionPoint.Y_Position - (TempPoint.Y_Position + TempPoint.Y_Offset));
-                                //    if (!(XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) ||
-                                //        !(YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4)))
-                                //    {
-                                //        SendTaskMessage($"当前锁附点位{positionIndex}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error);
-                                //        await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
-                                //        return;
-                                //    }
-                                //}
-                                //catch (Exception ex)
-                                //{
-                                //    SendTaskMessage($"当前锁附点位与设置锁附点位差值未设置:" + ex.Message, MessageLevel.Error);
-                                //    await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
-                                //    return;
-                                //}
+                                if (_configService.GetDeviceInfo().ScrewOverrun)
+                                {
+                                    try
+                                    {
+                                        var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1);
+                                        //判断实际偏差与设置的点位差数是否偏差过大
+                                        float XOffsetPoint = Math.Abs(PositionPoint.X_Position - (TempPoint.X_Position + TempPoint.X_Offset));
+                                        float YOffsetPoint = Math.Abs(PositionPoint.Y_Position - (TempPoint.Y_Position + TempPoint.Y_Offset));
+                                        if (!(XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) ||
+                                            !(YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4)))
+                                        {
+                                            SendTaskMessage($"当前锁附点位{positionIndex}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error);
+                                            await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
+                                            return;
+                                        }
+                                    }
+                                    catch (Exception ex)
+                                    {
+                                        SendTaskMessage($"当前锁附点位与设置锁附点位差值未设置:" + ex.Message, MessageLevel.Error);
+                                        await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
+                                        return;
+                                    }
+                                }
                                 await PositionPoint.WriteToPlcAddress(addressConfig.Out_Screw, plc, positionIndex);
                             }
                             //拍2打所有
@@ -2175,25 +2476,28 @@ namespace TeamAAS_VP.Core
                                     point.X_Position = (float)worldPoint[0];
                                     point.Y_Position = (float)worldPoint[1];
                                     SendTaskMessage($"点:{i},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info);
-                                    try
+                                    if (_configService.GetDeviceInfo().ScrewOverrun)
                                     {
-                                        var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1);
-                                        //判断实际偏差与设置的点位差数是否偏差过大
-                                        float XOffsetPoint = Math.Abs(localPoint.X - (float)worldPoint[0]);
-                                        float YOffsetPoint = Math.Abs(localPoint.Y - (float)worldPoint[1]);
-                                        if ((XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) ||
-                                            (YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4)))
+                                        try
+                                        {
+                                            var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1);
+                                            //判断实际偏差与设置的点位差数是否偏差过大
+                                            float XOffsetPoint = Math.Abs(localPoint.X - (float)worldPoint[0]);
+                                            float YOffsetPoint = Math.Abs(localPoint.Y - (float)worldPoint[1]);
+                                            if ((XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) ||
+                                                (YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4)))
+                                            {
+                                                SendTaskMessage($"当前锁附点位{i}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error);
+                                                await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
+                                                return;
+                                            }
+                                        }
+                                        catch (Exception ex)
                                         {
-                                            SendTaskMessage($"当前锁附点位{i}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error);
+                                            SendTaskMessage($"当前锁附点位与设置锁附点位差值超限:" + ex.Message, MessageLevel.Error);
                                             await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
-                                            return;
                                         }
                                     }
-                                    catch (Exception ex)
-                                    {
-                                        SendTaskMessage($"当前锁附点位与设置锁附点位差值超限:" + ex.Message, MessageLevel.Error);
-                                        await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
-                                    }
                                     await point.WriteToPlcAddress(addressConfig.Out_Screw, plc);
                                 }
                                 await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)currentProduct.ScrewPoints.Count);
@@ -2485,8 +2789,20 @@ namespace TeamAAS_VP.Core
                                                     return;
                                                 }
                                             }
-                                            SendTaskMessage($"二维码扫码成功:{code}", MessageLevel.Info);
-                                            await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)1);
+
+                                            bool _isSuccess = plc.WriteNode<string>(string.Format(addressConfig.In_Code.Address, 0), code);
+                                            if (_isSuccess)
+                                            {
+                                                SendTaskMessage($"二维码扫码成功:{code}", MessageLevel.Info);
+                                                await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)1);
+                                            }
+                                            else
+                                            {
+                                                SendTaskMessage($"二维码扫码成功:{code}", MessageLevel.Info);
+                                                SendTaskMessage($"发送plc二维码失败:{code}", MessageLevel.Error);
+                                                await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2);
+                                            }
+
                                         }
                                         else
                                         {
@@ -2511,7 +2827,7 @@ namespace TeamAAS_VP.Core
                                 }
 
                                 productSn = code;
-
+                                SendTaskMessage($"收到plc信号{state}", MessageLevel.Debug);
                                 //询问mes
                                 if (state == 1)
                                 {
@@ -2750,7 +3066,7 @@ namespace TeamAAS_VP.Core
                                                     //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
                                                     await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
                                                     await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
-
+                                                    return;
                                                 }
                                                 else
                                                 {
@@ -2762,10 +3078,14 @@ namespace TeamAAS_VP.Core
                                                     if (models)
                                                     {
                                                         await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3);
+                                                        SendTaskMessage($"发送plc信号3", MessageLevel.Debug);
+                                                        return;
                                                     }
                                                     else
                                                     {
                                                         await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+                                                        SendTaskMessage($"发送plc信号2", MessageLevel.Debug);
+                                                        return;
                                                     }
                                                 }
                                             }
@@ -2779,10 +3099,14 @@ namespace TeamAAS_VP.Core
                                                 if (models)
                                                 {
                                                     await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3);
+                                                    SendTaskMessage($"发送plc信号3", MessageLevel.Debug);
+                                                    return;
                                                 }
                                                 else
                                                 {
                                                     await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+                                                    SendTaskMessage($"发送plc信号2", MessageLevel.Debug);
+                                                    return;
                                                 }
                                             }
                                         }
@@ -2801,6 +3125,7 @@ namespace TeamAAS_VP.Core
                                         SendTaskMessage($"未启用mes", MessageLevel.Info);
                                         await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
                                         await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
+                                        SendTaskMessage($"发送plc信号2", MessageLevel.Debug);
                                     }
                                 }
                                 // 上传本站数据
@@ -3163,7 +3488,7 @@ namespace TeamAAS_VP.Core
                                     SendTaskMessage($"mes结束ADD", MessageLevel.Info);
                                 }
                                 //kb 螺丝站位询问mes 具体需求跟plc对接
-                                if (state == 5)
+                                else if (state == 5)
                                 {
 
                                     SendTaskMessage($"收到KB Screw站点{i}过站检查请求(当站是否生产)...", MessageLevel.Debug);
@@ -3313,8 +3638,7 @@ namespace TeamAAS_VP.Core
                                                 return;
                                             }
                                             // 后面还有一些判断条件也需跟当前AE对接
-
-                                            
+                                            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
                                         }
 
                                         LogHelper.WriteLogMes($"【mes结束QUERY】");
@@ -3338,6 +3662,7 @@ namespace TeamAAS_VP.Core
                                 {
                                     SendTaskMessage($"{i}过站检查信号:state={state},处理为默认状态", MessageLevel.Alarm);
                                     await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)0);
+                                    return;
                                 }
                             }
                         }
@@ -3871,6 +4196,97 @@ namespace TeamAAS_VP.Core
         }
         #endregion
 
+        #region 拍螺丝数据保存csv
+        /// <summary>
+        /// 通用方法:将存储XYU的字典导出为规范CSV文件
+        /// </summary>
+        /// <param name="dataDict">存储XYU的字典</param>
+        /// <param name="savePath">CSV文件保存路径</param>
+        /// <param name="appendMode">是否追加模式(true=每次运行新增一行,false=覆盖原有文件)</param>
+        /// <param name="decimalPlaces">数值保留的小数位数(默认4位,适配工业视觉精度)</param>
+        static void ExportDictionaryToCsv(Dictionary<string, double> dataDict, string savePath, bool appendMode = false, int decimalPlaces = 4)
+        {
+            // 自动创建目录(避免路径不存在报错)
+            string directory = Path.GetDirectoryName(savePath);
+            if (!Directory.Exists(directory))
+            {
+                Directory.CreateDirectory(directory);
+            }
+
+            // 构建CSV内容
+            StringBuilder csvContent = new StringBuilder();
+
+            // 处理表头:仅当文件不存在/非追加模式时,才写入表头
+            if (!appendMode || !File.Exists(savePath))
+            {
+                csvContent.AppendLine(string.Join(",", dataDict.Keys));
+            }
+
+            // 处理数据行:按指定小数位数格式化数值
+            List<string> formattedValues = new List<string>();
+            foreach (var value in dataDict.Values)
+            {
+                formattedValues.Add(Math.Round(value, decimalPlaces).ToString());
+            }
+            csvContent.AppendLine(string.Join(",", formattedValues));
+
+            // 写入文件:UTF-8带BOM编码,Excel打开无乱码
+            if (appendMode)
+            {
+                // 追加模式:在文件末尾新增内容
+                File.AppendAllText(savePath, csvContent.ToString(), Encoding.UTF8);
+            }
+            else
+            {
+                // 覆盖模式:新建/替换文件
+                File.WriteAllText(savePath, csvContent.ToString(), Encoding.UTF8);
+            }
+        }
+
+        public bool ExportDictionaryToCsv1(string rfid, double x, double y, double u)
+        {
+            try
+            {
+                DateTime dt = DateTime.Now;
+                if (rfid == null || rfid.Count() == 0)
+                {
+                    rfid = dt.ToString("MM-dd-mm");
+                }
+                string path = $"D:\\视觉检测数据XYU数据\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}\\{rfid}.csv";
+                StringBuilder sb = new StringBuilder();
+                sb.AppendLine($"SN,X,Y,U");
+                sb.AppendLine();
+                sb.AppendLine($"{rfid},{x},{y},{u}");
+                sb.AppendLine();
+                if (!Directory.Exists(Path.GetDirectoryName(path)))   //判断文件夹是否
+                {
+                    Directory.CreateDirectory(Path.GetDirectoryName(path));    //创建文件夹
+                }
+                if (File.Exists(path))
+                {
+                    // 使用 StreamWriter 以追加模式打开文件
+                    StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8);
+                    sw.WriteLine(sb.ToString().TrimEnd(','));
+                    //sw.WriteLine(sb1.ToString().TrimEnd(','));
+                    sw.Close();
+                    sw.Dispose();
+                }
+                else
+                {
+                    StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8);
+                    sw.WriteLine(sb.ToString().TrimEnd(','));
+                    sw.Close();
+                    sw.Dispose();
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("写入产品记录时出错!", ex);
+                return false;
+            }
+        }
+        #endregion
         #region 产品锁付记录进行存储
         /// <summary>
         /// 产品锁付记录进行存储

+ 23 - 0
TeamAAS-VM/Interfaces/IRemoteCommandService.cs

@@ -249,6 +249,29 @@ namespace TeamAAS_VP.Interfaces
         /// <returns></returns>
         bool ExecutePhoto(int index, ProcedureModel procedure, out CogToolBlockTerminalCollection outputCollection);
 
+        /// <summary>
+        /// 执行翻转相机拍照流程运行
+        /// </summary>
+        /// <param name="procedure"></param>
+        /// <param name="outputCollection"></param>
+        /// <returns></returns>
+        bool ExecutePhotoGetDistance(ProcedureModel procedure, Dictionary<string, string> InputTerminal);
+
+
+        /// <summary>
+        /// 执行相机拍照螺丝点位结果
+        /// </summary>
+        /// <param name="procedure"></param>
+        /// <param name="InputTerminal"></param>
+        /// <param name="robotCoord"></param>
+        /// <param name="cancellationToken"></param>
+        /// <param name="positionIndex"></param>
+        /// <param name="remark"></param>
+        /// <returns></returns>
+        Task<(bool IsSucceed, double X, double Y, double U)> ExecutePhotoGetSinglePoint1(ProcedureModel procedure, Dictionary<string, string> InputTerminal, double[] robotCoord, CancellationToken cancellationToken, int positionIndex, int num, string remark, ImageParameters imgPara);
+
+
+
         /// <summary>
         /// 执行相机拍照获取单个点位结果
         /// </summary>

+ 17 - 1
TeamAAS-VM/Models/CTQ_Data.cs

@@ -117,6 +117,18 @@ namespace TeamAAS_VP.Models
         /// </summary>
         public string PROCESS_MACHINE_ID { get => _PROCESS_MACHINE_ID; set => SetProperty(ref _PROCESS_MACHINE_ID, value); }
 
+        private string _Screwsize1;
+        /// <summary>
+        /// 螺丝型号1
+        /// </summary>
+        public string Screwsize1 { get => _Screwsize1; set => SetProperty(ref _Screwsize1, value); }
+
+        private string _Screwsize2;
+        /// <summary>
+        /// 螺丝型号2
+        /// </summary>
+        public string Screwsize2 { get => _Screwsize2; set => SetProperty(ref _Screwsize2, value); }
+
         private string _CTQ_UNIT_OF_MEASURE;
         /// <summary>
         /// 单位
@@ -149,6 +161,8 @@ namespace TeamAAS_VP.Models
             PROCESS_MACHINE_ID = string.Empty;
             CTQ_UNIT_OF_MEASURE = string.Empty;
             ERROR_MESSAGE = string.Empty;
+            Screwsize1= string.Empty;
+            Screwsize2= string.Empty;
         }
 
         public CTQ_Data Copy()
@@ -172,7 +186,9 @@ namespace TeamAAS_VP.Models
                 PROCESS_OUTCOME = this.PROCESS_OUTCOME,
                 PROCESS_MACHINE_ID = this.PROCESS_MACHINE_ID,
                 CTQ_UNIT_OF_MEASURE = this.CTQ_UNIT_OF_MEASURE,
-                ERROR_MESSAGE = this.ERROR_MESSAGE
+                ERROR_MESSAGE = this.ERROR_MESSAGE,
+                Screwsize1=this.Screwsize1,
+                Screwsize2 = this.Screwsize2
             };
         }
 

+ 7 - 0
TeamAAS-VM/Models/DeviceInfo.cs

@@ -89,6 +89,12 @@ namespace TeamAAS_VP.Models
         /// </summary>
         public bool SaveCsv { get => _saveCsv; set => SetProperty(ref _saveCsv, value); }
 
+        private bool _ScrewOverrun;
+        /// <summary>
+        /// 是否保存CSV文件,默认不保存
+        /// </summary>
+        public bool ScrewOverrun { get => _ScrewOverrun; set => SetProperty(ref _ScrewOverrun, value); }
+
         private bool _saveCsvPPID;
         /// <summary>
         /// 是否保存CSV文件的PPID,默认不保存
@@ -145,6 +151,7 @@ namespace TeamAAS_VP.Models
                 MesUrl = this.MesUrl,
                 EnableMES = this.EnableMES,
                 SaveCsv = this.SaveCsv,
+                ScrewOverrun=this.ScrewOverrun,
                 SaveCsvPath = this.SaveCsvPath,
                 //EnableDoubleMes = this.EnableDoubleMes,
                 DataInfo = this.DataInfo.Copy(),

+ 103 - 1
TeamAAS-VM/Models/PLC/PlcAddressConfig.cs

@@ -12,6 +12,14 @@ namespace TeamAAS_VP.Models.PLC
     public class PlcAddressConfig : BindableBase
     {
         #region 写入到PLC的地址
+
+        private PlcAddress _Out_PlcFail;
+        public PlcAddress Out_PlcFail
+        {
+            get { return _Out_PlcFail; }
+            set { SetProperty(ref _Out_PlcFail, value); }
+        }
+
         private int _PlcNo;
         public int PlcNo
         {
@@ -139,6 +147,27 @@ namespace TeamAAS_VP.Models.PLC
             set { SetProperty(ref _DownCameraStatus, value); }
         }
 
+        private PlcAddress _AddScrewView = new PlcAddress();
+        /// <summary>
+        /// 下相机拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK
+        /// </summary>
+        public PlcAddress Out_AddScrewView
+        {
+            get { return _AddScrewView; }
+            set { SetProperty(ref _AddScrewView, value); }
+        }
+
+
+        private PlcAddress _Out_Screw_check_status;
+        /// <summary>
+        /// 相机拍照螺丝的结果,1:OK,2:NG
+        /// </summary>
+        public PlcAddress Out_Screw_check_status
+        {
+            get { return _Out_Screw_check_status; }
+            set { SetProperty(ref _Out_Screw_check_status, value); }
+        }
+
         private PlcAddress _UpCameraPickStatus = new PlcAddress();
         /// <summary>
         /// 上相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK
@@ -198,7 +227,17 @@ namespace TeamAAS_VP.Models.PLC
             get { return _FixedCameracheckStatus; }
             set { SetProperty(ref _FixedCameracheckStatus, value); }
         }
-    
+
+        private PlcAddress _FzCameraPickStatus = new PlcAddress();
+        /// <summary>
+        /// 固定相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK
+        /// </summary>
+        public PlcAddress Out_FzCameraPickStatus
+        {
+            get { return _FzCameraPickStatus; }
+            set { SetProperty(ref _FzCameraPickStatus, value); }
+        }
+
         private PlcAddress _Out_CameraDone = new PlcAddress();
         /// <summary>
         /// 相机坐标拟合完成
@@ -432,6 +471,15 @@ namespace TeamAAS_VP.Models.PLC
             set { _Manu_ZAxis_Velocity = value; }
         }
 
+        private PlcAddress _PC_code = new PlcAddress();
+        /// <summary>
+        /// 发送plc流程码
+        /// </summary>
+        public PlcAddress Out_PC_code
+        {
+            get { return _PC_code; }
+            set { SetProperty(ref _PC_code, value); }
+        }
         #endregion
 
         #region 从PLC读取的地址
@@ -684,6 +732,16 @@ namespace TeamAAS_VP.Models.PLC
             set { SetProperty(ref _In_ScrewTeachExe, value); }
         }
 
+        private PlcAddress _In_Screw_checkExe;
+        /// <summary>
+        /// 触发相机拍照螺丝复检
+        /// </summary>
+        public PlcAddress In_Screw_checkExe
+        {
+            get { return _In_Screw_checkExe; }
+            set { SetProperty(ref _In_Screw_checkExe, value); }
+        }
+
         private PlcAddress _In_PickINC;
         /// <summary>
         /// 取一颗螺丝出发信号
@@ -772,6 +830,16 @@ namespace TeamAAS_VP.Models.PLC
             set { SetProperty(ref _Fault, value); }
         }
 
+        private PlcAddress _In_FzCameraPixExe = new PlcAddress();
+        /// <summary>
+        /// 翻转相机触发拍照
+        /// </summary>
+        public PlcAddress In_FzCameraPixExe
+        {
+            get { return _In_FzCameraPixExe; }
+            set { SetProperty(ref _In_FzCameraPixExe, value); }
+        }
+
         #endregion
 
         public PlcAddressConfig()
@@ -863,6 +931,16 @@ namespace TeamAAS_VP.Models.PLC
             Out_DownCameraStatus.DataType = "int16";
             Out_DownCameraStatus.Description = Lang.下相机拍照结果1拍照OK2拍照NG3取图拍照OK;
 
+            Out_AddScrewView = Ensure(Out_AddScrewView);
+            Out_AddScrewView.Address = "ns=4;s=DB_Communication|FromPC.Out_AddScrewView";
+            Out_AddScrewView.DataType = "int16";
+            Out_AddScrewView.Description = Lang.添加螺丝型号结果1成功2失败;
+
+            Out_Screw_check_status = Ensure(Out_Screw_check_status);
+            Out_Screw_check_status.Address = "ns=4;s=DB_Communication|FromPC.Screw_check_status";
+            Out_Screw_check_status.DataType = "int16";
+            Out_Screw_check_status.Description = Lang.相机拍照螺丝复检的结果1OK2NG;
+
             Out_UpCameraPickStatus = Ensure(Out_UpCameraPickStatus);
             Out_UpCameraPickStatus.Address = "ns=4;s=DB_Communication|FromPC.UpCameraPickStatus";
             Out_UpCameraPickStatus.DataType = "int16";
@@ -913,6 +991,10 @@ namespace TeamAAS_VP.Models.PLC
             Check_ScrewTorque.DataType = "Bool";
             Check_ScrewTorque.Description = "电批正转";
 
+            Out_PC_code = Ensure(Out_PC_code);
+            Out_PC_code.Address = "ns=4;s=DB_Communication|FromPC.PC_code[{0}]";
+            Out_PC_code.DataType = "string";
+            Out_PC_code.Description = "发送plc流程码";
             //首先到废料桶上方投料
             //有三个示教的地方
             //
@@ -972,6 +1054,11 @@ namespace TeamAAS_VP.Models.PLC
             Screw_Backward.DataType = "Bool";
             Screw_Backward.Description = "电批反转";
 
+            Out_FzCameraPickStatus = Ensure(Out_FzCameraPickStatus);
+            Out_FzCameraPickStatus.Address = "ns=4;s=DB_Communication|FromPC.FzCameraPickStatus";
+            Out_FzCameraPickStatus.DataType = "int16";
+            Out_FzCameraPickStatus.Description = "翻转相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK";
+
             // Out_PickCamera 初始化并赋地址
             Out_PickCamera = EnsurePoint(Out_PickCamera);
             Out_PickCamera.Description = Lang.取料拍照点位参数;
@@ -1242,6 +1329,11 @@ namespace TeamAAS_VP.Models.PLC
             In_ScrewTeachExe.DataType = "bool";
             In_ScrewTeachExe.Description = Lang.触发披头视觉矫正;
 
+            In_Screw_checkExe = Ensure(In_Screw_checkExe);
+            In_Screw_checkExe.Address = "ns=4;s=DB_Communication|ToPC.Screw_checkExe";
+            In_Screw_checkExe.DataType = "bool";
+            In_Screw_checkExe.Description = Lang.触发相机拍照螺丝复检;
+
             In_PickINC = Ensure(In_PickINC);
             In_PickINC.Address = "ns=4;s=DB_Communication|ToPC.PickINC";
             In_PickINC.DataType = "int16";
@@ -1294,6 +1386,16 @@ namespace TeamAAS_VP.Models.PLC
             Fault.DataType = "Bool";
             Fault.Description = "报警";
 
+            In_FzCameraPixExe = Ensure(In_FzCameraPixExe);
+            In_FzCameraPixExe.Address = "ns=4;s=DB_Communication|ToPC.In_FzCameraPickExe";
+            In_FzCameraPixExe.DataType = "bool";
+            In_FzCameraPixExe.Description = "翻转相机检测拍照触发";
+
+            Out_PlcFail = Ensure(Out_PlcFail);
+            Out_PlcFail.Address = "ns=4;s=DB_Communication|FromPC.PC反馈NG交互[0]";
+            Out_PlcFail.DataType = "bool";
+            Out_PlcFail.Description = "PLC触发报警信号";
+
             //ns=4;s=DB_Input|X16_螺丝供料器1螺丝到位
             //ns=4;s=DB_Input|X17_螺丝供料器2螺丝到位
 

+ 23 - 0
TeamAAS-VM/Models/Product/ProductModel.cs

@@ -165,6 +165,18 @@ namespace TeamAAS_VP.Models
             get { return _GlobalParamEx; }
             set { SetProperty(ref _GlobalParamEx, value); }
         }
+
+        private Guid _FixedUpCameraProcedureId;
+        /// <summary>
+        /// 相机拍照螺丝复检视觉流程ID
+        /// </summary>
+        public Guid FixedUpCameraProcedureId
+        {
+            get { return _FixedUpCameraProcedureId; }
+            set { SetProperty(ref _FixedUpCameraProcedureId, value); }
+        }
+
+
         private Guid _FixedUpCamera2ProcedureId;
         /// <summary>
         /// 固定向上相机2视觉流程ID
@@ -195,6 +207,17 @@ namespace TeamAAS_VP.Models
             set { SetProperty(ref _MoveDownCameraLockPhotoProcedureId, value); }
         }
 
+
+        private Guid _FzCameraProcedureId;
+        /// <summary>
+        /// 翻转相机流程ID
+        /// </summary>
+        public Guid FzCameraProcedureId
+        {
+            get { return _FzCameraProcedureId; }
+            set { SetProperty(ref _FzCameraProcedureId, value); }
+        }
+
         // 二维码扫码流程ID
         private Guid _QrCodeProcedureId;
         public Guid QrCodeProcedureId

+ 2 - 2
TeamAAS-VM/Properties/AssemblyInfo.cs

@@ -51,5 +51,5 @@ using System.Windows;
 //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
 //通过使用 "*",如下所示:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.3.0.3")]
-[assembly: AssemblyFileVersion("1.3.0.3")]
+[assembly: AssemblyVersion("1.3.0.2")]
+[assembly: AssemblyFileVersion("1.3.0.2")]

+ 48 - 3
TeamAAS-VM/Resources/Languages/Lang.Designer.cs

@@ -1940,7 +1940,30 @@ namespace TeamAAS_VP.Resources.Languages {
                 return ResourceManager.GetString("下相机拍照结果1拍照OK2拍照NG3取图拍照OK", resourceCulture);
             }
         }
-        
+
+
+        /// <summary>
+        ///   查找类似 下相机拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK 的本地化字符串。
+        /// </summary>
+        public static string 添加螺丝型号结果1成功2失败
+        {
+            get
+            {
+                return ResourceManager.GetString("添加螺丝型号结果1成功2失败", resourceCulture);
+            }
+        }
+
+        /// <summary>
+        ///   查找类似 下相机矫正披头的结果,1:OK,2:NG 的本地化字符串。
+        /// </summary>
+        public static string 相机拍照螺丝复检的结果1OK2NG
+        {
+            get
+            {
+                return ResourceManager.GetString("相机拍照螺丝复检的结果1OK2NG", resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   查找类似 下相机拍照编号 的本地化字符串。
         /// </summary>
@@ -3713,7 +3736,18 @@ namespace TeamAAS_VP.Resources.Languages {
                 return ResourceManager.GetString("固定向下相机取料流程ID", resourceCulture);
             }
         }
-        
+
+        /// <summary>
+        ///   查找类似 固定向上相机1视觉流程流程ID:{0} 的本地化字符串。
+        /// </summary>
+        public static string 固定向上相机视觉流程流程ID
+        {
+            get
+            {
+                return ResourceManager.GetString("固定向上相机视觉流程流程ID0", resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   查找类似 固定相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK 的本地化字符串。
         /// </summary>
@@ -10077,7 +10111,18 @@ namespace TeamAAS_VP.Resources.Languages {
                 return ResourceManager.GetString("触发披头视觉矫正", resourceCulture);
             }
         }
-        
+
+        /// <summary>
+        ///   查找类似 触发相机拍照螺丝复检 的本地化字符串。
+        /// </summary>
+        public static string 触发相机拍照螺丝复检
+        {
+            get
+            {
+                return ResourceManager.GetString("触发相机拍照螺丝复检", resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   查找类似 触发指令 的本地化字符串。
         /// </summary>

+ 328 - 0
TeamAAS-VM/Services/RemoteCommandService.cs

@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.Scripting;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using NPOI.XSSF.Streaming.Values;
+using OpenCvSharp.Flann;
 using Org.BouncyCastle.Tls;
 using Prism.Events;
 using Prism.Ioc;
@@ -1610,6 +1611,221 @@ namespace TeamAAS_VP.Services
                 return false;
             }
         }
+        /// <summary>
+        /// 执行翻转相机拍照流程运行
+        /// </summary>
+        /// <param name="procedure"></param>
+        /// <param name="outputCollection"></param>
+        /// <returns></returns>
+        public bool ExecutePhotoGetDistance(ProcedureModel procedure, Dictionary<string, string> InputTerminal)
+        {
+            try
+            {
+                //string dis = "";
+                //拍照失败时,需要多次拍照
+                for (int i = 0; i < procedure.FeederFailLimit; i++)
+                {
+                    SendTaskMessage(Lang.开始执行视觉流程.Replace("{0}", $"{i + 1}").Replace("{1}", procedure.Name), MessageLevel.Info);
+
+                    // 1. 执行相机拍照并运行视觉工具
+                    bool visionSucceed = ExecutePhotos(procedure, InputTerminal, out CogToolBlockTerminalCollection outputCollection);
+                    if (!visionSucceed)
+                    {
+                        SendTaskMessage(Lang.视觉任务执行失败, MessageLevel.Error);
+                        return false;
+                    }
+
+                    if (!outputCollection.Contains("Found"))
+                    {
+                        SendTaskMessage(Lang.未找到视觉输出结果, MessageLevel.Error);
+                        return false;
+                    }
+
+                    // 2. 获取视觉输出结果NA
+                    bool Found = (bool)(outputCollection["Found"].Value);
+                    if (!Found)
+                    {
+                        SendTaskMessage(Lang.拍照NG, MessageLevel.Debug);
+                        //dis = (string)(outputCollection["dis"].Value);
+                        return false;
+                    }
+
+                    SendTaskMessage(Lang.拍照OK, MessageLevel.Debug);
+
+                    LogHelper.WriteLogInfo("开始获取视觉输出结果");
+                    //dis = (string)(outputCollection["dis"].Value);
+                    return true;
+                }
+                SendTaskMessage(Lang.多次拍照失败, MessageLevel.Alarm);
+                return false;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("执行相机取图并获取单点位时出错!", ex);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 翻转相机拍照方法
+        /// 该方法会发布 RenderUpdateNotification 以更新 UI。
+        /// </summary>
+        public bool ExecutePhotos(ProcedureModel procedure, Dictionary<string, string> InputTerminal, out CogToolBlockTerminalCollection outputCollection)
+        {
+            try
+            {
+                // 1. 采集图像
+                var camera = _cameraService.GetCamera(procedure.CameraId);
+                bool succed = camera.SetExposureTime(procedure.ExposureTime);
+                if (!succed)
+                {
+                    SendTaskMessage($"相机曝光设置失败!", MessageLevel.Error);
+                }
+                succed = camera.SetGain(procedure.Gain);
+                if (!succed)
+                {
+                    SendTaskMessage($"相机增益设置失败!", MessageLevel.Error);
+                }
+                DateTime nowtime = DateTime.Now;
+                LogHelper.WriteLogInfo("开始采集图像");
+                //var image = camera.Grab();
+                var image = camera.Grab();
+
+                if (image == null)
+                {
+
+                    SendTaskMessage(Lang.图像采集失败, MessageLevel.Error);
+                    outputCollection = null;
+                    return false;
+                }
+                procedure.ToolBlock.Inputs["InputImage"].Value = image;
+                LogHelper.WriteLogInfo($"采集图像完成;用时:{(DateTime.Now - nowtime).Milliseconds}ms");
+
+                // 2. 为ToolBlock传入其他输入终端
+                if (InputTerminal != null)
+                {
+                    LogHelper.WriteLogInfo($"为ToolBlock传入输入终端");
+                    foreach (var item in InputTerminal)
+                    {
+                        if (procedure.ToolBlock.Inputs.Contains(item.Key))
+                        {
+                            LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
+                            procedure.ToolBlock.Inputs[item.Key].Value = item.Value;
+                        }
+                        else
+                        {
+                            LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
+                            procedure.ToolBlock.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
+                        }
+                    }
+                }
+
+                LogHelper.WriteLogInfo(Lang.开始运行视觉工具);
+                procedure.ToolBlock.Run();      //运行ToolBlock
+
+                if (procedure.ToolBlock.RunStatus.Result == CogToolResultConstants.Accept)
+                {
+                    SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", procedure.Name).Replace("{1}", $"{procedure.ToolBlock.RunStatus.ProcessingTime:F1}"), MessageLevel.Info);
+                    outputCollection = procedure.ToolBlock.Outputs;     //获取输出终端集合
+                    DatabaseHelper.AddCameraRecords(_productService.GetCurrentProduct().Name, procedure.Name, outputCollection);
+                    ICogRecord record = null;
+                    foreach (CogToolBlockTerminal item in outputCollection)
+                    {
+                        if (item.Value is ICogRecord)
+                            record = item.Value as ICogRecord;
+                    }
+                    bool Found = (bool)(outputCollection["Found"].Value);
+                    //获取图片存储的图片文件名
+                    string imgName = string.Empty;
+                    if (procedure.IsSaveImage && !string.IsNullOrEmpty(procedure.ImageFileNameFormat))
+                    {
+                        // 创建模板,使用 {变量名:格式} 语法
+                        var template = new FileNameTemplate(procedure.ImageFileNameFormat);
+
+                        // 注册变量
+                        template.RegisterVariable("ProductSN", () => _productService.CurrentProductCode);
+                        template.RegisterVariable("CameraName", () => procedure.CameraName);
+                        //template.RegisterVariable("ShotCount", () => 1);
+                        template.RegisterVariable("Result", () => Found ? "OK" : "NG");
+                        //template.RegisterVariable("Number", () => 1);
+                        template.RegisterVariable("DateTime", () => DateTime.Now);
+
+                        // 生成文件名
+                        imgName = template.Generate();
+                    }
+
+                    _eventAggregator.GetEvent<RenderUpdateNotification>().Publish(new ShowRender()
+                    {
+                        Id = procedure.Id,
+                        Image = (ICogImage)procedure.ToolBlock.Inputs["InputImage"].Value,
+                        Graphic = outputCollection["Graphic"].Value as CogGraphicCollection,
+                        Record = record,
+                        Result = Found,
+                        IsShow = true,
+                        IsSaveImage = procedure.IsSaveImage,
+                        SaveImageModel = procedure.SaveImageModel,
+                        SaveImagePathModel = procedure.SaveImagePathModel,
+                        SavePath = procedure.SavePath,
+                        IsCompress = procedure.IsCompress,
+                        IsDelaySaveImage = procedure.IsDelaySaveImage,
+                        //ImgPara = imgPara,
+                        ImageFileName = imgName,
+                        ProceductName = procedure.Name,
+                    });
+                    SendTaskMessage($"{procedure.CameraName},{procedure.CameraId}", MessageLevel.Info);
+                    return true;
+                }
+                else
+                {
+                    SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", procedure.Name).Replace("{1}", $"{procedure.ToolBlock.RunStatus.ProcessingTime:F1}"), MessageLevel.Error);
+                    SendTaskMessage(procedure.ToolBlock.RunStatus.Message, MessageLevel.Error);
+                    outputCollection = null;
+
+                    //获取图片存储的图片文件名
+                    string imgName = string.Empty;
+                    if (procedure.IsSaveImage && !string.IsNullOrEmpty(procedure.ImageFileNameFormat))
+                    {
+                        // 创建模板,使用 {变量名:格式} 语法
+                        var template = new FileNameTemplate(procedure.ImageFileNameFormat);
+
+                        // 注册变量
+                        template.RegisterVariable("ProductSN", () => _productService.CurrentProductCode);
+                        template.RegisterVariable("CameraName", () => procedure.CameraName);
+                        //template.RegisterVariable("ShotCount", () => index);
+                        template.RegisterVariable("Result", () => "NG");
+                        template.RegisterVariable("DateTime", () => DateTime.Now);
+
+                        // 生成文件名
+                        imgName = template.Generate();
+                    }
+                    _eventAggregator.GetEvent<RenderUpdateNotification>().Publish(new ShowRender()
+                    {
+                        Id = procedure.Id,
+                        Image = (ICogImage)procedure.ToolBlock.Inputs["InputImage"].Value,
+                        Graphic = new CogGraphicCollection(),
+                        Result = false,
+                        IsShow = true,
+                        IsSaveImage = procedure.IsSaveImage,
+                        SaveImageModel = procedure.SaveImageModel,
+                        SaveImagePathModel = procedure.SaveImagePathModel,
+                        SavePath = procedure.SavePath,
+                        IsCompress = procedure.IsCompress,
+                        IsDelaySaveImage = procedure.IsDelaySaveImage,
+                        ProceductName = procedure.Name,
+                        ImageFileName = imgName
+                    });
+                    return false;
+                }
+            }
+            catch (Exception ex)
+            {
+                SendTaskMessage(ex.Message, MessageLevel.Error);
+                LogHelper.WriteLogError("执行相机取图并执行视觉工具组时出错!", ex);
+                outputCollection = null;
+                return false;
+            }
+        }
+
         /// <summary>
         /// 执行拍照二维码流程运行
         /// </summary>
@@ -1848,6 +2064,118 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 执行相机拍照螺丝点位结果
+        /// </summary>
+        /// <param name="procedure"></param>
+        /// <param name="InputTerminal"></param>
+        /// <param name="robotCoord"></param>
+        /// <param name="cancellationToken"></param>
+        /// <param name="positionIndex"></param>
+        /// <param name="remark"></param>
+        /// <returns></returns>
+        public async Task<(bool IsSucceed, double X, double Y, double U)> ExecutePhotoGetSinglePoint1(ProcedureModel procedure, Dictionary<string, string> InputTerminal, double[] robotCoord, CancellationToken cancellationToken, int positionIndex, int num, string remark, ImageParameters imgPara)
+        {
+            var systemConfig = _configService.GetSystemConfiguration();
+            DateTime nowtime = DateTime.Now;
+            if (systemConfig.IsBasePartOpenLight)
+            {
+                if (positionIndex == 0 || positionIndex == 1)
+                {
+                    await SetLightBeforePhoto(procedure);
+                }
+            }
+            else
+            {
+                await SetLightBeforePhoto(procedure);
+            }
+
+            try
+            {
+                //拍照失败时,需要多次拍照
+                for (int i = 0; i < procedure.FeederFailLimit; i++)
+                {
+                    SendTaskMessage(Lang.开始执行视觉流程.Replace("{0}", $"{i + 1}").Replace("{1}", procedure.Name), MessageLevel.Info);
+
+                    // 1. 执行相机拍照并运行视觉工具
+                    bool visionSucceed = ExecutePhoto(i + 1, procedure, InputTerminal, out CogToolBlockTerminalCollection outputCollection, imgPara);
+                    if (!visionSucceed)
+                    {
+                        SendTaskMessage(Lang.视觉任务执行失败, MessageLevel.Error);
+                        continue;
+                    }
+
+                    if (!outputCollection.Contains("Found"))
+                    {
+                        SendTaskMessage(Lang.未找到视觉输出结果, MessageLevel.Error);
+                        await RecordPhotoCaptureAsync(procedure, robotCoord, false, new double[] { 0, 0, 0 }, new double[] { 0, 0, 0 }, string.Empty, positionIndex, remark);
+                        return (false, 0, 0, 0);
+                    }
+                    // 2. 获取视觉输出结果
+                    bool Found = (bool)(outputCollection["Found"].Value);
+                    if (!Found)
+                    {
+                        SendTaskMessage(Lang.拍照NG, MessageLevel.Debug);
+                        continue;
+                    }
+                    SendTaskMessage(Lang.拍照OK, MessageLevel.Debug);
+
+                    if (procedure.CalibrationId == Guid.Empty)
+                    {
+                        //没有关联校准时,则执行检测任务
+                        await RecordPhotoCaptureAsync(procedure, robotCoord, true, new double[] { 0, 0, 0 }, new double[] { 0, 0, 0 }, string.Empty, positionIndex, remark);
+                        return (true, 0, 0, 0);
+                    }
+
+                    // 3. 获取视觉输出结果
+                    LogHelper.WriteLogInfo(Lang.开始获取视觉输出结果);
+                    //获取相机校准
+                    var calib = _calibrationService.GetCalibration(procedure.CalibrationId);
+                    string points = (string)(outputCollection["Point"].Value);
+                    string[] strpoints = points.Split(';');
+                    //先将pos按照逗号进行分隔,拿到数组
+                    var posParts = strpoints[0].Split(',');
+                    double _pixel_x = double.Parse(posParts[0]);
+                    double _pixel_y = double.Parse(posParts[1]);
+                    double _pixel_u = double.Parse(posParts[2]);
+                    //转换点位-像素转换成机器人绝对坐标
+                    var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), robotCoord, calib, RobotBrand.XYZ_Platform);
+                    if (!calibResult.IsSucceed)
+                    {
+                        await RecordPhotoCaptureAsync(procedure, robotCoord, false, new double[] { _pixel_x, _pixel_y, _pixel_u }, new double[] { 0, 0, 0 }, string.Empty, positionIndex, remark);
+                        return (false, 0, 0, 0);
+                    }
+                    await RecordPhotoCaptureAsync(procedure, robotCoord, true, new double[] { _pixel_x, _pixel_y, _pixel_u }, new double[] { calibResult.X, calibResult.Y, calibResult.U }, string.Empty, positionIndex, remark);
+                    return (true, calibResult.X, calibResult.Y, calibResult.U);
+                }
+                SendTaskMessage(Lang.多次拍照失败, MessageLevel.Alarm);
+                await RecordPhotoCaptureAsync(procedure, robotCoord, false, new double[] { 0, 0, 0 }, new double[] { 0, 0, 0 }, string.Empty, positionIndex, remark);
+                return (false, 0, 0, 0);
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.执行相机取图并获取单点位时出错, ex);
+                await RecordPhotoCaptureAsync(procedure, robotCoord, false, new double[] { 0, 0, 0 }, new double[] { 0, 0, 0 }, string.Empty, positionIndex, remark);
+                return (false, 0, 0, 0);
+            }
+            finally
+            {
+                if (systemConfig.IsBasePartOpenLight)
+                {
+                    if (positionIndex == num)
+                    {
+                        await TurnOffLightAfterPhoto(procedure);
+                    }
+                }
+                else
+                {
+                    await TurnOffLightAfterPhoto(procedure);
+                }
+
+            }
+        }
+
+
         /// <summary>
         /// 执行相机拍照获取单个点位结果
         /// </summary>

+ 7 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -597,6 +597,9 @@
     <Compile Include="ViewModels\Statistics\LockResultRecoredQueryViewModel.cs" />
     <Compile Include="ViewModels\Product\VisionStaticAccuracyAnalyzerViewModel.cs" />
     <Compile Include="ViewModels\Product\VisionDynamicAccuracyAnalyzerViewModel.cs" />
+    <Compile Include="Views\AddScrewView.xaml.cs">
+      <DependentUpon>AddScrewView.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\DebugMod\FocusAnalyzerl.xaml.cs">
       <DependentUpon>FocusAnalyzerl.xaml</DependentUpon>
     </Compile>
@@ -1182,6 +1185,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\AddScrewView.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="Views\DebugMod\FocusAnalyzerl.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 31 - 10
TeamAAS-VM/ViewModels/HomeViewModel.cs

@@ -31,6 +31,7 @@ using TeamAAS_VP.Models;
 using TeamAAS_VP.Resources.Languages;
 using TeamAAS_VP.Services;
 using TeamAAS_VP.ViewModels.DebugMod;
+using TeamAAS_VP.Views;
 using TeamAAS_VP.Views.DebugMod;
 using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
 
@@ -45,6 +46,7 @@ namespace TeamAAS_VP.ViewModels
         IRemoteCommandService _remoteCommandService;
         ISystemDatabaseService _systemDatabaseService;
         IDialogService _dialogService;
+        IPlcService _plcService;
 
         #region 属性
         private PlotModel _PressurePlotModel = new PlotModel();
@@ -220,12 +222,13 @@ namespace TeamAAS_VP.ViewModels
         #endregion
 
         public HomeViewModel(IEventAggregator ea, IContainerProvider container, IProductService productService, IConfigService configService, IRemoteCommandService remoteCommandService
-            , ISystemDatabaseService systemDatabaseService, IDialogService dialogService)
+            , ISystemDatabaseService systemDatabaseService, IDialogService dialogService, IPlcService plcService)
         {
             _eventAggregator = ea;
             _container = container;
             _dialogService = dialogService;
             _productService = productService;
+            _plcService=plcService;
             LoadedCommand = new DelegateCommand(OnLoad);
             _eventAggregator.GetEvent<TaskMessageNotification>().Subscribe(AddMessageInvoke);
             //订阅权限登录事件
@@ -420,7 +423,7 @@ namespace TeamAAS_VP.ViewModels
         /// 重置计数
         /// </summary>
         async void ExecuteResetCounterCommand()
-        {
+        {           
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return;
             var view = new ShowMessage(Lang.计数清零, Lang.是否重置当前产品的计数);
@@ -583,16 +586,34 @@ namespace TeamAAS_VP.ViewModels
         /// </summary>
         private void ExecuteAddScrewCommand()
         {
-            _dialogService.ShowDialog("AddScrew", rst =>
+            //_dialogService.ShowDialog("AddScrew", rst =>
+            //{
+            //    //对话框关闭之后的回调函数,可以在这解析结果。
+            //    ButtonResult result1 = rst.Result;
+
+            //    if (result1 == ButtonResult.OK)
+            //    {
+            //        _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+            //    }
+            //});
+            var dialog = new AddScrewView(_configService, _eventAggregator, _plcService);
+            dialog.Owner = Application.Current.MainWindow;
+
+            // 显示弹窗,扫码枪结果会自动显示在输入框中
+            if (dialog.ShowDialog() == true)
             {
-                //对话框关闭之后的回调函数,可以在这解析结果。
-                ButtonResult result1 = rst.Result;
+                string scanResult = dialog.ScannedBarcode;
 
-                if (result1 == ButtonResult.OK)
-                {
-                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
-                }
-            });
+                _eventAggregator.GetEvent<SnackbarMessageNotification>()
+                    ?.Publish(new MessageParameter()
+                    {
+                        Msg = $"已扫描:{scanResult}",
+                        Duration = 1
+                    });
+
+                // 这里可以继续处理你的添加螺丝逻辑
+                // AddScrew(scanResult);
+            }
         }
 
         /// <summary>

+ 21 - 0
TeamAAS-VM/ViewModels/Product/ProductParamsViewModel.cs

@@ -149,6 +149,23 @@ namespace TeamAAS_VP.ViewModels.Product
             }
         }
 
+        private ProcedureModel _FzCameraProcedure;
+        /// <summary>
+        /// 翻转相机视觉流程
+        /// </summary>
+        public ProcedureModel FzCameraProcedure
+        {
+            get { return _FzCameraProcedure; }
+            set
+            {
+                SetProperty(ref _FzCameraProcedure, value);
+                if (value != null)
+                {
+                    SelectProduct.FzCameraProcedureId = value.Id;
+                }
+            }
+        }
+
         #endregion
 
         #region 命令
@@ -316,6 +333,10 @@ namespace TeamAAS_VP.ViewModels.Product
                 {
                     QrCodePhotoProcedure = ProcedureModels.FirstOrDefault(p => p.Id == SelectProduct.QrCodeProcedureId);
                 }
+                if (SelectProduct.FzCameraProcedureId != Guid.Empty)
+                {
+                    FzCameraProcedure = ProcedureModels.FirstOrDefault(p => p.Id == SelectProduct.FzCameraProcedureId);
+                }
             }
             catch (Exception ex)
             {

+ 45 - 0
TeamAAS-VM/Views/AddScrewView.xaml

@@ -0,0 +1,45 @@
+<Window x:Class="TeamAAS_VP.Views.AddScrewView"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:TeamAAS_VP.Views"
+        WindowStartupLocation="CenterOwner"
+        mc:Ignorable="d"
+        Title="AddScrewView" Height="450" Width="800">
+    <Grid Margin="20">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <TextBlock Grid.Row="0" 
+                   Text="请扫描螺丝条码:" 
+                   FontSize="14" 
+                   Margin="0,0,0,10"/>
+
+        <!-- 输入框会自动接收并显示扫码枪的结果 -->
+        <TextBox x:Name="BarcodeTextBox" 
+                 Grid.Row="1" 
+                 Height="30" 
+                 FontSize="14"
+                 Margin="0,0,0,20"/>
+
+        <StackPanel Grid.Row="2" 
+                    Orientation="Horizontal" 
+                    HorizontalAlignment="Right">
+            <Button x:Name="OkButton" 
+                    Content="确定" 
+                    Width="80" 
+                    Height="30" 
+                    Margin="0,0,10,0"
+                    Command="{Binding OkCommand}"/>
+            <Button x:Name="CancelButton" 
+                    Content="取消" 
+                    Width="80" 
+                    Height="30"
+                    Command="{Binding CancelCommand}"/>
+        </StackPanel>
+    </Grid>
+</Window>

+ 288 - 0
TeamAAS-VM/Views/AddScrewView.xaml.cs

@@ -0,0 +1,288 @@
+using Prism.Events;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using TeamAAS_VP.Core.PLCs;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Events;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Resources.Languages;
+using TeamAAS_VP.Services;
+using TouchSocket.Core;
+
+namespace TeamAAS_VP.Views
+{
+    /// <summary>
+    /// AddScrewView.xaml 的交互逻辑
+    /// </summary>
+    public partial class AddScrewView : Window
+    {
+
+        IConfigService _configService;
+        IEventAggregator _eventAggregator;
+        IPlcService _plcService;
+        public ICommand OkCommand { get; }
+        public ICommand CancelCommand { get; }
+        public string ScannedBarcode { get; private set; }
+        public AddScrewView(IConfigService configService, IEventAggregator eventAggregator, IPlcService plcService)
+        {
+            InitializeComponent();
+            _configService = configService;
+            _eventAggregator = eventAggregator;
+            _plcService=plcService;
+
+            OkCommand = new RelayCommand(OkExecute);
+            CancelCommand = new RelayCommand(CancelExecute);
+
+            this.Loaded += (s, e) => { BarcodeTextBox.Focus(); };
+
+            // 把Command赋值给DataContext,方便绑定
+            DataContext = this;
+        }
+        
+        ///// <summary>
+        ///// PLC命令触发时
+        ///// </summary>
+        ///// <param name="tuple"></param>
+        //private async void PlcCommandTrigger((string key, string nodeId, object value) tuple)
+        //{
+        //    try
+        //    {
+        //        if (tuple.key != "AutoSensor")
+        //        {
+        //            return;
+        //        }
+
+        //        var addressConfig = _configService.GetPlcAddresses();
+
+        //        var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
+
+        //        string a = "";
+        //        var stationConfig = _configService.GetDeviceInfo(0);
+        //        ScannedBarcode = BarcodeTextBox.Text;
+        //        if (ScannedBarcode.Contains("|"))
+        //        {
+        //            string[] arr = ScannedBarcode.Split("|");
+        //            for (int i = 0; i < 16; i++)
+        //            {
+        //                a += arr[i];
+        //            }
+        //        }
+        //        else
+        //        {
+        //            a = ScannedBarcode;
+        //        }
+        //        if (string.IsNullOrWhiteSpace(ScannedBarcode))
+        //        {
+        //            MessageBox.Show("请先扫描螺丝条码", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+        //            BarcodeTextBox.Focus(); // 重新聚焦
+        //            return;
+        //        }
+        //        if (a == stationConfig.DataInfo.Screwsize1 || a == stationConfig.DataInfo.Screwsize2)
+        //        {
+        //            MessageBox.Show("螺丝型号匹配成功", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+        //            //SendTaskMessage($"螺丝型号匹配成功", MessageLevel.Info);
+        //            ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, true, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+        //            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, 1), (Int16)1);
+        //        }
+        //        else
+        //        {
+        //            MessageBox.Show("螺丝型号匹配失败", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+        //            //SendTaskMessage($"螺丝型号匹配失败", MessageLevel.Error);
+        //            ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, false, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+        //            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, 1), (Int16)1);
+        //        }
+        //        DialogResult = true;
+        //        Close();
+        //    }
+        //    catch (Exception ex)
+        //    {
+
+        //    }
+        //}
+        private async void OkExecute(object sender)
+        {
+            try
+            {
+                var addressConfig = _configService.GetPlcAddresses();
+
+                var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
+
+                string a = "";
+                var stationConfig = _configService.GetDeviceInfo(0);
+                ScannedBarcode = BarcodeTextBox.Text;
+                if (ScannedBarcode.Contains("|"))
+                {
+                    string[] arr = ScannedBarcode.Split("|");
+                    for (int i = 0; i < 16; i++)
+                    {
+                        a += arr[i];
+                    }
+                }
+                else
+                {
+                    a = ScannedBarcode;
+                }
+                if (string.IsNullOrWhiteSpace(ScannedBarcode))
+                {
+                    MessageBox.Show("请先扫描螺丝条码", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+                    BarcodeTextBox.Focus(); // 重新聚焦
+                    return;
+                }
+                if (a == stationConfig.DataInfo.Screwsize1 || a == stationConfig.DataInfo.Screwsize2)
+                {
+                    await plc.WriteNodeAsync<bool>(addressConfig.Out_PlcFail.Address, false);
+                    MessageBox.Show("螺丝型号匹配成功", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+                    //SendTaskMessage($"螺丝型号匹配成功", MessageLevel.Info);
+                    ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, stationConfig.DataInfo, true, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+                }
+                else
+                {
+                    await plc.WriteNodeAsync<bool>(addressConfig.Out_PlcFail.Address, true);
+                    MessageBox.Show("螺丝型号匹配失败", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+
+                    //SendTaskMessage($"螺丝型号匹配失败", MessageLevel.Error);
+                    ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, stationConfig.DataInfo, false, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+                }
+                DialogResult = true;
+                Close();
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+
+            //PlcCommandTrigger(("AutoSensor", "", ""));
+            //string a = "";
+
+            //var stationConfig = _configService.GetDeviceInfo(0);
+            //ScannedBarcode = BarcodeTextBox.Text;
+            //if (ScannedBarcode.Contains("|"))
+            //{
+            //    string[] arr = ScannedBarcode.Split("|");
+            //    for (int i = 0; i < 16; i++)
+            //    {
+            //        a += arr[i];
+            //    }
+            //}
+            //else
+            //{
+            //    a = ScannedBarcode;
+            //}
+            //if (string.IsNullOrWhiteSpace(ScannedBarcode))
+            //{
+            //    MessageBox.Show("请先扫描螺丝条码", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+            //    BarcodeTextBox.Focus(); // 重新聚焦
+            //    return;
+            //}
+            //if (a == stationConfig.DataInfo.Screwsize1 || a == stationConfig.DataInfo.Screwsize2)
+            //{
+            //    MessageBox.Show("螺丝型号匹配成功", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+            //    //SendTaskMessage($"螺丝型号匹配成功", MessageLevel.Info);
+            //    ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, true, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+            //}
+            //else
+            //{
+            //    MessageBox.Show("螺丝型号匹配失败", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+            //    //SendTaskMessage($"螺丝型号匹配失败", MessageLevel.Error);
+            //    ExportDataToCsv(DateTime.Now.ToString("yyyy-MM-dd HH:mm"), a, false, @"D:\添加螺丝记录\螺丝上料Log.csv", appendMode: true);
+            //}
+            //DialogResult = true;
+            //Close();
+        }
+
+        private void CancelExecute(object sender)
+        {
+            DialogResult = false;
+            Close();
+        }
+
+        //public void SendTaskMessage(string msg, MessageLevel level)
+        //{
+        //    App.Current.Dispatcher.Invoke(() =>aZ
+        //    {
+        //        _eventAggregator.GetEvent<TaskMessageNotification>().Publish(new Models.MessageStruct() { Message = msg, level = level });
+        //    });
+        //}
+        public static void ExportDataToCsv(string dateTime, string model, CTQ_Data DataInfo, bool isMatch, string savePath, bool appendMode = false)
+        {
+            string directory = global::System.IO.Path.GetDirectoryName(savePath);
+
+            // 如果目录路径不为空且不存在,则创建
+            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+            {
+                Directory.CreateDirectory(directory);
+            }
+
+            // 构建CSV内容
+            StringBuilder csvContent = new StringBuilder();
+
+            // 处理表头:仅当文件不存在/非追加模式时,才写入表头
+            if (!appendMode || !File.Exists(savePath))
+            {
+                csvContent.AppendLine("日期时间,目标螺丝型号1,螺丝型号2,添加螺丝型号,是否匹配");
+            }
+
+            // 处理数据行
+            string matchStr = isMatch ? "是" : "否";
+
+            // 如果型号中包含逗号,需要用引号包裹
+            string safeModel = model;
+            if (safeModel.Contains(","))
+            {
+                safeModel = $"\"{safeModel}\"";
+            }
+
+            csvContent.AppendLine($"{dateTime},{DataInfo.Screwsize1},{DataInfo.Screwsize2},{model},{matchStr}");
+
+            // 写入文件:UTF-8带BOM编码,Excel打开无乱码
+            if (appendMode && File.Exists(savePath))
+            {
+                // 追加模式:在文件末尾新增内容
+                File.AppendAllText(savePath, csvContent.ToString(), Encoding.UTF8);
+            }
+            else
+            {
+                // 覆盖模式:新建/替换文件
+                File.WriteAllText(savePath, csvContent.ToString(), Encoding.UTF8);
+            }
+        }
+
+    }
+    public class RelayCommand : ICommand
+    {
+        private readonly Action<object> _execute;
+        private readonly Func<object, bool> _canExecute;
+
+        public event EventHandler CanExecuteChanged
+        {
+            add { CommandManager.RequerySuggested += value; }
+            remove { CommandManager.RequerySuggested -= value; }
+        }
+
+        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
+        {
+            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
+            _canExecute = canExecute;
+        }
+
+        public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
+
+        public void Execute(object parameter) => _execute(parameter);
+    }
+
+}

+ 24 - 0
TeamAAS-VM/Views/Product/ProductParams.xaml

@@ -201,6 +201,7 @@
                         <RowDefinition Height="auto" />
                         <RowDefinition Height="auto" />
                         <RowDefinition Height="auto" />
+                        <RowDefinition Height="auto" />
                     </Grid.RowDefinitions>
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="auto" />
@@ -437,6 +438,29 @@
                             </DataTemplate>
                         </ComboBox.ItemTemplate>
                     </ComboBox>
+                    <!--翻转相机流程id-->
+                    <TextBlock Grid.Row="6"
+                               Grid.Column="0"
+                               Text="翻转相机流程ID: " />
+                    <ComboBox Grid.Row="6"
+                              Grid.Column="1"
+                              ItemsSource="{Binding ProcedureModels}"
+                              SelectedItem="{Binding FzCameraProcedure}"
+                              materialDesign:TextFieldAssist.HasClearButton="True"
+                              materialDesign:HintAssist.Hint="{lex:Loc 选择需要配置的流程}">
+                        <ComboBox.ItemTemplate>
+                            <DataTemplate>
+                                <StackPanel Orientation="Horizontal">
+                                    <materialDesign:PackIcon Kind="VideoOutline" />
+                                    <TextBlock Text="{Binding CameraName}"
+                                               Margin="6,0" />
+                                    <materialDesign:PackIcon Kind="ProgressWrench" />
+                                    <TextBlock Text="{Binding Name}"
+                                               Margin="6,0" />
+                                </StackPanel>
+                            </DataTemplate>
+                        </ComboBox.ItemTemplate>
+                    </ComboBox>
                 </Grid>
             </Grid>
 

+ 43 - 10
TeamAAS-VM/Views/SettingView.xaml

@@ -541,18 +541,29 @@
                                     Margin="6,2"
                                     Text="{Binding SelectDeviceInfo.ApiKey, Mode=TwoWay}" />
 
-                                <CheckBox
+                                <StackPanel
                                     Grid.Row="11"
                                     Grid.Column="1"
-                                    Margin="6,2"
-                                    Command="{Binding CheckStatusCommand}"
-                                    Content="启用 MES"
-                                    IsChecked="{Binding SelectDeviceInfo.EnableMES, Mode=TwoWay}" />
-                                <!--<CheckBox Grid.Row="10"
-                                          Grid.Column="1"
-                                          Content="是否启用两次产品编号"
-                                          IsChecked="{Binding SelectDeviceInfo.EnableDoubleMes, Mode=TwoWay}"
-                                          Margin="6,2" />-->
+                                    Orientation="Horizontal">
+                                    <CheckBox
+     Grid.Row="11"
+     Grid.Column="1"
+     Margin="6,2"
+     Command="{Binding CheckStatusCommand}"
+     Content="启用 MES"
+     IsChecked="{Binding SelectDeviceInfo.EnableMES, Mode=TwoWay}" />
+                                    <!--<CheckBox Grid.Row="10"
+           Grid.Column="1"
+           Content="是否启用两次产品编号"
+           IsChecked="{Binding SelectDeviceInfo.EnableDoubleMes, Mode=TwoWay}"
+           Margin="6,2" />-->
+                                    <CheckBox
+     Margin="6,2"
+     Command="{Binding CheckStatusCommand}"
+     Content="是否启用锁螺丝螺丝XY超限(目前只有组装线锁电池启用)"
+     IsChecked="{Binding SelectDeviceInfo.ScrewOverrun, Mode=TwoWay}" />
+                                </StackPanel>
+                               
                                 <StackPanel
                                     Grid.Row="13"
                                     Grid.Column="1"
@@ -698,6 +709,28 @@
                                     Grid.Column="1"
                                     Margin="6,2"
                                     Text="{Binding SelectDeviceInfo.DataInfo.PROCESS_MACHINE_ID, Mode=TwoWay}" />
+                                <TextBlock
+    Grid.Row="24"
+    Grid.Column="0"
+    HorizontalAlignment="Right"
+    VerticalAlignment="Center"
+    Text="螺丝型号1:" />
+                                <TextBox
+    Grid.Row="24"
+    Grid.Column="1"
+    Margin="6,2"
+    Text="{Binding SelectDeviceInfo.DataInfo.Screwsize1, Mode=TwoWay}" />
+                                <TextBlock
+Grid.Row="25"
+Grid.Column="0"
+HorizontalAlignment="Right"
+VerticalAlignment="Center"
+Text="螺丝型号2:" />
+                                <TextBox
+Grid.Row="25"
+Grid.Column="1"
+Margin="6,2"
+Text="{Binding SelectDeviceInfo.DataInfo.Screwsize2, Mode=TwoWay}" />
                             </Grid>
                         </Grid>
                     </ScrollViewer>