KWD 3 месяцев назад
Родитель
Сommit
6451432628

+ 116 - 8
TeamAAS-VM/Core/Management.cs

@@ -1223,7 +1223,23 @@ namespace TeamAAS_VP.Core
                 monitorNodeIds.Add(string.Format(addressConfig.In_MesCheck.Address, i));
             }
             //订阅PLC地址变化
+            //plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling);
+
             plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling);
+
+            // 2. 监听重连事件,重连成功后自动重新订阅
+            plc.ConnectChangedEvent -= Plc_ConnectChanged; // 防止重复注册
+            plc.ConnectChangedEvent += Plc_ConnectChanged;
+
+            // 定义重连处理方法(写在同一个类里即可)
+            void Plc_ConnectChanged(object sender, bool isConnected)
+            {
+                if (isConnected)
+                {
+                    // 重连成功后,重新订阅节点
+                    plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling);
+                }
+            }
         }
 
         /// <summary>
@@ -1261,8 +1277,8 @@ namespace TeamAAS_VP.Core
             {
                 foreach (var item in current.PickPoints)
                 {
-                    //await item.WriteToPlcAddress(addressConfig.Out_Pick, plc, item.Feeder);
-                    await item.WriteToPlcAddress(addressConfig.Out_Pick, plc);
+                    await item.WriteToPlcAddress(addressConfig.Out_Pick, plc, item.Feeder);
+                    //await item.WriteToPlcAddress(addressConfig.Out_Pick, plc);
                 }
             }
             if (current.SecPosCameraPoints != null && current.SecPosCameraPoints.Count > 0)
@@ -1398,13 +1414,19 @@ namespace TeamAAS_VP.Core
             {
                 if (tuple.key != "AutoSensor")
                 {
+                    SendTaskMessage("PLC指令错误", MessageLevel.Debug);
                     return;
                 }
 
                 var addressConfig = _configService.GetPlcAddresses();
 
                 var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
-                if (plc == null || !plc.IsConnected) return;
+                if (plc == null || !plc.IsConnected)
+                {
+                    SendTaskMessage("plc为空", MessageLevel.Debug);
+                    return;
+                }
+
                 // 获取当前产品
                 var currentProduct = _productService.GetCurrentProduct();
                 if (currentProduct == null)
@@ -1676,10 +1698,21 @@ namespace TeamAAS_VP.Core
                                 {
                                     UpCameraPutResults[1] = new PointF((float)visionResult.X, (float)visionResult.Y);
                                 }
-                                else
+                                else if(systemConfig.WorkMode==2)
                                 {
                                     UpCameraPutResults[cameraIndex] = new PointF((float)visionResult.X, (float)visionResult.Y);
                                 }
+                                else if(systemConfig.WorkMode==3)
+                                {
+                                    if (cameraIndex <= 3)
+                                    {
+                                        UpCameraPutResults[1] = new PointF((float)visionResult.X, (float)visionResult.Y);
+                                    }
+                                    else
+                                    {
+                                        UpCameraPutResults[cameraIndex] = new PointF((float)visionResult.X, (float)visionResult.Y);
+                                    }
+                                }
                                 if (resultToMes.Contains($"Screw{cameraIndex}_Fail"))
                                 {
                                     resultToMes.Remove($"Screw{cameraIndex}_Fail");
@@ -2339,6 +2372,18 @@ namespace TeamAAS_VP.Core
                             {
                                 lastRes = true;
                             }
+                            if (dataToMes.Keys.Contains($"Torque{lockResult.Number}"))
+                            {
+                                dataToMes.Remove($"Torque{lockResult.Number}");
+                            }
+                            if (dataToMes.Keys.Contains($"Turn{lockResult.Number}"))
+                            {
+                                dataToMes.Remove($"Turn{lockResult.Number}");
+                            }
+                            if (dataToMes.Keys.Contains($"Pressure{lockResult.Number}"))
+                            {
+                                dataToMes.Remove($"Pressure{lockResult.Number}");
+                            }
                             lockResult.LockPassed = lastRes;
                             dataToMes.Add($"Torque{lockResult.Number}", $"{lockResult.LockTorque:F2}");
                             dataToMes.Add($"Turn{lockResult.Number}", $"{lockResult.LockTurns:F2}");
@@ -2392,7 +2437,7 @@ namespace TeamAAS_VP.Core
                             return;
                         }
                         if (state == 1)
-                        {
+                        {                           
                             SendTaskMessage(Lang.收到多相机坐标合并请求开始合并, MessageLevel.Debug);
                             //位置编号
                             Int16 positionIndex = plc.ReadNode<Int16>(addressConfig.In_ProductNum.Address);
@@ -2426,10 +2471,10 @@ namespace TeamAAS_VP.Core
                                     await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
                                     return;
                                 }
-                                //验证当前锁附点位与设置锁附点位的差值是否超限,超限则报警并停止流程
-                                //目前验证已OK 缺少切换按钮
-                                if (_configService.GetDeviceInfo().ScrewOverrun)
+                                if (_configService.GetDeviceInfo().EnableXY)
                                 {
+                                    //验证当前锁附点位与设置锁附点位的差值是否超限,超限则报警并停止流程
+                                    //目前验证已OK 缺少切换按钮
                                     try
                                     {
                                         var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1);
@@ -2502,6 +2547,59 @@ namespace TeamAAS_VP.Core
                                 }
                                 await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)currentProduct.ScrewPoints.Count);
                             }
+                            //拍2打所有
+                            else if (systemConfig.WorkMode == 3)
+                            {
+                                if (positionIndex <= 3)
+                                {
+                                    List<PlcPoint> plcPoints = new List<PlcPoint>();
+                                    foreach (var item in currentProduct.ScrewPoints)
+                                    {
+                                        if (item.IsUse)
+                                        {
+                                            plcPoints.Add(item);
+                                        }
+                                    }
+                                    var point = plcPoints[positionIndex - 1];
+                                    if (point == null)
+                                    {
+                                        SendTaskMessage($"点:{positionIndex}不存在", MessageLevel.Info);
+                                        await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2);
+                                        return;
+                                    }
+                                    point = point.Clone();
+                                    point.X_Position = UpCameraPutResults[1].X + DowmCameraResults[1].X;
+                                    point.Y_Position = UpCameraPutResults[1].Y + DowmCameraResults[1].Y;
+                                    SendTaskMessage($"点:{positionIndex},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info);
+                                    await point.WriteToPlcAddress(addressConfig.Out_Screw, plc, positionIndex);
+                                }
+                                else
+                                {
+                                    //根据UpCameraPutResults[]中的索引1、2,和拍照点对应的Local下的坐标,从创建Local坐标系
+                                    PointF worldP1 = new PointF(UpCameraPutResults[4].X, UpCameraPutResults[4].Y);
+                                    PointF worldP2 = new PointF(UpCameraPutResults[5].X, UpCameraPutResults[5].Y);
+                                    PointF localP1 = new PointF(currentProduct.ScrewCameraLocalPos1.X_Position, currentProduct.ScrewCameraLocalPos1.Y_Position);
+                                    PointF localP2 = new PointF(currentProduct.ScrewCameraLocalPos2.X_Position, currentProduct.ScrewCameraLocalPos2.Y_Position);
+                                    CoordinateTransformer local = new CoordinateTransformer(worldP1, worldP2, localP1, localP2);
+
+                                    for (int i = 0; i < currentProduct.ScrewPoints.Count; i++)
+                                    {
+                                        var localPoint = new PointF(currentProduct.ScrewPoints[i].X_Position + currentProduct.ScrewPoints[i].X_Offset,
+                                            currentProduct.ScrewPoints[i].Y_Position + currentProduct.ScrewPoints[i].Y_Offset);
+                                        var worldPoint = local.ToOldCoord(localPoint.X, localPoint.Y);
+                                        var point = currentProduct.ScrewPoints[i].Clone();
+
+                                        //锁付点位在Local中已经加了偏移,这里写回PLC时需要将偏移清零,否则写入PLC端时会重复计算偏移
+                                        point.X_Offset = 0;
+                                        point.Y_Offset = 0;
+                                        point.X_Position = (float)worldPoint[0];
+                                        point.Y_Position = (float)worldPoint[1];
+                                        SendTaskMessage($"点:{i},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info);
+                                        await point.WriteToPlcAddress(addressConfig.Out_Screw, plc);
+                                    }
+                                    await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)currentProduct.ScrewPoints.Count);
+                                }                              
+                            }
                             await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)1);
                         }
                         else
@@ -2729,6 +2827,15 @@ namespace TeamAAS_VP.Core
                         {
                             if (tuple.value is Int16 state)
                             {
+
+                                //var oldState = _LastMesCheck[i];
+                                //_LastMesCheck[i] = state;
+                                //SendTaskMessage($"站点{i},收到PLC状态为:{state},旧状态为:{oldState}",MessageLevel.Info);
+                                //if (oldState == state)
+                                //{
+                                //    return;
+                                //}
+
                                 if (_LastMesCheck[i] != state)
                                 {
                                     _LastMesCheck[i] = state;
@@ -3664,6 +3771,7 @@ namespace TeamAAS_VP.Core
                                     await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)0);
                                     return;
                                 }
+                               // _LastMesCheck[i] = 0;
                             }
                         }
                     }

+ 1 - 0
TeamAAS-VM/Core/PLCs/OPCuaClientPLC.cs

@@ -83,6 +83,7 @@ namespace TeamAAS_VP.Core.PLCs
         /// 在未显式传入订阅选项时,`SubscribeNodes` 将使用该模式决定采用 OPC UA 原生订阅还是客户端轮询订阅。
         /// 默认值为 <see cref="OpcUaSubscriptionMode.Native"/>。
         /// </summary>
+        //public OpcUaSubscriptionMode DefaultSubscriptionMode { get; set; } = OpcUaSubscriptionMode.CustomPolling;
         public OpcUaSubscriptionMode DefaultSubscriptionMode { get; set; } = OpcUaSubscriptionMode.Native;
 
         /// <summary>

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

@@ -51,6 +51,12 @@ namespace TeamAAS_VP.Models
         /// </summary>
         public bool EnableMES { get => _enableMes; set => SetProperty(ref _enableMes, value); }
 
+        private bool _enableXY;
+        /// <summary>
+        /// 是否启用锁螺丝XY超限 通讯功能,默认不启用
+        /// </summary>
+        public bool EnableXY { get => _enableXY; set => SetProperty(ref _enableXY, value); }
+
         private string _mesUrl;
         /// <summary>
         /// URL
@@ -133,6 +139,7 @@ namespace TeamAAS_VP.Models
             LogUrl = string.Empty;
             ApiKey = string.Empty;
             EnableMES = false;
+            EnableXY = false;
             SaveCsvPPID = true;
         }
 
@@ -150,6 +157,7 @@ namespace TeamAAS_VP.Models
                 comp = this.comp,
                 MesUrl = this.MesUrl,
                 EnableMES = this.EnableMES,
+                EnableXY = this.EnableXY,
                 SaveCsv = this.SaveCsv,
                 ScrewOverrun=this.ScrewOverrun,
                 SaveCsvPath = this.SaveCsvPath,

+ 29 - 0
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,17 @@ 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
@@ -912,6 +931,11 @@ 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";
@@ -1367,6 +1391,11 @@ namespace TeamAAS_VP.Models.PLC
             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螺丝到位
 

+ 1 - 1
TeamAAS-VM/Models/SystemConfiguration.cs

@@ -99,7 +99,7 @@ namespace TeamAAS_VP.Models
 
         private Int16 _WorkMode = 0;
         /// <summary>
-        /// 工作模式 =1 拍1打1 =2 拍1打所有
+        /// 工作模式 =1 拍1打1 =2 拍1打所有 =3先拍1打1打3颗再拍2打所有
         /// </summary>
         public Int16 WorkMode
         {

+ 23 - 0
TeamAAS-VM/Resources/Languages/Lang.Designer.cs

@@ -1941,6 +1941,29 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
 
+
+        /// <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>
         ///   查找类似 下相机矫正披头的结果,1:OK,2:NG 的本地化字符串。
         /// </summary>

+ 84 - 84
TeamAAS-VM/Services/ConfigService.cs

@@ -71,7 +71,7 @@ namespace TeamAAS_VP.Services
         // 锟斤拷源锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
         private ObservableCollection<LightControllerConfig> LightControllers { get; set; }
 
-        // Device info - 鏀�寔澶氳�澶
+        // Device info - 鏀�寔澶氳�澶?
         private List<DeviceInfo> _deviceInfoList;
         public CTQ_Data CtqData { get; set; }
 
@@ -101,7 +101,7 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 锟斤拷始锟斤拷一锟斤拷锟铰碉拷 <see cref="ConfigService"/> 实锟斤拷锟斤拷
-        /// 锟斤拷锟届函锟斤拷锟斤拷锟斤拷锟皆讹拷锟斤拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷锟斤拷锟斤拷锟节癸拷锟斤拷锟节硷拷锟斤拷锟I/O 锟斤拷锟斤拷锟斤拷锟斤拷锟缴碉拷锟斤拷 <see cref="LoadAll"/> 锟斤拷 <see cref="LoadAllAsync"/> 锟斤拷式锟斤拷锟截★拷
+        /// 锟斤拷锟届函锟斤拷锟斤拷锟斤拷锟皆讹拷锟斤拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷锟斤拷锟斤拷锟节癸拷锟斤拷锟节硷拷锟斤拷锟?I/O 锟斤拷锟斤拷锟斤拷锟斤拷锟缴碉拷锟斤拷 <see cref="LoadAll"/> 锟斤拷 <see cref="LoadAllAsync"/> 锟斤拷式锟斤拷锟截★拷
         /// </summary>
         public ConfigService()
         {
@@ -117,7 +117,7 @@ namespace TeamAAS_VP.Services
         {
             lock (_sync)
             {
-                // 锟斤拷取锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷募锟斤拷锟斤拷锟斤拷冢锟斤拷虮3锟DeviceConfig 默锟斤拷值
+                // 锟斤拷取锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷募锟斤拷锟斤拷锟斤拷冢锟斤拷虮3锟?DeviceConfig 默锟斤拷值
                 if (File.Exists(ConfigPaths.CamerasConfigurationPath))
                     Cameras = FileHelper.ReadJsonFile<ObservableCollection<CameraInfo>>(ConfigPaths.CamerasConfigurationPath);
                 else
@@ -211,7 +211,7 @@ namespace TeamAAS_VP.Services
                     FileHelper.WriteJsonFile(_screwDriverConfig, ConfigPaths.ScrewDriverConfigurationPath);
                 }
 
-                // load device info list (鏀�寔澶氳�澶�)
+                // load device info list (鏀�寔澶氳�澶?
                 if (File.Exists(ConfigPaths.DeviceInfoConfigurationPath))
                 {
                     try
@@ -263,7 +263,7 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 锟斤拷锟节达拷锟叫碉拷锟斤拷锟斤拷锟斤拷锟斤拷写锟截碉拷锟斤拷应锟斤拷锟斤拷锟侥硷拷锟斤拷
-        /// 锟竭程帮拷全锟斤拷锟斤拷锟斤拷锟节诧拷锟斤拷写锟斤拷锟斤拷锟绞癸拷锟<see cref="_sync"/> 锟斤拷锟斤拷锟斤拷
+        /// 锟竭程帮拷全锟斤拷锟斤拷锟斤拷锟节诧拷锟斤拷写锟斤拷锟斤拷锟绞癸拷锟?<see cref="_sync"/> 锟斤拷锟斤拷锟斤拷
         /// </summary>
         public void SaveAll()
         {
@@ -285,7 +285,7 @@ namespace TeamAAS_VP.Services
                     FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
                 // save plc addresses
                 //FileHelper.WriteJsonFile(PlcAddressConfig, "..//Config//PlcAddressConfiguration.cfg");
-                // save device info list (鏀�寔澶氳�澶�)
+                // save device info list (鏀�寔澶氳�澶?
                 if (_deviceInfoList != null)
                     FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
                 // save card reader config
@@ -297,7 +297,7 @@ namespace TeamAAS_VP.Services
         /// <summary>
         /// 锟届步执锟斤拷 <see cref="SaveAll"/> 锟斤拷锟斤拷锟斤拷
         /// </summary>
-        /// <returns>锟斤拷示锟届步锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟�</returns>
+        /// <returns>锟斤拷示锟届步锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟?/returns>
         public Task SaveAllAsync()
         {
             return Task.Run(() => SaveAll());
@@ -305,19 +305,19 @@ namespace TeamAAS_VP.Services
 
         #region Cameras
         /// <summary>
-        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷玫锟街伙拷锟斤拷锟斤拷稀锟
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷玫锟街伙拷锟斤拷锟斤拷稀锟?
         /// 锟竭程帮拷全锟斤拷锟斤拷锟节诧拷锟斤拷锟斤拷锟皆憋拷证锟斤拷锟斤拷锟斤拷取锟斤拷一锟斤拷锟皆★拷
         /// </summary>
-        /// <returns>锟斤拷锟斤拷锟斤拷玫锟街伙拷锟斤拷锟斤拷稀锟�</returns>
+        /// <returns>锟斤拷锟斤拷锟斤拷玫锟街伙拷锟斤拷锟斤拷稀锟?/returns>
         public IReadOnlyCollection<CameraInfo> GetAllCameras()
         {
             lock (_sync) { return Cameras.ToList().AsReadOnly(); }
         }
 
         /// <summary>
-        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟较�拷锟
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟较�拷锟?
         /// </summary>
-        /// <param name="id">锟斤拷锟斤拷锟Guid 锟斤拷识锟斤拷</param>
+        /// <param name="id">锟斤拷锟斤拷锟?Guid 锟斤拷识锟斤拷</param>
         /// <returns>锟揭碉拷锟津返回讹拷应锟斤拷 <see cref="CameraInfo"/>锟斤拷锟斤拷锟津返伙拷 <c>null</c>锟斤拷</returns>
         public CameraInfo GetCamera(Guid id)
         {
@@ -327,11 +327,11 @@ namespace TeamAAS_VP.Services
         /// <summary>
         /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫★拷
         /// - 锟斤拷锟斤拷锟斤拷 <paramref name="camera"/> 为 <c>null</c> 锟斤拷锟斤拷 <c>null</c>锟斤拷
-        /// - 锟斤拷 Id 锟窖达拷锟斤拷锟斤拷锟芥换锟斤拷锟斤拷锟斤拷追锟接诧拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷 CameraNo 锟斤拷拧锟
+        /// - 锟斤拷 Id 锟窖达拷锟斤拷锟斤拷锟芥换锟斤拷锟斤拷锟斤拷追锟接诧拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷 CameraNo 锟斤拷拧锟?
         /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷峤�拷锟斤拷锟斤拷锟较持久伙拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷
         /// </summary>
         /// <param name="camera">要锟斤拷锟接伙拷锟斤拷碌锟斤拷锟斤拷锟斤拷锟矫★拷</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="CameraInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="CameraInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public CameraInfo AddOrUpdateCamera(CameraInfo camera)
         {
             if (camera == null) return null;
@@ -354,10 +354,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟捷憋拷识锟狡筹拷锟斤拷锟斤拷锟斤拷貌锟斤拷锟斤拷卤锟斤拷剩锟斤拷锟斤拷锟斤拷锟CameraNo锟斤拷
+        /// 锟斤拷锟捷憋拷识锟狡筹拷锟斤拷锟斤拷锟斤拷貌锟斤拷锟斤拷卤锟斤拷剩锟斤拷锟斤拷锟斤拷锟?CameraNo锟斤拷
         /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷峤�拷锟斤拷锟斤拷锟较持久伙拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷
         /// </summary>
-        /// <param name="id">要锟狡筹拷锟斤拷锟斤拷锟Guid锟斤拷</param>
+        /// <param name="id">要锟狡筹拷锟斤拷锟斤拷锟?Guid锟斤拷</param>
         /// <returns>锟斤拷锟揭碉拷锟斤拷锟缴癸拷锟狡筹拷锟斤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
         public bool RemoveCamera(Guid id)
         {
@@ -366,7 +366,7 @@ namespace TeamAAS_VP.Services
             {
                 var exist = Cameras.FirstOrDefault(c => c.Id == id);
                 if (exist != null) result = Cameras.Remove(exist);
-                //锟斤拷锟铰憋拷锟
+                //锟斤拷锟铰憋拷锟?
                 int no = 1;
                 //锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟紺ameraNo锟斤拷锟斤拷
                 var sortedCameras = Cameras.OrderBy(c => c.CameraNo).ToList();
@@ -386,18 +386,18 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟斤拷欠锟斤拷锟斤拷指锟斤拷 Id 锟斤拷锟斤拷锟斤拷锟斤拷谩锟
+        /// 锟斤拷锟斤拷欠锟斤拷锟斤拷指锟斤拷 Id 锟斤拷锟斤拷锟斤拷锟斤拷谩锟?
         /// </summary>
-        /// <param name="id">锟斤拷锟斤拷锟Guid锟斤拷</param>
+        /// <param name="id">锟斤拷锟斤拷锟?Guid锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
         public bool ContainsCamera(Guid id) { lock (_sync) { return Cameras.Any(c => c.Id == id); } }
         #endregion
 
         #region DeviceInfo
         /// <summary>
-        /// 鑾峰彇鎵€鏈夎�澶囦俊鎭�厤缃�垪琛
+        /// 鑾峰彇鎵€鏈夎�澶囦俊鎭�厤缃�垪琛?
         /// </summary>
-        /// <returns>璁惧�淇℃伅鍒楄〃鐨勫彧璇婚泦鍚�</returns>
+        /// <returns>璁惧�淇℃伅鍒楄〃鐨勫彧璇婚泦鍚?/returns>
         public IReadOnlyCollection<DeviceInfo> GetAllDeviceInfos()
         {
             lock (_sync)
@@ -460,7 +460,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 娣诲姞鎴栨洿鏂拌�澶囦俊鎭
+        /// 娣诲姞鎴栨洿鏂拌�澶囦俊鎭?
         /// </summary>
         /// <param name="deviceInfo">璁惧�淇℃伅瀵硅薄</param>
         public void SaveDeviceInfo(DeviceInfo deviceInfo)
@@ -484,17 +484,17 @@ namespace TeamAAS_VP.Services
                 }
                 else
                 {
-                    // 娣诲姞鏂拌�澶
+                    // 娣诲姞鏂拌�澶?
                     _deviceInfoList.Add(deviceInfo);
                 }
-                // 鎸夎�澶囩紪鍙锋帓搴
+                // 鎸夎�澶囩紪鍙锋帓搴?
                 _deviceInfoList = _deviceInfoList.OrderBy(d => d.DeviceNo).ToList();
                 FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
             }
         }
 
         /// <summary>
-        /// 淇濆瓨鎵€鏈夎�澶囦俊鎭�垪琛
+        /// 淇濆瓨鎵€鏈夎�澶囦俊鎭�垪琛?
         /// </summary>
         /// <param name="deviceInfoList">璁惧�淇℃伅鍒楄〃</param>
         public void SaveAllDeviceInfos(IEnumerable<DeviceInfo> deviceInfoList)
@@ -508,7 +508,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 娣诲姞鏂拌�澶
+        /// 娣诲姞鏂拌�澶?
         /// </summary>
         /// <returns>鏂版坊鍔犵殑璁惧�</returns>
         public DeviceInfo AddDeviceInfo()
@@ -520,7 +520,7 @@ namespace TeamAAS_VP.Services
                     _deviceInfoList = new List<DeviceInfo>();
                 }
 
-                // 璁$畻鏂拌�澶囩紪鍙
+                // 璁$畻鏂拌�澶囩紪鍙?
                 int newDeviceNo = _deviceInfoList.Count > 0 ? _deviceInfoList.Max(d => d.DeviceNo) + 1 : 0;
                 var newDevice = new DeviceInfo { DeviceNo = newDeviceNo };
                 _deviceInfoList.Add(newDevice);
@@ -567,10 +567,10 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 锟斤拷锟斤拷锟斤拷锟斤拷禄锟斤拷锟斤拷锟斤拷锟斤拷茫锟斤拷锟斤拷锟斤拷锟街久伙拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷
-        /// 锟斤拷锟斤拷时为锟斤拷锟斤拷锟剿凤拷锟斤拷锟斤拷锟斤拷锟斤拷 RobotNo 锟斤拷拧锟
+        /// 锟斤拷锟斤拷时为锟斤拷锟斤拷锟剿凤拷锟斤拷锟斤拷锟斤拷锟斤拷 RobotNo 锟斤拷拧锟?
         /// </summary>
-        /// <param name="robot">要锟斤拷锟接伙拷锟斤拷碌幕锟斤拷锟斤拷锟斤拷锟斤拷谩锟�</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="RobotInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <param name="robot">要锟斤拷锟接伙拷锟斤拷碌幕锟斤拷锟斤拷锟斤拷锟斤拷谩锟?/param>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="RobotInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public RobotInfo AddOrUpdateRobot(RobotInfo robot)
         {
             if (robot == null) return null;
@@ -595,7 +595,7 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 锟斤拷锟捷憋拷识锟狡筹拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫诧拷锟斤拷锟铰憋拷锟绞o拷锟斤拷锟斤拷锟剿★拷
-        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟
+        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟?
         /// </summary>
         /// <param name="id">要锟狡筹拷锟侥伙拷锟斤拷锟斤拷 Guid锟斤拷</param>
         /// <returns>锟狡筹拷锟缴癸拷锟斤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -607,7 +607,7 @@ namespace TeamAAS_VP.Services
                 var e = Robots.FirstOrDefault(r => r.Id == id);
                 if (e != null)
                     result = Robots.Remove(e);
-                //锟斤拷锟铰憋拷锟
+                //锟斤拷锟铰憋拷锟?
                 int no = 1;
                 //锟斤拷锟斤拷锟叫伙拷锟斤拷锟剿帮拷RobotNo锟斤拷锟斤拷
                 var sortedRobots = Robots.OrderBy(r => r.RobotNo).ToList();
@@ -627,7 +627,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟Id 锟侥伙拷锟斤拷锟斤拷锟斤拷锟矫★拷
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟?Id 锟侥伙拷锟斤拷锟斤拷锟斤拷锟矫★拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟剿碉拷 Guid锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -697,10 +697,10 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 锟斤拷锟斤拷锟斤拷锟斤拷鹿锟斤拷锟斤拷锟斤拷锟斤拷茫锟斤拷锟斤拷锟斤拷锟街久伙拷锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷
-        /// 锟斤拷锟斤拷时为锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷 FeederNo 锟斤拷拧锟
+        /// 锟斤拷锟斤拷时为锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷 FeederNo 锟斤拷拧锟?
         /// </summary>
-        /// <param name="feeder">要锟斤拷锟接伙拷锟斤拷碌墓锟斤拷锟斤拷锟斤拷锟斤拷谩锟�</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="FeederInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <param name="feeder">要锟斤拷锟接伙拷锟斤拷碌墓锟斤拷锟斤拷锟斤拷锟斤拷谩锟?/param>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="FeederInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public FeederInfo AddOrUpdateFeeder(FeederInfo feeder)
         {
             if (feeder == null) return null;
@@ -723,8 +723,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟捷憋拷识锟狡筹拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫诧拷锟斤拷锟铰憋拷锟绞o拷喙╋拷锟斤拷锟斤拷锟
-        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟
+        /// 锟斤拷锟捷憋拷识锟狡筹拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫诧拷锟斤拷锟铰憋拷锟绞o拷喙╋拷锟斤拷锟斤拷锟?
+        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟?
         /// </summary>
         /// <param name="id">要锟狡筹拷锟侥癸拷锟斤拷锟斤拷 Guid锟斤拷</param>
         /// <returns>锟狡筹拷锟缴癸拷锟斤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -735,7 +735,7 @@ namespace TeamAAS_VP.Services
             {
                 var e = Feeders.FirstOrDefault(f => f.Id == id);
                 if (e != null) result = Feeders.Remove(e);
-                //锟斤拷锟铰憋拷锟
+                //锟斤拷锟铰憋拷锟?
                 int no = 1;
                 //锟斤拷锟斤拷锟叫癸拷锟斤拷锟斤拷锟斤拷FeederNo锟斤拷锟斤拷
                 var sortedFeeders = Feeders.OrderBy(f => f.FeederNo).ToList();
@@ -756,7 +756,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟Id 锟侥癸拷锟斤拷锟斤拷锟斤拷锟矫★拷
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟?Id 锟侥癸拷锟斤拷锟斤拷锟斤拷锟矫★拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟斤拷 Guid锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -774,14 +774,14 @@ namespace TeamAAS_VP.Services
         /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷 PLC 锟斤拷锟矫★拷
         /// </summary>
         /// <param name="id">PLC 锟斤拷 Guid 锟斤拷识锟斤拷</param>
-        /// <returns>匹锟斤拷锟<see cref="PlcInfo"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <returns>匹锟斤拷锟?<see cref="PlcInfo"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public PlcInfo GetPlc(Guid id) { lock (_sync) { return Plcs.FirstOrDefault(p => p.Id == id); } }
 
         /// <summary>
-        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟PLC 锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷锟斤拷募锟斤拷锟斤拷锟斤拷锟绞憋拷锟斤拷锟斤拷锟斤拷锟� PlcNo 锟斤拷拧锟�
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟?PLC 锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷锟斤拷募锟斤拷锟斤拷锟斤拷锟绞憋拷锟斤拷锟斤拷锟斤拷锟?PlcNo 锟斤拷拧锟?
         /// </summary>
-        /// <param name="plc">要锟斤拷锟接伙拷锟斤拷碌锟PLC 锟斤拷锟矫★拷</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="PlcInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <param name="plc">要锟斤拷锟接伙拷锟斤拷碌锟?PLC 锟斤拷锟矫★拷</param>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="PlcInfo"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public PlcInfo AddOrUpdatePlc(PlcInfo plc)
         {
             if (plc == null) return null;
@@ -804,8 +804,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟捷憋拷识锟狡筹拷 PLC 锟斤拷锟矫诧拷锟斤拷锟铰憋拷锟绞o拷锟PLC锟斤拷
-        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟
+        /// 锟斤拷锟捷憋拷识锟狡筹拷 PLC 锟斤拷锟矫诧拷锟斤拷锟铰憋拷锟绞o拷锟?PLC锟斤拷
+        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟?
         /// </summary>
         /// <param name="id">要锟狡筹拷锟斤拷 PLC Guid锟斤拷</param>
         /// <returns>锟狡筹拷锟缴癸拷锟斤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -816,7 +816,7 @@ namespace TeamAAS_VP.Services
             {
                 var e = Plcs.FirstOrDefault(p => p.Id == id);
                 if (e != null) result = Plcs.Remove(e);
-                //锟斤拷锟铰憋拷锟
+                //锟斤拷锟铰憋拷锟?
                 int no = 1;
                 //锟斤拷锟斤拷锟斤拷PLC锟斤拷PlcNo锟斤拷锟斤拷
                 var sortedPlcs = Plcs.OrderBy(p => p.PlcNo).ToList();
@@ -837,7 +837,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟Id 锟斤拷 PLC 锟斤拷锟矫★拷
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟?Id 锟斤拷 PLC 锟斤拷锟矫★拷
         /// </summary>
         /// <param name="id">PLC 锟斤拷 Guid锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -846,13 +846,13 @@ namespace TeamAAS_VP.Services
 
         #region BgTcp
         /// <summary>
-        /// 锟斤拷取 BG TCP/IP 通锟斤拷锟斤拷锟矫o拷锟节达拷锟斤拷螅�锟斤拷锟轿null锟斤拷锟斤拷
+        /// 锟斤拷取 BG TCP/IP 通锟斤拷锟斤拷锟矫o拷锟节达拷锟斤拷螅�锟斤拷锟轿?null锟斤拷锟斤拷
         /// </summary>
         /// <returns>锟斤拷前锟斤拷 <see cref="BgTcpIP"/> 锟斤拷锟矫讹拷锟斤拷</returns>
         public BgTcpIP GetBgTcp() { lock (_sync) { return BgCommunicate; } }
 
         /// <summary>
-        /// 锟斤拷锟斤拷 BG TCP/IP 通锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节达拷锟斤拷螅�锟斤拷锟斤拷远锟斤拷志没锟斤拷锟斤拷锟
+        /// 锟斤拷锟斤拷 BG TCP/IP 通锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节达拷锟斤拷螅�锟斤拷锟斤拷远锟斤拷志没锟斤拷锟斤拷锟?
         /// 锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷 <see cref="SaveBgTcp"/>锟斤拷
         /// </summary>
         /// <param name="cfg">锟铰碉拷 <see cref="BgTcpIP"/> 锟斤拷锟矫讹拷锟斤拷</param>
@@ -866,13 +866,13 @@ namespace TeamAAS_VP.Services
 
         #region BgModbus
         /// <summary>
-        /// 锟斤拷取 BG Modbus TCP 通锟斤拷锟斤拷锟矫o拷锟节达拷锟斤拷螅�锟
+        /// 锟斤拷取 BG Modbus TCP 通锟斤拷锟斤拷锟矫o拷锟节达拷锟斤拷螅�锟?
         /// </summary>
         /// <returns>锟斤拷前锟斤拷 <see cref="BgModbusTcp"/> 锟斤拷锟矫讹拷锟斤拷</returns>
         public BgModbusTcp GetBgModbusTcp() { lock (_sync) { return BgModbusCommunicate; } }
 
         /// <summary>
-        /// 锟斤拷锟斤拷 BG Modbus TCP 通锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节达拷锟斤拷螅�锟斤拷锟斤拷远锟斤拷志没锟斤拷锟斤拷锟
+        /// 锟斤拷锟斤拷 BG Modbus TCP 通锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节达拷锟斤拷螅�锟斤拷锟斤拷远锟斤拷志没锟斤拷锟斤拷锟?
         /// </summary>
         /// <param name="cfg">锟铰碉拷 <see cref="BgModbusTcp"/> 锟斤拷锟矫讹拷锟斤拷</param>
         public void SetBgModbusTcp(BgModbusTcp cfg) { lock (_sync) { BgModbusCommunicate = cfg; } }
@@ -942,7 +942,7 @@ namespace TeamAAS_VP.Services
         /// <summary>
         /// 锟斤拷锟斤拷系统锟斤拷锟矫碉拷锟斤拷锟斤拷锟侥硷拷锟斤拷锟结覆锟斤拷锟斤拷锟斤拷锟侥硷拷锟斤拷锟斤拷
         /// </summary>
-        /// <param name="systemConfiguration">要锟斤拷锟斤拷锟较低筹拷锟斤拷枚锟斤拷锟�</param>
+        /// <param name="systemConfiguration">要锟斤拷锟斤拷锟较低筹拷锟斤拷枚锟斤拷锟?/param>
         public void SaveSystemConfiguration(SystemConfiguration systemConfiguration)
         {
             lock (_sync)
@@ -1026,16 +1026,16 @@ namespace TeamAAS_VP.Services
 
         #region Feeder锟斤拷锟斤拷锟斤拷锟斤拷
         /// <summary>
-        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟街伙拷锟斤拷锟斤拷稀锟
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟街伙拷锟斤拷锟斤拷稀锟?
         /// </summary>
-        /// <returns>锟斤拷锟斤拷锟斤拷锟斤拷锟街伙拷锟斤拷锟斤拷稀锟�</returns>
+        /// <returns>锟斤拷锟斤拷锟斤拷锟斤拷锟街伙拷锟斤拷锟斤拷稀锟?/returns>
         public IReadOnlyCollection<FeederClearWork> GetAllClearanceTasks() { lock (_sync) { return FeederClearanceTasks.ToList().AsReadOnly(); } }
 
         /// <summary>
-        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟绞蔽�拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟TaskCode锟斤拷锟斤拷珊锟街久伙拷锟斤拷锟斤拷锟侥硷拷锟斤拷
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟绞蔽�拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟?TaskCode锟斤拷锟斤拷珊锟街久伙拷锟斤拷锟斤拷锟侥硷拷锟斤拷
         /// </summary>
         /// <param name="task">要锟斤拷锟接伙拷锟斤拷碌锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="FeederClearWork"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="FeederClearWork"/>锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public FeederClearWork AddOrUpdateClearanceTask(FeederClearWork task)
         {
             if (task == null) return null;
@@ -1049,7 +1049,7 @@ namespace TeamAAS_VP.Services
                 }
                 else
                 {
-                    // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷前锟斤拷锟斤拷偶锟�1
+                    // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷前锟斤拷锟斤拷偶锟?
                     task.TaskCode = FeederClearanceTasks.Count > 0 ? FeederClearanceTasks.Max(t => t.TaskCode) + 1 : 1;
                     FeederClearanceTasks.Add(task);
                 }
@@ -1080,7 +1080,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷癫⒊志没锟斤拷占锟斤拷系锟斤拷锟斤拷锟斤拷募锟斤拷锟
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷癫⒊志没锟斤拷占锟斤拷系锟斤拷锟斤拷锟斤拷募锟斤拷锟?
         /// </summary>
         public void ClearAllClearanceTasks()
         {
@@ -1092,7 +1092,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟Id 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟?Id 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟斤拷锟斤拷 Guid锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -1106,14 +1106,14 @@ namespace TeamAAS_VP.Services
         /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟斤拷锟斤拷 Guid锟斤拷</param>
-        /// <returns>匹锟斤拷锟<see cref="FeederClearWork"/>锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <returns>匹锟斤拷锟?<see cref="FeederClearWork"/>锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public FeederClearWork GetClearanceTask(Guid id)
         {
             lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.Id == id); }
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟斤拷锟斤拷诺锟斤拷锟斤拷锟斤拷锟斤拷锟
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟斤拷锟斤拷诺锟斤拷锟斤拷锟斤拷锟斤拷锟?
         /// </summary>
         /// <param name="taskNumber">锟斤拷锟斤拷锟脚o拷TaskCode锟斤拷锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -1126,7 +1126,7 @@ namespace TeamAAS_VP.Services
         /// 锟斤拷锟斤拷锟斤拷锟斤拷锟脚伙拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
         /// <param name="taskNumber">锟斤拷锟斤拷锟脚o拷TaskCode锟斤拷锟斤拷</param>
-        /// <returns>匹锟斤拷锟<see cref="FeederClearWork"/>锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <returns>匹锟斤拷锟?<see cref="FeederClearWork"/>锟斤拷锟揭诧拷锟斤拷锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         public FeederClearWork GetClearanceTaskByNumber(int taskNumber)
         {
             lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.TaskCode == taskNumber); }
@@ -1150,21 +1150,21 @@ namespace TeamAAS_VP.Services
         /// 锟竭程帮拷全锟斤拷锟矫凤拷锟斤拷锟斤拷锟节存集锟较斤拷锟斤拷只锟斤拷锟斤拷锟绞诧拷锟斤拷锟节诧拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷识锟斤拷Id锟斤拷锟斤拷</param>
-        /// <returns>匹锟斤拷锟<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟津返伙拷 <c>null</c>锟斤拷</returns>
+        /// <returns>匹锟斤拷锟?<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟津返伙拷 <c>null</c>锟斤拷</returns>
         public LightControllerConfig GetLightController(int id)
         {
             lock (_sync) { return LightControllers.FirstOrDefault(c => c.Id == id); }
         }
 
         /// <summary>
-        /// 锟斤拷锟斤拷锟斤拷锟斤拷鹿锟皆达拷锟斤拷锟斤拷锟斤拷锟斤拷谩锟
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷鹿锟皆达拷锟斤拷锟斤拷锟斤拷锟斤拷谩锟?
         /// - 锟斤拷 <paramref name="controller"/> 为 <c>null</c> 锟斤拷锟斤拷 <c>null</c>锟斤拷
         /// - 锟斤拷锟斤拷时锟斤拷 Id 匹锟斤拷锟斤拷锟斤拷锟筋并锟芥换锟斤拷
         /// - 锟斤拷锟斤拷时锟斤拷锟斤拷锟斤拷锟斤拷锟侥匡拷锟斤拷锟斤拷 Id锟斤拷锟斤拷为锟斤拷锟斤拷锟斤拷锟节碉拷每锟斤拷通锟斤拷锟斤拷锟斤拷全锟斤拷通锟斤拷锟斤拷牛锟紾lobalIndex锟斤拷锟斤拷
         /// 锟竭程帮拷全锟斤拷锟斤拷锟节诧拷使锟斤拷 <see cref="_sync"/> 锟斤拷锟斤拷锟斤拷锟较★拷
         /// </summary>
-        /// <param name="controller">要锟斤拷锟接伙拷锟斤拷碌锟<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷</param>
-        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
+        /// <param name="controller">要锟斤拷锟接伙拷锟斤拷碌锟?<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷锟接伙拷锟斤拷碌锟?<see cref="LightControllerConfig"/> 实锟斤拷锟斤拷锟斤拷锟斤拷为 <c>null</c> 时锟斤拷锟斤拷 <c>null</c>锟斤拷</returns>
         /// <exception cref="ArgumentException">锟斤拷要锟斤拷锟斤拷/锟斤拷锟铰的匡拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟截革拷时锟阶筹拷锟斤拷</exception>
         public LightControllerConfig AddOrUpdateLightController(LightControllerConfig controller)
         {
@@ -1185,10 +1185,10 @@ namespace TeamAAS_VP.Services
                 else
                 {
                     // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
-                    // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷前锟斤拷锟Id + 1锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷锟斤拷为 1锟斤拷
+                    // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷前锟斤拷锟?Id + 1锟斤拷锟斤拷锟斤拷锟斤拷为锟斤拷锟斤拷为 1锟斤拷
                     controller.Id = LightControllers.Count > 0 ? LightControllers.Max(c => c.Id) + 1 : 1;
 
-                    // 锟斤拷锟斤拷锟斤拷一锟斤拷全锟斤拷通锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟窖达拷锟节匡拷锟斤拷锟斤拷锟斤拷通锟斤拷 GlobalIndex 锟斤拷取锟斤拷锟街碉拷锟斤拷锟�1
+                    // 锟斤拷锟斤拷锟斤拷一锟斤拷全锟斤拷通锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟窖达拷锟节匡拷锟斤拷锟斤拷锟斤拷通锟斤拷 GlobalIndex 锟斤拷取锟斤拷锟街碉拷锟斤拷锟?
                     int nextGlobal = 0;
                     if (LightControllers.SelectMany(c => c.ChannelConfigs).Any())
                     {
@@ -1209,17 +1209,17 @@ namespace TeamAAS_VP.Services
                     LightControllers.Add(controller);
                 }
             }
-            // 锟街久伙拷锟斤拷锟铰猴拷墓锟皆达拷锟斤拷锟斤拷锟斤拷锟斤拷锟
+            // 锟街久伙拷锟斤拷锟铰猴拷墓锟皆达拷锟斤拷锟斤拷锟斤拷锟斤拷锟?
             FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
             return controller;
         }
 
         /// <summary>
-        /// 锟斤拷锟斤拷 Id 锟狡筹拷锟斤拷源锟斤拷锟斤拷锟斤拷锟斤拷锟矫诧拷锟斤拷剩锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷卤锟脚o拷锟斤拷 1 锟斤拷始锟斤拷锟斤拷锟斤拷牛锟斤拷锟
-        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟
+        /// 锟斤拷锟斤拷 Id 锟狡筹拷锟斤拷源锟斤拷锟斤拷锟斤拷锟斤拷锟矫诧拷锟斤拷剩锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷卤锟脚o拷锟斤拷 1 锟斤拷始锟斤拷锟斤拷锟斤拷牛锟斤拷锟?
+        /// 锟斤拷锟斤拷锟斤拷志没锟斤拷锟斤拷锟斤拷募锟斤拷锟?
         /// </summary>
         /// <param name="id">要锟狡筹拷锟侥匡拷锟斤拷锟斤拷 Id锟斤拷</param>
-        /// <returns>锟斤拷锟斤拷晒锟斤拷瞥锟斤拷锟斤拷锟<c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷锟斤拷锟斤拷未锟揭碉拷锟斤拷 Id锟斤拷锟斤拷</returns>
+        /// <returns>锟斤拷锟斤拷晒锟斤拷瞥锟斤拷锟斤拷锟?<c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷锟斤拷锟斤拷未锟揭碉拷锟斤拷 Id锟斤拷锟斤拷</returns>
         public bool RemoveLightController(int id)
         {
             bool result = false;
@@ -1229,7 +1229,7 @@ namespace TeamAAS_VP.Services
                 if (e != null)
                     result = LightControllers.Remove(e);
 
-                // 锟斤拷锟铰帮拷 Id 锟斤拷锟津并达拷 1 锟斤拷始锟斤拷锟铰憋拷牛锟斤拷员锟斤拷锟斤拷锟斤拷锟斤拷锟
+                // 锟斤拷锟铰帮拷 Id 锟斤拷锟津并达拷 1 锟斤拷始锟斤拷锟铰憋拷牛锟斤拷员锟斤拷锟斤拷锟斤拷锟斤拷锟?
                 int no = 1;
                 var sorted = LightControllers.OrderBy(c => c.Id).ToList();
                 foreach (var c in sorted)
@@ -1248,7 +1248,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟Id 锟侥癸拷源锟斤拷锟斤拷锟斤拷锟斤拷
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟?Id 锟侥癸拷源锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id">锟斤拷锟斤拷锟斤拷 Id锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -1257,7 +1257,7 @@ namespace TeamAAS_VP.Services
         /// <summary>
         /// 锟斤拷取锟斤拷锟叫癸拷源通锟斤拷锟斤拷只锟斤拷锟斤拷锟较o拷锟斤拷 GlobalIndex 锟斤拷锟津返回★拷
         /// </summary>
-        /// <returns>锟斤拷 GlobalIndex 锟斤拷锟斤拷锟<see cref="ChannelConfig"/> 只锟斤拷锟斤拷锟较★拷</returns>
+        /// <returns>锟斤拷 GlobalIndex 锟斤拷锟斤拷锟?<see cref="ChannelConfig"/> 只锟斤拷锟斤拷锟较★拷</returns>
         public IReadOnlyCollection<ChannelConfig> GetAllLightChannels()
         {
             lock (_sync) { return LightControllers.SelectMany(c => c.ChannelConfigs).OrderBy(ch => ch.GlobalIndex).ToList().AsReadOnly(); }
@@ -1267,14 +1267,14 @@ namespace TeamAAS_VP.Services
         /// 锟斤拷锟斤拷全锟斤拷通锟斤拷锟斤拷锟斤拷锟斤拷取锟斤拷锟斤拷通锟斤拷锟斤拷锟矫★拷
         /// </summary>
         /// <param name="globalIndex">通锟斤拷锟斤拷全锟斤拷锟斤拷锟斤拷锟斤拷GlobalIndex锟斤拷锟斤拷</param>
-        /// <returns>匹锟斤拷锟<see cref="ChannelConfig"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟津返伙拷 <c>null</c>锟斤拷</returns>
+        /// <returns>匹锟斤拷锟?<see cref="ChannelConfig"/> 实锟斤拷锟斤拷锟揭诧拷锟斤拷锟津返伙拷 <c>null</c>锟斤拷</returns>
         public ChannelConfig GetLightChannelByGlobalIndex(int globalIndex)
         {
             lock (_sync) { return LightControllers.SelectMany(c => c.ChannelConfigs).FirstOrDefault(ch => ch.GlobalIndex == globalIndex); }
         }
 
         /// <summary>
-        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟饺�拷锟酵�拷锟斤拷锟斤拷锟斤拷锟酵�拷锟斤拷锟斤拷谩锟
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟饺�拷锟酵�拷锟斤拷锟斤拷锟斤拷锟酵�拷锟斤拷锟斤拷谩锟?
         /// </summary>
         /// <param name="globalIndex">全锟斤拷通锟斤拷锟斤拷锟斤拷锟斤拷</param>
         /// <returns>锟斤拷锟节凤拷锟斤拷 <c>true</c>锟斤拷锟斤拷锟津返伙拷 <c>false</c>锟斤拷</returns>
@@ -1356,8 +1356,8 @@ namespace TeamAAS_VP.Services
         public ScrewFeederInfo GetScrewFeeder(Guid id) { lock (_sync) { return ScrewFeeders.FirstOrDefault(s => s.Id == id); } }
 
         /// <summary>
-        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷锟斤拷谩锟
-        /// 锟斤拷锟斤拷时锟斤拷未指锟斤拷 FeederNumber 锟斤拷锟斤拷锟轿�拷锟角帮拷锟斤拷锟�+1锟斤拷
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷锟斤拷谩锟?
+        /// 锟斤拷锟斤拷时锟斤拷未指锟斤拷 FeederNumber 锟斤拷锟斤拷锟轿�拷锟角帮拷锟斤拷锟?1锟斤拷
         /// </summary>
         public ScrewFeederInfo AddOrUpdateScrewFeeder(ScrewFeederInfo feeder)
         {
@@ -1401,7 +1401,7 @@ namespace TeamAAS_VP.Services
         public event Action<ScrewFeederInfo> ScrewFeederLowLevelAlarmTriggered;
 
         /// <summary>
-        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺锟绞o拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷也锟斤拷锟斤拷锟斤拷锟-1锟斤拷
+        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺锟绞o拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷也锟斤拷锟斤拷锟斤拷锟?-1锟斤拷
         /// </summary>
         public int GetRemainingCountByFeederNumber(int feederNumber)
         {
@@ -1414,7 +1414,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺牡锟斤拷锟斤拷锟斤拷锟阶刺�拷锟斤拷也锟斤拷锟斤拷锟斤拷锟false锟斤拷
+        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺牡锟斤拷锟斤拷锟斤拷锟阶刺�拷锟斤拷也锟斤拷锟斤拷锟斤拷锟?false锟斤拷
         /// </summary>
         public bool GetLowLevelAlarmStatusByFeederNumber(int feederNumber)
         {
@@ -1452,7 +1452,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 为指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺菁锟街革拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷惴碉拷锟-1
+        /// 为指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺菁锟街革拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷惴碉拷锟?-1
         /// </summary>
         public int DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1)
         {
@@ -1557,7 +1557,7 @@ namespace TeamAAS_VP.Services
                 }
                 catch
                 {
-                    // 锟斤拷锟斤拷锟斤拷锟捷匡拷写锟斤拷锟斤拷螅�锟铰硷拷锟斤拷锟斤拷锟斤拷锟街
+                    // 锟斤拷锟斤拷锟斤拷锟捷匡拷写锟斤拷锟斤拷螅�锟铰硷拷锟斤拷锟斤拷锟斤拷锟街?
                 }
             }
             catch
@@ -1567,7 +1567,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 锟斤拷锟绞o拷锟斤拷锟剿匡拷欠癖ň锟
+        /// 锟斤拷锟绞o拷锟斤拷锟剿匡拷欠癖ň锟?
         /// </summary>
         public bool CheckAlarm(int feederNumber)
         {

+ 4 - 2
TeamAAS-VM/ViewModels/HomeViewModel.cs

@@ -46,6 +46,7 @@ namespace TeamAAS_VP.ViewModels
         IRemoteCommandService _remoteCommandService;
         ISystemDatabaseService _systemDatabaseService;
         IDialogService _dialogService;
+        IPlcService _plcService;
 
         #region 属性
         private PlotModel _PressurePlotModel = new PlotModel();
@@ -221,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);
             //订阅权限登录事件
@@ -594,7 +596,7 @@ namespace TeamAAS_VP.ViewModels
             //        _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
             //    }
             //});
-            var dialog = new AddScrewView();
+            var dialog = new AddScrewView(_configService, _eventAggregator, _plcService);
             dialog.Owner = Application.Current.MainWindow;
 
             // 显示弹窗,扫码枪结果会自动显示在输入框中

+ 1 - 1
TeamAAS-VM/ViewModels/SettingViewModel.cs

@@ -1656,7 +1656,7 @@ namespace TeamAAS_VP.ViewModels
 
         private Int16 _WorkMode = 0;
         /// <summary>
-        /// 工作模式 =1 拍1打1 =2 拍1打所有
+        /// 工作模式 =1 拍1打1 =2 拍1打所有,=3:先拍1打1打3颗再拍2打所有  
         /// </summary>
         public Int16 WorkMode
         {

+ 3 - 2
TeamAAS-VM/Views/AddScrewView.xaml

@@ -4,6 +4,7 @@
         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">
@@ -33,12 +34,12 @@
                     Width="80" 
                     Height="30" 
                     Margin="0,0,10,0"
-                    Click="OkButton_Click"/>
+                    Command="{Binding OkCommand}"/>
             <Button x:Name="CancelButton" 
                     Content="取消" 
                     Width="80" 
                     Height="30"
-                    Click="CancelButton_Click"/>
+                    Command="{Binding CancelCommand}"/>
         </StackPanel>
     </Grid>
 </Window>

+ 238 - 27
TeamAAS-VM/Views/AddScrewView.xaml.cs

@@ -1,6 +1,8 @@
 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;
@@ -12,9 +14,14 @@ 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
 {
@@ -23,55 +30,259 @@ namespace TeamAAS_VP.Views
     /// </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()
+        public AddScrewView(IConfigService configService, IEventAggregator eventAggregator, IPlcService plcService)
         {
             InitializeComponent();
-            // 窗口加载完成后,自动让输入框获得焦点
-            this.Loaded += (s, e) =>
+            _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
             {
-                BarcodeTextBox.Focus();
-            };
+                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();
         }
-        private void OkButton_Click(object sender, RoutedEventArgs e)
+
+        //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)
         {
-            var stationConfig = _configService.GetDeviceInfo(0);
-            ScannedBarcode = BarcodeTextBox.Text;
+            string directory = global::System.IO.Path.GetDirectoryName(savePath);
+
+            // 如果目录路径不为空且不存在,则创建
+            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+            {
+                Directory.CreateDirectory(directory);
+            }
 
-            if (string.IsNullOrWhiteSpace(ScannedBarcode))
+            // 构建CSV内容
+            StringBuilder csvContent = new StringBuilder();
+
+            // 处理表头:仅当文件不存在/非追加模式时,才写入表头
+            if (!appendMode || !File.Exists(savePath))
             {
-                MessageBox.Show("请先扫描螺丝条码", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
-                BarcodeTextBox.Focus(); // 重新聚焦
-                return;
+                csvContent.AppendLine("日期时间,目标螺丝型号1,螺丝型号2,添加螺丝型号,是否匹配");
             }
-            if (ScannedBarcode == stationConfig.DataInfo.Screwsize1 || ScannedBarcode == stationConfig.DataInfo.Screwsize2)
+
+            // 处理数据行
+            string matchStr = isMatch ? "是" : "否";
+
+            // 如果型号中包含逗号,需要用引号包裹
+            string safeModel = model;
+            if (safeModel.Contains(","))
             {
-                MessageBox.Show("螺丝型号匹配成功", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
-                SendTaskMessage($"螺丝型号匹配成功", MessageLevel.Info);
+                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
             {
-                MessageBox.Show("螺丝型号匹配失败", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
-                SendTaskMessage($"螺丝型号匹配失败", MessageLevel.Error);
+                // 覆盖模式:新建/替换文件
+                File.WriteAllText(savePath, csvContent.ToString(), Encoding.UTF8);
             }
-            DialogResult = true;
-            Close();
         }
 
-        private void CancelButton_Click(object sender, RoutedEventArgs e)
+    }
+    public class RelayCommand : ICommand
+    {
+        private readonly Action<object> _execute;
+        private readonly Func<object, bool> _canExecute;
+
+        public event EventHandler CanExecuteChanged
         {
-            DialogResult = false;
-            Close();
+            add { CommandManager.RequerySuggested += value; }
+            remove { CommandManager.RequerySuggested -= value; }
         }
 
-        public void SendTaskMessage(string msg, MessageLevel level)
+        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
         {
-            App.Current.Dispatcher.Invoke(() =>
-            {
-                _eventAggregator.GetEvent<TaskMessageNotification>().Publish(new Models.MessageStruct() { Message = msg, level = level });
-            });
+            _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);
     }
+
 }

+ 50 - 16
TeamAAS-VM/Views/SettingView.xaml

@@ -540,30 +540,32 @@
                                     Grid.Column="1"
                                     Margin="6,2"
                                     Text="{Binding SelectDeviceInfo.ApiKey, Mode=TwoWay}" />
-
                                 <StackPanel
                                     Grid.Row="11"
                                     Grid.Column="1"
                                     Orientation="Horizontal">
                                     <CheckBox
-     Grid.Row="11"
-     Grid.Column="1"
-     Margin="6,2"
-     Command="{Binding CheckStatusCommand}"
-     Content="启用 MES"
-     IsChecked="{Binding SelectDeviceInfo.EnableMES, Mode=TwoWay}" />
+    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" />-->
+          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}" />
+                                        Grid.Row="11"
+                                        Grid.Column="1"
+                                        Margin="6,2"
+                                        Command="{Binding CheckStatusCommand}"
+Content="是否启用锁螺丝XY超限"
+IsChecked="{Binding SelectDeviceInfo.EnableXY, Mode=TwoWay}" />
                                 </StackPanel>
-                               
+
+                                
                                 <StackPanel
                                     Grid.Row="13"
                                     Grid.Column="1"
@@ -2978,6 +2980,38 @@ Text="{Binding SelectDeviceInfo.DataInfo.Screwsize2, Mode=TwoWay}" />
                                 Maximum="100"
                                 Minimum="0"
                                 Value="{Binding WorkMode, Mode=TwoWay}" />
+                        <!-- 固定相机检测锁附/组装次数 -->
+                        <TextBlock Grid.Row="6"
+                                   Grid.Column="0"
+                                   Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
+                                   VerticalAlignment="Center"
+                                   HorizontalAlignment="Right"
+                                   Margin="0,6,10,6"
+                                   Text="{lex:Loc 固定相机检测锁附组装次数}" />
+                        <mah:NumericUpDown Grid.Row="6"
+                                           Grid.Column="1"
+                                           Margin="0,6"
+                                           Value="{Binding FixedCameracheckNum,Mode=TwoWay}"
+                                           Minimum="0"
+                                           Maximum="100"
+                                           FontSize="{DynamicResource Font.Size.Body3}" />
+                        <!-- 工作模式  -->
+                        <TextBlock Grid.Row="7"
+                                   Grid.Column="0"
+                                   Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
+                                   VerticalAlignment="Center"
+                                   HorizontalAlignment="Right"
+                                   Margin="0,6,10,6"
+                                   Text="{lex:Loc 工作模式}" />
+                        <mah:NumericUpDown Grid.Row="7"
+                                           Grid.Column="1"
+                                           Margin="0,10"
+                                           Value="{Binding WorkMode,Mode=TwoWay}"
+                                           Minimum="0"
+                                           Maximum="100"
+                                           FontSize="{DynamicResource Font.Size.Body3}"
+                                           materialDesign:HintAssist.HelperTextFontSize="10"
+                                           materialDesign:HintAssist.HelperText="1:拍1打1 - 2:拍2打所有 - 3:先拍1打1打3颗再拍2打所有" />
 
                             <!--  取料位置  -->
                             <TextBlock