Update mission flow and inspection log scrolling

This commit is contained in:
2026-06-22 16:13:38 +08:00
parent 7dadcb8bcc
commit d45a72c6a6
9 changed files with 732 additions and 317 deletions
+31
View File
@@ -0,0 +1,31 @@
# Repository Guidelines
## 项目结构与模块组织
本仓库是 AGV 智能巡检系统,后端、前端、ROS2 启动链路分目录维护。`agv_app/` 是 Flask 后端,包含 API、模板、静态资源与 `data/*.json` 持久化配置;`agv_app/utils/` 放置 AGV、Nav2、机械臂、二维码与任务执行逻辑。`arm_server/` 是机械臂端 TCP/摄像头服务及 systemd 配置。`scan_fixer/` 提供生产链路中的 ROS2 时间戳修正工具。`public-frontend/` 是 Next.js 平板端,源码在 `src/app``src/components``src/services``src/store``src/types``scripts/``Makefile` 负责本地开发、生产启动和停止流程,`docs/` 存放技术文档。
## 构建、测试与开发命令
- `make install`:执行 `uv sync` 并安装前端 npm 依赖。
- `make dev`:显示本地开发启动说明。
- `make dev-backend`:以 Mock 硬件模式启动 Flask 后端。
- `make dev-frontend`:启动 Next.js 开发服务器。
- `make prod`:在 AGV 上启动 ROS2、Nav2、scan fixer 与 Flask 完整生产链路。
- `cd public-frontend && npm run build`:构建前端。
- `cd public-frontend && npm run lint && npm run typecheck`:执行前端 lint 与 TypeScript 检查。
## 编码风格与命名约定
Python 目标版本为 3.10,依赖由根目录 `pyproject.toml``uv.lock` 管理。保持模块职责清晰,配置集中放在 `agv_app/config.py`,复用硬件抽象时优先放入 `agv_app/utils/`。前端使用 TypeScript、React、Next.js App Router 与 Ant Design;组件使用 PascalCase,例如 `CameraFrame.tsx`,服务和工具使用 camelCase,例如 `apiClient.ts`。新增代码应自解释命名,避免魔术值,必要注释只说明业务背景或非常规取舍。
## 测试指南
当前仓库未配置独立单元测试框架。提交前至少运行相关静态检查:前端改动运行 `npm run lint``npm run typecheck`,后端改动用 `make dev-backend` 做 Flask 启动与关键接口冒烟验证。涉及硬件、ROS2 或生产脚本的改动,应说明是否已在真实 AGV 或 Mock 模式验证。
## 提交与 Pull Request 规范
历史提交主要使用英文祈使句式,例如 `Improve camera status and production startup``Refactor ROS startup scripts``Fix shell compatibility issues in prod-backend.sh`。继续使用简短英文提交信息,避免无意义占位。PR 应包含变更摘要、验证命令、硬件/环境影响、相关 issue;前端界面变化需附截图或录屏,生产启动链路变化需列出影响的脚本和服务。
## 安全与配置提示
不要提交新的密钥、令牌、设备私有地址或本地日志。环境示例放在 `.env.example`,运行时覆盖优先使用环境变量,例如 `BACKEND_URL``MOCK_HARDWARE``AGV_PROJECT_DIR`。涉及 AGV、机械臂或 ROS2 生产配置时,先核对 `scripts/README.md` 中的默认路径与日志位置。
+81 -4
View File
@@ -46,6 +46,15 @@ logging.basicConfig(
) )
logger = logging.getLogger("agv_app") logger = logging.getLogger("agv_app")
ORIGIN_X = 0.0
ORIGIN_Y = 0.0
ORIGIN_YAW = 0.0
ORIGIN_MATCH_TOLERANCE = 1e-6
ARM_RETURN_SPEED = 500
ARM_INITIAL_POSE_TIMEOUT = 15.0
ARM_INITIAL_POSE_TOLERANCE = 2.0
ARM_INITIAL_POSE_UNSET_TOLERANCE = 0.01
app = Flask(__name__, template_folder="templates", static_folder="static", static_url_path="/static") app = Flask(__name__, template_folder="templates", static_folder="static", static_url_path="/static")
app.config["SECRET_KEY"] = SERVER_CONFIG["secret_key"] app.config["SECRET_KEY"] = SERVER_CONFIG["secret_key"]
CORS(app) CORS(app)
@@ -88,6 +97,60 @@ gs = GlobalState()
# ========== 辅助函数 ========== # ========== 辅助函数 ==========
def _is_origin_goal(x: float, y: float, yaw: float = None) -> bool:
yaw_is_origin = yaw is None or abs(yaw - ORIGIN_YAW) <= ORIGIN_MATCH_TOLERANCE
return (
abs(x - ORIGIN_X) <= ORIGIN_MATCH_TOLERANCE
and abs(y - ORIGIN_Y) <= ORIGIN_MATCH_TOLERANCE
and yaw_is_origin
)
def _normalize_arm_pose(raw_pose):
if not isinstance(raw_pose, list) or len(raw_pose) != 6:
return None
try:
return [float(angle) for angle in raw_pose]
except (TypeError, ValueError):
return None
def _wait_arm_initial_pose(target_angles, timeout=ARM_INITIAL_POSE_TIMEOUT, tolerance=ARM_INITIAL_POSE_TOLERANCE):
deadline = time.time() + timeout
while time.time() < deadline:
try:
ok, current_angles = gs.arm_client.get_angles()
if ok and current_angles and len(current_angles) >= 6:
current_angles = [float(angle) for angle in current_angles[:6]]
if all(abs(current_angles[index] - target_angles[index]) <= tolerance for index in range(6)):
return True
except Exception as e:
logger.warning(f"等待机械臂初始姿态失败: {e}")
time.sleep(0.5)
return False
def _restore_arm_initial_pose_for_origin():
arm_initial_pose = _normalize_arm_pose(gs.mission_config.get("arm_initial_pose"))
if not arm_initial_pose:
return {"ok": True, "skipped": True, "message": "未配置有效机械臂初始姿态,跳过复原"}
if not any(abs(angle) > ARM_INITIAL_POSE_UNSET_TOLERANCE for angle in arm_initial_pose):
return {"ok": True, "skipped": True, "message": "未配置机械臂初始姿态,跳过复原"}
if not gs.arm_client:
return {"ok": False, "error": "机械臂未连接,无法先复原再回原点"}
try:
ok = gs.arm_client.set_angles(arm_initial_pose, ARM_RETURN_SPEED)
if not ok:
return {"ok": False, "error": "机械臂初始姿态指令发送失败,已取消回原点"}
if not _wait_arm_initial_pose(arm_initial_pose):
return {"ok": False, "error": "机械臂未在规定时间内复原,已取消回原点"}
return {"ok": True, "skipped": False, "message": "机械臂已复原到初始姿态"}
except Exception as e:
logger.error(f"回原点前复原机械臂失败: {e}")
return {"ok": False, "error": f"回原点前复原机械臂失败: {e}"}
def get_data_path(name): def get_data_path(name):
return os.path.join(DATA_DIR, name) return os.path.join(DATA_DIR, name)
@@ -464,24 +527,38 @@ def api_navigate_to():
if not gs.map_config or "map_yaml" not in gs.map_config: if not gs.map_config or "map_yaml" not in gs.map_config:
return jsonify({"ok": False, "error": "地图未加载,请先在设置中加载地图"}), 400 return jsonify({"ok": False, "error": "地图未加载,请先在设置中加载地图"}), 400
data = request.json data = request.json or {}
goal_x = data.get("x") goal_x = data.get("x")
goal_y = data.get("y") goal_y = data.get("y")
goal_yaw = data.get("yaw") # 姿态参数,可选 goal_yaw = data.get("yaw") # 姿态参数,可选
if goal_x is None or goal_y is None: if goal_x is None or goal_y is None:
return jsonify({"ok": False, "error": "缺少目标坐标 x, y"}), 400 return jsonify({"ok": False, "error": "缺少目标坐标 x, y"}), 400
try:
goal_x = float(goal_x)
goal_y = float(goal_y)
yaw_arg = float(goal_yaw) if goal_yaw is not None else None
except (TypeError, ValueError):
return jsonify({"ok": False, "error": "目标坐标格式错误"}), 400
if not gs.agv_controller or not gs.agv_controller.is_connected(): if not gs.agv_controller or not gs.agv_controller.is_connected():
return jsonify({"ok": False, "error": "AGV 未连接,请先连接 AGV"}), 400 return jsonify({"ok": False, "error": "AGV 未连接,请先连接 AGV"}), 400
try: try:
restore_message = ""
restore_arm = bool(data.get("restore_arm", True))
if restore_arm and _is_origin_goal(goal_x, goal_y, yaw_arg):
restore_result = _restore_arm_initial_pose_for_origin()
if not restore_result["ok"]:
return jsonify({"ok": False, "error": restore_result["error"]}), 400
restore_message = restore_result.get("message", "")
if gs.navigator is None: if gs.navigator is None:
gs.navigator = Nav2Navigator() gs.navigator = Nav2Navigator()
# navigate_to_pose(x, y, yaw=None, timeout_sec=120, blocking=False) # navigate_to_pose(x, y, yaw=None, timeout_sec=120, blocking=False)
yaw_arg = float(goal_yaw) if goal_yaw is not None else None ok = gs.navigator.navigate_to_pose(goal_x, goal_y, yaw_arg, blocking=False)
ok = gs.navigator.navigate_to_pose(float(goal_x), float(goal_y), yaw_arg, blocking=False)
if ok: if ok:
return jsonify({"ok": True, "message": "导航已启动"}) message = f"{restore_message},导航已启动" if restore_message else "导航已启动"
return jsonify({"ok": True, "message": message})
else: else:
return jsonify({"ok": False, "error": "导航启动失败,可能是Nav2未运行或AGV未连接"}), 400 return jsonify({"ok": False, "error": "导航启动失败,可能是Nav2未运行或AGV未连接"}), 400
except Exception as e: except Exception as e:
+212 -146
View File
@@ -1,25 +1,4 @@
[ [
{
"id": "m_2_0",
"row": 2,
"col": 0,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
}
},
{ {
"id": "m_2_2", "id": "m_2_2",
"row": 2, "row": 2,
@@ -167,27 +146,6 @@
"poses": [] "poses": []
} }
}, },
{
"id": "m_0_4",
"row": 0,
"col": 4,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
}
},
{ {
"id": "m_0_5", "id": "m_0_5",
"row": 0, "row": 0,
@@ -251,27 +209,6 @@
"poses": [] "poses": []
} }
}, },
{
"id": "m_2_1",
"row": 2,
"col": 1,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
}
},
{ {
"id": "m_2_4", "id": "m_2_4",
"row": 2, "row": 2,
@@ -293,27 +230,6 @@
"poses": [] "poses": []
} }
}, },
{
"id": "m_2_5",
"row": 2,
"col": 5,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
}
},
{ {
"id": "m_0_0", "id": "m_0_0",
"row": 0, "row": 0,
@@ -356,36 +272,6 @@
"poses": [] "poses": []
} }
}, },
{
"id": "m_0_1",
"row": 0,
"col": 1,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{ {
"id": "m_0_2", "id": "m_0_2",
"row": 0, "row": 0,
@@ -416,36 +302,6 @@
"model_id": "" "model_id": ""
} }
}, },
{
"id": "m_0_3",
"row": 0,
"col": 3,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{ {
"id": "m_1_1", "id": "m_1_1",
"row": 1, "row": 1,
@@ -506,6 +362,126 @@
"model_id": "" "model_id": ""
} }
}, },
{
"id": "m_1_4",
"row": 1,
"col": 4,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_0_6",
"row": 0,
"col": 6,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_1_6",
"row": 1,
"col": 6,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_0_3",
"row": 0,
"col": 3,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{ {
"id": "m_1_3", "id": "m_1_3",
"row": 1, "row": 1,
@@ -537,8 +513,98 @@
} }
}, },
{ {
"id": "m_1_4", "id": "m_2_0",
"row": 1, "row": 2,
"col": 0,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_2_1",
"row": 2,
"col": 1,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_0_1",
"row": 0,
"col": 1,
"front": {
"coords": [
0,
0,
0
],
"poses": []
},
"back": {
"coords": [
0,
0,
0
],
"poses": []
},
"qr": {
"coords": [
0,
0,
0
],
"qr_value": "",
"model_id": ""
}
},
{
"id": "m_0_4",
"row": 0,
"col": 4, "col": 4,
"front": { "front": {
"coords": [ "coords": [
+360 -140
View File
@@ -1,6 +1,6 @@
{ {
"rows": 2, "rows": 3,
"cols": 5, "cols": 7,
"grid": [], "grid": [],
"positions": [ "positions": [
{ {
@@ -8,9 +8,9 @@
"col": 0, "col": 0,
"side": "front", "side": "front",
"coords": [ "coords": [
0.5391402360519819, 0.726789462066099,
-1.3221212932804989, 0.38329959318460166,
-0.04968159116162075 0.023383889548769105
], ],
"poses": [] "poses": []
}, },
@@ -19,9 +19,9 @@
"col": 1, "col": 1,
"side": "front", "side": "front",
"coords": [ "coords": [
1.1801154538454173, 1.5997282224959652,
-1.3641834306281595, 0.3969445083875386,
-0.0384636372066124 0.021611522117307245
], ],
"poses": [] "poses": []
}, },
@@ -30,9 +30,9 @@
"col": 1, "col": 1,
"side": "back", "side": "back",
"coords": [ "coords": [
1.3273254588744863, 1.5365709266996668,
-3.5287940020200854, -1.5897806027940793,
-3.11993523836094 -3.138003565212702
], ],
"poses": [] "poses": []
}, },
@@ -41,108 +41,9 @@
"col": 0, "col": 0,
"side": "back", "side": "back",
"coords": [ "coords": [
0.6499623251724095, 0.7250933954217442,
-3.634895898964233, -1.585626974855502,
-3.06371982741706 3.1407193246894285
],
"poses": []
},
{
"row": 0,
"col": 2,
"side": "front",
"coords": [
1.9780660285152205,
-1.4118225222055494,
-0.03933461738640764
],
"poses": []
},
{
"row": 0,
"col": 3,
"side": "front",
"coords": [
2.783104887196572,
-1.4531680360293173,
-0.005407493209801511
],
"poses": []
},
{
"row": 0,
"col": 4,
"side": "front",
"coords": [
3.4135017183966694,
-1.463517938299615,
-0.0022379727318056074
],
"poses": []
},
{
"row": 1,
"col": 4,
"side": "back",
"coords": [
3.595502564320599,
-3.5861571623928663,
3.105599537556842
],
"poses": []
},
{
"row": 1,
"col": 4,
"side": "front",
"coords": [
3.595502564320599,
-3.5861571623928663,
3.105599537556842
],
"poses": []
},
{
"row": 1,
"col": 3,
"side": "back",
"coords": [
2.8436692518324937,
-3.5087893361886504,
-3.0640151322957476
],
"poses": []
},
{
"row": 1,
"col": 3,
"side": "front",
"coords": [
2.8436692518324937,
-3.5087893361886504,
-3.0640151322957476
],
"poses": []
},
{
"row": 1,
"col": 2,
"side": "back",
"coords": [
2.0238357078548397,
-3.519588818855445,
-3.0949553553741684
],
"poses": []
},
{
"row": 1,
"col": 2,
"side": "front",
"coords": [
2.0238357078548397,
-3.519588818855445,
-3.0949553553741684
], ],
"poses": [] "poses": []
}, },
@@ -151,9 +52,9 @@
"col": 1, "col": 1,
"side": "front", "side": "front",
"coords": [ "coords": [
1.3273254588744863, 1.5365709266996668,
-3.5287940020200854, -1.5897806027940793,
-3.11993523836094 -3.138003565212702
], ],
"poses": [] "poses": []
}, },
@@ -162,9 +63,9 @@
"col": 0, "col": 0,
"side": "front", "side": "front",
"coords": [ "coords": [
0.6499623251724095, 0.7250933954217442,
-3.634895898964233, -1.585626974855502,
-3.06371982741706 3.1407193246894285
], ],
"poses": [] "poses": []
}, },
@@ -173,9 +74,9 @@
"col": 0, "col": 0,
"side": "back", "side": "back",
"coords": [ "coords": [
0.39025594509020667, 0.695891857743624,
-5.593393651151741, -3.6093540626907155,
-0.09001000079607593 -0.01891669546737457
], ],
"poses": [] "poses": []
}, },
@@ -184,9 +85,196 @@
"col": 1, "col": 1,
"side": "back", "side": "back",
"coords": [ "coords": [
0.9989880876134289, 1.7054640840200619,
-5.633047903271201, -3.6172190671922944,
-0.08518177305398167 0.04050928996758301
],
"poses": []
},
{
"row": 0,
"col": 2,
"side": "front",
"coords": [
2.569630979384912,
0.39887714413011477,
0.013956327040945097
],
"poses": []
},
{
"row": 0,
"col": 3,
"side": "front",
"coords": [
3.5131599402572933,
0.3616416504697895,
0.0177534721641621
],
"poses": []
},
{
"row": 0,
"col": 4,
"side": "front",
"coords": [
4.385626456624971,
0.37358124345384475,
0.014464186351950986
],
"poses": []
},
{
"row": 0,
"col": 5,
"side": "front",
"coords": [
5.286651848873122,
0.36270375923170595,
-0.02478120105661721
],
"poses": []
},
{
"row": 0,
"col": 6,
"side": "front",
"coords": [
6.301663107708812,
0.35009837193686855,
0.05028809910702322
],
"poses": []
},
{
"row": 1,
"col": 6,
"side": "back",
"coords": [
6.302211902696101,
-1.6741865723108142,
-3.141294889035836
],
"poses": []
},
{
"row": 1,
"col": 6,
"side": "front",
"coords": [
6.302211902696101,
-1.6741865723108142,
-3.141294889035836
],
"poses": []
},
{
"row": 1,
"col": 5,
"side": "back",
"coords": [
5.385423949389957,
-1.6569851054137088,
3.114584306608913
],
"poses": []
},
{
"row": 1,
"col": 5,
"side": "front",
"coords": [
5.385423949389957,
-1.6569851054137088,
3.114584306608913
],
"poses": []
},
{
"row": 1,
"col": 4,
"side": "back",
"coords": [
4.520056315813734,
-1.6370730446353792,
3.12134578550089
],
"poses": []
},
{
"row": 1,
"col": 4,
"side": "front",
"coords": [
4.520056315813734,
-1.6370730446353792,
3.12134578550089
],
"poses": []
},
{
"row": 1,
"col": 3,
"side": "back",
"coords": [
3.600184234659078,
-1.6299160740114962,
-3.127182700330921
],
"poses": []
},
{
"row": 1,
"col": 3,
"side": "front",
"coords": [
3.600184234659078,
-1.6299160740114962,
-3.127182700330921
],
"poses": []
},
{
"row": 1,
"col": 2,
"side": "back",
"coords": [
2.5627723519921295,
-1.606403204776104,
3.1392697865149666
],
"poses": []
},
{
"row": 1,
"col": 2,
"side": "front",
"coords": [
2.5627723519921295,
-1.606403204776104,
3.1392697865149666
],
"poses": []
},
{
"row": 2,
"col": 0,
"side": "front",
"coords": [
0.695891857743624,
-3.6093540626907155,
-0.01891669546737457
],
"poses": []
},
{
"row": 2,
"col": 1,
"side": "front",
"coords": [
1.7054640840200619,
-3.6172190671922944,
0.04050928996758301
], ],
"poses": [] "poses": []
}, },
@@ -195,9 +283,20 @@
"col": 2, "col": 2,
"side": "back", "side": "back",
"coords": [ "coords": [
1.7493440267548326, 2.49138966620064,
-5.7036971258959746, -3.6258193640543435,
-0.10505541857885684 0.011734020269766346
],
"poses": []
},
{
"row": 2,
"col": 2,
"side": "front",
"coords": [
2.49138966620064,
-3.6258193640543435,
0.011734020269766346
], ],
"poses": [] "poses": []
}, },
@@ -206,9 +305,20 @@
"col": 3, "col": 3,
"side": "back", "side": "back",
"coords": [ "coords": [
2.407336669431407, 3.384299605055195,
-5.76958512207572, -3.633059580331103,
-0.10251877401062247 0.00825489611393585
],
"poses": []
},
{
"row": 2,
"col": 3,
"side": "front",
"coords": [
3.384299605055195,
-3.633059580331103,
0.00825489611393585
], ],
"poses": [] "poses": []
}, },
@@ -217,19 +327,129 @@
"col": 4, "col": 4,
"side": "back", "side": "back",
"coords": [ "coords": [
3.132062476985721, 4.499614777619532,
-5.850166571217611, -3.6568143703418405,
-0.09127007182804729 0.006128126167414207
],
"poses": []
},
{
"row": 2,
"col": 4,
"side": "front",
"coords": [
4.499614777619532,
-3.6568143703418405,
0.006128126167414207
],
"poses": []
},
{
"row": 2,
"col": 5,
"side": "back",
"coords": [
5.358194832629194,
-3.6555884207351923,
0.00220732555627522
],
"poses": []
},
{
"row": 2,
"col": 5,
"side": "front",
"coords": [
5.358194832629194,
-3.6555884207351923,
0.00220732555627522
],
"poses": []
},
{
"row": 2,
"col": 6,
"side": "back",
"coords": [
6.186931240051006,
-3.660069561737178,
-0.004740651362042846
],
"poses": []
},
{
"row": 2,
"col": 6,
"side": "front",
"coords": [
6.186931240051006,
-3.660069561737178,
-0.004740651362042846
],
"poses": []
},
{
"row": 3,
"col": 4,
"side": "back",
"coords": [
4.437106056578727,
-5.675385129487837,
3.113166940596223
],
"poses": []
},
{
"row": 3,
"col": 3,
"side": "back",
"coords": [
3.4577008440661285,
-5.703184532839961,
-3.109685273113602
],
"poses": []
},
{
"row": 3,
"col": 2,
"side": "back",
"coords": [
2.3341924779097645,
-5.623728684341702,
-3.095440697434253
],
"poses": []
},
{
"row": 3,
"col": 1,
"side": "back",
"coords": [
1.6126711501886855,
-5.64506932868408,
-3.0994397969930265
],
"poses": []
},
{
"row": 3,
"col": 0,
"side": "back",
"coords": [
0.6321940488248773,
-5.633649464598426,
3.129000825841382
], ],
"poses": [] "poses": []
} }
], ],
"arm_initial_pose": [ "arm_initial_pose": [
-90.333323, -89.999795,
-90.079952, -89.999946,
0.160037, -0.000131,
-90.571318, -90.00001,
0.093654, 4.1e-05,
22.232898 0.000116
] ]
} }
+12 -12
View File
@@ -9,12 +9,12 @@
"name": "front_1", "name": "front_1",
"photo_type": "front", "photo_type": "front",
"arm_angles": [ "arm_angles": [
-93.586541, -81.796775,
-184.343305, -85.406752,
50.583239, -5.803223,
-38.326674, -109.799747,
-85.153333, 91.66639,
20.399989 2.446712
], ],
"speed": 500, "speed": 500,
"description": "" "description": ""
@@ -24,12 +24,12 @@
"name": "back_1", "name": "back_1",
"photo_type": "back", "photo_type": "back",
"arm_angles": [ "arm_angles": [
15.860045, -81.796823,
-161.133416, -85.406717,
137.999016, -5.803284,
-161.996719, -109.799953,
168.000989, 91.666459,
15.653445 2.44676
], ],
"speed": 500, "speed": 500,
"description": "" "description": ""
+6 -6
View File
@@ -3,12 +3,12 @@
"id": "qr_1779278140334", "id": "qr_1779278140334",
"name": "二维码1", "name": "二维码1",
"joint_angles": [ "joint_angles": [
-89.796645, 89.999979,
-2.013175, -0.000118,
-87.176721, -120.09949,
-82.49663, -30,
-93.323403, -105,
20.399941 0
], ],
"qr_value": "BG042110276", "qr_value": "BG042110276",
"model_id": "" "model_id": ""
+3 -2
View File
@@ -381,8 +381,9 @@ createApp({
} catch (e) { alert('导航失败: ' + e.message) } } catch (e) { alert('导航失败: ' + e.message) }
}, },
async goToOrigin() { async goToOrigin() {
if (!confirm('确认导航到原点 (0, 0, 0)?')) return if (!confirm('确认先复原机械臂,再导航到原点 (0, 0, 0)?')) return
try { try {
this.mapMsg = '正在复原机械臂并发送原点导航...'
const res = await fetch(API + '/api/navigate/to', { const res = await fetch(API + '/api/navigate/to', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -390,7 +391,7 @@ createApp({
}) })
const data = await res.json() const data = await res.json()
if (data.ok) { if (data.ok) {
this.mapMsg = '✅ 已发送导航到原点' this.mapMsg = '✅ ' + (data.message || '已发送导航到原点')
} else { } else {
this.mapMsg = '❌ ' + (data.error || '导航失败') this.mapMsg = '❌ ' + (data.error || '导航失败')
} }
+9 -1
View File
@@ -414,6 +414,14 @@ class MissionExecutorV3:
model_name = self._lookup_model(qr_value) model_name = self._lookup_model(qr_value)
self._log(f" 🏷️ 机型: {model_name}") self._log(f" 🏷️ 机型: {model_name}")
if qr_value and opt_arm_init and has_arm_pose and opt_agv_move and not self._stop.is_set():
self._log(" 🦾 扫码完成,恢复机械臂初始姿态")
try:
self.arm_client.set_angles(arm_initial_pose, speed=self.arm_speed)
self._wait_arm_ready(arm_initial_pose)
except Exception as e:
self._log(f" ⚠️ 机械臂复位失败: {e}")
if opt_front_photo and not self._stop.is_set(): if opt_front_photo and not self._stop.is_set():
model = self._find_model(models, model_name) model = self._find_model(models, model_name)
if model: if model:
@@ -642,7 +650,7 @@ class MissionExecutorV3:
self._log("↪️ 调用设置页同款接口返回原点") self._log("↪️ 调用设置页同款接口返回原点")
resp = requests.post( resp = requests.post(
"http://127.0.0.1:5000/api/navigate/to", "http://127.0.0.1:5000/api/navigate/to",
json={"x": 0, "y": 0, "yaw": 0}, json={"x": 0, "y": 0, "yaw": 0, "restore_arm": False},
timeout=8, timeout=8,
) )
data = resp.json() if resp.content else {} data = resp.json() if resp.content else {}
+17 -5
View File
@@ -39,6 +39,7 @@ import type { ActivityItem, CameraInfo, CustomsDeclaration, InspectionIssue, Ins
const { Text } = Typography; const { Text } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
const LOG_AUTO_SCROLL_THRESHOLD = 48;
interface ProgressItem extends InspectionItem { interface ProgressItem extends InspectionItem {
currentInspected: number; currentInspected: number;
@@ -78,7 +79,8 @@ function InspectionContent() {
const [operationLoading, setOperationLoading] = useState(false); const [operationLoading, setOperationLoading] = useState(false);
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const { token } = theme.useToken(); const { token } = theme.useToken();
const logsEndRef = useRef<HTMLDivElement>(null); const logsContainerRef = useRef<HTMLDivElement>(null);
const [isUserNearBottom, setIsUserNearBottom] = useState(true);
const refreshRuntime = async () => { const refreshRuntime = async () => {
const [missionState, currentInspection, missionLogs, nextIssues] = await Promise.all([ const [missionState, currentInspection, missionLogs, nextIssues] = await Promise.all([
@@ -231,8 +233,19 @@ function InspectionContent() {
}, [setInspection]); }, [setInspection]);
useEffect(() => { useEffect(() => {
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }); const container = logsContainerRef.current;
}, [logs]); if (container && isUserNearBottom) {
container.scrollTop = container.scrollHeight;
}
}, [logs, isUserNearBottom]);
const handleLogsScroll = () => {
const container = logsContainerRef.current;
if (!container) return;
const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
setIsUserNearBottom(distanceToBottom <= LOG_AUTO_SCROLL_THRESHOLD);
};
const calculateTotalProgress = () => { const calculateTotalProgress = () => {
if (!progressData.length) return 0; if (!progressData.length) return 0;
@@ -524,7 +537,7 @@ function InspectionContent() {
style={{ flex: 3, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} style={{ flex: 3, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
styles={{ body: { padding: 12, flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, background: token.colorFillQuaternary } }} styles={{ body: { padding: 12, flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, background: token.colorFillQuaternary } }}
> >
<div style={{ flex: 1, overflowY: 'auto', paddingRight: 8 }}> <div ref={logsContainerRef} onScroll={handleLogsScroll} style={{ flex: 1, overflowY: 'auto', paddingRight: 8 }}>
{logs.length > 0 ? ( {logs.length > 0 ? (
<Timeline <Timeline
items={logs.map((item) => ({ items={logs.map((item) => ({
@@ -540,7 +553,6 @@ function InspectionContent() {
) : ( ) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无日志" style={{ margin: '20px 0' }} /> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无日志" style={{ margin: '20px 0' }} />
)} )}
<div ref={logsEndRef} />
</div> </div>
</Card> </Card>
</Col> </Col>