diff --git a/src/iiwa_config/config/api_endpoints.yaml b/src/iiwa_config/config/api_endpoints.yaml index c78f075..34f2727 100644 --- a/src/iiwa_config/config/api_endpoints.yaml +++ b/src/iiwa_config/config/api_endpoints.yaml @@ -24,16 +24,6 @@ endpoints: timeout: 5.0 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 - path: /robot/move/named diff --git a/src/iiwa_web/iiwa_web/main.py b/src/iiwa_web/iiwa_web/main.py index 5949962..e12f9e5 100644 --- a/src/iiwa_web/iiwa_web/main.py +++ b/src/iiwa_web/iiwa_web/main.py @@ -6,6 +6,7 @@ import uvicorn from fastapi import FastAPI from fastmcp import FastMCP from sensor_msgs.msg import JointState +from std_srvs.srv import Trigger from .dynamic_router import build_dynamic_router from .ros_node import CobotWebNode, get_bridge, set_bridge @@ -47,6 +48,16 @@ def main(): app.include_router(positions.router) 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) diff --git a/src/iiwa_web/iiwa_web/runner.py b/src/iiwa_web/iiwa_web/runner.py index 6c5cef5..8a80949 100644 --- a/src/iiwa_web/iiwa_web/runner.py +++ b/src/iiwa_web/iiwa_web/runner.py @@ -9,8 +9,6 @@ from typing import Optional import yaml from fastapi import APIRouter, Form, HTTPException, Query, UploadFile, File -from std_srvs.srv import Trigger - from .ros_node import get_bridge router = APIRouter(prefix="/sequences", tags=["sequences"]) @@ -95,12 +93,12 @@ async def start_runner( return {"status": "started", "pid": _process.pid, "config": filename} -@router.post("/stop", summary="Остановить motion_sequence_runner и послать cobot/stop") -def stop_runner(): +def stop_if_running() -> Optional[int]: + """Kill the runner process if it is running. Returns exit code or None if not running.""" global _process with _lock: if not _process or _process.poll() is not None: - raise HTTPException(404, "Runner не запущен") + return None pgid = os.getpgid(_process.pid) os.killpg(pgid, signal.SIGTERM) try: @@ -108,10 +106,8 @@ def stop_runner(): except subprocess.TimeoutExpired: os.killpg(pgid, signal.SIGKILL) _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") diff --git a/src/iiwa_web/iiwa_web/trajectory.py b/src/iiwa_web/iiwa_web/trajectory.py index aedfe5a..c4693c5 100644 --- a/src/iiwa_web/iiwa_web/trajectory.py +++ b/src/iiwa_web/iiwa_web/trajectory.py @@ -5,7 +5,6 @@ from datetime import datetime from builtin_interfaces.msg import Duration from fastapi import APIRouter, File, HTTPException, Query, UploadFile from pydantic import BaseModel, Field -from std_srvs.srv import Trigger from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint 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} -@router.post("/stop", summary="Остановить выполнение траектории") -def stop_trajectory(): +def send_stop_trajectory() -> None: + """Publish a hold-position (or empty) trajectory to freeze joint motion.""" bridge = get_bridge() - - # Replace ongoing trajectory with single point at current position joint_states = bridge.get_latest("/joint_states") if joint_states is not None and len(joint_states.position) >= N_JOINTS: current_positions = list(joint_states.position[:N_JOINTS]) @@ -189,10 +186,6 @@ def stop_trajectory(): _publish(msg) _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="Последние лог-записи траекторного модуля")