# Origincar Simulation 基于 MotrixSim 的阿克曼小车仿真,支持键盘控制和 HTTP API 局域网共享。 ![仿真截图](doc/image.png) ## 依赖 | 依赖 | 说明 | 安装 | |------|------|------| | Python ≥3.13 | 运行环境 | `brew install python` 或 [python.org](https://python.org) | | [uv](https://docs.astral.sh/uv/) | 包管理器 | `curl -LsSf https://astral.sh/uv/install.sh \| sh` | | `motrixsim-core` | 物理仿真引擎 | `uv sync` 自动安装 | | `pillow` | 摄像头图像转 JPEG | `uv sync` 自动安装 | ```bash # 克隆后一键安装 uv sync ``` > `mxpython` 是 motrixsim-core 自带的启动器,**macOS 上必须使用**(主线程需保留给渲染),Linux / Windows 可直接用 `python`。 ## 启动 ```bash # macOS uv run mxpython main.py # Linux / Windows uv run python main.py ``` ## HTTP API(端口 8765) 仿真启动后,局域网内其他设备可通过 HTTP 获取摄像头图像、IMU 数据和发送控制指令。 ### GET `/image` 获取前置摄像头最新 JPEG 图像。浏览器可直接打开。 ```bash # 浏览器 http://<仿真机IP>:8765/image # curl curl http://1{IP}:8765/image -o frame.jpg # Python import requests r = requests.get("http://{IP}:8765/image") with open("frame.jpg", "wb") as f: f.write(r.content) ``` ### GET `/state` 获取小车当前状态(JSON)。 ```bash curl http://{IP}:8765/state ``` 返回示例: ```json { "x": -2.0, "y": -2.3, "z": 0.08, "yaw_deg": 0.0, "speed_target": 1.0, "speed_actual": 0.98, "steer_target": 0.0, "speed_amp": 1.0, "steer_amp": 0.35 } ``` ### GET `/imu` 获取 IMU 数据(含 MPU6050 噪声),JSON。 ```bash curl http://{IP}:8765/imu ``` 返回示例: ```json { "orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0}, "angular_velocity": {"x": 0.001, "y": -0.003, "z": 0.008}, "linear_acceleration": {"x": 0.02, "y": -0.01, "z": 9.81} } ``` 噪声参数(MPU6050 级别): - 陀螺仪白噪声 σ = 0.01 rad/s,bias 随机游走 - 加速度计白噪声 σ = 0.05 m/s²,bias 随机游走 ### POST `/cmd` 发送控制指令(JSON)。 ```bash curl -X POST http://{IP}:8765/cmd \ -H "Content-Type: application/json" \ -d '{"speed": 1.0, "steer": 0.3}' ``` | 字段 | 类型 | 说明 | |------|------|------| | `speed` | float | 目标速度 m/s,正=前进,负=后退 | | `steer` | float | 目标转向角 rad,正=左转,负=右转 | **Python 客户端示例**: ```python import time import requests IP = "192.168.1.100" # 获取图像 r = requests.get(f"http://{IP}:8765/image") with open("frame.jpg", "wb") as f: f.write(r.content) # 获取状态 state = requests.get(f"http://{IP}:8765/state").json() print(f"位置: ({state['x']}, {state['y']}), 速度: {state['speed_actual']} m/s") # 发送控制指令 requests.post(f"http://{IP}:8765/cmd", json={"speed": 1.0, "steer": 0.2}) time.sleep(2) requests.post(f"http://{IP}:8765/cmd", json={"speed": 0.0, "steer": 0.0}) ``` **ROS2 桥接示例**: ```python import rclpy from rclpy.node import Node from sensor_msgs.msg import Image as RosImage from geometry_msgs.msg import Twist from cv_bridge import CvBridge import requests import numpy as np import cv2 IP = "192.168.1.100" class OrigincarBridge(Node): def __init__(self): super().__init__("origincar_bridge") self.bridge = CvBridge() self.pub = self.create_publisher(RosImage, "/image", 10) self.sub = self.create_subscription(Twist, "/cmd_vel", self.cmd_cb, 10) self.timer = self.create_timer(0.1, self.fetch_and_publish) def fetch_and_publish(self): r = requests.get(f"http://{IP}:8765/image", timeout=1) if r.status_code == 200: arr = np.frombuffer(r.content, np.uint8) cv_img = cv2.imdecode(arr, cv2.IMREAD_COLOR) msg = self.bridge.cv2_to_imgmsg(cv_img, "bgr8") self.pub.publish(msg) def cmd_cb(self, msg: Twist): requests.post(f"http://{IP}:8765/cmd", json={ "speed": msg.linear.x, "steer": msg.angular.z }) rclpy.init() node = OrigincarBridge() rclpy.spin(node) ``` ## 键盘控制 | 按键 | 方式 | 作用 | |------|------|------| | W/S | 按住 | 前进/后退 | | A/D | 按住 | 左转/右转 | | ↑/↓ | 点按 | 速度幅度 ±0.2 m/s(0~2.0) | | ←/→ | 点按 | 转向幅度 ±0.05 rad(0~0.5) | | Space | 点按 | 截图保存到 `captures/` | 键盘与 HTTP `/cmd` 可同时使用:按住 W/A/S/D 时键盘优先,松开后自动切回 HTTP 控制。 ## 场景结构 ``` scene.xml — 5m×5m 地图 + 40cm 白色围栏 + 阿克曼小车 main.py — 仿真控制 + HTTP API 服务 origincar.urdf / origincar.xacro — 小车模型(参考) ```