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
+81 -4
View File
@@ -46,6 +46,15 @@ logging.basicConfig(
)
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.config["SECRET_KEY"] = SERVER_CONFIG["secret_key"]
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):
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:
return jsonify({"ok": False, "error": "地图未加载,请先在设置中加载地图"}), 400
data = request.json
data = request.json or {}
goal_x = data.get("x")
goal_y = data.get("y")
goal_yaw = data.get("yaw") # 姿态参数,可选
if goal_x is None or goal_y is None:
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():
return jsonify({"ok": False, "error": "AGV 未连接,请先连接 AGV"}), 400
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:
gs.navigator = Nav2Navigator()
# 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(float(goal_x), float(goal_y), yaw_arg, blocking=False)
ok = gs.navigator.navigate_to_pose(goal_x, goal_y, yaw_arg, blocking=False)
if ok:
return jsonify({"ok": True, "message": "导航已启动"})
message = f"{restore_message},导航已启动" if restore_message else "导航已启动"
return jsonify({"ok": True, "message": message})
else:
return jsonify({"ok": False, "error": "导航启动失败,可能是Nav2未运行或AGV未连接"}), 400
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",
"row": 2,
@@ -167,27 +146,6 @@
"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",
"row": 0,
@@ -251,27 +209,6 @@
"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",
"row": 2,
@@ -293,27 +230,6 @@
"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",
"row": 0,
@@ -356,36 +272,6 @@
"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",
"row": 0,
@@ -416,36 +302,6 @@
"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",
"row": 1,
@@ -506,6 +362,126 @@
"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",
"row": 1,
@@ -537,8 +513,98 @@
}
},
{
"id": "m_1_4",
"row": 1,
"id": "m_2_0",
"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,
"front": {
"coords": [
+360 -140
View File
@@ -1,6 +1,6 @@
{
"rows": 2,
"cols": 5,
"rows": 3,
"cols": 7,
"grid": [],
"positions": [
{
@@ -8,9 +8,9 @@
"col": 0,
"side": "front",
"coords": [
0.5391402360519819,
-1.3221212932804989,
-0.04968159116162075
0.726789462066099,
0.38329959318460166,
0.023383889548769105
],
"poses": []
},
@@ -19,9 +19,9 @@
"col": 1,
"side": "front",
"coords": [
1.1801154538454173,
-1.3641834306281595,
-0.0384636372066124
1.5997282224959652,
0.3969445083875386,
0.021611522117307245
],
"poses": []
},
@@ -30,9 +30,9 @@
"col": 1,
"side": "back",
"coords": [
1.3273254588744863,
-3.5287940020200854,
-3.11993523836094
1.5365709266996668,
-1.5897806027940793,
-3.138003565212702
],
"poses": []
},
@@ -41,108 +41,9 @@
"col": 0,
"side": "back",
"coords": [
0.6499623251724095,
-3.634895898964233,
-3.06371982741706
],
"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
0.7250933954217442,
-1.585626974855502,
3.1407193246894285
],
"poses": []
},
@@ -151,9 +52,9 @@
"col": 1,
"side": "front",
"coords": [
1.3273254588744863,
-3.5287940020200854,
-3.11993523836094
1.5365709266996668,
-1.5897806027940793,
-3.138003565212702
],
"poses": []
},
@@ -162,9 +63,9 @@
"col": 0,
"side": "front",
"coords": [
0.6499623251724095,
-3.634895898964233,
-3.06371982741706
0.7250933954217442,
-1.585626974855502,
3.1407193246894285
],
"poses": []
},
@@ -173,9 +74,9 @@
"col": 0,
"side": "back",
"coords": [
0.39025594509020667,
-5.593393651151741,
-0.09001000079607593
0.695891857743624,
-3.6093540626907155,
-0.01891669546737457
],
"poses": []
},
@@ -184,9 +85,196 @@
"col": 1,
"side": "back",
"coords": [
0.9989880876134289,
-5.633047903271201,
-0.08518177305398167
1.7054640840200619,
-3.6172190671922944,
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": []
},
@@ -195,9 +283,20 @@
"col": 2,
"side": "back",
"coords": [
1.7493440267548326,
-5.7036971258959746,
-0.10505541857885684
2.49138966620064,
-3.6258193640543435,
0.011734020269766346
],
"poses": []
},
{
"row": 2,
"col": 2,
"side": "front",
"coords": [
2.49138966620064,
-3.6258193640543435,
0.011734020269766346
],
"poses": []
},
@@ -206,9 +305,20 @@
"col": 3,
"side": "back",
"coords": [
2.407336669431407,
-5.76958512207572,
-0.10251877401062247
3.384299605055195,
-3.633059580331103,
0.00825489611393585
],
"poses": []
},
{
"row": 2,
"col": 3,
"side": "front",
"coords": [
3.384299605055195,
-3.633059580331103,
0.00825489611393585
],
"poses": []
},
@@ -217,19 +327,129 @@
"col": 4,
"side": "back",
"coords": [
3.132062476985721,
-5.850166571217611,
-0.09127007182804729
4.499614777619532,
-3.6568143703418405,
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": []
}
],
"arm_initial_pose": [
-90.333323,
-90.079952,
0.160037,
-90.571318,
0.093654,
22.232898
-89.999795,
-89.999946,
-0.000131,
-90.00001,
4.1e-05,
0.000116
]
}
+12 -12
View File
@@ -9,12 +9,12 @@
"name": "front_1",
"photo_type": "front",
"arm_angles": [
-93.586541,
-184.343305,
50.583239,
-38.326674,
-85.153333,
20.399989
-81.796775,
-85.406752,
-5.803223,
-109.799747,
91.66639,
2.446712
],
"speed": 500,
"description": ""
@@ -24,12 +24,12 @@
"name": "back_1",
"photo_type": "back",
"arm_angles": [
15.860045,
-161.133416,
137.999016,
-161.996719,
168.000989,
15.653445
-81.796823,
-85.406717,
-5.803284,
-109.799953,
91.666459,
2.44676
],
"speed": 500,
"description": ""
+6 -6
View File
@@ -3,12 +3,12 @@
"id": "qr_1779278140334",
"name": "二维码1",
"joint_angles": [
-89.796645,
-2.013175,
-87.176721,
-82.49663,
-93.323403,
20.399941
89.999979,
-0.000118,
-120.09949,
-30,
-105,
0
],
"qr_value": "BG042110276",
"model_id": ""
+3 -2
View File
@@ -381,8 +381,9 @@ createApp({
} catch (e) { alert('导航失败: ' + e.message) }
},
async goToOrigin() {
if (!confirm('确认导航到原点 (0, 0, 0)?')) return
if (!confirm('确认先复原机械臂,再导航到原点 (0, 0, 0)?')) return
try {
this.mapMsg = '正在复原机械臂并发送原点导航...'
const res = await fetch(API + '/api/navigate/to', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -390,7 +391,7 @@ createApp({
})
const data = await res.json()
if (data.ok) {
this.mapMsg = '✅ 已发送导航到原点'
this.mapMsg = '✅ ' + (data.message || '已发送导航到原点')
} else {
this.mapMsg = '❌ ' + (data.error || '导航失败')
}
+10 -2
View File
@@ -414,6 +414,14 @@ class MissionExecutorV3:
model_name = self._lookup_model(qr_value)
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():
model = self._find_model(models, model_name)
if model:
@@ -642,7 +650,7 @@ class MissionExecutorV3:
self._log("↪️ 调用设置页同款接口返回原点")
resp = requests.post(
"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,
)
data = resp.json() if resp.content else {}
@@ -676,7 +684,7 @@ class MissionExecutorV3:
return False
def _scan_qr_with_poses(self, qr_configs: list, machine_row: int = 0) -> Optional[str]:
"""用二维码配置中的姿态依次尝试,逐一调整姿态+等2秒+扫码,全部失败才弹框
machine_row: 机器所在行,奇数行(1,3,5)时关节角度需取反
"""
if not qr_configs: