gazebo_world_manager/http_server.py
2025-08-10 11:58:37 +08:00

197 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import dataclasses
import importlib
import os
import socket
import subprocess
import threading
import flask
import werkzeug.serving
from flask import jsonify, request
import gazebo_ctrl
import scene_mgr
@dataclasses.dataclass
class HttpHost():
"""
HTTP主机信息
"""
hostname: str
port: int
def __post_init__(self):
if not self.hostname:
raise ValueError('Hostname cannot be empty')
if not (0 <= self.port <= 65535):
raise ValueError(f'Invalid port: {self.port}')
class HttpRPCServer():
"""
来自ability-sdk-python的http服务器。
HTTP RPC服务端实现。
使用add_route方法添加路由重写类以实现自定义功能。
"""
def __init__(self):
self._server = None
self._app = flask.Flask(__name__)
def add_route(self, uri: str, handler, methods: list):
"""
添加HTTP路由handler应符合Flask视图函数的规范。
"""
if not callable(handler):
raise ValueError('Handler must be a callable function')
self._app.add_url_rule(uri, view_func=handler, methods=methods)
def start(self, hostname: str = '0.0.0.0', port: int = None):
"""
启动并以阻塞方式提供服务。
"""
if self._server:
raise RuntimeError('Server is already running')
self.host = HttpHost(
hostname=hostname, port=port if port else self.find_free_port())
self._server = werkzeug.serving.make_server(
self.host.hostname, self.host.port, self._app, threaded=True)
self._server.serve_forever()
def stop(self):
"""
停止服务。
"""
if self._server:
self._server.shutdown()
self._server.server_close()
self._server = None
def find_free_port(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
class GazeboSimHttpServer(HttpRPCServer):
"""
Gazebo仿真环境的HTTP服务器。
"""
def __init__(self):
super().__init__()
self.scene_mgr = scene_mgr.SceneManager()
self.gazebo_ctrl = gazebo_ctrl.GazeboROSController()
# Gazebo仿真/程序控制
self.add_route('/simulation/scene', self.get_simulation_scene, ['GET'])
self.add_route('/simulation/start', self.start_simulation, ['GET'])
self.add_route('/simulation/shutdown',
self.shutdown_simulation, ['GET'])
self.add_route('/simulation/run-script', self.run_script, ['POST'])
# Gazebo仿真/仿真控制
self.add_route('/physics/continue', self.continue_physics, ['GET'])
self.add_route('/physics/pause', self.pause_physics, ['GET'])
self.add_route('/physics/reset', self.reset_physics, ['GET'])
# Gazebo仿真/模型控制
self.add_route('/model/state', self.get_model_state, ['GET'])
self.add_route('/model/state', self.set_model_state, ['POST'])
self.add_route('/model/spawn', self.spawn_model, ['POST'])
self.add_route('/model/delete', self.delete_model, ['POST'])
# Gazebo仿真/场景专有控制
self.add_route('/<scene>/model/state',
self.get_scene_model_state, ['GET'])
self.add_route('/<scene>/model/state',
self.set_scene_model_state, ['POST'])
self.add_route('/<scene>/model/spawn',
self.spawn_scene_model, ['POST'])
# ===== 处理函数实现 =====
def get_simulation_scene(self):
try:
scenes = self.scene_mgr.get_scene()
except Exception as e:
return str(e), 400
return jsonify(scenes)
def start_simulation(self):
scene = request.args.get('scene', 'grasp-box')
try:
self.scene_mgr.start_scene(scene)
except Exception as e:
return str(e), 400
return 'OK', 200
def shutdown_simulation(self):
try:
self.scene_mgr.shut_scene()
except Exception as e:
return str(e), 400
return 'OK', 200
def run_script(self):
name = request.args.get('name')
try:
self.scene_mgr.run_script(name)
except Exception as e:
return str(e), 400
return 'OK', 200
def continue_physics(self):
try:
self.gazebo_ctr???
except Exception as e:
return str(e), 400
return 'OK', 200
def pause_physics(self):
return jsonify({'status': 'physics paused'})
def reset_physics(self):
return jsonify({'status': 'physics reset'})
def get_model_state(self):
names = request.args.getlist('names') or []
return jsonify([{'name': n, 'pose': {}, 'twist': {}} for n in names])
def set_model_state(self):
data = request.get_json(silent=True)
if not data or 'name' not in data:
return jsonify({'error': 'Missing required field name'}), 400
return jsonify({'status': 'model state updated', 'data': data})
def spawn_model(self):
data = request.get_json(silent=True)
required = ['pose', 'name', 'xml']
if not data or not all(k in data for k in required):
return jsonify({'error': f'Missing required fields {required}'}), 400
return jsonify({'status': 'model spawned', 'data': data})
def delete_model(self):
data = request.get_json(silent=True)
if not data or 'name' not in data:
return jsonify({'error': 'Missing required field name'}), 400
return jsonify({'status': 'model deleted', 'name': data['name']})
def get_scene_model_state(self, scene):
if scene != self.scene_name:
return jsonify({'error': f'running {self.scene_name} but try to access {scene}'}), 400
return self.scene_mgr.scene_plugin.get_scene_model_state()
def set_scene_model_state(self, scene):
if scene != self.scene_name:
return jsonify({'error': f'running {self.scene_name} but try to access {scene}'}), 400
return self.scene_mgr.scene_plugin.post_scene_model_state()
def spawn_scene_model(self, scene):
if scene != self.scene_name:
return jsonify({'error': f'running {self.scene_name} but try to access {scene}'}), 400
return self.scene_mgr.scene_plugin.post_scene_model_spawn()
if __name__ == '__main__':
server = GazeboSimHttpServer()
server.start(hostname='0.0.0.0', port=12300)