ソースを参照

支持XYZ/XYZU平台机器人及多轴通用模型

新增XYZU_Robot通用多轴机器人实现,采用Axis轴对象配置,支持XYZ/XYZU平台灵活适配。调整PlcRobotParameter为轴列表,增强OPCuaClientPLC批量节点读写与订阅能力。RobotService支持XYZ/XYZU平台,IRobot接口与枚举同步适配,原XYZ_Platform实现注释。项目文件新增相关编译项。
孝锋 徐 8 ヶ月 前
コミット
74cd40023e

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

@@ -1,4 +1,5 @@
 using Opc.Ua;
+using Opc.Ua.Client;
 using OpcUaHelper;
 using System;
 using System.Collections.Generic;
@@ -27,14 +28,20 @@ namespace TeamAAS_VP.Core.PLCs
 
         public bool IsConnected { get { return OpcUaClient.Connected; } }
 
+        /// <summary>
+        /// 节点头
+        /// </summary>
+        public string NodeHeader { get; private set; }
+
         public event Action<object, bool> ConnectChangedEvent;
 
-        public OpcUaClientPLC(Guid id,int index, string name, string endpointUrl, CommunicationType communicationType)
+        public OpcUaClientPLC(Guid id,int index, string name, string endpointUrl,string nodeHeader, CommunicationType communicationType)
         {
             Id = id;
             Index = index;
             Name = name;
             EndpointUrl = endpointUrl;
+            NodeHeader = nodeHeader;
             CommunicationType = communicationType;
             OpcUaClient = new OpcUaClient();
             // 初始化PLC连接
@@ -101,6 +108,233 @@ namespace TeamAAS_VP.Core.PLCs
             OpcUaClient.OpcStatusChange -= OpcUaClient_OpcStatusChange;
         }
 
+        /// <summary>
+        /// 读取多个节点的值
+        /// </summary>
+        /// <param name="nodeIds"></param>
+        /// <returns></returns>
+        public Dictionary<string, object> ReadNodes(string[] nodeIds)
+        {
+            var result = new Dictionary<string, object>();
+            var readNodeIds = nodeIds.Select(s =>
+            {
+                if (s.StartsWith(NodeHeader))
+                {
+                    return s;
+                }
+                else
+                {
+                    return NodeHeader + s;
+                }
+            }).ToArray();
+            List<NodeId> readNodeIdList = new List<NodeId>();
+            foreach (var readNodeId in readNodeIds)
+            {
+                readNodeIdList.Add(new NodeId(readNodeId));
+            }
+            var values = OpcUaClient.ReadNodes(readNodeIdList.ToArray());
+            for (int i = 0; i < nodeIds.Length; i++)
+            {
+                result[nodeIds[i]] = values[i].Value;
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 异步读取多个节点的值
+        /// </summary>
+        /// <param name="nodeIds"></param>
+        /// <returns></returns>
+        public async Task<Dictionary<string, object>> ReadNodesAsync(string[] nodeIds)
+        {
+            var result = new Dictionary<string, object>();
+            var readNodeIds = nodeIds.Select(s =>
+            {
+                if (s.StartsWith(NodeHeader))
+                {
+                    return s;
+                }
+                else
+                {
+                    return NodeHeader + s;
+                }
+            }).ToArray();
+            List<NodeId> readNodeIdList = new List<NodeId>();
+            foreach (var readNodeId in readNodeIds)
+            {
+                readNodeIdList.Add(new NodeId(readNodeId));
+            }
+            var values = await OpcUaClient.ReadNodesAsync(readNodeIdList.ToArray());
+            for (int i = 0; i < nodeIds.Length; i++)
+            {
+                result[nodeIds[i]] = values[i].Value;
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 读取单个节点的值
+        /// </summary>
+        /// <param name="nodeId"></param>
+        /// <returns></returns>
+        public object ReadNode(string nodeId)
+        {
+            string readNodeId = nodeId;
+            if (!nodeId.StartsWith(NodeHeader))
+            {
+                readNodeId = NodeHeader + nodeId;
+            }
+
+            var value = OpcUaClient.ReadNode(new NodeId(readNodeId));
+            return value.Value;
+        }
+
+        /// <summary>
+        /// 异步读取单个节点的值
+        /// </summary>
+        /// <param name="nodeId"></param>
+        /// <returns></returns>
+        public Task<object> ReadNodeAsync(string nodeId)
+        {
+            return Task.Run(() =>
+            {
+                return ReadNode(nodeId);
+            });
+        }
+
+        /// <summary>
+        /// 读取单个节点的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="nodeId"></param>
+        /// <returns></returns>
+        public T ReadNode<T>(string nodeId)
+        {
+            string readNodeId = nodeId;
+            if (!nodeId.StartsWith(NodeHeader))
+            {
+                readNodeId = NodeHeader + nodeId;
+            }
+            return OpcUaClient.ReadNode<T>(NodeHeader + nodeId);
+        }
+
+        /// <summary>
+        /// 异步读取单个节点的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="nodeId"></param>
+        /// <returns></returns>
+        public async Task<T> ReadNodeAsync<T>(string nodeId)
+        {
+            string readNodeId = nodeId;
+            if (!nodeId.StartsWith(NodeHeader))
+            {
+                readNodeId = NodeHeader + nodeId;
+            }
+            return await OpcUaClient.ReadNodeAsync<T>(NodeHeader + nodeId);
+        }
+
+        /// <summary>
+        /// 写入单个节点的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="nodeId"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public bool WriteNode<T>(string nodeId, T value)
+        {
+            string writeNodeId = nodeId;
+            if (!nodeId.StartsWith(NodeHeader))
+            {
+                writeNodeId = NodeHeader + nodeId;
+            }
+            return OpcUaClient.WriteNode<T>(writeNodeId, value);
+        }
+
+        /// <summary>
+        /// 异步写入单个节点的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="nodeId"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public async Task<bool> WriteNodeAsync<T>(string nodeId, T value)
+        {
+            string writeNodeId = nodeId;
+            if (!nodeId.StartsWith(NodeHeader))
+            {
+                writeNodeId = NodeHeader + nodeId;
+            }
+            return await OpcUaClient.WriteNodeAsync<T>(writeNodeId, value);
+        }
+
+        /// <summary>
+        /// 写入多个节点
+        /// </summary>
+        /// <param name="nodeValues"></param>
+        /// <returns></returns>
+        public bool WriteNodes(Dictionary<string, object> nodeValues)
+        {
+            var writeNodeValues = new Dictionary<string, object>();
+            foreach (var kvp in nodeValues)
+            {
+                string writeNodeId = kvp.Key;
+                if (!kvp.Key.StartsWith(NodeHeader))
+                {
+                    writeNodeId = NodeHeader + kvp.Key;
+                }
+                writeNodeValues[writeNodeId] = kvp.Value;
+            }
+            return OpcUaClient.WriteNodes(writeNodeValues.Keys.ToArray(), writeNodeValues.Values.ToArray());
+        }
+
+        /// <summary>
+        /// 写入多个节点异步
+        /// </summary>
+        /// <param name="nodeValues"></param>
+        /// <returns></returns>
+        public Task<bool> WriteNodesAsync(Dictionary<string, object> nodeValues)
+        {
+            return Task.Run(() =>
+            {
+                return WriteNodes(nodeValues);
+            });
+        }
+
+        /// <summary>
+        /// 订阅多个节点
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="nodeIds"></param>
+        /// <param name="dataChangeHandler"></param>
+        public void SubscribeNodes(string key, List<string> nodeIds, Action<(string key,string nodeId,object value)> dataChangeHandler)
+        {
+            OpcUaClient.AddSubscription("InputOutput", nodeIds.Select(s =>
+            {
+                if (s.StartsWith(NodeHeader))
+                {
+                    return s;
+                }
+                else
+                {
+                    return NodeHeader + s;
+                }
+            }).ToArray(), (key1, monitoredItem, args) =>
+            {
+                MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
+                if (notification == null)
+                {
+                    return;
+                }
+                string nodeId = monitoredItem.StartNodeId.ToString();
+                if (nodeId.StartsWith(NodeHeader))
+                {
+                    nodeId= nodeId.Substring(NodeHeader.Length);
+                }
+                // 触发数据变化事件
+                dataChangeHandler?.Invoke((key, nodeId, notification.Value.WrappedValue.Value));
+            });
+        }
         #region OPC事件
         private void OpcUaClient_OpcStatusChange(object sender, OpcUaStatusEventArgs e)
         {

+ 203 - 0
TeamAAS-VM/Core/Robots/Axis.cs

@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace TeamAAS_VP.Core.Robots
+{
+    /// <summary>
+    /// Represents command node names for an axis.
+    /// </summary>
+    public class AxisCommand
+    {
+        /// <summary>
+        /// 使能,Bool类型
+        /// </summary>
+        public string PowerNode { get; set; }
+        /// <summary>
+        /// 停止,Bool类型
+        /// </summary>
+        public string StopNode { get; set; }
+        /// <summary>
+        /// 手动回原,Bool类型
+        /// </summary>
+        public string ManuToHomeNode { get; set; }
+        /// <summary>
+        /// 自动回原,Bool类型
+        /// </summary>
+        public string AutoToHomeNode { get; set; }
+        /// <summary>
+        /// 手动J0G+,Bool类型
+        /// </summary>
+        public string ManuJogFowardNode { get; set; }
+        /// <summary>
+        /// 手动J0G-,Bool类型
+        /// </summary>
+        public string ManuJogBackwardNode { get; set; }
+        /// <summary>
+        /// 速度模式控制,Bool类型
+        /// </summary>
+        public string VelocityControlNode { get; set; }
+        /// <summary>
+        /// 速度模式变速,Bool类型
+        /// </summary>
+        public string VelocityChangeNode { get; set; }
+        /// <summary>
+        /// 自动相对运动,Int类型
+        /// </summary>
+        public string AutoINCNode { get; set; }
+        /// <summary>
+        /// 自动绝对运动,Int类型
+        /// </summary>
+        public string AutoABSNode { get; set; }
+        /// <summary>
+        /// 转矩控制运动,Int类型
+        /// </summary>
+        public string TorqueNode { get; set; }
+        /// <summary>
+        /// 点位选择,Int类型
+        /// </summary>
+        public string PointSelectNode { get; set; }
+        /// <summary>
+        /// 示教使能,Bool类型
+        /// </summary>
+        public string TeachOnNode { get; set; }
+        /// <summary>
+        /// 示教位置,Bool类型
+        /// </summary>
+        public string TeachNode { get; set; }
+        /// <summary>
+        /// 手动绝对运动点位,Bool类型
+        /// </summary>
+        public string ManuPointNode { get; set; }
+        /// <summary>
+        /// 手动绝对运动指定位置,Bool类型
+        /// </summary>
+        public string ManuPositionNode { get; set; }
+    }
+
+    /// <summary>
+    /// Represents parameter node names for an axis.
+    /// </summary>
+    public class AxisParameter
+    {
+        /// <summary>
+        /// 手动定位点位,Real类型
+        /// </summary>
+        public string ManuPositionNode { get; set; }
+        /// <summary>
+        /// 手动定位速度,Real类型
+        /// </summary>
+        public string ManuVelocityNode { get; set; }
+        /// <summary>
+        /// 速度模式定位速度,Real类型
+        /// </summary>
+        public string VelModeVelocityNode { get; set; }
+        /// <summary>
+        /// 轴名称,String类型
+        /// </summary>
+        public string CommentNode { get; set; }
+    }
+
+    /// <summary>
+    /// Represents state node names for an axis.
+    /// </summary>
+    public class AxisState
+    {
+        /// <summary>
+        /// 轴已使能,Bool类型
+        /// </summary>
+        public string PowerOnNode { get; set; }
+        /// <summary>
+        /// 运转中,Bool类型
+        /// </summary>
+        public string BusyNode { get; set; }
+        /// <summary>
+        /// 定位完成,Bool类型
+        /// </summary>
+        public string PosOKNode { get; set; }
+        /// <summary>
+        /// 回原完成,Bool类型
+        /// </summary>
+        public string InitialedNode { get; set; }
+        /// <summary>
+        /// 轴暂停中,Bool类型
+        /// </summary>
+        public string PausedNode { get; set; }
+        /// <summary>
+        /// 轴正转中,Bool类型
+        /// </summary>
+        public string PosiNode { get; set; }
+        /// <summary>
+        /// 轴反转中,Bool类型
+        /// </summary>
+        public string NegaNode { get; set; }
+        /// <summary>
+        /// 错误,Bool类型
+        /// </summary>
+        public string ErrorNode { get; set; }
+        /// <summary>
+        /// 错误代码,Int类型
+        /// </summary>
+        public string ErrorIdNode { get; set; }
+        /// <summary>
+        /// 实际位置,Real类型
+        /// </summary>
+        public string ActPositionNode { get; set; }
+        /// <summary>
+        /// 实际速度,Real类型
+        /// </summary>
+        public string ActVelocityNode { get; set; }
+        /// <summary>
+        /// 实际扭矩,Real类型
+        /// </summary>
+        public string ActTorqueNode { get; set; }
+        /// <summary>
+        /// 停止位置,Real类型
+        /// </summary>
+        public string StopPositionNode { get; set; }
+        /// <summary>
+        /// 绝对位置点位,Real类型
+        /// </summary>
+        public string HmiPositionNode { get; set; }
+        /// <summary>
+        /// 运动时间,Int类型
+        /// </summary>
+        public string SpTimeNode { get; set; }
+        /// <summary>
+        /// 轴状态,Int类型
+        /// </summary>
+        public string StateNode { get; set; }
+    }
+
+    /// <summary>
+    /// Axis model that groups command, parameter and state node names.
+    /// </summary>
+    public class Axis
+    {
+        public Axis()
+        {
+            Command = new AxisCommand();
+            Parameter = new AxisParameter();
+            State = new AxisState();
+        }
+
+        /// <summary>
+        /// Logical name of the axis, e.g. "X","Y","Z","U" or "A1".
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Axis index (1-based) when applicable.
+        /// </summary>
+        public int Index { get; set; }
+
+        public AxisCommand Command { get; set; }
+
+        public AxisParameter Parameter { get; set; }
+
+        public AxisState State { get; set; }
+    }
+}

+ 635 - 0
TeamAAS-VM/Core/Robots/XYZU_Robot.cs

@@ -0,0 +1,635 @@
+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 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 XYZU_Robot : 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 XYZU_Robot(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;
+            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);
+            // U optional
+            if (robot.PlcRobotParameter.AxixList.Count >= 4)
+            {
+                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);
+                Brand = RobotBrand.XYZU_Platform;
+            }
+        }
+
+        private void Plc_ConnectChangedEvent(object arg1, bool arg2)
+        {
+            CanExecute = arg2;
+        }
+
+        #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; } = 120000;
+
+        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; }
+
+        #endregion
+
+        #region 连接
+        public void Connect()
+        {
+
+        }
+
+        public Task ConnectAsync()
+        {
+            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.HmiPositionNode)).Select(a => a.State.HmiPositionNode).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;
+                        default: break;
+                    }
+                }
+                return point;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("获取机器人位置出错", 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) => Go(position);
+        public Task<bool> JumpAsync(RPoint position, double? LimZ) => GoAsync(position);
+        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;
+                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;
+            if (LimZ.HasValue)
+            {
+                currentPos.Z = (float)LimZ.Value;
+                if (!Go(currentPos)) return false;
+            }
+            //再XYU轴到位
+            currentPos.X = position.X;
+            currentPos.Y = position.Y;
+            currentPos.U = position.U;
+            if (!Go(currentPos)) return false;
+            //最后Z轴到目标高度
+            currentPos.Z = position.Z;
+            return Go(currentPos);
+        }
+        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 Task<bool> CalibParameAsync(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow) => Task.FromResult(false);
+
+        /// <summary>
+        /// 阻塞式等待运动完成
+        /// </summary>
+        /// <param name="position"></param>
+        /// <returns></returns>
+        private bool WaitMoveFinished(RPoint position)
+        {
+            if (Plc == null || !Plc.IsConnected) return false;
+            try
+            {
+                CanExecute = false;
+                Dictionary<string, object> keyValues = new Dictionary<string, object>();
+
+                // 1. 复位所有轴的开始移动命令
+                StopMove();
+
+                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;
+                        default: value = 0; break;
+                    }
+                    keyValues.Add(nodeid, value);
+                }
+                // 2. 写入位置给所有轴
+                if (!Plc.WriteNodes(keyValues))
+                {
+                    return false;
+                }
+
+                // 3. 写入开始移动命令给所有轴
+                if (!StartMove())
+                {
+                    return false;
+                }
+
+                // 4. 等待移动完成
+                Stopwatch sw = new Stopwatch();
+                sw.Start();
+                while (true)
+                {
+                    //检查各轴是否到位和目标位置一致
+                    var (isFinished, isError) = CheckMoveFinished(position);
+                    if (isFinished)
+                    {
+                        StopMove();
+                        if (isError)
+                        {
+                            LogHelper.WriteLogInfo($"机器人执行移动时发生错误,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
+                            return false;
+                        }
+                        return true;
+                    }
+                    if (sw.ElapsedMilliseconds > Timeout)
+                    {
+                        LogHelper.WriteLogInfo($"机器人执行移动超时,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
+                        StopMove();
+                        return false;
+                    }
+                    Thread.Sleep(10);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("执行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);
+                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.HmiPositionNode))
+                {
+                    nodeIds.Add(axis.State.HmiPositionNode);
+                }
+                if (!string.IsNullOrWhiteSpace(axis.State.ErrorNode))
+                {
+                    nodeIds.Add(axis.State.ErrorNode);
+                }
+            }
+            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.HmiPositionNode))
+                {
+                    try { actualPos = Convert.ToSingle(res[axis.State.HmiPositionNode]); } catch { }
+                }
+                if (res.ContainsKey(axis.State.ErrorNode))
+                {
+                    isError = (bool)res[axis.State.ErrorNode];
+                }
+                //检查是否到位和位置一致
+                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;
+                    default: targetPos = 0; break;
+                }
+                //如果定位完成且位置一致,则此轴完成,如果有错误则失败
+                if (!(posOk && 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
+    }
+}

+ 39 - 36
TeamAAS-VM/Core/Robots/XYZ_Platform.cs

@@ -27,6 +27,7 @@ using static TeamAAS_VP.Core.CoordinateTransformer;
 
 namespace TeamAAS_VP.Core.Robots
 {
+    /*
     public class XYZ_Platform : IRobot, INotifyPropertyChanged
     {
         public event Action<Guid, object, ConnectedEventArgs> ConnectedEvent;
@@ -242,43 +243,43 @@ namespace TeamAAS_VP.Core.Robots
 
         public RPoint GetRobotPos()
         {
-            if (Plc == null || !Plc.IsConnected)
-            {
-                return null;
-            }
-            try
-            {
-                for (int i = 0; i < 3; i++)
-                {
-                    var data = Plc.OpcUaClient.ReadNodes(new List<string>
-                    {
-                        RobotInfo.PlcRobotParameter.StateParameter.X,
-                        RobotInfo.PlcRobotParameter.StateParameter.Y,
-                        RobotInfo.PlcRobotParameter.StateParameter.Z,
-                        RobotInfo.PlcRobotParameter.StateParameter.U,
-                    }.Select(p => new Opc.Ua.NodeId(p)).ToArray());
-                    if (StatusCode.IsGood(data[0].StatusCode) && StatusCode.IsGood(data[1].StatusCode) && StatusCode.IsGood(data[2].StatusCode) && StatusCode.IsGood(data[3].StatusCode))
-                    {
-                        RPoint point = new RPoint();
-                        point.X = Convert.ToSingle(data[0].Value);
-                        point.Y = Convert.ToSingle(data[1].Value);
-                        point.Z = Convert.ToSingle(data[2].Value);
-                        point.U = Convert.ToSingle(data[3].Value);
-                        point.V = 0;
-                        point.W = 0;
-                        point.Hand = RobotHand.Right;
-                        point.Local = 0;
-                        point.Tool = 0;
-                        return point;
-                    }
-                }
+            //if (Plc == null || !Plc.IsConnected)
+            //{
                 return null;
-            }
-            catch (Exception ex)
-            {
-                LogHelper.WriteLogError("执行机器人命令时出错!", ex);
-                throw ex;
-            }
+            //}
+            //try
+            //{
+            //    for (int i = 0; i < 3; i++)
+            //    {
+            //        var data = Plc.OpcUaClient.ReadNodes(new List<string>
+            //        {
+            //            RobotInfo.PlcRobotParameter.StateParameter.X,
+            //            RobotInfo.PlcRobotParameter.StateParameter.Y,
+            //            RobotInfo.PlcRobotParameter.StateParameter.Z,
+            //            RobotInfo.PlcRobotParameter.StateParameter.U,
+            //        }.Select(p => new Opc.Ua.NodeId(p)).ToArray());
+            //        if (StatusCode.IsGood(data[0].StatusCode) && StatusCode.IsGood(data[1].StatusCode) && StatusCode.IsGood(data[2].StatusCode) && StatusCode.IsGood(data[3].StatusCode))
+            //        {
+            //            RPoint point = new RPoint();
+            //            point.X = Convert.ToSingle(data[0].Value);
+            //            point.Y = Convert.ToSingle(data[1].Value);
+            //            point.Z = Convert.ToSingle(data[2].Value);
+            //            point.U = Convert.ToSingle(data[3].Value);
+            //            point.V = 0;
+            //            point.W = 0;
+            //            point.Hand = RobotHand.Right;
+            //            point.Local = 0;
+            //            point.Tool = 0;
+            //            return point;
+            //        }
+            //    }
+            //    return null;
+            //}
+            //catch (Exception ex)
+            //{
+            //    LogHelper.WriteLogError("执行机器人命令时出错!", ex);
+            //    throw ex;
+            //}
         }
 
         public async Task<RPoint> GetRobotPosAsync()
@@ -1485,4 +1486,6 @@ namespace TeamAAS_VP.Core.Robots
         }
         #endregion
     }
+
+    */
 }

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

@@ -39,5 +39,6 @@ namespace TeamAAS_VP.Enums
         [Description("XYZU模组平台")]
         [Localization(ResourceName = "RobotBrand_XYZU_Platform", ResourceType = typeof(Lang))]
         XYZU_Platform = 12,
+
     }
 }

+ 0 - 3
TeamAAS-VM/Interfaces/IRobot.cs

@@ -317,9 +317,6 @@ namespace TeamAAS_VP.Interfaces
         Task<bool> CalibParameAsync(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow);
 
 
-        List<DataValue> GetNodesValue();
-
-
         /// <summary>
         /// 校准移动机器人
         /// </summary>

+ 6 - 174
TeamAAS-VM/Models/PLC/PlcRobotParameter.cs

@@ -4,183 +4,15 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using TeamAAS_VP.Core.Robots;
 
 namespace TeamAAS_VP.Models.PLC
 {
-    public class PlcRobotParameter : BindableBase
+    public class PlcRobotParameter
     {
-        private CommandParameter _CommandParameter=new CommandParameter();
-        public CommandParameter CommandParameter
-        {
-            get { return _CommandParameter; }
-            set { SetProperty(ref _CommandParameter, value); }
-        }
-
-        private StateParameter _StateParameter=new StateParameter();
-        public StateParameter StateParameter
-        {
-            get { return _StateParameter; }
-            set { SetProperty(ref _StateParameter, value); }
-        }
-    }
-
-    public class CommandParameter : BindableBase
-    {
-        private string _X;
-        public string X
-        {
-            get { return _X; }
-            set { SetProperty(ref _X, value); }
-        }
-
-        private string _Y;
-        public string Y
-        {
-            get { return _Y; }
-            set { SetProperty(ref _Y, value); }
-        }
-
-        private string _Z;
-        public string Z
-        {
-            get { return _Z; }
-            set { SetProperty(ref _Z, value); }
-        }
-
-        private string _U;
-        public string U
-        {
-            get { return _U; }
-            set { SetProperty(ref _U, value); }
-        }
-
-        private string _Speed;
-        public string Speed
-        {
-            get { return _Speed; }
-            set { SetProperty(ref _Speed, value); }
-        }
-
-        private string _Accel;
-        public string Accel
-        {
-            get { return _Accel; }
-            set { SetProperty(ref _Accel, value); }
-        }
-
-        private string _WaitVacuumOn;
-        public string WaitVacuumOn
-        {
-            get { return _WaitVacuumOn; }
-            set { SetProperty(ref _WaitVacuumOn, value); }
-        }
-
-        private string _WaitVacuumOff;
-        public string WaitVacuumOff
-        {
-            get { return _WaitVacuumOff; }
-            set { SetProperty(ref _WaitVacuumOff, value); }
-        }
-
-        private string _Limz;
-        public string Limz
-        {
-            get { return _Limz; }
-            set { SetProperty(ref _Limz, value); }
-        }
-
-        private string _ExecuteMove;
-        public string ExecuteMove
-        {
-            get { return _ExecuteMove; }
-            set { SetProperty(ref _ExecuteMove, value); }
-        }
-
-        private string _VacuumOn;
-        public string VacuumOn
-        {
-            get { return _VacuumOn; }
-            set { SetProperty(ref _VacuumOn, value); }
-        }
-
-        private string _VacuumOff;
-        public string VacuumOff
-        {
-            get { return _VacuumOff; }
-            set { SetProperty(ref _VacuumOff, value); }
-        }
-
-        private string _Distance;
-        public string Distance
-        {
-            get { return _Distance; }
-            set { SetProperty(ref _Distance, value); }
-        }
-
-        private string _JogCmd;
-        public string JogCmd
-        {
-            get { return _JogCmd; }
-            set { SetProperty(ref _JogCmd, value); }
-        }
-    }
-
-    public class StateParameter : BindableBase
-    {
-        private string _X;
-        public string X
-        {
-            get { return _X; }
-            set { SetProperty(ref _X, value); }
-        }
-
-        private string _Y;
-        public string Y
-        {
-            get { return _Y; }
-            set { SetProperty(ref _Y, value); }
-        }
-
-        private string _Z;
-        public string Z
-        {
-            get { return _Z; }
-            set { SetProperty(ref _Z, value); }
-        }
-
-        private string _U;
-        public string U
-        {
-            get { return _U; }
-            set { SetProperty(ref _U, value); }
-        }
-
-        private string _MoveFinish;
-        public string MoveFinish
-        {
-            get { return _MoveFinish; }
-            set { SetProperty(ref _MoveFinish, value); }
-        }
-
-        private string _Error;
-        public string Error
-        {
-            get { return _Error; }
-            set { SetProperty(ref _Error, value); }
-        }
-
-        private string _VacuumOn;
-        public string VacuumOn
-        {
-            get { return _VacuumOn; }
-            set { SetProperty(ref _VacuumOn, value); }
-        }
-
-        private string _VacuumOff;
-        public string VacuumOff
-        {
-            get { return _VacuumOff; }
-            set { SetProperty(ref _VacuumOff, value); }
-        }
+        /// <summary>
+        /// 轴集合
+        /// </summary>
+        public List<Axis> AxixList { get; set; }
     }
 }

+ 6 - 2
TeamAAS-VM/Services/RobotService.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Team.FFFeederService;
 using Team.FFFeederService.Interfaces;
+using TeamAAS_VP.Core.PLCs;
 using TeamAAS_VP.Core.Robots;
 using TeamAAS_VP.Interfaces;
 using TeamAAS_VP.Models;
@@ -15,12 +16,14 @@ namespace TeamAAS_VP.Services
 {
     public class RobotService : IRobotService, IDisposable
     {
+        private readonly IPlcService _plcService;
         private readonly Dictionary<Guid, IRobot> _robotCollection;
         private readonly object _sync = new object();
 
-        public RobotService()
+        public RobotService(IPlcService plcService)
         {
             _robotCollection = new Dictionary<Guid, IRobot>();
+            _plcService = plcService;
         }
 
         /// <summary>
@@ -362,7 +365,8 @@ namespace TeamAAS_VP.Services
             }
             else if (robotInfo.RobotBrand == Enums.RobotBrand.XYZ_Platform || robotInfo.RobotBrand == Enums.RobotBrand.XYZU_Platform)
             {
-                robot = new XYZ_Platform(robotInfo);
+                var plc= _plcService.GetPlc(robotInfo.PLC) as OpcUaClientPLC;
+                robot = new XYZU_Robot(robotInfo, plc);
             }
             else
             {

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

@@ -546,6 +546,8 @@
     <Compile Include="Core\BgCommunicate.cs" />
     <Compile Include="Core\BgModbusTcpCommunicate.cs" />
     <Compile Include="Core\BraceFoldingStrategy.cs" />
+    <Compile Include="Core\Robots\Axis.cs" />
+    <Compile Include="Core\Robots\XYZU_Robot.cs" />
     <Compile Include="Data\DatabaseInitializer.cs" />
     <Compile Include="Data\SystemDatabaseService.cs" />
     <Compile Include="Enums\OutputPointMode.cs" />