unity2moveit2/unity-project/Assets/Scripts/Tests/RealROSConnectionTest.cs
ayuan9957 fe15edcbd5 Initial commit: Unity-MoveIt2 integrated robotic arm simulation system
- Unity frontend with ROS-TCP-Connector for ROS2 communication
- Docker-based ROS2 Jazzy backend with MoveIt2 integration
- Support for 1-9 DOF manipulators
- UR5 robot configuration and URDF files
- Assembly task feasibility analysis tools
- Comprehensive documentation and deployment guides

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 12:08:34 +08:00

447 lines
15 KiB
C#

using UnityEngine;
using UnityMoveIt2.Communication;
using Unity.Robotics.ROSTCPConnector;
namespace UnityMoveIt2.Tests
{
/// <summary>
/// 真实ROS连接测试组件 / Real ROS Connection Test Component
/// 附加到GameObject上以测试真实的ROS-TCP通信 / Attach to GameObject to test real ROS-TCP communication
/// </summary>
public class RealROSConnectionTest : MonoBehaviour
{
[Header("连接配置 / Connection Configuration")]
[Tooltip("ROS2服务器IP / ROS2 Server IP")]
public string rosIP = "127.0.0.1";
[Tooltip("ROS2服务器端口 / ROS2 Server Port")]
public int rosPort = 10000;
[Tooltip("启动时自动连接 / Auto connect on start")]
public bool autoConnect = true;
[Tooltip("显示详细日志 / Verbose logging")]
public bool verboseLogging = true;
[Header("测试配置 / Test Configuration")]
[Tooltip("连接保持时间(秒) / Keep alive duration (seconds)")]
public float keepAliveDuration = 30f;
[Tooltip("心跳间隔(秒) / Heartbeat interval (seconds)")]
public float heartbeatInterval = 5f;
[Header("状态显示 / Status Display")]
[SerializeField] private bool isConnected = false;
[SerializeField] private float connectionTime = 0f;
[SerializeField] private int heartbeatCount = 0;
[SerializeField] private string lastError = "";
private ROSTCPBridge rosBridge;
private ROSConnection rosConnection;
private float connectedSince = 0f;
private float lastHeartbeat = 0f;
#region Unity生命周期 / Unity Lifecycle
private void Awake()
{
Log("=== RealROSConnectionTest 初始化 / Initialized ===");
}
private void Start()
{
// 查找或创建ROSTCPBridge
rosBridge = FindObjectOfType<ROSTCPBridge>();
if (rosBridge == null)
{
LogWarning("场景中未找到ROSTCPBridge组件 / ROSTCPBridge not found in scene");
LogWarning("将手动创建ROSConnection / Creating ROSConnection manually");
ConnectManually();
}
else
{
Log("找到ROSTCPBridge组件 / Found ROSTCPBridge component");
// 订阅事件
rosBridge.OnConnected += OnROSConnected;
rosBridge.OnDisconnected += OnROSDisconnected;
rosBridge.OnConnectionError += OnROSConnectionError;
if (autoConnect)
{
Log("等待ROSTCPBridge连接... / Waiting for ROSTCPBridge to connect...");
}
}
}
private void Update()
{
if (isConnected)
{
connectionTime = Time.time - connectedSince;
// 发送心跳
if (Time.time - lastHeartbeat >= heartbeatInterval)
{
SendHeartbeat();
lastHeartbeat = Time.time;
}
// 自动断开
if (keepAliveDuration > 0 && connectionTime >= keepAliveDuration)
{
Log($"已达到保持时间 {keepAliveDuration} 秒,断开连接 / Reached keep-alive duration, disconnecting");
Disconnect();
}
}
}
private void OnDestroy()
{
if (rosBridge != null)
{
rosBridge.OnConnected -= OnROSConnected;
rosBridge.OnDisconnected -= OnROSDisconnected;
rosBridge.OnConnectionError -= OnROSConnectionError;
}
Disconnect();
}
#endregion
#region / Connection Management
/// <summary>
/// 手动连接到ROS / Manually connect to ROS
/// </summary>
[ContextMenu("手动连接 / Manual Connect")]
public void ConnectManually()
{
try
{
Log("======================================");
Log("开始手动连接到ROS2 / Starting manual connection to ROS2");
Log($"目标: {rosIP}:{rosPort}");
Log("======================================");
rosConnection = ROSConnection.GetOrCreateInstance();
if (rosConnection == null)
{
LogError("无法创建ROSConnection实例 / Failed to create ROSConnection");
return;
}
rosConnection.RosIPAddress = rosIP;
rosConnection.RosPort = rosPort;
rosConnection.ConnectOnStart = false;
Log("调用Connect()... / Calling Connect()...");
rosConnection.Connect();
// 检查连接状态
Invoke(nameof(CheckConnectionStatus), 1f);
}
catch (System.Exception ex)
{
LogError($"连接异常 / Connection exception: {ex.Message}");
lastError = ex.Message;
}
}
/// <summary>
/// 断开连接 / Disconnect
/// </summary>
[ContextMenu("断开连接 / Disconnect")]
public void Disconnect()
{
if (rosConnection != null)
{
Log("正在断开连接... / Disconnecting...");
rosConnection.Disconnect();
}
isConnected = false;
connectionTime = 0f;
Log("✓ 已断开 / Disconnected");
}
private void CheckConnectionStatus()
{
if (rosConnection == null)
{
LogError("ROSConnection为null / ROSConnection is null");
return;
}
bool hasError = rosConnection.HasConnectionError;
if (!hasError)
{
OnROSConnected();
}
else
{
OnROSConnectionError("连接检查失败 / Connection check failed");
}
}
#endregion
#region / Event Handlers
private void OnROSConnected()
{
isConnected = true;
connectedSince = Time.time;
connectionTime = 0f;
lastError = "";
heartbeatCount = 0;
lastHeartbeat = Time.time;
Log("======================================");
Log("✓✓✓ 真实ROS连接建立成功! / Real ROS Connection Established! ✓✓✓");
Log("======================================");
Log($"服务器: {rosIP}:{rosPort}");
Log($"连接时间: {System.DateTime.Now:HH:mm:ss}");
Log($"保持时长: {keepAliveDuration}秒 / seconds");
Log("======================================");
}
private void OnROSDisconnected()
{
isConnected = false;
Log("======================================");
Log("✗ 连接已断开 / Connection Disconnected");
Log("======================================");
Log($"连接持续时间: {connectionTime:F1}秒 / seconds");
Log($"发送心跳次数: {heartbeatCount}");
Log("======================================");
}
private void OnROSConnectionError(string error)
{
isConnected = false;
lastError = error;
Log("======================================");
Log("✗✗✗ 连接错误! / Connection Error! ✗✗✗");
Log("======================================");
LogError($"错误信息 / Error: {error}");
Log("======================================");
LogError("可能的原因 / Possible causes:");
LogError("1. ROS2服务未启动 / ROS2 service not started");
LogError("2. IP或端口错误 / Wrong IP or port");
LogError("3. 防火墙阻止 / Firewall blocking");
LogError("4. Docker容器未运行 / Docker not running");
Log("======================================");
LogError("解决方案 / Solutions:");
LogError("cd docker && docker-compose up -d");
LogError("docker-compose ps");
LogError("docker-compose logs ros2-unity");
Log("======================================");
}
#endregion
#region / Test Functions
/// <summary>
/// 发送心跳测试 / Send heartbeat test
/// </summary>
private void SendHeartbeat()
{
if (!isConnected || rosConnection == null)
return;
try
{
heartbeatCount++;
// 创建简单的测试数据
byte[] heartbeatData = System.Text.Encoding.UTF8.GetBytes($"HEARTBEAT_{heartbeatCount}_{Time.time}");
rosConnection.SendMessage(heartbeatData);
if (verboseLogging)
{
Log($"❤ 心跳 #{heartbeatCount} 已发送 / Heartbeat sent ({heartbeatData.Length} bytes)");
}
}
catch (System.Exception ex)
{
LogError($"心跳发送失败 / Heartbeat failed: {ex.Message}");
}
}
/// <summary>
/// 发送测试消息 / Send test message
/// </summary>
[ContextMenu("发送测试消息 / Send Test Message")]
public void SendTestMessage()
{
if (!isConnected || rosConnection == null)
{
LogWarning("未连接,无法发送 / Not connected, cannot send");
return;
}
try
{
string testMsg = $"TEST_MESSAGE_{System.DateTime.Now:HHmmss}";
byte[] data = System.Text.Encoding.UTF8.GetBytes(testMsg);
rosConnection.SendMessage(data);
Log($"✓ 测试消息已发送 / Test message sent: {testMsg} ({data.Length} bytes)");
}
catch (System.Exception ex)
{
LogError($"发送失败 / Send failed: {ex.Message}");
}
}
/// <summary>
/// 运行完整连接测试 / Run full connection test
/// </summary>
[ContextMenu("运行完整测试 / Run Full Test")]
public void RunFullTest()
{
StartCoroutine(FullTestCoroutine());
}
private System.Collections.IEnumerator FullTestCoroutine()
{
Log("\n\n");
Log("██████████████████████████████████████████████");
Log("█ 真实ROS连接完整测试 / Real ROS Full Test █");
Log("██████████████████████████████████████████████");
// 测试1: 连接
Log("\n[测试 1/4] 建立连接 / Establishing Connection");
ConnectManually();
yield return new WaitForSeconds(2f);
if (!isConnected)
{
LogError("✗ 测试失败: 无法连接 / Test Failed: Cannot connect");
yield break;
}
Log("✓ 测试1通过: 连接成功 / Test 1 Passed");
// 测试2: 保持连接
Log("\n[测试 2/4] 连接保持 / Keep Alive");
yield return new WaitForSeconds(5f);
if (isConnected)
{
Log($"✓ 测试2通过: 连接保持 {connectionTime:F1}秒 / Test 2 Passed");
}
else
{
LogError("✗ 测试2失败: 连接断开 / Test 2 Failed");
yield break;
}
// 测试3: 发送数据
Log("\n[测试 3/4] 数据发送 / Data Send");
for (int i = 0; i < 3; i++)
{
SendTestMessage();
yield return new WaitForSeconds(1f);
}
Log("✓ 测试3完成: 发送3条消息 / Test 3 Complete: 3 messages sent");
// 测试4: 断开连接
Log("\n[测试 4/4] 断开连接 / Disconnection");
Disconnect();
yield return new WaitForSeconds(1f);
Log("✓ 测试4完成: 已断开 / Test 4 Complete");
Log("\n");
Log("██████████████████████████████████████████████");
Log("█ 测试完成! / Test Complete! █");
Log("██████████████████████████████████████████████");
Log($"总连接时间 / Total connection time: {connectionTime:F1}秒");
Log($"发送心跳 / Heartbeats sent: {heartbeatCount}");
Log("\n\n");
}
#endregion
#region / Logging
private void Log(string message)
{
if (verboseLogging)
{
Debug.Log($"[RealROSTest] {message}");
}
}
private void LogWarning(string message)
{
Debug.LogWarning($"[RealROSTest] {message}");
}
private void LogError(string message)
{
Debug.LogError($"[RealROSTest] {message}");
}
#endregion
#region GUI显示 / GUI Display
private void OnGUI()
{
if (!verboseLogging) return;
GUIStyle boxStyle = new GUIStyle(GUI.skin.box);
boxStyle.alignment = TextAnchor.UpperLeft;
boxStyle.fontSize = 14;
boxStyle.normal.textColor = isConnected ? Color.green : Color.red;
string statusText = "真实ROS连接状态 / Real ROS Connection Status\n";
statusText += "==========================================\n";
statusText += $"服务器 / Server: {rosIP}:{rosPort}\n";
statusText += $"状态 / Status: {(isConnected ? " / Connected" : " / Disconnected")}\n";
statusText += $"连接时长 / Duration: {connectionTime:F1}s\n";
statusText += $"心跳次数 / Heartbeats: {heartbeatCount}\n";
if (!string.IsNullOrEmpty(lastError))
{
statusText += $"错误 / Error: {lastError}\n";
}
GUI.Box(new Rect(10, 10, 450, 130), statusText, boxStyle);
// 按钮
if (GUI.Button(new Rect(10, 150, 100, 30), "连接 / Connect"))
{
ConnectManually();
}
if (GUI.Button(new Rect(120, 150, 100, 30), "断开 / Disconnect"))
{
Disconnect();
}
if (GUI.Button(new Rect(230, 150, 110, 30), "发送测试 / Send Test"))
{
SendTestMessage();
}
if (GUI.Button(new Rect(350, 150, 110, 30), "完整测试 / Full Test"))
{
RunFullTest();
}
}
#endregion
}
}