zly 3 сар өмнө
parent
commit
d49fb4c249
30 өөрчлөгдсөн 3189 нэмэгдсэн , 853 устгасан
  1. 41 42
      TeamAAS-VM/Controls/myNumericUpDowm.xaml
  2. 22 11
      TeamAAS-VM/Core/Management.cs
  3. 881 0
      TeamAAS-VM/Core/Robots/XYZURWV_Platform.cs
  4. 856 0
      TeamAAS-VM/Core/Robots/XYZUR_Platform.cs
  5. 1 1
      TeamAAS-VM/Core/ScrewDriver/XYD_ScrewDriver.cs
  6. 7 0
      TeamAAS-VM/Enums/RobotBrand.cs
  7. 30 3
      TeamAAS-VM/Models/PLC/PlcAddressConfig.cs
  8. 20 0
      TeamAAS-VM/Models/PLC/PlcPoint.cs
  9. 2 0
      TeamAAS-VM/Models/PLC/PlcPointAddress.cs
  10. 8 0
      TeamAAS-VM/Models/Robot/RPoint.cs
  11. 22 0
      TeamAAS-VM/Models/Robot/RobotPoint.cs
  12. 110 47
      TeamAAS-VM/Resources/Languages/Lang.Designer.cs
  13. 36 9
      TeamAAS-VM/Resources/Languages/Lang.en.resx
  14. 36 9
      TeamAAS-VM/Resources/Languages/Lang.ja-JP.resx
  15. 29 0
      TeamAAS-VM/Resources/Languages/Lang.resx
  16. 42 15
      TeamAAS-VM/Resources/Languages/Lang.vi.resx
  17. 39 12
      TeamAAS-VM/Resources/Languages/Lang.zh-TW.resx
  18. 99 0
      TeamAAS-VM/Services/ConfigService.cs
  19. 36 0
      TeamAAS-VM/Services/RobotService.cs
  20. 2 0
      TeamAAS-VM/TeamAAS-VP.csproj
  21. 8 1
      TeamAAS-VM/ValueConverter/EnumLocalizationConverter.cs
  22. 1 1
      TeamAAS-VM/ValueConverter/RobotBrandToVisibiltyConverter.cs
  23. 1 1
      TeamAAS-VM/ViewModels/MainWindowViewModel.cs
  24. 2 1
      TeamAAS-VM/ViewModels/Product/PalletManageViewModel.cs
  25. 41 16
      TeamAAS-VM/ViewModels/Product/PlcPointParamsViewModel.cs
  26. 55 2
      TeamAAS-VM/ViewModels/Statistics/ProductionStatementViewModel.cs
  27. 3 3
      TeamAAS-VM/Views/HomeView.xaml
  28. 715 679
      TeamAAS-VM/Views/Product/PlcPointParams.xaml
  29. 4 0
      TeamAAS-VM/Views/Product/PlcPointParams.xaml.cs
  30. 40 0
      TeamAAS-VM/Views/Statistics/ProductionStatement.xaml

+ 41 - 42
TeamAAS-VM/Controls/myNumericUpDowm.xaml

@@ -1,24 +1,23 @@
-<UserControl x:Class="TeamAAS_VP.Controls.myNumericUpDowm"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:local="clr-namespace:TeamAAS_VP.Controls"
-             xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
-             xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
-             xmlns:lex="http://wpflocalizeextension.codeplex.com"
-             lex:LocalizeDictionary.DesignCulture="zh-CN"
-             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
-             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
-             FontFamily="{DynamicResource DefaultFont}">
+<UserControl
+    x:Class="TeamAAS_VP.Controls.myNumericUpDowm"
+    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:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
+    xmlns:lex="http://wpflocalizeextension.codeplex.com"
+    xmlns:local="clr-namespace:TeamAAS_VP.Controls"
+    xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    lex:LocalizeDictionary.DesignCulture="zh-CN"
+    lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+    lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+    FontFamily="{DynamicResource DefaultFont}">
     <UserControl.Triggers>
         <EventTrigger RoutedEvent="GotFocus">
             <BeginStoryboard>
                 <Storyboard>
-                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="SliderPopup"
-                                                    Storyboard.TargetProperty="IsOpen">
-                        <DiscreteBooleanKeyFrame KeyTime="0"
-                                                 Value="True" />
+                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="SliderPopup" Storyboard.TargetProperty="IsOpen">
+                        <DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
                     </BooleanAnimationUsingKeyFrames>
                 </Storyboard>
             </BeginStoryboard>
@@ -26,40 +25,40 @@
         <EventTrigger RoutedEvent="LostFocus">
             <BeginStoryboard>
                 <Storyboard>
-                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="SliderPopup"
-                                                    Storyboard.TargetProperty="IsOpen">
-                        <DiscreteBooleanKeyFrame KeyTime="0"
-                                                 Value="False" />
+                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="SliderPopup" Storyboard.TargetProperty="IsOpen">
+                        <DiscreteBooleanKeyFrame KeyTime="0" Value="False" />
                     </BooleanAnimationUsingKeyFrames>
                 </Storyboard>
             </BeginStoryboard>
         </EventTrigger>
     </UserControl.Triggers>
     <!--<Viewbox Stretch="Fill">
-        
+
     </Viewbox>-->
     <Grid>
-        <mah:NumericUpDown x:Name="MotorAmplitudeNumeric1"
-                           Style="{StaticResource MahApps.Styles.NumericUpDown.DataGrid.Editing}"
-                           BorderThickness="0,0,0,1"
-                           FontSize="{Binding FontSize, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
-        <Popup x:Name="SliderPopup"
-               Height="{Binding ElementName=MotorAmplitudeNumeric1, Path=Height}"
-               PlacementTarget="{Binding ElementName=MotorAmplitudeNumeric1}"
-               Placement="Bottom"
-               AllowsTransparency="True"
-               StaysOpen="False"
-               Width="150">
+        <mah:NumericUpDown
+            x:Name="MotorAmplitudeNumeric1"
+            BorderThickness="0,0,0,1"
+            FontSize="{Binding FontSize, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
+            Style="{StaticResource MahApps.Styles.NumericUpDown.DataGrid.Editing}" />
+        <Popup
+            x:Name="SliderPopup"
+            Width="150"
+            Height="{Binding ElementName=MotorAmplitudeNumeric1, Path=Height}"
+            AllowsTransparency="True"
+            Placement="Bottom"
+            PlacementTarget="{Binding ElementName=MotorAmplitudeNumeric1}"
+            StaysOpen="False">
             <Grid Background="Transparent">
-                <Border Background="#575757"
-                        CornerRadius="0,0,3,3">
-                    <Slider x:Name="slider2"
-                            Margin="5,2,5,2"
-                            Foreground="#2196f3"
-                            IsSnapToTickEnabled="True"
-                            Maximum="{Binding ElementName=MotorAmplitudeNumeric1, Path=Maximum}"
-                            Minimum="{Binding ElementName=MotorAmplitudeNumeric1, Path=Minimum}"
-                            Value="{Binding ElementName=MotorAmplitudeNumeric1,Path=Value,Mode=TwoWay}" />
+                <Border Background="#575757" CornerRadius="0,0,3,3">
+                    <Slider
+                        x:Name="slider2"
+                        Margin="5,2,5,2"
+                        Foreground="#2196f3"
+                        IsSnapToTickEnabled="True"
+                        Maximum="{Binding ElementName=MotorAmplitudeNumeric1, Path=Maximum}"
+                        Minimum="{Binding ElementName=MotorAmplitudeNumeric1, Path=Minimum}"
+                        Value="{Binding ElementName=MotorAmplitudeNumeric1, Path=Value, Mode=TwoWay}" />
                 </Border>
             </Grid>
         </Popup>

+ 22 - 11
TeamAAS-VM/Core/Management.cs

@@ -1334,6 +1334,10 @@ namespace TeamAAS_VP.Core
                     await item.WriteToPlcAddress(addressConfig.Out_QrCodeCamera, plc);
                 }
             }
+
+           await plc.WriteNodeAsync(addressConfig.Out_RobotWPosition.Address, current.RobotPoint.W_Position);
+           await plc.WriteNodeAsync(addressConfig.Out_RobotVPosition.Address, current.RobotPoint.V_Position);
+
         }
 
         /// <summary>
@@ -1745,7 +1749,7 @@ namespace TeamAAS_VP.Core
 
                                 if (!resultToMes.Contains($"Screw{cameraIndex}_Fail"))
                                 {
-                                    resultToMes.Add($"{device.FixtureId}_Screw{cameraIndex}_Fail");
+                                    resultToMes.Add($"Screw{cameraIndex}_Fail");
                                     SendTaskMessage($"Screw{cameraIndex}:拍照失败", MessageLevel.Error);
                                 }
 
@@ -2275,7 +2279,7 @@ namespace TeamAAS_VP.Core
                             //位置编号
                             Int16 positionIndex = plc.ReadNode<Int16>(addressConfig.In_ProductNum.Address);
                             SendTaskMessage(string.Format(Lang.编号0, positionIndex), MessageLevel.Info);
-                          
+
                             //获取当前锁付点
                             var screwPoint = currentProduct.ScrewPoints.FirstOrDefault(p => p.Number == positionIndex);
                             //获取当前的机器人点位
@@ -2298,6 +2302,10 @@ namespace TeamAAS_VP.Core
                             await Task.Delay(60);
                             //获取电批结果
                             bool isSucced = ScrewDriver.GetData();
+                            if (!isSucced)
+                            {
+                                SendTaskMessage("电批数据获取失败!",MessageLevel.Error);
+                            }
                             LockResult lockResult = new LockResult();
                             lockResult.ScrewdriverGetDataSucceed = isSucced;
                             lockResult.Number = positionIndex;
@@ -3207,7 +3215,7 @@ namespace TeamAAS_VP.Core
                                             else
                                             {
                                                 LogHelper.WriteLogMes($"询问mes失败");
-                                                SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
+                                               SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
 
                                                 //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
                                                 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
@@ -3219,7 +3227,9 @@ namespace TeamAAS_VP.Core
                                                 }
                                                 else
                                                 {
-                                                    await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+                                                    await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address,i), (Int16)2);
+                                                   // await plc.WriteNodeAsync(string.Format(addressConfig.Out_CameraDone.Address), (Int16)0);
+                                                    //await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)9);
                                                     SendTaskMessage($"发送plc信号2", MessageLevel.Debug);
                                                     return;
                                                 }
@@ -3240,7 +3250,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);
+                                        SendTaskMessage($"发送plc信号1", MessageLevel.Debug);
                                     }
                                 }
                                 // 上传本站数据
@@ -3509,11 +3519,10 @@ namespace TeamAAS_VP.Core
                                                         var device = _configService.GetDeviceInfo();
                                                         var config = _configService.GetSystemConfiguration();
 
-                                                        (bool isSuc, string resp) = await _mesService.SendFtpFileAsync(ProductMainSN[i], filename);
-                                                        if (isSuc)
+                                                        //(bool isSuc, string resp) = await _mesService.SendFtpFileAsync(ProductMainSN[i], filename);
+                                                        if (true)
                                                         {
-                                                            LogHelper.WriteLogMes($"上传数据成功");
-                                                            SendTaskMessage($"上传数据成功", MessageLevel.Debug);
+                                                           
                                                             string filePath = $"D:\\image\\Recored\\{dt.ToString("yyyy-MM")}\\{dt.ToString("MM-dd")}\\{currentProduct.Name}\\{ProductMainSN[i]}";
                                                             //string source_csv2 = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}\\{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}";
                                                             if (!Directory.Exists(filePath))
@@ -3551,6 +3560,8 @@ namespace TeamAAS_VP.Core
                                                                 }
                                                             }
                                                             CompressionImage(filePath, filename, device.Station, device.FixtureId);
+                                                            LogHelper.WriteLogMes($"上传数据成功");
+                                                            SendTaskMessage($"上传数据成功", MessageLevel.Debug);
                                                             //上传csv
                                                             bool isAfter20 = dt.Hour >= 20;
                                                             //判断是否是新的一天
@@ -3574,8 +3585,8 @@ namespace TeamAAS_VP.Core
                                                         }
                                                         else
                                                         {
-                                                            LogHelper.WriteLogMes($"上传数据失败");
-                                                            SendTaskMessage($"上传数据失败", MessageLevel.Error);
+                                                           // LogHelper.WriteLogMes($"上传数据失败");
+                                                            //SendTaskMessage($"上传数据失败", MessageLevel.Error);
                                                         }
                                                     }
                                                     else

+ 881 - 0
TeamAAS-VM/Core/Robots/XYZURWV_Platform.cs

@@ -0,0 +1,881 @@
+using MahApps.Metro.Controls;
+using Opc.Ua;
+using Prism;
+using Prism.Ioc;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using TeamAAS_VP.Core.PLCs;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Resources.Languages;
+using TouchSocket.Core;
+using TouchSocket.Sockets;
+using static System.Windows.Forms.AxHost;
+
+namespace TeamAAS_VP.Core.Robots
+{
+    /// <summary>
+    /// Generic multi-axis robot that composes axis objects and uses PLC nodes configured in RobotInfo.PlcRobotParameter
+    /// to perform coordinated moves (Go/Move/CalibMotion) for 3 or 4 axes.
+    /// This implementation maps X/Y/Z/(U) axes by name and writes their position nodes then triggers ExecuteMove node.
+    /// </summary>
+    public class XYZURWV_Platform : IRobot, INotifyPropertyChanged
+    {
+        public event Action<Guid, object, ConnectedEventArgs> ConnectedEvent;
+        public event Action<Guid, object, ClosedEventArgs> DisconnectedEvent;
+        public event Action<Guid, object, ReceivedDataEventArgs> ReceivedEvent;
+        public event Action<Guid, object, string> SendEvent;
+
+        public XYZURWV_Platform(RobotInfo robot, OpcUaClientPLC pLC)
+        {
+            RobotInfo = robot;
+            Name = robot.RobotName;
+            Id = robot.Id;
+            RobotNo = robot.RobotNo;
+            RobotIp = robot.IP;
+            RobotPort = robot.Port;
+            ConnectType = robot.ConnectType;
+            Terminator = robot.Terminator;
+            DataEncoding = robot.DataEncoding;
+            Brand = RobotBrand.XYZ_Platform;
+            SafeZ = robot.SafeZ;
+            Plc = pLC;
+            Plc.ConnectChangedEvent += Plc_ConnectChangedEvent;
+
+            // build axis list from known RobotInfo.PlcRobotParameter nodes (best-effort)
+            Axes = new List<Axis>();
+            // X
+            var ax = new Axis();
+            ax.Name = "X";
+            ax.Index = 1;
+            ax.Command = robot.PlcRobotParameter.AxixList[0].Command;
+            ax.Parameter = robot.PlcRobotParameter.AxixList[0].Parameter;
+            ax.State = robot.PlcRobotParameter.AxixList[0].State;
+            Axes.Add(ax);
+
+            // Y
+            var ay = new Axis();
+            ay.Name = "Y";
+            ay.Index = 2;
+            ay.Command = robot.PlcRobotParameter.AxixList[1].Command;
+            ay.Parameter = robot.PlcRobotParameter.AxixList[1].Parameter;
+            ay.State = robot.PlcRobotParameter.AxixList[1].State;
+            Axes.Add(ay);
+            // Z
+            var az = new Axis();
+            az.Name = "Z";
+            az.Index = 3;
+            az.Command = robot.PlcRobotParameter.AxixList[2].Command;
+            az.Parameter = robot.PlcRobotParameter.AxixList[2].Parameter;
+            az.State = robot.PlcRobotParameter.AxixList[2].State;
+            Axes.Add(az);
+
+            var au = new Axis();
+            au.Name = "U";
+            au.Index = 4;
+            au.Command = robot.PlcRobotParameter.AxixList[3].Command;
+            au.Parameter = robot.PlcRobotParameter.AxixList[3].Parameter;
+            au.State = robot.PlcRobotParameter.AxixList[3].State;
+            Axes.Add(au);
+
+            var ar = new Axis();
+            ar.Name = "R";
+            ar.Index = 5;
+            ar.Command = robot.PlcRobotParameter.AxixList[4].Command;
+            ar.Parameter = robot.PlcRobotParameter.AxixList[4].Parameter;
+            ar.State = robot.PlcRobotParameter.AxixList[4].State;
+            Axes.Add(ar);
+
+            var aw = new Axis();
+            aw.Name = "W";
+            aw.Index = 6;
+            aw.Command = robot.PlcRobotParameter.AxixList[5].Command;
+            aw.Parameter = robot.PlcRobotParameter.AxixList[5].Parameter;
+            aw.State = robot.PlcRobotParameter.AxixList[5].State;
+            Axes.Add(aw);
+
+            var av = new Axis();
+            av.Name = "V";
+            av.Index = 7;
+            av.Command = robot.PlcRobotParameter.AxixList[6].Command;
+            av.Parameter = robot.PlcRobotParameter.AxixList[6].Parameter;
+            av.State = robot.PlcRobotParameter.AxixList[6].State;
+            Axes.Add(av);
+            Brand = RobotBrand.XYZURWV_Platform;
+
+
+            if (Plc.IsConnected)
+            {
+                try
+                {
+                    //所有轴的位置节点
+                    var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
+                    List<string> positionNodes = new List<string>(nodeIds);
+                    pLC.SubscribeNodes("AxisPosition", positionNodes, (res) =>
+                    {
+                        if (res.key != "AxisPosition") return;
+
+                        for (int i = 0; i < Axes.Count; i++)
+                        {
+                            var axis = Axes[i];
+                            if (axis.State.ActPositionNode.Contains(res.nodeId))
+                            {
+                                float v = Convert.ToSingle(res.value);
+                                switch (axis.Name)
+                                {
+                                    case "X": CurrentPosition.X = v; break;
+                                    case "Y": CurrentPosition.Y = v; break;
+                                    case "Z": CurrentPosition.Z = v; break;
+                                    case "U": CurrentPosition.U = v; break;
+                                    case "R": CurrentPosition.R = v; break;
+                                    case "W": CurrentPosition.W = v; break;
+                                    case "V": CurrentPosition.V = v; break;
+                                }
+                            }
+                        }
+                    });
+                }
+                catch (Exception)
+                {
+
+                }
+            }
+        }
+
+        private void Plc_ConnectChangedEvent(object arg1, bool arg2)
+        {
+            CanExecute = arg2;
+            if (arg2)
+                ConnectedEvent?.Invoke(Id, this, null);
+            else
+                DisconnectedEvent?.Invoke(Id, this, null);
+        }
+
+        #region 属性
+        public TcpClient TcpClient { get; private set; }
+
+        public TcpService TcpService { get; private set; }
+
+        public Guid Id { get; set; }
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 机器人编号
+        /// </summary>
+        public int RobotNo { get; set; }
+
+        public int RobotPort { get; private set; }
+
+        public string RobotIp { get; private set; }
+
+        public TCPConnectType ConnectType { get; private set; }
+
+        public Terminator Terminator { get; private set; }
+
+        public DataEncoding DataEncoding { get; private set; }
+
+        public bool IsConnected
+        {
+            get
+            {
+                if (Plc != null)
+                {
+                    return Plc.IsConnected;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+        }
+
+        public int Timeout { get; set; } = 5000;
+
+        private bool _CanExecute = true;
+
+        public bool CanExecute
+        {
+            get { return _CanExecute; }
+            set { SetProperty(ref _CanExecute, value); }
+        }
+
+        public int SelectedTool { get; private set; } = 0;
+
+        public RobotBrand Brand { get; private set; }
+
+        public OpcUaClientPLC Plc { get; private set; }
+
+        public RobotInfo RobotInfo { get; private set; }
+
+        public List<Axis> Axes { get; private set; }
+
+        /// <summary>
+        /// 进入调试模式
+        /// </summary>
+        /// <returns></returns>
+        public bool EnterDebugMode { get; set; }
+
+        private RPoint _CurrentPosition = new RPoint();
+        /// <summary>
+        /// 当前位置
+        /// </summary>
+        public RPoint CurrentPosition
+        {
+            get { return _CurrentPosition; }
+            set { SetProperty(ref _CurrentPosition, value); }
+        }
+
+        private double _SafeZ;
+        /// <summary>
+        /// Z轴的安全高度
+        /// </summary>
+        public double SafeZ
+        {
+            get { return _SafeZ; }
+            set { SetProperty(ref _SafeZ, value); }
+        }
+        #endregion
+
+        #region 连接
+        public void Connect()
+        {
+            if (Plc.IsConnected)
+            {
+                StopMove();
+                ConnectedEvent?.Invoke(Id, this, null);
+            }
+        }
+
+        public Task ConnectAsync()
+        {
+            if (Plc.IsConnected)
+            {
+                StopMove();
+                ConnectedEvent?.Invoke(Id, this, null);
+            }
+            return Task.CompletedTask;
+        }
+
+        public void Disconnect()
+        {
+
+        }
+
+        public void Dispose()
+        {
+
+        }
+        #endregion
+
+        #region 控制
+        public bool Reset() => true;
+        public Task<bool> ResetAsync() { return Task.Run(() => Reset()); }
+        public bool Motor(bool state)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = state;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return Plc.WriteNodes(nodesToWrite);
+        }
+        public async Task<bool> MotorAsync(bool state)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = state;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return await Plc.WriteNodesAsync(nodesToWrite);
+        }
+        public bool Power(bool state) => true;
+        public Task<bool> PowerAsync(bool state) => Task.FromResult(true);
+        public bool Speed(int value)
+        {
+            return true;
+        }
+        public Task<bool> SpeedAsync(int value) => Task.FromResult(true);
+        public bool Speedfactor(int value) => true;
+        public Task<bool> SpeedfactorAsync(int value) => Task.FromResult(true);
+        public bool Speeds(double value)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = (float)value;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return Plc.WriteNodes(nodesToWrite);
+        }
+        public async Task<bool> SpeedsAsync(double value)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = (float)value;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return await Plc.WriteNodesAsync(nodesToWrite);
+        }
+        public bool Accel(int value) => true;
+        public Task<bool> AccelAsync(int value) => Task.FromResult(true);
+        public bool Accels(double value) => true;
+        public Task<bool> AccelsAsync(double value) => Task.FromResult(true);
+
+        public RPoint GetRobotPos()
+        {
+            if (Plc == null || !Plc.IsConnected) return null;
+            try
+            {
+                var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
+                var data = Plc.ReadNodes(nodeIds);
+                if (data == null || data.Count == 0) return null;
+
+                //获取所有的值
+                var values = data.Values.ToArray();
+
+                RPoint point = new RPoint();
+                for (int i = 0; i < Axes.Count && i < values.Length; i++)
+                {
+                    var val = values[i];
+                    float v = 0;
+                    if (val != null)
+                    {
+                        try { v = Convert.ToSingle(val); } catch { }
+                    }
+                    switch (Axes[i].Name)
+                    {
+                        case "X": point.X = v; break;
+                        case "Y": point.Y = v; break;
+                        case "Z": point.Z = v; break;
+                        case "U": point.U = v; break;
+                        case "V": point.V = v; break;
+                        case "W": point.W = v; break;
+                        case "R": point.R = v; break;
+                        default: break;
+                    }
+                }
+                return point;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.获取机器人位置出错, ex);
+                throw;
+            }
+        }
+
+        public Task<RPoint> GetRobotPosAsync()
+        {
+            return Task.Run(() => GetRobotPos());
+        }
+
+        public bool Go(RPoint position)
+        {
+            return WaitMoveFinished(position);
+        }
+
+        public Task<bool> GoAsync(RPoint position)
+        {
+            return Task.Run(() => Go(position));
+        }
+
+        public bool Move(RPoint position) => Go(position);
+        public Task<bool> MoveAsync(RPoint position) => GoAsync(position);
+        public bool Jump(RPoint position, double? LimZ)
+        {
+            return CalibMotion(position, LimZ);
+        }
+        public Task<bool> JumpAsync(RPoint position, double? LimZ) => CalibMotionAsync(position, LimZ);
+        public bool Jog(string axis, double distance)
+        {
+            //获取当前机器人坐标
+            var currentPos = GetRobotPos();
+            if (currentPos == null) return false;
+            switch (axis.ToUpper())
+            {
+                case "X":
+                    currentPos.X += (float)distance;
+                    break;
+                case "Y":
+                    currentPos.Y += (float)distance;
+                    break;
+                case "Z":
+                    currentPos.Z += (float)distance;
+                    break;
+                case "U":
+                    currentPos.U += (float)distance;
+                    break;
+                case "R":
+                    currentPos.R += (float)distance;
+                    break;
+                case "W":
+                    currentPos.W += (float)distance;
+                    break;
+                case "V":
+                    currentPos.V += (float)distance;
+                    break;
+                default:
+                    return false;
+            }
+            return Go(currentPos);
+
+        }
+        public Task<bool> JogAsync(string axis, double distance)
+        {
+            return Task.Run(() => Jog(axis, distance));
+        }
+        public bool Joint(int joint, double distance)
+        {
+            return false;
+        }
+        public Task<bool> JointAsync(int joint, double distance) => Task.FromResult(false);
+        public bool SFree()
+        {
+            return Motor(false);
+        }
+        public Task<bool> SFreeAsync() => Task.Run(() => SFree());
+        public bool SLock() => Motor(true);
+        public Task<bool> SLockAsync() => Task.Run(() => SLock());
+        public bool CalibMotion(RPoint position, double? LimZ)
+        {
+            //先Z轴到安全高度
+            var currentPos = GetRobotPos();
+            if (currentPos == null) return false;
+
+            currentPos.U = position.U;
+            currentPos.R = position.R;
+            if (!Go(currentPos)) return false;
+
+            if (LimZ.HasValue)
+            {
+                currentPos.Z = (float)LimZ.Value;
+                if (!Go(currentPos)) return false;
+            }
+            else
+            {
+                currentPos.Z = (float)SafeZ;
+                if (!Go(currentPos)) return false;
+            }
+            //再XYU轴到位
+            currentPos.X = position.X;
+            currentPos.Y = position.Y;
+            if (!Go(currentPos)) return false;
+            //最后Z轴到目标高度
+            currentPos.Z = position.Z;
+            var isSucceed = Go(currentPos);
+            Thread.Sleep(150);
+            return isSucceed;
+        }
+        public Task<bool> CalibMotionAsync(RPoint position, double? LimZ) => Task.Run(() => CalibMotion(position, LimZ));
+        public bool CalibOutIO(bool state) => false;
+        public Task<bool> CalibOutIOAsync(bool state) => Task.FromResult(false);
+        public bool CalibParame(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
+        {
+            return false;
+        }
+
+        public async Task<bool> CalibParameAsync(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
+        {
+            if (Plc == null || !Plc.IsConnected) return false;
+            try
+            {
+                Dictionary<string, object> keyValues = new Dictionary<string, object>();
+                foreach (var axis in Axes)
+                {
+                    if (string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode)) continue;
+                    string nodeid = axis.Parameter.ManuVelocityNode;
+                    float value = Speed;
+
+                    keyValues.Add(nodeid, value);
+                }
+                // 2. 写入速度给所有轴
+                if (!Plc.WriteNodes(keyValues))
+                {
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.为XYZU平台设置速度时出错, ex);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 阻塞式等待运动完成
+        /// </summary>
+        /// <param name="position"></param>
+        /// <returns></returns>
+        private bool WaitMoveFinished(RPoint position)
+        {
+            if (Plc == null || !Plc.IsConnected) return false;
+            try
+            {
+                CanExecute = false;
+                Motor(true);
+                Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+                // 1. 写入目标位置到各轴的 ManuPositionNode
+                keyValues = new Dictionary<string, object>();
+                foreach (var axis in Axes)
+                {
+                    if (string.IsNullOrWhiteSpace(axis.Parameter.ManuPositionNode)) continue;
+                    string nodeid = axis.Parameter.ManuPositionNode;
+                    float value = 0;
+                    switch (axis.Name)
+                    {
+                        case "X": value = position.X; break;
+                        case "Y": value = position.Y; break;
+                        case "Z": value = position.Z; break;
+                        case "U": value = position.U; break;
+                        case "R": value = position.R; break;
+                        case "W": value = position.W; break;
+                        case "V": value = position.V; break;
+                        default: value = 0; break;
+                    }
+                    keyValues.Add(nodeid, value);
+                }
+                // 2. 写入位置给所有轴
+                if (!Plc.WriteNodes(keyValues))
+                {
+                    return false;
+                }
+
+                // 3. 写入开始移动命令给所有轴
+                if (!StartMove())
+                {
+                    return false;
+                }
+
+                // 准备停滞检测:收集所有实际位置节点
+                var actNodes = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
+                                   .Select(a => a.State.ActPositionNode)
+                                   .Distinct()
+                                   .ToArray();
+
+                // lastPos 存储上一次读取到的位置,lastChange 存储上次发生“实质性”变化的时间
+                Dictionary<string, float> lastPos = new Dictionary<string, float>();
+                Dictionary<string, DateTime> lastChange = new Dictionary<string, DateTime>();
+                foreach (var node in actNodes)
+                {
+                    lastPos[node] = float.NaN;
+                    lastChange[node] = DateTime.Now;
+                }
+
+                const float movementThreshold = 0.01f; // 判断位置变化的阈值,避免噪声
+                Stopwatch sw = new Stopwatch();
+                sw.Start();
+                while (true)
+                {
+                    // 先读取各轴的实际位置,用于停滞检测
+                    Dictionary<string, object> posRead = new Dictionary<string, object>();
+                    try
+                    {
+                        if (actNodes.Length > 0)
+                        {
+                            posRead = Plc.ReadNodes(actNodes);
+                        }
+                    }
+                    catch
+                    {
+                        // 如果读取失败,继续让 CheckMoveFinished 来处理状态或超时
+                    }
+
+                    var now = DateTime.Now;
+
+                    // 更新每个节点的变化时间:只要任意一个轴在最近 Timeout 时间内有变化,就视为系统仍在运动
+                    foreach (var node in actNodes)
+                    {
+                        float actual = 0;
+                        if (posRead != null && posRead.ContainsKey(node))
+                        {
+                            var raw = posRead[node];
+                            if (raw != null)
+                            {
+                                try { actual = Convert.ToSingle(raw); } catch { /* 保持 actual = 0 */ }
+                            }
+                        }
+
+                        if (float.IsNaN(lastPos[node]))
+                        {
+                            lastPos[node] = actual;
+                            lastChange[node] = now;
+                        }
+                        else
+                        {
+                            if (Math.Abs(actual - lastPos[node]) > movementThreshold)
+                            {
+                                // 有实质性变化,更新记录时间和值
+                                lastPos[node] = actual;
+                                lastChange[node] = now;
+                            }
+                            // 否则保持 lastChange 不变(表示该轴最近一次变化的时间)
+                        }
+                    }
+
+                    // 判断是否至少有一个轴在最近 Timeout 时间内发生过变化(即认为还在运动)
+                    bool anyAxisMovedRecently = actNodes.Any(node => (now - lastChange[node]).TotalMilliseconds <= 500);
+
+                    // 如果没有任何轴在最近 Timeout 时间内发生变化,则认为出现停滞异常
+                    if (!anyAxisMovedRecently && actNodes.Length > 0)
+                    {
+                        StopMove();
+                        LogHelper.WriteLogInfo(string.Format(Lang.轴停滞超时整体判定目标位置X0Y1Z2U3超时阈值ms4, position.X, position.Y, position.Z, position.U, Timeout));
+                        return false;
+                    }
+
+                    // 检查是否整体到位或有错误(保留原有逻辑)
+                    var (isFinished, isError) = CheckMoveFinished(position);
+                    if (isFinished)
+                    {
+                        StopMove();
+                        if (isError)
+                        {
+                            LogHelper.WriteLogInfo(string.Format(Lang.机器人执行移动时发生错误位置X0Y1Z2U3, position.X, position.Y, position.Z, position.U));
+                            return false;
+                        }
+                        return true;
+                    }
+                    if (isError)
+                    {
+                        StopMove();
+                        LogHelper.WriteLogInfo($"机器人执行移动时发生错误,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
+                        return false;
+                    }
+
+                    // 检查整体超时(防止一直有抖动导致 anyAxisMovedRecently 一直为 true)
+                    if (sw.ElapsedMilliseconds > 60000) // 保守超时,一分钟,可根据需求调整
+                    {
+                        LogHelper.WriteLogInfo(string.Format(Lang.机器人执行移动超时位置X0Y1Z2U3, position.X, position.Y, position.Z, position.U));
+                        StopMove();
+                        return false;
+                    }
+
+                    Thread.Sleep(10);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.执行XYZU平台阻塞式等待运动完成时出错, ex);
+                CanExecute = true;
+                return false;
+            }
+            finally
+            {
+                CanExecute = true;
+            }
+        }
+
+        /// <summary>
+        /// 开始运动
+        /// </summary>
+        /// <returns></returns>
+        private bool StartMove()
+        {
+            Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+            // 1. 复位所有轴的开始移动命令
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
+                {
+                    keyValues.Add(axis.Command.ManuPositionNode, true);
+                }
+            }
+            return Plc.WriteNodes(keyValues);
+        }
+
+        /// <summary>
+        /// 停止运动
+        /// </summary>
+        /// <returns></returns>
+        private bool StopMove()
+        {
+            Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+            // 1. 复位所有轴的开始移动命令
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
+                {
+                    keyValues.Add(axis.Command.ManuPositionNode, false);
+                }
+                //if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
+                //{
+                //    keyValues.Add(axis.Command.StopNode, true);
+                //}
+            }
+            bool result = Plc.WriteNodes(keyValues);
+
+            // 异步等待100ms后复位停止命令
+            //Task.Run(async () =>
+            //{
+            //    await Task.Delay(100);
+            //Thread.Sleep(50);
+            //Dictionary<string, object> resetValues = new Dictionary<string, object>();
+            //foreach (var axis in Axes)
+            //{
+            //    if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
+            //    {
+            //        resetValues.Add(axis.Command.StopNode, false);
+            //    }
+            //}
+            //Plc.WriteNodes(resetValues);
+            //});
+            return result;
+        }
+
+        /// <summary>
+        /// 检查运动是否结束,返回是否停止、是否有错误
+        /// </summary>
+        /// <param name="position"></param>
+        /// <returns></returns>
+        private (bool isFinished, bool isError) CheckMoveFinished(RPoint position)
+        {
+            //检查各轴是否到位和目标位置一致
+            //批量获取所有轴的定位状态和位置、是否错误
+            var nodeIds = new List<string>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.State.PosOKNode))
+                {
+                    nodeIds.Add(axis.State.PosOKNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.ActPositionNode))
+                {
+                    nodeIds.Add(axis.State.ActPositionNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.ErrorNode))
+                {
+                    nodeIds.Add(axis.State.ErrorNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.PausedNode))
+                {
+                    nodeIds.Add(axis.State.PausedNode);
+                }
+            }
+            var res = Plc.ReadNodes(nodeIds.ToArray());
+            bool allOk = true;
+            bool isAnyError = false;
+            foreach (var axis in Axes)
+            {
+                bool posOk = false;
+                float actualPos = 0;
+                bool isError = false;
+                //获取此轴的定位状态、实际位置、错误状态
+                if (res.ContainsKey(axis.State.PosOKNode))
+                {
+                    posOk = (bool)res[axis.State.PosOKNode];
+                }
+                if (res.ContainsKey(axis.State.ActPositionNode))
+                {
+                    try { actualPos = Convert.ToSingle(res[axis.State.ActPositionNode]); } catch { }
+                }
+                if (res.ContainsKey(axis.State.ErrorNode))
+                {
+                    isError = (bool)res[axis.State.ErrorNode];
+                }
+                if (res.ContainsKey(axis.State.PausedNode))
+                {
+                    isError = (bool)res[axis.State.PausedNode];
+                }
+                //if (isError)
+                //{
+                //    isAnyError = true;
+                //    break;
+                //}
+                //检查是否到位和位置一致
+                float targetPos = 0;
+                switch (axis.Name)
+                {
+                    case "X": targetPos = position.X; break;
+                    case "Y": targetPos = position.Y; break;
+                    case "Z": targetPos = position.Z; break;
+                    case "U": targetPos = position.U; break;
+                    case "R": targetPos = position.R; break;
+                    case "W": targetPos = position.W; break;
+                    case "V": targetPos = position.V; break;
+                    default: targetPos = 0; break;
+                }
+                //如果定位完成且位置一致,则此轴完成,如果有错误则失败
+                if (!(Math.Abs(actualPos - targetPos) < 0.02))
+                {
+                    //if (isError)
+                    //{
+                    //    isAnyError = true;
+                    //    break;
+                    //}
+                    allOk = false;
+                    break;
+                }
+            }
+            return (allOk, isAnyError);
+        }
+
+        #endregion
+
+        #region 收发数据
+        public string SendAndReceive(string send) => string.Empty;
+        public Task<string> SendAndReceiveAsync(string send) => Task.FromResult(string.Empty);
+        public string SendAndReceive(ITcpSessionClient client, string send) => string.Empty;
+        public Task<string> SendAndReceiveAsync(ITcpSessionClient client, string send) => Task.FromResult(string.Empty);
+        public void Send(string send) { }
+        public Task SendAsync(string send) => Task.CompletedTask;
+        public void Send(ITcpSessionClient client, string send) { }
+        public Task SendAsync(ITcpSessionClient client, string send) => Task.CompletedTask;
+        public Encoding GetEncoding() => Encoding.Default;
+        #endregion
+
+        #region 属性通知
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
+        {
+            if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
+            storage = value;
+            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
+            return true;
+        }
+        protected void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+        #endregion
+
+        #region IRobot SetTool/SelectTool stubs
+        public bool SetTool(int index, double x, double y) => true;
+        public Task<bool> SetToolAsync(int index, double x, double y) => Task.FromResult(true);
+        public bool SelectTool(int index) => true;
+        public Task<bool> SelectToolAsync(int index) => Task.FromResult(true);
+        #endregion
+    }
+}

+ 856 - 0
TeamAAS-VM/Core/Robots/XYZUR_Platform.cs

@@ -0,0 +1,856 @@
+using MahApps.Metro.Controls;
+using Opc.Ua;
+using Prism;
+using Prism.Ioc;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using TeamAAS_VP.Core.PLCs;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Resources.Languages;
+using TouchSocket.Core;
+using TouchSocket.Sockets;
+using static System.Windows.Forms.AxHost;
+
+namespace TeamAAS_VP.Core.Robots
+{
+    /// <summary>
+    /// Generic multi-axis robot that composes axis objects and uses PLC nodes configured in RobotInfo.PlcRobotParameter
+    /// to perform coordinated moves (Go/Move/CalibMotion) for 3 or 4 axes.
+    /// This implementation maps X/Y/Z/(U) axes by name and writes their position nodes then triggers ExecuteMove node.
+    /// </summary>
+    public class XYZUR_Platform : IRobot, INotifyPropertyChanged
+    {
+        public event Action<Guid, object, ConnectedEventArgs> ConnectedEvent;
+        public event Action<Guid, object, ClosedEventArgs> DisconnectedEvent;
+        public event Action<Guid, object, ReceivedDataEventArgs> ReceivedEvent;
+        public event Action<Guid, object, string> SendEvent;
+
+        public XYZUR_Platform(RobotInfo robot, OpcUaClientPLC pLC)
+        {
+            RobotInfo = robot;
+            Name = robot.RobotName;
+            Id = robot.Id;
+            RobotNo = robot.RobotNo;
+            RobotIp = robot.IP;
+            RobotPort = robot.Port;
+            ConnectType = robot.ConnectType;
+            Terminator = robot.Terminator;
+            DataEncoding = robot.DataEncoding;
+            Brand = RobotBrand.XYZ_Platform;
+            SafeZ = robot.SafeZ;
+            Plc = pLC;
+            Plc.ConnectChangedEvent += Plc_ConnectChangedEvent;
+
+            // build axis list from known RobotInfo.PlcRobotParameter nodes (best-effort)
+            Axes = new List<Axis>();
+            // X
+            var ax = new Axis();
+            ax.Name = "X";
+            ax.Index = 1;
+            ax.Command = robot.PlcRobotParameter.AxixList[0].Command;
+            ax.Parameter = robot.PlcRobotParameter.AxixList[0].Parameter;
+            ax.State = robot.PlcRobotParameter.AxixList[0].State;
+            Axes.Add(ax);
+
+            // Y
+            var ay = new Axis();
+            ay.Name = "Y";
+            ay.Index = 2;
+            ay.Command = robot.PlcRobotParameter.AxixList[1].Command;
+            ay.Parameter = robot.PlcRobotParameter.AxixList[1].Parameter;
+            ay.State = robot.PlcRobotParameter.AxixList[1].State;
+            Axes.Add(ay);
+            // Z
+            var az = new Axis();
+            az.Name = "Z";
+            az.Index = 3;
+            az.Command = robot.PlcRobotParameter.AxixList[2].Command;
+            az.Parameter = robot.PlcRobotParameter.AxixList[2].Parameter;
+            az.State = robot.PlcRobotParameter.AxixList[2].State;
+            Axes.Add(az);
+
+
+            var au = new Axis();
+            au.Name = "U";
+            au.Index = 4;
+            au.Command = robot.PlcRobotParameter.AxixList[3].Command;
+            au.Parameter = robot.PlcRobotParameter.AxixList[3].Parameter;
+            au.State = robot.PlcRobotParameter.AxixList[3].State;
+            Axes.Add(au);
+
+            var ar = new Axis();
+            ar.Name = "R";
+            ar.Index = 5;
+            ar.Command = robot.PlcRobotParameter.AxixList[4].Command;
+            ar.Parameter = robot.PlcRobotParameter.AxixList[4].Parameter;
+            ar.State = robot.PlcRobotParameter.AxixList[4].State;
+            Axes.Add(ar);
+            Brand = RobotBrand.XYZUR_Platform;
+
+
+
+            if (Plc.IsConnected)
+            {
+                try
+                {
+                    //所有轴的位置节点
+                    var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
+                    List<string> positionNodes = new List<string>(nodeIds);
+                    pLC.SubscribeNodes("AxisPosition", positionNodes, (res) =>
+                    {
+                        if (res.key != "AxisPosition") return;
+
+                        for (int i = 0; i < Axes.Count; i++)
+                        {
+                            var axis = Axes[i];
+                            if (axis.State.ActPositionNode.Contains(res.nodeId))
+                            {
+                                float v = Convert.ToSingle(res.value);
+                                switch (axis.Name)
+                                {
+                                    case "X": CurrentPosition.X = v; break;
+                                    case "Y": CurrentPosition.Y = v; break;
+                                    case "Z": CurrentPosition.Z = v; break;
+                                    case "U": CurrentPosition.U = v; break;
+                                    case "R": CurrentPosition.R = v; break;
+                                }
+                            }
+                        }
+                    });
+                }
+                catch (Exception)
+                {
+
+                }
+            }
+        }
+
+        private void Plc_ConnectChangedEvent(object arg1, bool arg2)
+        {
+            CanExecute = arg2;
+            if (arg2)
+                ConnectedEvent?.Invoke(Id, this, null);
+            else
+                DisconnectedEvent?.Invoke(Id, this, null);
+        }
+
+        #region 属性
+        public TcpClient TcpClient { get; private set; }
+
+        public TcpService TcpService { get; private set; }
+
+        public Guid Id { get; set; }
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 机器人编号
+        /// </summary>
+        public int RobotNo { get; set; }
+
+        public int RobotPort { get; private set; }
+
+        public string RobotIp { get; private set; }
+
+        public TCPConnectType ConnectType { get; private set; }
+
+        public Terminator Terminator { get; private set; }
+
+        public DataEncoding DataEncoding { get; private set; }
+
+        public bool IsConnected
+        {
+            get
+            {
+                if (Plc != null)
+                {
+                    return Plc.IsConnected;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+        }
+
+        public int Timeout { get; set; } = 5000;
+
+        private bool _CanExecute = true;
+
+        public bool CanExecute
+        {
+            get { return _CanExecute; }
+            set { SetProperty(ref _CanExecute, value); }
+        }
+
+        public int SelectedTool { get; private set; } = 0;
+
+        public RobotBrand Brand { get; private set; }
+
+        public OpcUaClientPLC Plc { get; private set; }
+
+        public RobotInfo RobotInfo { get; private set; }
+
+        public List<Axis> Axes { get; private set; }
+
+        /// <summary>
+        /// 进入调试模式
+        /// </summary>
+        /// <returns></returns>
+        public bool EnterDebugMode { get; set; }
+
+        private RPoint _CurrentPosition = new RPoint();
+        /// <summary>
+        /// 当前位置
+        /// </summary>
+        public RPoint CurrentPosition
+        {
+            get { return _CurrentPosition; }
+            set { SetProperty(ref _CurrentPosition, value); }
+        }
+
+        private double _SafeZ;
+        /// <summary>
+        /// Z轴的安全高度
+        /// </summary>
+        public double SafeZ
+        {
+            get { return _SafeZ; }
+            set { SetProperty(ref _SafeZ, value); }
+        }
+        #endregion
+
+        #region 连接
+        public void Connect()
+        {
+            if (Plc.IsConnected)
+            {
+                StopMove();
+                ConnectedEvent?.Invoke(Id, this, null);
+            }
+        }
+
+        public Task ConnectAsync()
+        {
+            if (Plc.IsConnected)
+            {
+                StopMove();
+                ConnectedEvent?.Invoke(Id, this, null);
+            }
+            return Task.CompletedTask;
+        }
+
+        public void Disconnect()
+        {
+
+        }
+
+        public void Dispose()
+        {
+
+        }
+        #endregion
+
+        #region 控制
+        public bool Reset() => true;
+        public Task<bool> ResetAsync() { return Task.Run(() => Reset()); }
+        public bool Motor(bool state)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = state;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return Plc.WriteNodes(nodesToWrite);
+        }
+        public async Task<bool> MotorAsync(bool state)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = state;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return await Plc.WriteNodesAsync(nodesToWrite);
+        }
+        public bool Power(bool state) => true;
+        public Task<bool> PowerAsync(bool state) => Task.FromResult(true);
+        public bool Speed(int value)
+        {
+            return true;
+        }
+        public Task<bool> SpeedAsync(int value) => Task.FromResult(true);
+        public bool Speedfactor(int value) => true;
+        public Task<bool> SpeedfactorAsync(int value) => Task.FromResult(true);
+        public bool Speeds(double value)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = (float)value;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return Plc.WriteNodes(nodesToWrite);
+        }
+        public async Task<bool> SpeedsAsync(double value)
+        {
+            //遍历所有轴,设置电机状态
+            //获取所有轴的Poer节点,组成一个集合,一次性写入
+            Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
+                {
+                    nodesToWrite[axis.Command.PowerNode] = (float)value;
+                }
+            }
+            if (Plc == null || !Plc.IsConnected) return false;
+            return await Plc.WriteNodesAsync(nodesToWrite);
+        }
+        public bool Accel(int value) => true;
+        public Task<bool> AccelAsync(int value) => Task.FromResult(true);
+        public bool Accels(double value) => true;
+        public Task<bool> AccelsAsync(double value) => Task.FromResult(true);
+
+        public RPoint GetRobotPos()
+        {
+            if (Plc == null || !Plc.IsConnected) return null;
+            try
+            {
+                var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
+                var data = Plc.ReadNodes(nodeIds);
+                if (data == null || data.Count == 0) return null;
+
+                //获取所有的值
+                var values = data.Values.ToArray();
+
+                RPoint point = new RPoint();
+                for (int i = 0; i < Axes.Count && i < values.Length; i++)
+                {
+                    var val = values[i];
+                    float v = 0;
+                    if (val != null)
+                    {
+                        try { v = Convert.ToSingle(val); } catch { }
+                    }
+                    switch (Axes[i].Name)
+                    {
+                        case "X": point.X = v; break;
+                        case "Y": point.Y = v; break;
+                        case "Z": point.Z = v; break;
+                        case "U": point.U = v; break;
+                        case "R": point.R = v; break;
+                        default: break;
+                    }
+                }
+                return point;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.获取机器人位置出错, ex);
+                throw;
+            }
+        }
+
+        public Task<RPoint> GetRobotPosAsync()
+        {
+            return Task.Run(() => GetRobotPos());
+        }
+
+        public bool Go(RPoint position)
+        {
+            return WaitMoveFinished(position);
+        }
+
+        public Task<bool> GoAsync(RPoint position)
+        {
+            return Task.Run(() => Go(position));
+        }
+
+        public bool Move(RPoint position) => Go(position);
+        public Task<bool> MoveAsync(RPoint position) => GoAsync(position);
+        public bool Jump(RPoint position, double? LimZ)
+        {
+            return CalibMotion(position, LimZ);
+        }
+        public Task<bool> JumpAsync(RPoint position, double? LimZ) => CalibMotionAsync(position, LimZ);
+        public bool Jog(string axis, double distance)
+        {
+            //获取当前机器人坐标
+            var currentPos = GetRobotPos();
+            if (currentPos == null) return false;
+            switch (axis.ToUpper())
+            {
+                case "X":
+                    currentPos.X += (float)distance;
+                    break;
+                case "Y":
+                    currentPos.Y += (float)distance;
+                    break;
+                case "Z":
+                    currentPos.Z += (float)distance;
+                    break;
+                case "U":
+                    currentPos.U += (float)distance;
+                    break;
+                case "R":
+                    currentPos.R += (float)distance;
+                    break;         
+                default:
+                    return false;
+            }
+            return Go(currentPos);
+
+        }
+        public Task<bool> JogAsync(string axis, double distance)
+        {
+            return Task.Run(() => Jog(axis, distance));
+        }
+        public bool Joint(int joint, double distance)
+        {
+            return false;
+        }
+        public Task<bool> JointAsync(int joint, double distance) => Task.FromResult(false);
+        public bool SFree()
+        {
+            return Motor(false);
+        }
+        public Task<bool> SFreeAsync() => Task.Run(() => SFree());
+        public bool SLock() => Motor(true);
+        public Task<bool> SLockAsync() => Task.Run(() => SLock());
+        public bool CalibMotion(RPoint position, double? LimZ)
+        {
+            //先Z轴到安全高度
+            var currentPos = GetRobotPos();
+            if (currentPos == null) return false;
+
+            currentPos.U = position.U;
+            currentPos.R = position.R;
+            if (!Go(currentPos)) return false;
+
+            if (LimZ.HasValue)
+            {
+                currentPos.Z = (float)LimZ.Value;
+                if (!Go(currentPos)) return false;
+            }
+            else
+            {
+                currentPos.Z = (float)SafeZ;
+                if (!Go(currentPos)) return false;
+            }
+            //再XYU轴到位
+            currentPos.X = position.X;
+            currentPos.Y = position.Y;
+            currentPos.U = position.U;
+            currentPos.R = position.R;
+            if (!Go(currentPos)) return false;
+            //最后Z轴到目标高度
+            currentPos.Z = position.Z;
+            var isSucceed = Go(currentPos);
+            Thread.Sleep(150);
+            return isSucceed;
+        }
+        public Task<bool> CalibMotionAsync(RPoint position, double? LimZ) => Task.Run(() => CalibMotion(position, LimZ));
+        public bool CalibOutIO(bool state) => false;
+        public Task<bool> CalibOutIOAsync(bool state) => Task.FromResult(false);
+        public bool CalibParame(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
+        {
+            return false;
+
+        }
+
+        public async Task<bool> CalibParameAsync(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
+        {
+            if (Plc == null || !Plc.IsConnected) return false;
+            try
+            {
+                Dictionary<string, object> keyValues = new Dictionary<string, object>();
+                foreach (var axis in Axes)
+                {
+                    if (string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode)) continue;
+                    string nodeid = axis.Parameter.ManuVelocityNode;
+                    float value = Speed;
+
+                    keyValues.Add(nodeid, value);
+                }
+                // 2. 写入速度给所有轴
+                if (!Plc.WriteNodes(keyValues))
+                {
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.为XYZU平台设置速度时出错, ex);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 阻塞式等待运动完成
+        /// </summary>
+        /// <param name="position"></param>
+        /// <returns></returns>
+        private bool WaitMoveFinished(RPoint position)
+        {
+            if (Plc == null || !Plc.IsConnected) return false;
+            try
+            {
+                CanExecute = false;
+                Motor(true);
+                Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+                // 1. 写入目标位置到各轴的 ManuPositionNode
+                keyValues = new Dictionary<string, object>();
+                foreach (var axis in Axes)
+                {
+                    if (string.IsNullOrWhiteSpace(axis.Parameter.ManuPositionNode)) continue;
+                    string nodeid = axis.Parameter.ManuPositionNode;
+                    float value = 0;
+                    switch (axis.Name)
+                    {
+                        case "X": value = position.X; break;
+                        case "Y": value = position.Y; break;
+                        case "Z": value = position.Z; break;
+                        case "U": value = position.U; break;
+                        case "R": value = position.R; break;
+                        default: value = 0; break;
+                    }
+                    keyValues.Add(nodeid, value);
+                }
+                // 2. 写入位置给所有轴
+                if (!Plc.WriteNodes(keyValues))
+                {
+                    return false;
+                }
+
+                // 3. 写入开始移动命令给所有轴
+                if (!StartMove())
+                {
+                    return false;
+                }
+
+                // 准备停滞检测:收集所有实际位置节点
+                var actNodes = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
+                                   .Select(a => a.State.ActPositionNode)
+                                   .Distinct()
+                                   .ToArray();
+
+                // lastPos 存储上一次读取到的位置,lastChange 存储上次发生“实质性”变化的时间
+                Dictionary<string, float> lastPos = new Dictionary<string, float>();
+                Dictionary<string, DateTime> lastChange = new Dictionary<string, DateTime>();
+                foreach (var node in actNodes)
+                {
+                    lastPos[node] = float.NaN;
+                    lastChange[node] = DateTime.Now;
+                }
+
+                const float movementThreshold = 0.01f; // 判断位置变化的阈值,避免噪声
+                Stopwatch sw = new Stopwatch();
+                sw.Start();
+                while (true)
+                {
+                    // 先读取各轴的实际位置,用于停滞检测
+                    Dictionary<string, object> posRead = new Dictionary<string, object>();
+                    try
+                    {
+                        if (actNodes.Length > 0)
+                        {
+                            posRead = Plc.ReadNodes(actNodes);
+                        }
+                    }
+                    catch
+                    {
+                        // 如果读取失败,继续让 CheckMoveFinished 来处理状态或超时
+                    }
+
+                    var now = DateTime.Now;
+
+                    // 更新每个节点的变化时间:只要任意一个轴在最近 Timeout 时间内有变化,就视为系统仍在运动
+                    foreach (var node in actNodes)
+                    {
+                        float actual = 0;
+                        if (posRead != null && posRead.ContainsKey(node))
+                        {
+                            var raw = posRead[node];
+                            if (raw != null)
+                            {
+                                try { actual = Convert.ToSingle(raw); } catch { /* 保持 actual = 0 */ }
+                            }
+                        }
+
+                        if (float.IsNaN(lastPos[node]))
+                        {
+                            lastPos[node] = actual;
+                            lastChange[node] = now;
+                        }
+                        else
+                        {
+                            if (Math.Abs(actual - lastPos[node]) > movementThreshold)
+                            {
+                                // 有实质性变化,更新记录时间和值
+                                lastPos[node] = actual;
+                                lastChange[node] = now;
+                            }
+                            // 否则保持 lastChange 不变(表示该轴最近一次变化的时间)
+                        }
+                    }
+
+                    // 判断是否至少有一个轴在最近 Timeout 时间内发生过变化(即认为还在运动)
+                    bool anyAxisMovedRecently = actNodes.Any(node => (now - lastChange[node]).TotalMilliseconds <= 500);
+
+                    // 如果没有任何轴在最近 Timeout 时间内发生变化,则认为出现停滞异常
+                    if (!anyAxisMovedRecently && actNodes.Length > 0)
+                    {
+                        StopMove();
+                        LogHelper.WriteLogInfo(string.Format(Lang.轴停滞超时整体判定目标位置X0Y1Z2U3超时阈值ms4, position.X, position.Y, position.Z, position.U, Timeout));
+                        return false;
+                    }
+
+                    // 检查是否整体到位或有错误(保留原有逻辑)
+                    var (isFinished, isError) = CheckMoveFinished(position);
+                    if (isFinished)
+                    {
+                        StopMove();
+                        if (isError)
+                        {
+                            LogHelper.WriteLogInfo(string.Format(Lang.机器人执行移动时发生错误位置X0Y1Z2U3, position.X, position.Y, position.Z, position.U));
+                            return false;
+                        }
+                        return true;
+                    }
+                    if (isError)
+                    {
+                        StopMove();
+                        LogHelper.WriteLogInfo($"机器人执行移动时发生错误,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
+                        return false;
+                    }
+
+                    // 检查整体超时(防止一直有抖动导致 anyAxisMovedRecently 一直为 true)
+                    if (sw.ElapsedMilliseconds > 60000) // 保守超时,一分钟,可根据需求调整
+                    {
+                        LogHelper.WriteLogInfo(string.Format(Lang.机器人执行移动超时位置X0Y1Z2U3, position.X, position.Y, position.Z, position.U));
+                        StopMove();
+                        return false;
+                    }
+
+                    Thread.Sleep(10);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError(Lang.执行XYZU平台阻塞式等待运动完成时出错, ex);
+                CanExecute = true;
+                return false;
+            }
+            finally
+            {
+                CanExecute = true;
+            }
+        }
+
+        /// <summary>
+        /// 开始运动
+        /// </summary>
+        /// <returns></returns>
+        private bool StartMove()
+        {
+            Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+            // 1. 复位所有轴的开始移动命令
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
+                {
+                    keyValues.Add(axis.Command.ManuPositionNode, true);
+                }
+            }
+            return Plc.WriteNodes(keyValues);
+        }
+
+        /// <summary>
+        /// 停止运动
+        /// </summary>
+        /// <returns></returns>
+        private bool StopMove()
+        {
+            Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+            // 1. 复位所有轴的开始移动命令
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
+                {
+                    keyValues.Add(axis.Command.ManuPositionNode, false);
+                }
+                //if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
+                //{
+                //    keyValues.Add(axis.Command.StopNode, true);
+                //}
+            }
+            bool result = Plc.WriteNodes(keyValues);
+
+            // 异步等待100ms后复位停止命令
+            //Task.Run(async () =>
+            //{
+            //    await Task.Delay(100);
+            //Thread.Sleep(50);
+            //Dictionary<string, object> resetValues = new Dictionary<string, object>();
+            //foreach (var axis in Axes)
+            //{
+            //    if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
+            //    {
+            //        resetValues.Add(axis.Command.StopNode, false);
+            //    }
+            //}
+            //Plc.WriteNodes(resetValues);
+            //});
+            return result;
+        }
+
+        /// <summary>
+        /// 检查运动是否结束,返回是否停止、是否有错误
+        /// </summary>
+        /// <param name="position"></param>
+        /// <returns></returns>
+        private (bool isFinished, bool isError) CheckMoveFinished(RPoint position)
+        {
+            //检查各轴是否到位和目标位置一致
+            //批量获取所有轴的定位状态和位置、是否错误
+            var nodeIds = new List<string>();
+            foreach (var axis in Axes)
+            {
+                if (!string.IsNullOrWhiteSpace(axis.State.PosOKNode))
+                {
+                    nodeIds.Add(axis.State.PosOKNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.ActPositionNode))
+                {
+                    nodeIds.Add(axis.State.ActPositionNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.ErrorNode))
+                {
+                    nodeIds.Add(axis.State.ErrorNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.PausedNode))
+                {
+                    nodeIds.Add(axis.State.PausedNode);
+                }
+            }
+            var res = Plc.ReadNodes(nodeIds.ToArray());
+            bool allOk = true;
+            bool isAnyError = false;
+            foreach (var axis in Axes)
+            {
+                bool posOk = false;
+                float actualPos = 0;
+                bool isError = false;
+                //获取此轴的定位状态、实际位置、错误状态
+                if (res.ContainsKey(axis.State.PosOKNode))
+                {
+                    posOk = (bool)res[axis.State.PosOKNode];
+                }
+                if (res.ContainsKey(axis.State.ActPositionNode))
+                {
+                    try { actualPos = Convert.ToSingle(res[axis.State.ActPositionNode]); } catch { }
+                }
+                if (res.ContainsKey(axis.State.ErrorNode))
+                {
+                    isError = (bool)res[axis.State.ErrorNode];
+                }
+                if (res.ContainsKey(axis.State.PausedNode))
+                {
+                    isError = (bool)res[axis.State.PausedNode];
+                }
+                //if (isError)
+                //{
+                //    isAnyError = true;
+                //    break;
+                //}
+                //检查是否到位和位置一致
+                float targetPos = 0;
+                switch (axis.Name)
+                {
+                    case "X": targetPos = position.X; break;
+                    case "Y": targetPos = position.Y; break;
+                    case "Z": targetPos = position.Z; break;
+                    case "U": targetPos = position.U; break;
+                    case "R": targetPos = position.R; break;
+                    default: targetPos = 0; break;
+                }
+                //如果定位完成且位置一致,则此轴完成,如果有错误则失败
+                if (!(Math.Abs(actualPos - targetPos) < 0.02))
+                {
+                    //if (isError)
+                    //{
+                    //    isAnyError = true;
+                    //    break;
+                    //}
+                    allOk = false;
+                    break;
+                }
+            }
+            return (allOk, isAnyError);
+        }
+
+        #endregion
+
+        #region 收发数据
+        public string SendAndReceive(string send) => string.Empty;
+        public Task<string> SendAndReceiveAsync(string send) => Task.FromResult(string.Empty);
+        public string SendAndReceive(ITcpSessionClient client, string send) => string.Empty;
+        public Task<string> SendAndReceiveAsync(ITcpSessionClient client, string send) => Task.FromResult(string.Empty);
+        public void Send(string send) { }
+        public Task SendAsync(string send) => Task.CompletedTask;
+        public void Send(ITcpSessionClient client, string send) { }
+        public Task SendAsync(ITcpSessionClient client, string send) => Task.CompletedTask;
+        public Encoding GetEncoding() => Encoding.Default;
+        #endregion
+
+        #region 属性通知
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
+        {
+            if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
+            storage = value;
+            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
+            return true;
+        }
+        protected void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+        #endregion
+
+        #region IRobot SetTool/SelectTool stubs
+        public bool SetTool(int index, double x, double y) => true;
+        public Task<bool> SetToolAsync(int index, double x, double y) => Task.FromResult(true);
+        public bool SelectTool(int index) => true;
+        public Task<bool> SelectToolAsync(int index) => Task.FromResult(true);
+        #endregion
+    }
+}

+ 1 - 1
TeamAAS-VM/Core/ScrewDriver/XYD_ScrewDriver.cs

@@ -316,7 +316,7 @@ namespace TeamAAS_VP.Core.ScrewDriver
                     UpdateConnectState(false);
                 }
             }
-
+            UpdateConnectState(false);
             return false;
         }
 

+ 7 - 0
TeamAAS-VM/Enums/RobotBrand.cs

@@ -40,5 +40,12 @@ namespace TeamAAS_VP.Enums
         [Localization(ResourceName = "RobotBrand_XYZU_Platform", ResourceType = typeof(Lang))]
         XYZU_Platform = 12,
 
+        [Description("XYZUR模组平台")]
+        [Localization(ResourceName = "RobotBrand_XYZUR_Platform", ResourceType = typeof(Lang))]
+        XYZUR_Platform = 13,
+
+        [Description("XYZURWV模组平台")]
+        [Localization(ResourceName = "RobotBrand_XYZURWV_Platform", ResourceType = typeof(Lang))]
+        XYZURWV_Platform = 14,
     }
 }

+ 30 - 3
TeamAAS-VM/Models/PLC/PlcAddressConfig.cs

@@ -347,6 +347,22 @@ namespace TeamAAS_VP.Models.PLC
             set { SetProperty(ref _QrCodeCamera, value); }
         }
 
+        private PlcAddress _Out_RobotWPosition;
+
+        public PlcAddress Out_RobotWPosition
+        {
+            get { return _Out_RobotWPosition; }
+            set { SetProperty(ref _Out_RobotWPosition, value); }
+        }
+
+        private PlcAddress _Out_RobotVPosition;
+
+        public PlcAddress Out_RobotVPosition
+        {
+            get { return _Out_RobotVPosition; }
+            set { SetProperty(ref _Out_RobotVPosition, value); }
+        }
+
         private PlcAddress _Out_ScrewTeachOk;
         /// <summary>
         /// 下相机矫正披头的结果,1:OK,2:NG
@@ -1388,15 +1404,26 @@ namespace TeamAAS_VP.Models.PLC
 
             In_FzCameraPixExe = Ensure(In_FzCameraPixExe);
             In_FzCameraPixExe.Address = "ns=4;s=DB_Communication|ToPC.In_FzCameraPickExe";
-            In_FzCameraPixExe.DataType = "bool";
+            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.DataType = "Bool";
             Out_PlcFail.Description = "PLC触发报警信号";
 
-            //ns=4;s=DB_Input|X16_螺丝供料器1螺丝到位
+            
+            Out_RobotWPosition = Ensure(Out_RobotWPosition);
+            Out_RobotWPosition.Address = "ns=4;s=DB_Communication|FromPC.Point_Clamp[1].X_Position";
+            Out_RobotWPosition.DataType = "float";
+            Out_RobotWPosition.Description = "";
+
+            Out_RobotVPosition = Ensure(Out_RobotVPosition);
+            Out_RobotVPosition.Address = "ns=4;s=DB_Communication|FromPC.Point_Clamp[1].Y_Position";
+            Out_RobotVPosition.DataType = "float";
+            Out_RobotVPosition.Description = "";
+
+            //ns=4;s=DB_Input|X16_螺丝供料器1螺丝到位  FromPC.Point_Clamp[1].X_Velocity  FromPC.Point_Clamp[1].Y_Velocity
             //ns=4;s=DB_Input|X17_螺丝供料器2螺丝到位
 
             // 说明:若项目中 PlcAddress 的实际属性名或类型与此不同(例如使用枚举、不同字段名),

+ 20 - 0
TeamAAS-VM/Models/PLC/PlcPoint.cs

@@ -282,6 +282,16 @@ namespace TeamAAS_VP.Models.PLC
             set { SetProperty(ref _R_Offset, value); }
         }
 
+        private Int16 _PickScrew = 1;
+        /// <summary>
+        /// "是否取螺丝 =0 不需要取螺丝 =1 需要取螺丝"
+        /// </summary>
+        public Int16 PickScrew
+        {
+            get { return _PickScrew; }
+            set { SetProperty(ref _PickScrew, value); }
+        }
+
         public PlcPoint()
         {
             Number = 0;
@@ -308,6 +318,7 @@ namespace TeamAAS_VP.Models.PLC
             U_Offset = 0;
             R_Offset = 0;
             IsUse = true;
+            PickScrew = 1;
         }
 
         public PlcPoint Clone()
@@ -341,6 +352,7 @@ namespace TeamAAS_VP.Models.PLC
                 U_Offset = this.U_Offset,
                 R_Offset = this.R_Offset,
                 IsUse = this.IsUse,
+                PickScrew = this.PickScrew
             };
         }
 
@@ -368,6 +380,10 @@ namespace TeamAAS_VP.Models.PLC
             nodeValues.Add(string.Format(plcAddress.Torque.Address, Number), Torque);
             nodeValues.Add(string.Format(plcAddress.Feeder.Address, Number), Feeder);
             nodeValues.Add(string.Format(plcAddress.ScrewProNum.Address, Number), ScrewProNum);
+
+            if(plcAddress.Description== "锁附点位参数")//PLC好像只有这个点了这个参数地址
+                nodeValues.Add(string.Format(plcAddress.PickScrew.Address, Number), PickScrew);
+
             return await pLC.WriteNodesAsync(nodeValues);
         }
 
@@ -395,6 +411,9 @@ namespace TeamAAS_VP.Models.PLC
             nodeValues.Add(string.Format(plcAddress.Torque.Address, index), Torque);
             nodeValues.Add(string.Format(plcAddress.Feeder.Address, index), Feeder);
             nodeValues.Add(string.Format(plcAddress.ScrewProNum.Address, index), ScrewProNum);
+
+            if (plcAddress.Description == "锁附点位参数")//只给这个点写这个参数地址
+                nodeValues.Add(string.Format(plcAddress.PickScrew.Address, Number), PickScrew);
             return await pLC.WriteNodesAsync(nodeValues);
         }
 
@@ -422,6 +441,7 @@ namespace TeamAAS_VP.Models.PLC
             //nodeValues.Add(string.Format(plcAddress.Torque.Address, Number), Torque);
             nodeValues.Add(string.Format(plcAddress.Feeder.Address, index), Feeder);
             nodeValues.Add(string.Format(plcAddress.ScrewProNum.Address, index), ScrewProNum);
+            nodeValues.Add(string.Format(plcAddress.PickScrew.Address, index), PickScrew);
             return await pLC.WriteNodesAsync(nodeValues);
         }
     }

+ 2 - 0
TeamAAS-VM/Models/PLC/PlcPointAddress.cs

@@ -26,6 +26,7 @@ namespace TeamAAS_VP.Models.PLC
         public PlcAddress Torque { get; set; }
         public PlcAddress Feeder { get; set; }
         public PlcAddress ScrewProNum { get; set; }
+        public PlcAddress PickScrew { get; set; }
 
         public PlcPointAddress()
         {
@@ -45,6 +46,7 @@ namespace TeamAAS_VP.Models.PLC
             Torque = new PlcAddress() { Description = Lang.扭矩地址, DataType = "float" };
             Feeder = new PlcAddress() { Description = Lang.送料器地址, DataType = "int16" };
             ScrewProNum = new PlcAddress() { Description = Lang.螺杆程序号地址, DataType = "int16" };
+            PickScrew = new PlcAddress() { Description ="取不取螺丝地址", DataType = "int16" };
         }
     }
 }

+ 8 - 0
TeamAAS-VM/Models/Robot/RPoint.cs

@@ -62,6 +62,14 @@ namespace TeamAAS_VP.Models
             get { return _U; }
             set { SetProperty(ref _U, value); }
         }
+
+        private float _R;
+        public float R
+        {
+            get { return _R; }
+            set { SetProperty(ref _R, value); }
+        }
+
         private float _V;
         public float V
         {

+ 22 - 0
TeamAAS-VM/Models/Robot/RobotPoint.cs

@@ -26,6 +26,28 @@ namespace TeamAAS_VP.Models.Robot
             set => SetProperty(ref _Robots, value);
         }
 
+
+        private float _W_Position;
+        /// <summary>
+        /// W轴位置
+        /// </summary>
+        public float W_Position
+        {
+            get { return _W_Position; }
+            set { SetProperty(ref _W_Position, value); }
+        }
+
+        private float _V_Position;
+        /// <summary>
+        /// V轴位置
+        /// </summary>
+        public float V_Position
+        {
+            get { return _V_Position; }
+            set { SetProperty(ref _V_Position, value); }
+        }
+
+
         /// <summary>
         /// 增加机器人
         /// </summary>

+ 110 - 47
TeamAAS-VM/Resources/Languages/Lang.Designer.cs

@@ -19,7 +19,7 @@ namespace TeamAAS_VP.Resources.Languages {
     // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
     // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
     // (以 /str 作为命令选项),或重新生成 VS 项目。
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
     public class Lang {
@@ -825,6 +825,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 NG数量 的本地化字符串。
+        /// </summary>
+        public static string NG数量 {
+            get {
+                return ResourceManager.GetString("NG数量", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 一次输出所有点位 的本地化字符串。
         /// </summary>
@@ -1023,6 +1032,24 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 XYZUR模组平台 的本地化字符串。
+        /// </summary>
+        public static string RobotBrand_XYZUR_Platform {
+            get {
+                return ResourceManager.GetString("RobotBrand_XYZUR_Platform", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   查找类似 XYZURWV模组平台 的本地化字符串。
+        /// </summary>
+        public static string RobotBrand_XYZURWV_Platform {
+            get {
+                return ResourceManager.GetString("RobotBrand_XYZURWV_Platform", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 默认 的本地化字符串。
         /// </summary>
@@ -1940,30 +1967,7 @@ 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>
@@ -3729,25 +3733,23 @@ namespace TeamAAS_VP.Resources.Languages {
         }
         
         /// <summary>
-        ///   查找类似 固定向下相机取料流程ID: 的本地化字符串。
+        ///   查找类似 固定向上相机视觉流程ID:{0} 的本地化字符串。
         /// </summary>
-        public static string 固定向下相机取料流程ID {
+        public static string 固定向上相机视觉流程流程ID {
             get {
-                return ResourceManager.GetString("固定向下相机取料流程ID", resourceCulture);
+                return ResourceManager.GetString("固定向上相机视觉流程流程ID", resourceCulture);
             }
         }
-
+        
         /// <summary>
-        ///   查找类似 固定向上相机1视觉流程流程ID:{0} 的本地化字符串。
+        ///   查找类似 固定向下相机取料流程ID: 的本地化字符串。
         /// </summary>
-        public static string 固定向上相机视觉流程流程ID
-        {
-            get
-            {
-                return ResourceManager.GetString("固定向上相机视觉流程流程ID0", resourceCulture);
+        public static string 固定向下相机取料流程ID {
+            get {
+                return ResourceManager.GetString("固定向下相机取料流程ID", resourceCulture);
             }
         }
-
+        
         /// <summary>
         ///   查找类似 固定相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK 的本地化字符串。
         /// </summary>
@@ -5530,6 +5532,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 抛料数 的本地化字符串。
+        /// </summary>
+        public static string 抛料数 {
+            get {
+                return ResourceManager.GetString("抛料数", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 报告已导出: {0} 的本地化字符串。
         /// </summary>
@@ -7780,6 +7791,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 流线 的本地化字符串。
+        /// </summary>
+        public static string 流线 {
+            get {
+                return ResourceManager.GetString("流线", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 测试 的本地化字符串。
         /// </summary>
@@ -7897,6 +7917,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 添加螺丝型号结果 =1 成功 =2 失败 的本地化字符串。
+        /// </summary>
+        public static string 添加螺丝型号结果1成功2失败 {
+            get {
+                return ResourceManager.GetString("添加螺丝型号结果1成功2失败", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 添加通道 的本地化字符串。
         /// </summary>
@@ -8068,6 +8097,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 生产 的本地化字符串。
+        /// </summary>
+        public static string 生产 {
+            get {
+                return ResourceManager.GetString("生产", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 生产排班 的本地化字符串。
         /// </summary>
@@ -8365,6 +8403,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 相机拍照螺丝复检的结果 =1 OK =2 NG 的本地化字符串。
+        /// </summary>
+        public static string 相机拍照螺丝复检的结果1OK2NG {
+            get {
+                return ResourceManager.GetString("相机拍照螺丝复检的结果1OK2NG", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 相机曝光时间 的本地化字符串。
         /// </summary>
@@ -9545,6 +9592,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 良率 的本地化字符串。
+        /// </summary>
+        public static string 良率 {
+            get {
+                return ResourceManager.GetString("良率", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 节点头 的本地化字符串。
         /// </summary>
@@ -10111,24 +10167,22 @@ namespace TeamAAS_VP.Resources.Languages {
                 return ResourceManager.GetString("触发披头视觉矫正", resourceCulture);
             }
         }
-
+        
         /// <summary>
-        ///   查找类似 触发相机拍照螺丝复检 的本地化字符串。
+        ///   查找类似 触发指令 的本地化字符串。
         /// </summary>
-        public static string 触发相机拍照螺丝复检
-        {
-            get
-            {
-                return ResourceManager.GetString("触发相机拍照螺丝复检", resourceCulture);
+        public static string 触发指令 {
+            get {
+                return ResourceManager.GetString("触发指令", resourceCulture);
             }
         }
-
+        
         /// <summary>
-        ///   查找类似 触发指令 的本地化字符串。
+        ///   查找类似 触发相机拍照螺丝复检 的本地化字符串。
         /// </summary>
-        public static string 触发指令 {
+        public static string 触发相机拍照螺丝复检 {
             get {
-                return ResourceManager.GetString("触发指令", resourceCulture);
+                return ResourceManager.GetString("触发相机拍照螺丝复检", resourceCulture);
             }
         }
         
@@ -10474,6 +10528,15 @@ namespace TeamAAS_VP.Resources.Languages {
             }
         }
         
+        /// <summary>
+        ///   查找类似 调机 的本地化字符串。
+        /// </summary>
+        public static string 调机 {
+            get {
+                return ResourceManager.GetString("调机", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   查找类似 调试 的本地化字符串。
         /// </summary>

+ 36 - 9
TeamAAS-VM/Resources/Languages/Lang.en.resx

@@ -2724,6 +2724,9 @@
   <data name="编号0" xml:space="preserve">
     <value>No.: {0}</value>
   </data>
+  <data name="固定向上相机视觉流程流程ID" xml:space="preserve">
+    <value>Fixed upward camera vision process ID: {0}</value>
+  </data>
   <data name="固定向上相机1视觉流程流程ID0" xml:space="preserve">
     <value>Fixed upward camera 1 vision process ID: {0}</value>
   </data>
@@ -2922,6 +2925,12 @@
   <data name="下相机拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>Lower Camera Shooting Result = 1 Shooting OK = 2 Shooting NG = 3 Image Capture OK</value>
   </data>
+  <data name="添加螺丝型号结果1成功2失败" xml:space="preserve">
+    <value>Add screw model result = 1 Success = 2 Failed</value>
+  </data>
+  <data name="相机拍照螺丝复检的结果1OK2NG" xml:space="preserve">
+    <value>Camera photo screw recheck result = 1 OK = 2 NG</value>
+  </data>
   <data name="上相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>Upper Camera Pickup Shooting Result = 1 Shooting OK = 2 Shooting NG = 3 Image Capture OK</value>
   </data>
@@ -3039,6 +3048,9 @@
   <data name="触发披头视觉矫正" xml:space="preserve">
     <value>Trigger Bit Head Visual Correction</value>
   </data>
+  <data name="触发相机拍照螺丝复检" xml:space="preserve">
+    <value>Trigger camera photo screw recheck</value>
+  </data>
   <data name="取一颗螺丝触发信号" xml:space="preserve">
     <value>Trigger Signal for Picking One Screw</value>
   </data>
@@ -3966,15 +3978,6 @@
   <data name="设置吸嘴报警值" xml:space="preserve">
     <value>Set Nozzle Alarm Value</value>
   </data>
-  <data name="二维码拍照点位" xml:space="preserve">
-    <value>QR Code Photo Position</value>
-  </data>
-  <data name="延迟保存图片" xml:space="preserve">
-    <value>Delay saving the image</value>
-  </data>
-  <data name="是否延迟保存图片" xml:space="preserve">
-    <value>Do you want to delay saving the picture</value>
-  </data>
   <data name="视觉静态精度分析" xml:space="preserve">
     <value>Vision Static Accuracy Analysis</value>
   </data>
@@ -3987,12 +3990,21 @@
   <data name="ProcedureSaveImagePathModel_Month_Day_Formula_ProductSN_Time" xml:space="preserve">
     <value>Year-Month / Day / Recipe Name / Product SN / Product SN + Time</value>
   </data>
+  <data name="二维码拍照点位" xml:space="preserve">
+    <value>QR Code Photo Position</value>
+  </data>
   <data name="二维码拍照请求" xml:space="preserve">
     <value>QR Code Photo Request</value>
   </data>
   <data name="二维码拍照点位参数" xml:space="preserve">
     <value>QR Code Photo Position Parameters</value>
   </data>
+  <data name="延迟保存图片" xml:space="preserve">
+    <value>Delay saving the image</value>
+  </data>
+  <data name="是否延迟保存图片" xml:space="preserve">
+    <value>Do you want to delay saving the picture</value>
+  </data>
   <data name="SettingView_Language_Vietnamese" xml:space="preserve">
     <value>Vietnamese</value>
   </data>
@@ -4005,4 +4017,19 @@
   <data name="流线" xml:space="preserve">
     <value>streamline</value>
   </data>
+  <data name="RobotBrand_XYZUR_Platform" xml:space="preserve">
+    <value>XYZUR_Platform</value>
+  </data>
+  <data name="RobotBrand_XYZURWV_Platform" xml:space="preserve">
+    <value>XYZURWV_Platform</value>
+  </data>
+  <data name="抛料数" xml:space="preserve">
+    <value>抛料数</value>
+  </data>
+  <data name="NG数量" xml:space="preserve">
+    <value>NG数量</value>
+  </data>
+  <data name="良率" xml:space="preserve">
+    <value>良率</value>
+  </data>
 </root>

+ 36 - 9
TeamAAS-VM/Resources/Languages/Lang.ja-JP.resx

@@ -2724,6 +2724,9 @@
   <data name="编号0" xml:space="preserve">
     <value>番号:{0}</value>
   </data>
+  <data name="固定向上相机视觉流程流程ID" xml:space="preserve">
+    <value>固定アップカメラビジョンプロセス ID:{0}</value>
+  </data>
   <data name="固定向上相机1视觉流程流程ID0" xml:space="preserve">
     <value>固定アップカメラ 1 ビジョンプロセス ID:{0}</value>
   </data>
@@ -2922,6 +2925,12 @@
   <data name="下相机拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>下部カメラ撮影結果 = 1 撮影 OK = 2 撮影 NG = 3 画像取得 OK</value>
   </data>
+  <data name="添加螺丝型号结果1成功2失败" xml:space="preserve">
+    <value>ネジ型番追加結果 = 1 成功 = 2 失敗</value>
+  </data>
+  <data name="相机拍照螺丝复检的结果1OK2NG" xml:space="preserve">
+    <value>カメラ撮影ネジ再検査結果 = 1 OK = 2 NG</value>
+  </data>
   <data name="上相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>上部カメラピックアップ撮影結果 = 1 撮影 OK = 2 撮影 NG = 3 画像取得 OK</value>
   </data>
@@ -3039,6 +3048,9 @@
   <data name="触发披头视觉矫正" xml:space="preserve">
     <value>ビットヘッドビジュアル補正トリガー</value>
   </data>
+  <data name="触发相机拍照螺丝复检" xml:space="preserve">
+    <value>カメラ撮影ネジ再検査をトリガー</value>
+  </data>
   <data name="取一颗螺丝触发信号" xml:space="preserve">
     <value>ネジ 1 本ピックトリガー信号</value>
   </data>
@@ -3966,15 +3978,6 @@
   <data name="设置吸嘴报警值" xml:space="preserve">
     <value>ノズルのアラーム値設定</value>
   </data>
-  <data name="二维码拍照点位" xml:space="preserve">
-    <value>QRコード撮影ポイント</value>
-  </data>
-  <data name="延迟保存图片" xml:space="preserve">
-    <value>画像の保存を遅延</value>
-  </data>
-  <data name="是否延迟保存图片" xml:space="preserve">
-    <value>画像の保存を遅延しますか</value>
-  </data>
   <data name="视觉静态精度分析" xml:space="preserve">
     <value>ビジョン静態精度分析</value>
   </data>
@@ -3987,12 +3990,21 @@
   <data name="ProcedureSaveImagePathModel_Month_Day_Formula_ProductSN_Time" xml:space="preserve">
     <value>年/月/日 / レシピ名 / 製品SN / 製品SN+時間</value>
   </data>
+  <data name="二维码拍照点位" xml:space="preserve">
+    <value>QRコード撮影ポイント</value>
+  </data>
   <data name="二维码拍照请求" xml:space="preserve">
     <value>QRコード撮影リクエスト</value>
   </data>
   <data name="二维码拍照点位参数" xml:space="preserve">
     <value>QRコード撮影ポイントパラメータ</value>
   </data>
+  <data name="延迟保存图片" xml:space="preserve">
+    <value>画像の保存を遅延</value>
+  </data>
+  <data name="是否延迟保存图片" xml:space="preserve">
+    <value>画像の保存を遅延しますか</value>
+  </data>
   <data name="SettingView_Language_Vietnamese" xml:space="preserve">
     <value>ベトナム語</value>
   </data>
@@ -4005,4 +4017,19 @@
   <data name="流线" xml:space="preserve">
     <value>流線</value>
   </data>
+  <data name="RobotBrand_XYZUR_Platform" xml:space="preserve">
+    <value>XYZUR_Platform</value>
+  </data>
+  <data name="RobotBrand_XYZURWV_Platform" xml:space="preserve">
+    <value>XYZURWV_Platform</value>
+  </data>
+  <data name="抛料数" xml:space="preserve">
+    <value>抛料数</value>
+  </data>
+  <data name="NG数量" xml:space="preserve">
+    <value>NG数量</value>
+  </data>
+  <data name="良率" xml:space="preserve">
+    <value>良率</value>
+  </data>
 </root>

+ 29 - 0
TeamAAS-VM/Resources/Languages/Lang.resx

@@ -2816,6 +2816,9 @@
   <data name="编号0" xml:space="preserve">
     <value>编号:{0}</value>
   </data>
+  <data name="固定向上相机视觉流程流程ID" xml:space="preserve">
+    <value>固定向上相机视觉流程ID:{0}</value>
+  </data>
   <data name="固定向上相机1视觉流程流程ID0" xml:space="preserve">
     <value>固定向上相机1视觉流程流程ID:{0}</value>
   </data>
@@ -3014,6 +3017,12 @@
   <data name="下相机拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>下相机拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK</value>
   </data>
+  <data name="添加螺丝型号结果1成功2失败" xml:space="preserve">
+    <value>添加螺丝型号结果 =1 成功 =2 失败</value>
+  </data>
+  <data name="相机拍照螺丝复检的结果1OK2NG" xml:space="preserve">
+    <value>相机拍照螺丝复检的结果 =1 OK =2 NG</value>
+  </data>
   <data name="上相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>上相机取料拍照结果 =1 拍照OK =2 拍照NG =3 取图拍照OK</value>
   </data>
@@ -3131,6 +3140,9 @@
   <data name="触发披头视觉矫正" xml:space="preserve">
     <value>触发披头视觉矫正</value>
   </data>
+  <data name="触发相机拍照螺丝复检" xml:space="preserve">
+    <value>触发相机拍照螺丝复检</value>
+  </data>
   <data name="取一颗螺丝触发信号" xml:space="preserve">
     <value>取一颗螺丝触发信号</value>
   </data>
@@ -4100,4 +4112,21 @@
   <data name="流线" xml:space="preserve">
     <value>流线</value>
   </data>
+  <data name="RobotBrand_XYZUR_Platform" xml:space="preserve">
+    <value>XYZUR模组平台</value>
+    <comment>枚举</comment>
+  </data>
+  <data name="RobotBrand_XYZURWV_Platform" xml:space="preserve">
+    <value>XYZURWV模组平台</value>
+    <comment>枚举</comment>
+  </data>
+  <data name="抛料数" xml:space="preserve">
+    <value>抛料数</value>
+  </data>
+  <data name="NG数量" xml:space="preserve">
+    <value>NG数量</value>
+  </data>
+  <data name="良率" xml:space="preserve">
+    <value>良率</value>
+  </data>
 </root>

+ 42 - 15
TeamAAS-VM/Resources/Languages/Lang.vi.resx

@@ -1998,12 +1998,12 @@
   <data name="正在初始化光源控制器" xml:space="preserve">
     <value>Đang khởi tạo bộ điều khiển nguồn sáng</value>
   </data>
-  <data name="选择下相机视觉流程" xml:space="preserve">
-    <value>Chọn quy trình hình ảnh của máy ảnh phía dưới</value>
-  </data>
   <data name="通过下相机获取P0" xml:space="preserve">
     <value>Lấy P0 qua camera dưới</value>
   </data>
+  <data name="选择下相机视觉流程" xml:space="preserve">
+    <value>Chọn quy trình hình ảnh của máy ảnh phía dưới</value>
+  </data>
   <data name="光源通道列表" xml:space="preserve">
     <value>Danh sách kênh nguồn sáng</value>
   </data>
@@ -2547,12 +2547,12 @@
   <data name="Feeder料斗振动超时时出错" xml:space="preserve">
     <value>Lỗi khi thay đổi thời gian quá hạn rung động của bể chứa Feeder!</value>
   </data>
-  <data name="连接机器人时出错" xml:space="preserve">
-    <value>Lỗi khi kết nối robot!</value>
-  </data>
   <data name="Feeder保存系统参数时出错" xml:space="preserve">
     <value>Lỗi khi lưu thông số hệ thống Feeder!</value>
   </data>
+  <data name="连接机器人时出错" xml:space="preserve">
+    <value>Lỗi khi kết nối robot!</value>
+  </data>
   <data name="断开机器人时出错" xml:space="preserve">
     <value>Lỗi khi ngắt robot!</value>
   </data>
@@ -2724,6 +2724,9 @@
   <data name="编号0" xml:space="preserve">
     <value>Số hiệu: {0}</value>
   </data>
+  <data name="固定向上相机视觉流程流程ID" xml:space="preserve">
+    <value>ID quy trình thị giác camera hướng lên cố định: {0}</value>
+  </data>
   <data name="固定向上相机1视觉流程流程ID0" xml:space="preserve">
     <value>ID quy trình thị giác camera cố định lên 1: {0}</value>
   </data>
@@ -2922,6 +2925,12 @@
   <data name="下相机拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>Chụp ảnh từ máy ảnh dưới =1 OK chụp ảnh =2 NG chụp ảnh =3 OK lấy hình chụp ảnh</value>
   </data>
+  <data name="添加螺丝型号结果1成功2失败" xml:space="preserve">
+    <value>Kết quả thêm mẫu vít =1 Thành công =2 Thất bại</value>
+  </data>
+  <data name="相机拍照螺丝复检的结果1OK2NG" xml:space="preserve">
+    <value>Kết quả kiểm tra lại vít bằng camera =1 OK =2 NG</value>
+  </data>
   <data name="上相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>Chụp ảnh lấy mẫu từ máy ảnh trên =1 OK chụp ảnh =2 NG chụp ảnh =3 OK lấy hình chụp ảnh</value>
   </data>
@@ -3039,6 +3048,9 @@
   <data name="触发披头视觉矫正" xml:space="preserve">
     <value>Kích hoạt hiệu chỉnh thị giác đầu dò</value>
   </data>
+  <data name="触发相机拍照螺丝复检" xml:space="preserve">
+    <value>Kích hoạt kiểm tra lại vít bằng camera</value>
+  </data>
   <data name="取一颗螺丝触发信号" xml:space="preserve">
     <value>Nhỏng một ốc vít kích hoạt tín hiệu</value>
   </data>
@@ -3969,6 +3981,9 @@
   <data name="视觉静态精度分析" xml:space="preserve">
     <value>Phân tích độ chính xác tĩnh bằng thị giác</value>
   </data>
+  <data name="点检螺丝" xml:space="preserve">
+    <value>Kiểm tra ốc vít</value>
+  </data>
   <data name="下压距离" xml:space="preserve">
     <value>Khoảng cách ép xuống</value>
   </data>
@@ -3978,21 +3993,18 @@
   <data name="二维码拍照点位" xml:space="preserve">
     <value>Vị trí chụp mã QR</value>
   </data>
-  <data name="延迟保存图片" xml:space="preserve">
-    <value>Lùi lại việc lưu hình ảnh</value>
-  </data>
-  <data name="是否延迟保存图片" xml:space="preserve">
-    <value>Liệu có phải là việc lưu ảnh bị trễ không</value>
-  </data>
-  <data name="点检螺丝" xml:space="preserve">
-    <value>Kiểm tra ốc vít</value>
-  </data>
   <data name="二维码拍照请求" xml:space="preserve">
     <value>Yêu cầu chụp mã QR</value>
   </data>
   <data name="二维码拍照点位参数" xml:space="preserve">
     <value>Thông số vị trí chụp mã QR</value>
   </data>
+  <data name="延迟保存图片" xml:space="preserve">
+    <value>Lùi lại việc lưu hình ảnh</value>
+  </data>
+  <data name="是否延迟保存图片" xml:space="preserve">
+    <value>Liệu có phải là việc lưu ảnh bị trễ không</value>
+  </data>
   <data name="SettingView_Language_Vietnamese" xml:space="preserve">
     <value>Tiếng Việt</value>
   </data>
@@ -4005,4 +4017,19 @@
   <data name="流线" xml:space="preserve">
     <value>đường dòng</value>
   </data>
+  <data name="RobotBrand_XYZUR_Platform" xml:space="preserve">
+    <value>XYZUR_Platform</value>
+  </data>
+  <data name="RobotBrand_XYZURWV_Platform" xml:space="preserve">
+    <value>XYZURWV_Platform</value>
+  </data>
+  <data name="抛料数" xml:space="preserve">
+    <value>抛料数</value>
+  </data>
+  <data name="NG数量" xml:space="preserve">
+    <value>NG数量</value>
+  </data>
+  <data name="良率" xml:space="preserve">
+    <value>良率</value>
+  </data>
 </root>

+ 39 - 12
TeamAAS-VM/Resources/Languages/Lang.zh-TW.resx

@@ -2724,6 +2724,9 @@
   <data name="编号0" xml:space="preserve">
     <value>編號:{0}</value>
   </data>
+  <data name="固定向上相机视觉流程流程ID" xml:space="preserve">
+    <value>固定向上相機視覺流程 ID:{0}</value>
+  </data>
   <data name="固定向上相机1视觉流程流程ID0" xml:space="preserve">
     <value>固定向上相機 1 視覺流程流程 ID:{0}</value>
   </data>
@@ -2922,6 +2925,12 @@
   <data name="下相机拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>下相機拍照結果 =1 拍照 OK =2 拍照 NG =3 取圖拍照 OK</value>
   </data>
+  <data name="添加螺丝型号结果1成功2失败" xml:space="preserve">
+    <value>新增螺絲型號結果 =1 成功 =2 失敗</value>
+  </data>
+  <data name="相机拍照螺丝复检的结果1OK2NG" xml:space="preserve">
+    <value>相機拍照螺絲複檢結果 =1 OK =2 NG</value>
+  </data>
   <data name="上相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>上相機取料拍照結果 =1 拍照 OK =2 拍照 NG =3 取圖拍照 OK</value>
   </data>
@@ -2931,6 +2940,9 @@
   <data name="上相机检测拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>上相機檢測拍照結果 =1 拍照 OK =2 拍照 NG =3 取圖拍照 OK</value>
   </data>
+  <data name="固定相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
+    <value>固定相機取料拍照結果 =1 拍照OK =2 拍照NG =3 取圖拍照OK</value>
+  </data>
   <data name="固定相机锁附组装拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
     <value>固定相機鎖附 / 組裝拍照結果 =1 拍照 OK =2 拍照 NG =3 取圖拍照 OK</value>
   </data>
@@ -3036,6 +3048,9 @@
   <data name="触发披头视觉矫正" xml:space="preserve">
     <value>觸發披頭視覺矯正</value>
   </data>
+  <data name="触发相机拍照螺丝复检" xml:space="preserve">
+    <value>觸發相機拍照螺絲複檢</value>
+  </data>
   <data name="取一颗螺丝触发信号" xml:space="preserve">
     <value>取一顆螺絲觸發信號</value>
   </data>
@@ -3963,18 +3978,6 @@
   <data name="设置吸嘴报警值" xml:space="preserve">
     <value>設定吸嘴警報值</value>
   </data>
-  <data name="二维码拍照点位" xml:space="preserve">
-    <value>二維碼拍照點位</value>
-  </data>
-  <data name="延迟保存图片" xml:space="preserve">
-    <value>延遲保存圖片</value>
-  </data>
-  <data name="是否延迟保存图片" xml:space="preserve">
-    <value>是否延遲保存圖片</value>
-  </data>
-  <data name="固定相机取料拍照结果1拍照OK2拍照NG3取图拍照OK" xml:space="preserve">
-    <value>固定相機取料拍照結果 =1 拍照OK =2 拍照NG =3 取圖拍照OK</value>
-  </data>
   <data name="视觉静态精度分析" xml:space="preserve">
     <value>視覺靜態精度分析</value>
   </data>
@@ -3987,12 +3990,21 @@
   <data name="ProcedureSaveImagePathModel_Month_Day_Formula_ProductSN_Time" xml:space="preserve">
     <value>年月/日/配方名/產品SN/產品SN+時間</value>
   </data>
+  <data name="二维码拍照点位" xml:space="preserve">
+    <value>二維碼拍照點位</value>
+  </data>
   <data name="二维码拍照请求" xml:space="preserve">
     <value>二維碼拍照請求</value>
   </data>
   <data name="二维码拍照点位参数" xml:space="preserve">
     <value>二維碼拍照點位參數</value>
   </data>
+  <data name="延迟保存图片" xml:space="preserve">
+    <value>延遲保存圖片</value>
+  </data>
+  <data name="是否延迟保存图片" xml:space="preserve">
+    <value>是否延遲保存圖片</value>
+  </data>
   <data name="SettingView_Language_Vietnamese" xml:space="preserve">
     <value>越南語</value>
   </data>
@@ -4005,4 +4017,19 @@
   <data name="流线" xml:space="preserve">
     <value>流線</value>
   </data>
+  <data name="RobotBrand_XYZUR_Platform" xml:space="preserve">
+    <value>XYZUR_Platform</value>
+  </data>
+  <data name="RobotBrand_XYZURWV_Platform" xml:space="preserve">
+    <value>XYZURWV_Platform</value>
+  </data>
+  <data name="抛料数" xml:space="preserve">
+    <value>抛料数</value>
+  </data>
+  <data name="NG数量" xml:space="preserve">
+    <value>NG数量</value>
+  </data>
+  <data name="良率" xml:space="preserve">
+    <value>良率</value>
+  </data>
 </root>

+ 99 - 0
TeamAAS-VM/Services/ConfigService.cs

@@ -10,6 +10,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using TeamAAS_VP.Core;
+using TeamAAS_VP.Core.Robots;
 using TeamAAS_VP.Data; // for ISystemDatabaseService (if used)
 using TeamAAS_VP.Enums;
 using TeamAAS_VP.Interfaces;
@@ -576,6 +577,7 @@ namespace TeamAAS_VP.Services
             if (robot == null) return null;
             lock (_sync)
             {
+                EnsureRobotPlcAxisParameters(robot);
                 var exist = Robots.FirstOrDefault(r => r.Id == robot.Id);
                 if (exist != null)
                 {
@@ -593,6 +595,103 @@ namespace TeamAAS_VP.Services
             return robot;
         }
 
+
+        private static void EnsureRobotPlcAxisParameters(RobotInfo robot)
+        {
+            var axisNames = GetRobotAxisNames(robot.RobotBrand);
+            if (axisNames == null) return;
+
+            if (robot.PlcRobotParameter == null)
+                robot.PlcRobotParameter = new PlcRobotParameter();
+
+            var existingAxes = robot.PlcRobotParameter.AxixList ?? new List<Axis>();
+            var axes = new List<Axis>();
+            for (var i = 0; i < axisNames.Length; i++)
+            {
+                var axisName = axisNames[i];
+                var defaultIndex = i + 1;
+                var existingAxis = existingAxes.FirstOrDefault(a => string.Equals(a.Name, axisName, StringComparison.OrdinalIgnoreCase))
+                                   ?? existingAxes.FirstOrDefault(a => a.Index == defaultIndex);
+                var axisIndex = existingAxis != null && existingAxis.Index > 0 ? existingAxis.Index : defaultIndex;
+                axes.Add(CreatePlcAxis(axisName, axisIndex));
+            }
+
+            robot.PlcRobotParameter.AxixList = axes;
+        }
+
+        private static string[] GetRobotAxisNames(RobotBrand robotBrand)
+        {
+            switch (robotBrand)
+            {
+                case RobotBrand.XYZ_Platform:
+                    return new[] { "X", "Y", "Z" };
+                case RobotBrand.XYZU_Platform:
+                    return new[] { "X", "Y", "Z", "U" };
+                case RobotBrand.XYZUR_Platform:
+                    return new[] { "X", "Y", "Z", "U", "R" };
+                case RobotBrand.XYZURWV_Platform:
+                    return new[] { "X", "Y", "Z", "U", "R", "W", "V" };
+                default:
+                    return null;
+            }
+        }
+
+        private static Axis CreatePlcAxis(string name, int index)
+        {
+            var axisPath = $"ns=4;s=DB_DriveControl|AxisCtrl[{index}]";
+            return new Axis
+            {
+                Name = name,
+                Index = index,
+                Command = new AxisCommand
+                {
+                    PowerNode = $"{axisPath}.Cmd.Power",
+                    StopNode = $"{axisPath}.Cmd.Stop",
+                    ManuToHomeNode = $"{axisPath}.Cmd.ManuToHome",
+                    AutoToHomeNode = $"{axisPath}.Cmd.AutoToHome",
+                    ManuJogFowardNode = $"{axisPath}.Cmd.ManuJogFoward",
+                    ManuJogBackwardNode = $"{axisPath}.Cmd.ManuJogBackward",
+                    VelocityControlNode = $"{axisPath}.Cmd.VelocityControl",
+                    VelocityChangeNode = $"{axisPath}.Cmd.VelocityChange",
+                    AutoINCNode = $"{axisPath}.Cmd.AutoINC",
+                    AutoABSNode = $"{axisPath}.Cmd.AutoABS",
+                    TorqueNode = $"{axisPath}.Cmd.TeachOn",
+                    PointSelectNode = $"{axisPath}.Cmd.PointSelect",
+                    TeachOnNode = $"{axisPath}.Cmd.TeachOn",
+                    TeachNode = $"{axisPath}.Cmd.Teach",
+                    ManuPointNode = $"{axisPath}.Cmd.ManuPoint",
+                    ManuPositionNode = $"{axisPath}.Cmd.ManuPosition"
+                },
+                Parameter = new AxisParameter
+                {
+                    ManuPositionNode = $"{axisPath}.Parm.ManuPosition",
+                    ManuVelocityNode = $"{axisPath}.Parm.ManuVelocity",
+                    VelModeVelocityNode = $"{axisPath}.Parm.VelModeVelocity",
+                    CommentNode = $"{axisPath}.Parm.Comment"
+                },
+                State = new AxisState
+                {
+                    PowerOnNode = $"{axisPath}.Status.PowerON",
+                    BusyNode = $"{axisPath}.Status.Busy",
+                    PosOKNode = $"{axisPath}.Status.PosOK",
+                    InitialedNode = $"{axisPath}.Status.Intialed",
+                    PausedNode = $"{axisPath}.Status.Paused",
+                    PosiNode = $"{axisPath}.Status.Posi",
+                    NegaNode = $"{axisPath}.Status.Nega",
+                    ErrorNode = $"{axisPath}.Status.Error",
+                    ErrorIdNode = $"{axisPath}.Status.ErrorID",
+                    ActPositionNode = $"{axisPath}.Status.ActPosition",
+                    ActVelocityNode = $"{axisPath}.Status.ActVelocity",
+                    ActTorqueNode = $"{axisPath}.Status.ActTorque",
+                    StopPositionNode = $"{axisPath}.Status.StopPositon",
+                    HmiPositionNode = $"{axisPath}.Status.HmiPosition",
+                    SpTimeNode = $"{axisPath}.Status.SpTime",
+                    StateNode = $"{axisPath}.Status.State"
+                }
+            };
+        }
+
+
         /// <summary>
         /// ���ݱ�ʶ�Ƴ����������ò����±��ʣ������ˡ�
         /// ������־û������ļ���

+ 36 - 0
TeamAAS-VM/Services/RobotService.cs

@@ -380,6 +380,42 @@ namespace TeamAAS_VP.Services
 
                 robot = new XYZU_Robot(robotInfo, plc);
             }
+            else if (robotInfo.RobotBrand == Enums.RobotBrand.XYZUR_Platform )
+            {
+                var plc = _plcService.GetPlc(robotInfo.PLC) as OpcUaClientPLC;
+                if (robotInfo.PlcRobotParameter == null)
+                    robotInfo.PlcRobotParameter = new Models.PLC.PlcRobotParameter();
+                if (robotInfo.PlcRobotParameter.AxixList == null)
+                {
+                    robotInfo.PlcRobotParameter.AxixList = new List<Axis>();
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 1, Name = "X", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 2, Name = "Y", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 3, Name = "Z", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 4, Name = "U", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 5, Name = "R", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                }
+
+                robot = new XYZUR_Platform(robotInfo, plc);
+            }
+            else if (robotInfo.RobotBrand == Enums.RobotBrand.XYZURWV_Platform)
+            {
+                var plc = _plcService.GetPlc(robotInfo.PLC) as OpcUaClientPLC;
+                if (robotInfo.PlcRobotParameter == null)
+                    robotInfo.PlcRobotParameter = new Models.PLC.PlcRobotParameter();
+                if (robotInfo.PlcRobotParameter.AxixList == null)
+                {
+                    robotInfo.PlcRobotParameter.AxixList = new List<Axis>();
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 1, Name = "X", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 2, Name = "Y", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 3, Name = "Z", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 4, Name = "U", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 5, Name = "R", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 6, Name = "W", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                    robotInfo.PlcRobotParameter.AxixList.Add(new Axis() { Index = 7, Name = "V", Command = new AxisCommand(), Parameter = new AxisParameter(), State = new AxisState() });
+                }
+
+                robot = new XYZURWV_Platform(robotInfo, plc);
+            }
             else
             {
                 robot = new EpsonRobot(robotInfo);

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

@@ -537,6 +537,8 @@
     <Compile Include="Core\FileNameTemplate.cs" />
     <Compile Include="Core\FtpUploader.cs" />
     <Compile Include="Core\FtpUploaderTest.cs" />
+    <Compile Include="Core\Robots\XYZUR_Platform.cs" />
+    <Compile Include="Core\Robots\XYZURWV_Platform.cs" />
     <Compile Include="Core\StabilityAnalyzer.cs" />
     <Compile Include="Core\TxtFile.cs" />
     <Compile Include="Core\VisionProFileHelper.cs" />

+ 8 - 1
TeamAAS-VM/ValueConverter/EnumLocalizationConverter.cs

@@ -29,7 +29,14 @@ namespace TeamAAS_VP.ValueConverter
 
             if (attr !=null)
             {
-                return attr.ResourceType.GetProperty(attr.ResourceName, BindingFlags.Static | BindingFlags.Public).GetValue(null) as string;
+                try
+                {
+                    return attr.ResourceType.GetProperty(attr.ResourceName, BindingFlags.Static | BindingFlags.Public).GetValue(null) as string;
+                }
+                catch (Exception)
+                {
+                    return null;
+                }
             }
 
             return null;

+ 1 - 1
TeamAAS-VM/ValueConverter/RobotBrandToVisibiltyConverter.cs

@@ -17,7 +17,7 @@ namespace TeamAAS_VP.ValueConverter
         {
             if (value != null && value is RobotBrand robotBrand)
             {
-                if (robotBrand== RobotBrand.XYZ_Platform || robotBrand==  RobotBrand.XYZU_Platform)
+                if (robotBrand== RobotBrand.XYZ_Platform || robotBrand==  RobotBrand.XYZU_Platform || robotBrand == RobotBrand.XYZUR_Platform || robotBrand == RobotBrand.XYZURWV_Platform)
                 {
                     return Inverse ? Visibility.Visible : Visibility.Collapsed;
                 }

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

@@ -71,7 +71,7 @@ namespace TeamAAS_VP.ViewModels
         /// 工程师和管理员权限的无操作超时时长。
         /// 超过该时长后自动切回操作员权限。
         /// </summary>
-        private readonly TimeSpan PermissionIdleTimeout = TimeSpan.FromMinutes(0.5);
+        private readonly TimeSpan PermissionIdleTimeout = TimeSpan.FromMinutes(10000);
 
         /// <summary>
         /// 权限空闲检测计时器。

+ 2 - 1
TeamAAS-VM/ViewModels/Product/PalletManageViewModel.cs

@@ -578,6 +578,7 @@ namespace TeamAAS_VP.ViewModels.Product
         {
             var values = (object[])parameter;
             var point = values[0] as RPoint;
+            //var currentPos = GetRobotPos();
             MotionCommand motionCmd = (MotionCommand)values[1];
             if (Robot == null || !Robot.IsConnected) return;
             try
@@ -589,7 +590,7 @@ namespace TeamAAS_VP.ViewModels.Product
                         await Robot.GoAsync(point);
                         break;
                     case MotionCommand.Jump:
-                        await Robot.JumpAsync(point, 0);
+                        await Robot.JumpAsync(point,Robot.SafeZ);
                         break;
                     case MotionCommand.Move:
                         await Robot.MoveAsync(point);

+ 41 - 16
TeamAAS-VM/ViewModels/Product/PlcPointParamsViewModel.cs

@@ -197,6 +197,8 @@ namespace TeamAAS_VP.ViewModels.Product
             }
         }
 
+
+
         private float _LockPointStartZ;
         /// <summary>
         /// 批量设置锁付点的起始Z坐标
@@ -596,6 +598,12 @@ namespace TeamAAS_VP.ViewModels.Product
                     case "Z-":
                         await Robot.JogAsync("Z", -distance);
                         break;
+                    case "R+":
+                        await Robot.JogAsync("R", distance);
+                        break;
+                    case "R-":
+                        await Robot.JogAsync("R", -distance);
+                        break;
                     case "U+":
                         await Robot.JogAsync("U", distance);
                         break;
@@ -644,13 +652,15 @@ namespace TeamAAS_VP.ViewModels.Product
                 return;
             }
             var values = (object[])parameter;
-            var point = new RPoint()
+               var point = new RPoint()
             {
                 X = SelectedPoint.X_Position,
                 Y = SelectedPoint.Y_Position,
                 Z = SelectedPoint.Z_Position_Start,
                 U = SelectedPoint.U_Position,
-                V = SelectedPoint.R_Position,
+                R = SelectedPoint.R_Position,
+                W = SelectProduct.RobotPoint.W_Position,
+                V = SelectProduct.RobotPoint.V_Position,
             };
             MotionCommand motionCmd = (MotionCommand)values[1];
             if (Robot == null || !Robot.IsConnected) return;
@@ -667,24 +677,27 @@ namespace TeamAAS_VP.ViewModels.Product
                     {
                         return;
                     }
-
+                    bool result = false;
                     //Robot.Timeout = 6000000;
                     switch (motionCmd)
                     {
                         case MotionCommand.Go:
-                            await Robot.GoAsync(point);
+                            result = await Robot.GoAsync(point);
                             break;
                         case MotionCommand.Jump:
-                            await Robot.JumpAsync(point, 0);
+                            result = await Robot.JumpAsync(point, 0);
                             break;
                         case MotionCommand.Move:
-                            await Robot.MoveAsync(point);
+                            result = await Robot.MoveAsync(point);
                             break;
                     }
                     //Robot.Timeout = 5000;
-
-                    point.Z = SelectedPoint.Z_Position_Stop;
-                    await Robot.GoAsync(point);
+                    if (result)
+                    {
+                        point.Z = SelectedPoint.Z_Position_Stop;
+                        await Robot.GoAsync(point);
+                    }
+                
                 }
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
             }
@@ -706,8 +719,20 @@ namespace TeamAAS_VP.ViewModels.Product
 
             try
             {
+                //锁附拍照点
+                if (SelectedIndex == 3)
+                {
+                    //让锁附拍照点的U,R和锁附点U,R一致
+                    int num = SelectedPoint.Number;
+                    var point= SelectProduct.ScrewPoints.Where(a=>a.Number == num).FirstOrDefault();
+                    if (point!=null)
+                    {
+                        point.U_Position = SelectedPoint.U_Position;
+                        point.R_Position = SelectedPoint.R_Position;
+                    }
+                }
                 //如果当前的选项卡处于锁付点位,则提示用户当前模式不允许进行示教
-                if (SelectedIndex == 4)
+                else if (SelectedIndex == 4)
                 {
                     //如果local为空,则必须先通过相机定位产品,否则无法示教锁付点位
                     if (local == null)
@@ -729,7 +754,7 @@ namespace TeamAAS_VP.ViewModels.Product
                     SelectedPoint.Y_Position = (float)localPoint[1];
                     SelectedPoint.Z_Position_Start = curpos1.Z;
                     SelectedPoint.U_Position = curpos1.U;
-                    SelectedPoint.R_Position = curpos1.V;
+                    SelectedPoint.R_Position = curpos1.R;
                     _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
                     return;
                 }
@@ -746,7 +771,7 @@ namespace TeamAAS_VP.ViewModels.Product
                     SelectedPoint.Y_Position = curpos.Y;
                     SelectedPoint.Z_Position_Start = curpos.Z;
                     SelectedPoint.U_Position = curpos.U;
-                    SelectedPoint.R_Position = curpos.V;
+                    SelectedPoint.R_Position = curpos.R;
                     _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
                 }
             }
@@ -1669,7 +1694,7 @@ namespace TeamAAS_VP.ViewModels.Product
         /// </summary>
         void ExecuteSetOffsetCommand()
         {
-            if (SelectedIndex<0) return;
+            if (SelectedIndex < 0) return;
             IDialogParameters parameters1 = new DialogParameters();
             if (SelectedIndex == 0)
             {
@@ -1758,8 +1783,8 @@ namespace TeamAAS_VP.ViewModels.Product
                     SoftwareVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
                     ProductName = SelectProduct.Name,
                     ProductDescription = SelectProduct.Description,
-                    DeviceName=_configService.GetDeviceInfo().Station,
-                    Created= DateTime.Now,
+                    DeviceName = _configService.GetDeviceInfo().Station,
+                    Created = DateTime.Now,
                     PickCameraPoints = SelectProduct.PickCameraPoints?.Select(p => p.Clone()).ToList() ?? new List<PlcPoint>(),
                     PickPoints = SelectProduct.PickPoints?.Select(p => p.Clone()).ToList() ?? new List<PlcPoint>(),
                     SecPosCameraPoints = SelectProduct.SecPosCameraPoints?.Select(p => p.Clone()).ToList() ?? new List<PlcPoint>(),
@@ -1875,7 +1900,7 @@ namespace TeamAAS_VP.ViewModels.Product
                         Points = SelectProduct.PickCameraPoints;
                         break;
                 }
-                
+
                 LogHelper.WriteLogDebug("导入点位文件成功");
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = "导入完成", Duration = 1 });
             }

+ 55 - 2
TeamAAS-VM/ViewModels/Statistics/ProductionStatementViewModel.cs

@@ -7,6 +7,7 @@ using Prism.Ioc;
 using Prism.Mvvm;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows.Documents;
 using TeamAAS_VP.Core;
@@ -25,9 +26,15 @@ namespace TeamAAS_VP.ViewModels.Statistics
         ISystemDatabaseService _systemDatabaseService;
         IProductService _productService;
 
-        Management management;
-
         #region 属性
+        private Management _management;
+
+        public Management management
+        {
+            get { return _management; }
+            set { SetProperty(ref _management, value); }
+        }
+
         private Int64 _TotalCount;
         /// <summary>
         /// 总产能
@@ -107,6 +114,36 @@ namespace TeamAAS_VP.ViewModels.Statistics
             get { return _DayChartModel; }
             set { SetProperty(ref _DayChartModel, value); }
         }
+
+        //private Int64 _ThrowNumber;
+        ///// <summary>
+        ///// 抛料数
+        ///// </summary>
+        //public Int64 ThrowNumber
+        //{
+        //    get { return _ThrowNumber; }
+        //    set { SetProperty(ref _ThrowNumber, value); }
+        //}
+
+        //private Int64 _NgNumber;
+        ///// <summary>
+        ///// ng数量
+        ///// </summary>
+        //public Int64 NgNumber
+        //{
+        //    get { return _NgNumber; }
+        //    set { SetProperty(ref _NgNumber, value); }
+        //}
+
+        //private double _Yield;
+        ///// <summary>
+        ///// 良率
+        ///// </summary>
+        //public double Yield
+        //{
+        //    get { return _Yield; }
+        //    set { SetProperty(ref _Yield, value); }
+        //}
         #endregion
 
         #region 命令
@@ -126,6 +163,7 @@ namespace TeamAAS_VP.ViewModels.Statistics
             _container = container;
             _systemDatabaseService = systemDatabaseService;
             _productService = productService;
+            management = _container.Resolve<Management>();
         }
 
         #region 方法
@@ -143,6 +181,21 @@ namespace TeamAAS_VP.ViewModels.Statistics
                 MonthChartModel = CreateMonth(await _systemDatabaseService.GetMonthlyProductionByDayAndCategoryAsync(currentproduct.Name));
                 WeekChartModel = CreateWeek(await _systemDatabaseService.GetWeeklyProductionByDayAndCategoryAsync(currentproduct.Name));
                 DayChartModel = CreateDay(await _systemDatabaseService.GetDailyProductionByHourAndCategoryAsync(currentproduct.Name));
+
+                //var TotalQuantityToday = 0;
+                //var CurrentUPH = 0;
+                //ThrowNumber = await _systemDatabaseService.GetThrowNumberAsync();
+                //NgNumber = 0;
+                //Yield = 0;
+                //ObservableCollection<ProductModel> ProductList = new ObservableCollection<ProductModel>(_productService.GetAllProducts());
+                //foreach (var product in ProductList)
+                //{
+                //    //TotalQuantity += await _systemDatabaseService.GetOverallProductionAsync(product.Name);
+                //    TotalQuantityToday += await _systemDatabaseService.GetTodayProductionAsync(product.Name);
+                //    CurrentUPH += await _systemDatabaseService.GetLastHourProductionAsync(product.Name);
+                //    NgNumber += await _systemDatabaseService.GetNGProductionAsync(product.Name);
+                //}
+                //Yield += ((double)TotalQuantityToday - (double)NgNumber) / (double)TotalQuantityToday * 100;
             }));
 
         }

+ 3 - 3
TeamAAS-VM/Views/HomeView.xaml

@@ -190,7 +190,7 @@
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="*" />
                         <ColumnDefinition Width="*" />
-                        <ColumnDefinition Width="*" />
+                        <!--<ColumnDefinition Width="*" />-->
                     </Grid.ColumnDefinitions>
                     <!--加载产品-->
                     <materialDesign:Card Grid.Row="0"
@@ -338,7 +338,7 @@
                                        Text="{Binding management.CurrentUPH}" />
                         </Grid>
                     </materialDesign:Card>
-                    <!--生产数量2-->
+                    <!--生产数量2--><!--
                     <materialDesign:Card Grid.Row="0"
                       Grid.Column="2"
                       UniformCornerRadius="10"
@@ -412,7 +412,7 @@
                     Grid.Column="1"
                     Text="{Binding management.Yield,StringFormat={}{0:F2}%}" />
                         </Grid>
-                    </materialDesign:Card>
+                    </materialDesign:Card>-->
                 </Grid>
 
                 <StackPanel Grid.Row="2"

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 715 - 679
TeamAAS-VM/Views/Product/PlcPointParams.xaml


+ 4 - 0
TeamAAS-VM/Views/Product/PlcPointParams.xaml.cs

@@ -42,12 +42,16 @@ namespace TeamAAS_VP.Views.Product
                 {
                     var visible = VM.SelectedIndex == 3|| VM.SelectedIndex == 5 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                     var Usevisible = VM.SelectedIndex == 3 || VM.SelectedIndex == 4 || VM.SelectedIndex == 5 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
+                    var PickScrewvisible= VM.SelectedIndex == 4 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                     this.Dispatcher.BeginInvoke(new Action(() =>
                     {
                         if (colProcedure != null)
                             colProcedure.Visibility = visible;
                         if (colIsUse != null)
                             colIsUse.Visibility = Usevisible;
+                        if (colPickScrew!=null)
+                            colPickScrew.Visibility = PickScrewvisible;
+
                     }));
                 }
                 catch { }

+ 40 - 0
TeamAAS-VM/Views/Statistics/ProductionStatement.xaml

@@ -47,6 +47,9 @@
                     <RowDefinition Height="auto" />
                     <RowDefinition Height="auto" />
                     <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
                 </Grid.RowDefinitions>
                 <oxy:PlotView Grid.Row="0"
                               Grid.Column="0"
@@ -103,6 +106,43 @@
                          TextAlignment="Center"
                          BorderThickness="1"
                          Margin="5" />
+
+                <TextBlock Grid.Row="5"
+                           Text="{lex:Loc 抛料数,Converter={StaticResource StringFormatConverter},ConverterParameter='{}{0}: '}"
+                           VerticalAlignment="Center"
+                           Margin="5" />
+                <TextBox Grid.Row="5"
+                         Grid.Column="1"
+                         Text="{Binding management.ThrowNumber}"
+                         IsReadOnly="True"
+                         BorderBrush="Gray"
+                         TextAlignment="Center"
+                         BorderThickness="1"
+                         Margin="5" />
+                <TextBlock Grid.Row="6"
+                           Text="{lex:Loc NG数量,Converter={StaticResource StringFormatConverter},ConverterParameter='{}{0}: '}"
+                           VerticalAlignment="Center"
+                           Margin="5" />
+                <TextBox Grid.Row="6"
+                         Grid.Column="1"
+                         Text="{Binding management.NgNumber}"
+                         IsReadOnly="True"
+                         BorderBrush="Gray"
+                         TextAlignment="Center"
+                         BorderThickness="1"
+                         Margin="5" />
+                <TextBlock Grid.Row="7"
+                           Text="{lex:Loc 良率,Converter={StaticResource StringFormatConverter},ConverterParameter='{}{0}: '}"
+                           VerticalAlignment="Center"
+                           Margin="5" />
+                <TextBox Grid.Row="7"
+                         Grid.Column="1"
+                         Text="{Binding management.Yield,StringFormat={}{0:F2}%}"
+                         IsReadOnly="True"
+                         BorderBrush="Gray"
+                         TextAlignment="Center"
+                         BorderThickness="1"
+                         Margin="5" />
             </Grid>
             <Button Content="{lex:Loc 生成报表}"
                     Margin="10"

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно