在做数字孪生时需要接入海康机器人的数据,数据源为OPC类型,下面就进入干货时间。

using Opc.Ua;
using Opc.Ua.Client;
using OpcUaHelper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

/// <summary>
/// OPC UA 管理器:负责连接、读取、订阅
/// </summary>
public class OpcUaManager : MonoBehaviour
{
    public static OpcUaManager Instance { get; private set; }

    private OpcUaClient client;
    public bool IsConnected { get; private set; } = false;

    private void Awake()
    {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    public async Task ConnectAsync(string serverUrl)
    {
        try
        {
            client = new OpcUaClient();
            client.UserIdentity = new UserIdentity(new AnonymousIdentityToken());
            await client.ConnectServer(serverUrl);
            IsConnected = true;
            Debug.Log("OPC UA 连接成功:" + serverUrl);
        }
        catch (Exception ex)
        {
            Debug.LogError("OPC UA 连接失败:" + ex.Message);
            IsConnected = false;
        }
    }

    public void Disconnect()
    {
        if (client != null)
        {
            client.Disconnect();
            IsConnected = false;
            Debug.Log("OPC UA 已断开连接");
        }
    }

    #region 节点读取

    public DataValue ReadNode(string nodeId)
    {
        if (!IsConnected) return null;
        try { return client.ReadNode(nodeId); }
        catch (Exception ex)
        {
            Debug.LogError($"读取节点失败 {nodeId}: {ex.Message}");
            return null;
        }
    }

    public string ReadNodeAsString(string nodeId)
    {
        var val = ReadNode(nodeId);
        if (val?.WrappedValue?.Value != null) return val.WrappedValue.Value.ToString();
        return "暂无数据";
    }

    public Dictionary<string, string> ReadNodesBatch(List<string> nodeIds)
    {
        var dict = new Dictionary<string, string>();
        if (!IsConnected || nodeIds == null || nodeIds.Count == 0) return dict;

        try
        {
            var nodes = nodeIds.Select(id => new NodeId(id)).ToArray();
            var values = client.ReadNodes(nodes);
            for (int i = 0; i < nodeIds.Count; i++)
            {
                dict[nodeIds[i]] = values[i]?.WrappedValue?.Value?.ToString() ?? "暂无数据";
            }
        }
        catch (Exception ex)
        {
            Debug.LogError("批量读取节点失败:" + ex.Message);
        }

        return dict;
    }

    #endregion

    #region 节点订阅

    public void SubscribeNode(string key, string nodeId, Action<float[]> callback)
    {
        if (!IsConnected) return;

        client.AddSubscription(key, nodeId, (k, item, args) =>
        {
            if (args.NotificationValue is MonitoredItemNotification notification &&
                notification.Value.WrappedValue.TypeInfo.ValueRank == 1 &&
                notification.Value.WrappedValue.Value is float[] arr)
            {
                callback?.Invoke(arr);
            }
        });
    }

    public void RemoveSubscription(string key)
    {
        if (!IsConnected) return;
        client.RemoveSubscription(key);
    }

    public void RemoveAllSubscriptions()
    {
        if (!IsConnected) return;
        client.RemoveAllSubscription();
    }

    #endregion
}

/// <summary>
/// 机器人数据管理
/// </summary>
public class RobotOpcData : MonoBehaviour
{
    public string opcUrl = "opc.tcp://192.168.148.3:48201";

    public RobotState Robot1 = new RobotState();
    public RobotState Robot2 = new RobotState();

    public DataDrivenJoints Join_1;
    public DataDrivenJoints Join_2;

    private bool robot1Connected = false;
    private bool robot2Connected = false;

    private void Start()
    {
        ConnectOpc();
    }

    private async void ConnectOpc()
    {
        await OpcUaManager.Instance.ConnectAsync(opcUrl);
        if (!OpcUaManager.Instance.IsConnected) return;

        // 添加关节订阅
        AddJointSubscription("Robot1_Joint", "ns=2;s=Robots.转子车间.ZZ231.Curpose.WorldPos.Joint", data => UpdateJointData(Join_1, data, ref robot1Connected));
        AddJointSubscription("Robot2_Joint", "ns=2;s=Robots.转子车间.ZZ232.Curpose.WorldPos.Joint", data => UpdateJointData(Join_2, data, ref robot2Connected));

        // 启动定时刷新机器人状态
        StartCoroutine(UpdateRobotStatusRoutine());
    }

    private void AddJointSubscription(string key, string nodeId, Action<float[]> callback)
    {
        OpcUaManager.Instance.SubscribeNode(key, nodeId, callback);
    }

    private void UpdateJointData(DataDrivenJoints join, float[] data, ref bool isConnectedFlag)
    {
        if (!isConnectedFlag) return;
        var jointDict = new Dictionary<string, float>();
        for (int i = 0; i < data.Length; i++)
            jointDict[$"轴_{i}"] = data[i];
        join?.AddJointsData(jointDict);
    }

    private System.Collections.IEnumerator UpdateRobotStatusRoutine()
    {
        while (true)
        {
            UpdateRobotState(Robot1, "ZZ231", ref robot1Connected);
            UpdateRobotState(Robot2, "ZZ232", ref robot2Connected);
            yield return new WaitForSeconds(0.2f); // 0.2秒刷新一次
        }
    }

    private void UpdateRobotState(RobotState robot, string robotPrefix, ref bool isConnectedFlag)
    {
        string connNode = $"ns=2;s=Robots.转子车间.{robotPrefix}.Connected";
        string connValue = OpcUaManager.Instance.ReadNodeAsString(connNode);
        isConnectedFlag = connValue.Equals("True", StringComparison.OrdinalIgnoreCase);

        if (!isConnectedFlag) return;

        // 批量读取任务状态
        var taskNodes = new List<string>
        {
            $"ns=2;s=Robots.转子车间.{robotPrefix}.Task.PRG[1].ParentProgName",
            $"ns=2;s=Robots.转子车间.{robotPrefix}.Task.PRG[1].ProgName",
            $"ns=2;s=Robots.转子车间.{robotPrefix}.Task.PRG[1].State"
        };
        var taskValues = OpcUaManager.Instance.ReadNodesBatch(taskNodes);
        robot.ParentProgName = SafeString(taskValues[taskNodes[0]]);
        robot.ProgName = SafeString(taskValues[taskNodes[1]]);
        robot.State = SafeString(taskValues[taskNodes[2]]);

        // 批量读取报警
        robot.Alarms.Clear();
        for (int i = 1; i <= 5; i++)
        {
            var alarm = new RobotAlarm
            {
                AlarmTime = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.CurrentAlarm.ALM[{i}].AlarmTime")),
                AlarmSeverity = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.CurrentAlarm.ALM[{i}].AlarmSeverity")),
                AlarmMessage = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.CurrentAlarm.ALM[{i}].AlarmMessage"))
            };
            robot.Alarms.Add(alarm);
        }

        // 读取运行状态
        robot.Run = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.UO[1-8].UO[3].Status"));
        robot.Ready = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.UO[1-8].UO[2].Status"));
        robot.Fault = SafeString(OpcUaManager.Instance.ReadNodeAsString($"ns=2;s=Robots.转子车间.{robotPrefix}.UO[1-8].UO[6].Status"));
    }

    private string SafeString(string s) => string.IsNullOrEmpty(s) ? "暂无数据" : s;

    private void OnDestroy()
    {
        OpcUaManager.Instance.Disconnect();
    }
}

/// <summary>
/// 机器人状态
/// </summary>
public class RobotState
{
    public string ParentProgName;
    public string ProgName;
    public string State;
    public string Run;
    public string Ready;
    public string Fault;
    public List<RobotAlarm> Alarms = new List<RobotAlarm>();
}

/// <summary>
/// 机器人报警
/// </summary>
public class RobotAlarm
{
    public string AlarmTime;
    public string AlarmSeverity;
    public string AlarmMessage;
}
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐