[real] 整理 ROS 2 v2 里程计导航联调
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0\.."
|
||||
|
||||
python .\tools\pcd_nav_pygame.py --pcd .\map\A_min.pcd --route-name A_min_route --route-map A_min --route-obstacle slalom --save-route .\map\routes\A_min\A_min_route.json --default-speed 0.35 --default-policy rough --default-tolerance 0.15
|
||||
|
||||
endlocal
|
||||
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0\.."
|
||||
|
||||
python .\tools\pcd_nav_pygame.py --pcd .\map\map_b.pcd --overlay-json .\tools\semantic_overlay_example.json
|
||||
|
||||
endlocal
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"name": "wall_01",
|
||||
"label": "wall",
|
||||
"type": "rect",
|
||||
"x": 1.8,
|
||||
"y": -7.0,
|
||||
"length": 1.0,
|
||||
"width": 0.05,
|
||||
"yawDeg": 90.0
|
||||
},
|
||||
{
|
||||
"name": "low_bar_01",
|
||||
"label": "low_bar",
|
||||
"type": "rect",
|
||||
"x": 5.7,
|
||||
"y": -9.0,
|
||||
"length": 1.0,
|
||||
"width": 0.03,
|
||||
"yawDeg": 0.0
|
||||
},
|
||||
{
|
||||
"name": "slalom_pole_01",
|
||||
"label": "slalom_pole",
|
||||
"type": "circle",
|
||||
"x": 1.8,
|
||||
"y": -10.5,
|
||||
"radius": 0.05
|
||||
},
|
||||
{
|
||||
"name": "stairs_area",
|
||||
"label": "stairs",
|
||||
"type": "rect",
|
||||
"x": 2.4,
|
||||
"y": -3.5,
|
||||
"length": 2.4,
|
||||
"width": 1.2,
|
||||
"yawDeg": 0.0
|
||||
},
|
||||
{
|
||||
"name": "ramp_area",
|
||||
"label": "ramp",
|
||||
"type": "rect",
|
||||
"x": 5.2,
|
||||
"y": -5.2,
|
||||
"length": 2.0,
|
||||
"width": 3.0,
|
||||
"yawDeg": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -25,6 +25,9 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
self._json(200, data)
|
||||
return
|
||||
if self.path == "/api/map":
|
||||
global LATEST_MAP
|
||||
with STATE_LOCK:
|
||||
LATEST_MAP = None
|
||||
send_udp({"type": "map_request", "stamp": time.time()})
|
||||
deadline = time.time() + 1.0
|
||||
while time.time() < deadline:
|
||||
|
||||
@@ -25,6 +25,7 @@ let goalPointsByName = {};
|
||||
let defaultMissionName = '';
|
||||
let defaultMissionGoals = [];
|
||||
let routeAlignmentInfo = {};
|
||||
let odomFallbackInfo = { active: false };
|
||||
let activeNavPath = { goal_name: '', stage: 'idle', path_index: 0, points: [] };
|
||||
|
||||
const mapCanvas = $('map-canvas');
|
||||
@@ -89,18 +90,37 @@ function highlightMode(mode) {
|
||||
|
||||
function updateTaskButtonState() {
|
||||
const btn = $('btn-run-task');
|
||||
if (!btn) return;
|
||||
const odomBtn = $('btn-odom-task');
|
||||
const odomStopBtn = $('btn-odom-stop');
|
||||
const hasMission = Boolean(defaultMissionName);
|
||||
const enabled = latestConnected && currentMode === 'NAV' && hasMission;
|
||||
btn.disabled = !enabled;
|
||||
btn.textContent = hasMission ? `Run ${defaultMissionName}` : 'Run Mission';
|
||||
if (!hasMission) {
|
||||
btn.title = 'No default mission loaded from backend';
|
||||
return;
|
||||
const runEnabled = latestConnected && currentMode === 'NAV' && hasMission;
|
||||
const odomEnabled = latestConnected && hasMission;
|
||||
|
||||
if (btn) {
|
||||
btn.disabled = !runEnabled;
|
||||
btn.textContent = hasMission ? `Run ${defaultMissionName}` : 'Run Mission';
|
||||
if (!hasMission) {
|
||||
btn.title = 'No default mission loaded from backend';
|
||||
} else {
|
||||
btn.title = runEnabled
|
||||
? `Run default mission ${defaultMissionName}`
|
||||
: 'Available only in NAV mode while connected';
|
||||
}
|
||||
}
|
||||
|
||||
if (odomBtn) {
|
||||
odomBtn.disabled = !odomEnabled;
|
||||
odomBtn.title = hasMission
|
||||
? 'Initialize map->odom from waypoint id1, switch to NAV, then run the current route'
|
||||
: 'No default mission loaded from backend';
|
||||
}
|
||||
|
||||
if (odomStopBtn) {
|
||||
odomStopBtn.disabled = !(latestConnected && odomFallbackInfo.active);
|
||||
odomStopBtn.title = odomFallbackInfo.active
|
||||
? 'Stop pure odom fallback and stop simple_nav'
|
||||
: 'Odom fallback is not active';
|
||||
}
|
||||
btn.title = enabled
|
||||
? `Run default mission ${defaultMissionName}`
|
||||
: 'Available only in NAV mode while connected';
|
||||
}
|
||||
|
||||
function setText(id, text, cls) {
|
||||
@@ -355,6 +375,17 @@ function formatRouteAlignment(info) {
|
||||
return `${applied} hits=${hits} ${reason}`;
|
||||
}
|
||||
|
||||
function formatOdomFallback(info) {
|
||||
if (!info || !info.active) return 'inactive';
|
||||
const anchor = info.anchor || 'id1';
|
||||
const pose = info.map_pose || {};
|
||||
const x = Number.isFinite(pose.x) ? Number(pose.x).toFixed(2) : '--';
|
||||
const y = Number.isFinite(pose.y) ? Number(pose.y).toFixed(2) : '--';
|
||||
const yaw = Number.isFinite(pose.yaw_deg) ? Number(pose.yaw_deg).toFixed(1) : '--';
|
||||
const handoff = info.handoff_pending ? ' handoff pending' : '';
|
||||
return `${anchor} x=${x} y=${y} yaw=${yaw}deg${handoff}`;
|
||||
}
|
||||
|
||||
function renderTaskGoals() {
|
||||
const list = $('task-goals-list');
|
||||
if (!list) return;
|
||||
@@ -392,6 +423,7 @@ function applyMapMetadata(data) {
|
||||
defaultMissionName = data.default_mission_name || '';
|
||||
defaultMissionGoals = Array.isArray(data.default_mission_goals) ? data.default_mission_goals : [];
|
||||
routeAlignmentInfo = data.route_alignment || {};
|
||||
odomFallbackInfo = data.odom_fallback || odomFallbackInfo;
|
||||
activeNavPath = data.nav_path || activeNavPath;
|
||||
currentGoalIndex = currentGoalName
|
||||
? defaultMissionGoals.findIndex(goal => goal.name === currentGoalName)
|
||||
@@ -401,6 +433,11 @@ function applyMapMetadata(data) {
|
||||
setText('d-task-mission', defaultMissionName || '--', defaultMissionName ? 'ok' : '');
|
||||
setText('d-task-goals', String(defaultMissionGoals.length || 0), defaultMissionGoals.length ? 'ok' : '');
|
||||
setText('d-route-align', formatRouteAlignment(routeAlignmentInfo), routeAlignmentInfo.enabled ? 'ok' : 'warn');
|
||||
setText(
|
||||
'd-odom-fallback',
|
||||
formatOdomFallback(odomFallbackInfo),
|
||||
odomFallbackInfo.handoff_pending ? 'warn' : odomFallbackInfo.active ? 'active' : '',
|
||||
);
|
||||
updateTaskButtonState();
|
||||
renderTaskGoals();
|
||||
}
|
||||
@@ -527,6 +564,14 @@ function applyState(data) {
|
||||
syncGoalMarkerFromNavStatus(data.nav_status || '');
|
||||
|
||||
const nav = data.nav || {};
|
||||
if (nav.odom_fallback) {
|
||||
odomFallbackInfo = nav.odom_fallback;
|
||||
setText(
|
||||
'd-odom-fallback',
|
||||
formatOdomFallback(odomFallbackInfo),
|
||||
odomFallbackInfo.handoff_pending ? 'warn' : odomFallbackInfo.active ? 'active' : '',
|
||||
);
|
||||
}
|
||||
if (nav.pose) {
|
||||
latestPose = nav.pose;
|
||||
setText('d-nav-pose', `x=${nav.pose.x.toFixed(2)} y=${nav.pose.y.toFixed(2)} yaw=${(nav.pose.yaw * 57.2958).toFixed(1)}deg`);
|
||||
@@ -605,6 +650,47 @@ $('btn-estop').onclick = async () => {
|
||||
}
|
||||
};
|
||||
$('btn-refresh-map').onclick = fetchMap;
|
||||
$('btn-odom-task').onclick = async () => {
|
||||
if (!latestConnected) {
|
||||
appendEvent('ODOM', 'bridge not connected', 'warn');
|
||||
updateTaskButtonState();
|
||||
return;
|
||||
}
|
||||
if (!defaultMissionName || !defaultMissionGoals.length) {
|
||||
appendEvent('ODOM', 'no route mission loaded', 'warn');
|
||||
return;
|
||||
}
|
||||
const start = defaultMissionGoals[0];
|
||||
const ok = confirm(
|
||||
`Run odom fallback mission ${defaultMissionName}?\n\n` +
|
||||
`Place the robot at waypoint id1 before confirming:\n` +
|
||||
`x=${Number(start.x).toFixed(3)} y=${Number(start.y).toFixed(3)} yaw=${Number(start.yaw_deg || 0).toFixed(1)}deg`
|
||||
);
|
||||
if (!ok) return;
|
||||
setLastGoalByName(start.name);
|
||||
drawMap();
|
||||
const result = await post({ type: 'odom_task' });
|
||||
if (result.ok) {
|
||||
highlightMode('NAV');
|
||||
appendEvent('ODOM', `init at ${start.name}, run ${defaultMissionName}`, 'ok');
|
||||
setTimeout(fetchMap, 400);
|
||||
}
|
||||
};
|
||||
$('btn-odom-stop').onclick = async () => {
|
||||
if (!latestConnected) {
|
||||
appendEvent('ODOM', 'bridge not connected', 'warn');
|
||||
updateTaskButtonState();
|
||||
return;
|
||||
}
|
||||
const result = await post({ type: 'odom_stop' });
|
||||
if (result.ok) {
|
||||
odomFallbackInfo = { active: false };
|
||||
setText('d-odom-fallback', formatOdomFallback(odomFallbackInfo), '');
|
||||
updateTaskButtonState();
|
||||
appendEvent('ODOM', 'fallback stopped', 'ok');
|
||||
setTimeout(fetchMap, 400);
|
||||
}
|
||||
};
|
||||
$('btn-run-task').onclick = async () => {
|
||||
if (!latestConnected) {
|
||||
appendEvent('TASK', 'bridge not connected', 'warn');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>sim2real ROS2 Console</title>
|
||||
<link rel="stylesheet" href="style.css?v=mission-sync-2">
|
||||
<link rel="stylesheet" href="style.css?v=odom-fallback-1">
|
||||
</head>
|
||||
<body>
|
||||
<header class="glass-panel top-bar">
|
||||
@@ -56,6 +56,7 @@
|
||||
<div class="diag-row"><span class="diag-label">active goal</span><span class="diag-value" id="d-active-goal">--</span></div>
|
||||
<div class="diag-row"><span class="diag-label">goal count</span><span class="diag-value" id="d-task-goals">--</span></div>
|
||||
<div class="diag-row"><span class="diag-label">route align</span><span class="diag-value" id="d-route-align">--</span></div>
|
||||
<div class="diag-row"><span class="diag-label">odom fallback</span><span class="diag-value" id="d-odom-fallback">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="panel-section flex-1">
|
||||
@@ -70,6 +71,8 @@
|
||||
<h2 class="panel-title">PCD 2D Nav</h2>
|
||||
<div class="map-actions">
|
||||
<button class="btn" id="btn-refresh-map">Refresh Map</button>
|
||||
<button class="btn btn-odom" id="btn-odom-task" disabled>odom</button>
|
||||
<button class="btn" id="btn-odom-stop" disabled>Exit odom</button>
|
||||
<button class="btn btn-nav" id="btn-run-task" disabled>Run Mission</button>
|
||||
<button class="btn" id="btn-record">Record Pose</button>
|
||||
<button class="btn" id="btn-stop-nav">Stop Nav</button>
|
||||
@@ -121,6 +124,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="app.js?v=mission-sync-2"></script>
|
||||
<script src="app.js?v=odom-fallback-1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -105,6 +105,7 @@ body {
|
||||
.btn-remote { background: rgba(48,209,88,0.2); border-color: rgba(48,209,88,0.4); color: #8deda7; }
|
||||
.btn-web { background: rgba(10,132,255,0.2); border-color: rgba(10,132,255,0.4); color: #82c4ff; }
|
||||
.btn-nav { background: rgba(255,214,10,0.2); border-color: rgba(255,214,10,0.4); color: #ffe680; }
|
||||
.btn-odom { background: rgba(255,159,10,0.22); border-color: rgba(255,159,10,0.45); color: #ffd199; }
|
||||
.btn.active-mode { box-shadow: 0 0 0 2px white; }
|
||||
|
||||
.route-select {
|
||||
|
||||
Reference in New Issue
Block a user