|
|
|
|
@ -99,7 +99,9 @@ const WsModule = (() => {
|
|
|
|
|
function connectDrone(url) {
|
|
|
|
|
try {
|
|
|
|
|
if (typeof ROSLIB === 'undefined') {
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: 'roslibjs 库未加载' });
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: '❌ [环境检查] roslibjs 库未加载' });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ' 原因: 浏览器可能无法访问 unpkg.com CDN' });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ' 解决: 已在页面中加入 cdnjs 备选地址,刷新重试;或切换网络' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -107,18 +109,95 @@ const WsModule = (() => {
|
|
|
|
|
try { ros.close(); } catch (e) { /* ignore */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: `正在连接 rosbridge: ${url} ...` });
|
|
|
|
|
// === Step 0: URL 格式检查 ===
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: '═══════ 无人机连接诊断 ═══════' });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: `[Step 0/5] 地址检查: ${url}` });
|
|
|
|
|
var parsedOk = true;
|
|
|
|
|
try {
|
|
|
|
|
var parsed = new URL(url);
|
|
|
|
|
if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ' ⚠️ 协议不是 ws:// 或 wss://,请检查地址格式' });
|
|
|
|
|
parsedOk = false;
|
|
|
|
|
}
|
|
|
|
|
if (parsed.hostname === '192.168.1.14') {
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ' ⚠️ 当前使用默认地址 192.168.1.14,如果 P600 的 IP 不同请修改' });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ' 💡 在 P600 上执行 hostname -I 查看实际 IP' });
|
|
|
|
|
}
|
|
|
|
|
} catch(e) {
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: ' ❌ 地址格式错误,应为 ws://IP:9090 格式' });
|
|
|
|
|
parsedOk = false;
|
|
|
|
|
}
|
|
|
|
|
if (!parsedOk) return;
|
|
|
|
|
|
|
|
|
|
// === Step 1: 尝试 WebSocket 连接 ===
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: `[Step 1/5] 正在建立 WebSocket 连接到 ${url} ...` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` 提示: 如果长时间无响应,请逐项排查:` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` ① 电脑是否连接到 P600 的 WiFi (或同一局域网)` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` ② 在电脑 CMD 中执行: ping ${parsed.hostname}` });
|
|
|
|
|
|
|
|
|
|
// 连接超时检测(15秒)
|
|
|
|
|
var connTimeout = setTimeout(function() {
|
|
|
|
|
if (!rosConnected) {
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: `⏱ [Step 1/5] 连接超时 (15s)` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` 🔍 排查步骤:` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ① 确认电脑 WiFi 已连接到 P600` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ② 在 CMD 执行 ping ${parsed.hostname} 检测连通性` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ③ SSH 进 P600 确认 rosbridge 是否运行:` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ps aux | grep rosbridge` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ④ 如果没运行,启动它:` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` roslaunch rosbridge_server rosbridge_websocket.launch` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ⑤ 检查 P600 防火墙: sudo ufw status | grep 9090` });
|
|
|
|
|
}
|
|
|
|
|
}, 15000);
|
|
|
|
|
|
|
|
|
|
ros = new ROSLIB.Ros({ url: url });
|
|
|
|
|
|
|
|
|
|
ros.on('connection', () => {
|
|
|
|
|
clearTimeout(connTimeout);
|
|
|
|
|
rosConnected = true;
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: 'rosbridge 已连接,正在检测 MAVROS 话题...' });
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `[Step 2/5] ✅ WebSocket 连接成功!` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` rosbridge 地址: ${url}` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: `[Step 3/5] 正在订阅 MAVROS 话题 (等待 5 秒内收到 state)...` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` 如果长时间无响应:` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` ① 在 P600 上确认 MAVROS 已启动:` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` roslaunch mavros px4.launch fcu_url:=/dev/ttyTHS1:921600` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` ② 检查话题列表: rostopic list | grep mavros/state` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` ③ 话题命名空间可能是 /uav1/mavros/state 或 /mavros/state` });
|
|
|
|
|
setupSubscriptions();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ros.on('error', (error) => {
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: `rosbridge 连接错误: ${error || '未知'}` });
|
|
|
|
|
clearTimeout(connTimeout);
|
|
|
|
|
var msg = String(error || '');
|
|
|
|
|
var detail = '';
|
|
|
|
|
var steps = '';
|
|
|
|
|
|
|
|
|
|
if (msg.includes('ECONNREFUSED') || msg.includes('Connection refused')) {
|
|
|
|
|
detail = '❌ 连接被拒绝 (ECONNREFUSED)';
|
|
|
|
|
steps = ' rosbridge 未在 P600 上运行或端口不是 9090\n 解决: roslaunch rosbridge_server rosbridge_websocket.launch';
|
|
|
|
|
} else if (msg.includes('ENETUNREACH') || msg.includes('Network is unreachable')) {
|
|
|
|
|
detail = '❌ 网络不可达 (ENETUNREACH)';
|
|
|
|
|
steps = ' 电脑与 P600 不在同一网络\n 解决: 连接 P600 的 WiFi,或检查网络设置';
|
|
|
|
|
} else if (msg.includes('EHOSTUNREACH') || msg.includes('No route to host')) {
|
|
|
|
|
detail = '❌ 主机不可达 (EHOSTUNREACH)';
|
|
|
|
|
steps = ` IP 地址 ${parsed.hostname} 可能不正确\n 解决: 在 P600 上执行 hostname -I 获取正确 IP`;
|
|
|
|
|
} else if (msg.includes('ETIMEDOUT') || msg.includes('timed out')) {
|
|
|
|
|
detail = '❌ 连接超时 (ETIMEDOUT)';
|
|
|
|
|
steps = ` 能 ping 通 ${parsed.hostname} 吗?\n 解决: CMD 中执行 ping ${parsed.hostname}\n 如果不通: 检查网络连接\n 如果通: 检查 P600 上 rosbridge 是否卡住,尝试重启`;
|
|
|
|
|
} else if (msg.includes('SecurityError') || msg.includes('security')) {
|
|
|
|
|
detail = '❌ 安全错误 (SecurityError)';
|
|
|
|
|
steps = ' 浏览器安全策略阻止了连接\n 解决: 使用 HTTP 页面而非 HTTPS,或使用 localhost';
|
|
|
|
|
} else {
|
|
|
|
|
detail = `❌ 连接失败: ${msg || '未知错误'}`;
|
|
|
|
|
steps = ' 查看浏览器控制台 (F12 > Console) 获取详细错误\n 或在 CMD 中测试: curl -v http://' + parsed.hostname + ':5000/api/ping';
|
|
|
|
|
}
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: `[Step 1/5] ${detail}` });
|
|
|
|
|
if (steps) {
|
|
|
|
|
var parts = steps.split('\n');
|
|
|
|
|
for (var si = 0; si < parts.length; si++) {
|
|
|
|
|
UIModule.onStatus({ type: (detail.includes('❌') ? 'warning' : 'info'), message: parts[si] });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ros.on('close', () => {
|
|
|
|
|
@ -127,11 +206,12 @@ const WsModule = (() => {
|
|
|
|
|
droneConnected = false;
|
|
|
|
|
stopAll();
|
|
|
|
|
if (wasConnected) {
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: '🔌 与 rosbridge 的连接已断开' });
|
|
|
|
|
UIModule.onDroneDisconnected();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: `连接失败: ${e.message || e}` });
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: `❌ 连接异常: ${e.message || e}` });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -168,10 +248,13 @@ const WsModule = (() => {
|
|
|
|
|
// Try without namespace
|
|
|
|
|
if (tryNS !== '') {
|
|
|
|
|
uavNS = '';
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: `未收到 ${tryNS}/mavros/state,尝试无命名空间...` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: `[Step 3/4] ⏱ 未收到 ${tryNS}/mavros/state (5s 超时),尝试无命名空间...` });
|
|
|
|
|
trySubscribesWithFallback();
|
|
|
|
|
} else {
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: '未检测到 MAVROS 话题,请确认仿真/实机已启动' });
|
|
|
|
|
UIModule.onStatus({ type: 'error', message: `[Step 3/4] ❌ 未检测到 MAVROS 话题` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` 请确认 P600 上 MAVROS 与 rosbridge 是否正常运行:` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` roslaunch mavros px4.launch fcu_url:=/dev/ttyTHS1:921600 (或对应设备)` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` roslaunch rosbridge_server rosbridge_websocket.launch` });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 5000);
|
|
|
|
|
@ -190,9 +273,12 @@ const WsModule = (() => {
|
|
|
|
|
droneConnected = true;
|
|
|
|
|
if (modeValid) {
|
|
|
|
|
fcuReady = true;
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `无人机已连接 (模式: ${msg.mode}, ${msg.armed ? '已解锁' : '未解锁'})` });
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `[Step 4/4] ✅ 无人机已连接 (命名空间: ${uavNS || '无'})` });
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: ` 模式: ${msg.mode}, ${msg.armed ? '已解锁' : '未解锁'}` });
|
|
|
|
|
} else {
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: `MAVROS 已连接,但飞控未就绪 (mode=空)。等待 PX4 初始化...` });
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `[Step 3/4] ✅ MAVROS 已连接,但飞控未就绪 (mode=空)` });
|
|
|
|
|
UIModule.onStatus({ type: 'warning', message: ` ⏳ 等待 PX4 飞控初始化 (通常需要 10-30 秒)...` });
|
|
|
|
|
UIModule.onStatus({ type: 'info', message: ` 也可以在 P600 上手动检查: rostopic echo /mavros/state` });
|
|
|
|
|
}
|
|
|
|
|
setDefaultOrigin();
|
|
|
|
|
}
|
|
|
|
|
@ -200,7 +286,7 @@ const WsModule = (() => {
|
|
|
|
|
// Detect when FCU becomes ready
|
|
|
|
|
if (!fcuReady && modeValid) {
|
|
|
|
|
fcuReady = true;
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `飞控已就绪! 当前模式: ${msg.mode}, 状态: ${msg.armed ? '已解锁' : '未解锁'}` });
|
|
|
|
|
UIModule.onStatus({ type: 'success', message: `✅ 飞控已就绪! 当前模式: ${msg.mode}, 状态: ${msg.armed ? '已解锁' : '未解锁'}` });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|