Files
car_simulation/README.md
2026-06-14 23:38:56 +08:00

198 lines
4.7 KiB
Markdown
Raw Permalink 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.
# 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/sbias 随机游走
- 加速度计白噪声 σ = 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/s0~2.0 |
| ←/→ | 点按 | 转向幅度 ±0.05 rad0~0.5 |
| Space | 点按 | 截图保存到 `captures/` |
键盘与 HTTP `/cmd` 可同时使用:按住 W/A/S/D 时键盘优先,松开后自动切回 HTTP 控制。
## 场景结构
```
scene.xml — 5m×5m 地图 + 40cm 白色围栏 + 阿克曼小车
main.py — 仿真控制 + HTTP API 服务
origincar.urdf / origincar.xacro — 小车模型(参考)
```