feat: implement stop functionality for robot operations and remove obsolete stop endpoint

This commit is contained in:
Даниил Грабарь
2026-07-01 11:17:41 +10:00
parent 9e3ee7ebc8
commit 41f516bb58
4 changed files with 17 additions and 27 deletions
-10
View File
@@ -24,16 +24,6 @@ endpoints:
timeout: 5.0 timeout: 5.0
enabled: true enabled: true
- path: /robot/stop
method: POST
type: service
ros_name: cobot/stop
msg_type: std_srvs/srv/Trigger
summary: "Немедленно остановить движение"
description: "Вызывает сервис экстренной остановки — движение прерывается немедленно."
tags: [motion]
response_fields: [success, message]
timeout: 5.0
enabled: true enabled: true
- path: /robot/move/named - path: /robot/move/named
+11
View File
@@ -6,6 +6,7 @@ import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from fastmcp import FastMCP from fastmcp import FastMCP
from sensor_msgs.msg import JointState from sensor_msgs.msg import JointState
from std_srvs.srv import Trigger
from .dynamic_router import build_dynamic_router from .dynamic_router import build_dynamic_router
from .ros_node import CobotWebNode, get_bridge, set_bridge from .ros_node import CobotWebNode, get_bridge, set_bridge
@@ -47,6 +48,16 @@ def main():
app.include_router(positions.router) app.include_router(positions.router)
app.mount("/mcp", mcp_http) app.mount("/mcp", mcp_http)
@app.post("/stop", tags=["stop"], summary="Остановить всё: runner, траекторию и планировщик")
def stop_all():
runner.stop_if_running()
trajectory.send_stop_trajectory()
try:
result = get_bridge().call_service(Trigger, "cobot/stop", Trigger.Request())
return {"status": "stopped", "success": result.success, "message": result.message}
except RuntimeError:
return {"status": "stopped", "success": True, "message": "Планировщик не запущен"}
uvicorn.run(app, host=host, port=port) uvicorn.run(app, host=host, port=port)
+4 -8
View File
@@ -9,8 +9,6 @@ from typing import Optional
import yaml import yaml
from fastapi import APIRouter, Form, HTTPException, Query, UploadFile, File from fastapi import APIRouter, Form, HTTPException, Query, UploadFile, File
from std_srvs.srv import Trigger
from .ros_node import get_bridge from .ros_node import get_bridge
router = APIRouter(prefix="/sequences", tags=["sequences"]) router = APIRouter(prefix="/sequences", tags=["sequences"])
@@ -95,12 +93,12 @@ async def start_runner(
return {"status": "started", "pid": _process.pid, "config": filename} return {"status": "started", "pid": _process.pid, "config": filename}
@router.post("/stop", summary="Остановить motion_sequence_runner и послать cobot/stop") def stop_if_running() -> Optional[int]:
def stop_runner(): """Kill the runner process if it is running. Returns exit code or None if not running."""
global _process global _process
with _lock: with _lock:
if not _process or _process.poll() is not None: if not _process or _process.poll() is not None:
raise HTTPException(404, "Runner не запущен") return None
pgid = os.getpgid(_process.pid) pgid = os.getpgid(_process.pid)
os.killpg(pgid, signal.SIGTERM) os.killpg(pgid, signal.SIGTERM)
try: try:
@@ -108,10 +106,8 @@ def stop_runner():
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
os.killpg(pgid, signal.SIGKILL) os.killpg(pgid, signal.SIGKILL)
_process.wait() _process.wait()
code = _process.returncode return _process.returncode
result = get_bridge().call_service(Trigger, "cobot/stop", Trigger.Request())
return {"status": "stopped", "returncode": code, "success": result.success, "message": result.message}
@router.get("/status", summary="Статус motion_sequence_runner") @router.get("/status", summary="Статус motion_sequence_runner")
+2 -9
View File
@@ -5,7 +5,6 @@ from datetime import datetime
from builtin_interfaces.msg import Duration from builtin_interfaces.msg import Duration
from fastapi import APIRouter, File, HTTPException, Query, UploadFile from fastapi import APIRouter, File, HTTPException, Query, UploadFile
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from std_srvs.srv import Trigger
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
from .config_loader import load_joint_limits, load_joint_names from .config_loader import load_joint_limits, load_joint_names
@@ -172,11 +171,9 @@ async def send_csv_trajectory(
return {"status": "sent", "points": len(rows), "filename": file.filename} return {"status": "sent", "points": len(rows), "filename": file.filename}
@router.post("/stop", summary="Остановить выполнение траектории") def send_stop_trajectory() -> None:
def stop_trajectory(): """Publish a hold-position (or empty) trajectory to freeze joint motion."""
bridge = get_bridge() bridge = get_bridge()
# Replace ongoing trajectory with single point at current position
joint_states = bridge.get_latest("/joint_states") joint_states = bridge.get_latest("/joint_states")
if joint_states is not None and len(joint_states.position) >= N_JOINTS: if joint_states is not None and len(joint_states.position) >= N_JOINTS:
current_positions = list(joint_states.position[:N_JOINTS]) current_positions = list(joint_states.position[:N_JOINTS])
@@ -189,10 +186,6 @@ def stop_trajectory():
_publish(msg) _publish(msg)
_log("[stop] joint_states недоступны, отправлена пустая траектория") _log("[stop] joint_states недоступны, отправлена пустая траектория")
# cobot/stop cancels MoveIt action-based motion
result = bridge.call_service(Trigger, "cobot/stop", Trigger.Request())
_log(f"[stop] cobot/stop -> success={result.success}, message={result.message}")
return {"status": "stopped", "success": result.success, "message": result.message}
@router.get("/logs", summary="Последние лог-записи траекторного модуля") @router.get("/logs", summary="Последние лог-записи траекторного модуля")