feat: separate laser API endpoint (POST /api/laser)
This commit is contained in:
@@ -34,7 +34,7 @@ server {
|
||||
proxy_send_timeout 86400s;
|
||||
}
|
||||
|
||||
# 模式切换 API (HTTP → ROS2 web_node)
|
||||
# API (HTTP → ROS2 web_node)
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8080/api/;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
+27
-11
@@ -109,11 +109,6 @@ curl -X POST http://localhost:8080/api/mode/switch \
|
||||
curl -X POST http://localhost:8080/api/mode/switch \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"mode": "debug"}'
|
||||
|
||||
# 激光开关
|
||||
curl -X POST http://localhost:8080/api/mode/switch \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"mode": "laser_on"}'
|
||||
```
|
||||
|
||||
也可直接通过 ROS2 话题发送:
|
||||
@@ -122,7 +117,28 @@ curl -X POST http://localhost:8080/api/mode/switch \
|
||||
ros2 topic pub /gimbal/mode std_msgs/String "data: 'auto'"
|
||||
```
|
||||
|
||||
## 6. 手动控制云台
|
||||
## 6. 激光控制
|
||||
|
||||
```bash
|
||||
# 开激光
|
||||
curl -X POST http://localhost:8080/api/laser \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action": "on"}'
|
||||
|
||||
# 关激光
|
||||
curl -X POST http://localhost:8080/api/laser \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action": "off"}'
|
||||
```
|
||||
|
||||
也可直接通过 ROS2 话题发送:
|
||||
|
||||
```bash
|
||||
ros2 topic pub /gimbal/mode std_msgs/String "data: 'laser_on'"
|
||||
ros2 topic pub /gimbal/mode std_msgs/String "data: 'laser_off'"
|
||||
```
|
||||
|
||||
## 7. 手动控制云台
|
||||
|
||||
```bash
|
||||
# angular.y = pitch 角速度, angular.z = yaw 角速度
|
||||
@@ -130,7 +146,7 @@ ros2 topic pub /cmd_gimbal geometry_msgs/Twist \
|
||||
"{angular: {y: 0.0, z: 0.0}}"
|
||||
```
|
||||
|
||||
## 7. PID 调参(调试模式)
|
||||
## 8. PID 调参(调试模式)
|
||||
|
||||
先切换到 debug 模式,然后通过 `/pid_config` 话题发送参数:
|
||||
|
||||
@@ -144,13 +160,13 @@ ros2 topic pub /pid_config std_msgs/String \
|
||||
|
||||
参数立即生效,调试完毕后切回 auto 或 manual 模式。
|
||||
|
||||
## 8. 查看视觉结果
|
||||
## 9. 查看视觉结果
|
||||
|
||||
浏览器打开 `http://<设备IP>:8080`,可看到:
|
||||
- 原始图像 + 轮廓标注(result)
|
||||
- Otsu 二值化 + 闭运算结果(binary)
|
||||
|
||||
## 9. 查看话题
|
||||
## 10. 查看话题
|
||||
|
||||
```bash
|
||||
# 列出所有活跃话题
|
||||
@@ -164,7 +180,7 @@ ros2 topic hz /vision/target
|
||||
ros2 topic echo /vision/target
|
||||
```
|
||||
|
||||
## 10. 测试
|
||||
## 11. 测试
|
||||
|
||||
```bash
|
||||
cd workspace
|
||||
@@ -179,7 +195,7 @@ colcon test --packages-select gimbal_control_pkg
|
||||
colcon test-result --all
|
||||
```
|
||||
|
||||
## 11. 故障排查
|
||||
## 12. 故障排查
|
||||
|
||||
| 现象 | 检查项 |
|
||||
|------|--------|
|
||||
|
||||
@@ -89,40 +89,77 @@ class _ImageHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
path = self.path.split('?')[0]
|
||||
if path == '/api/mode/switch':
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else b'{}'
|
||||
try:
|
||||
data = json.loads(body)
|
||||
mode = data.get('mode', '')
|
||||
except json.JSONDecodeError:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"error":"invalid json"}')
|
||||
return
|
||||
|
||||
valid_modes = ('auto', 'manual', 'debug', 'laser_on', 'laser_off')
|
||||
if mode not in valid_modes:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(
|
||||
f'{{"error":"invalid mode, must be one of {valid_modes}"}}'
|
||||
.encode()
|
||||
)
|
||||
return
|
||||
|
||||
if _mode_publisher is not None:
|
||||
msg = String()
|
||||
msg.data = mode
|
||||
_mode_publisher.publish(msg)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(f'{{"status":"ok","mode":"{mode}"}}'.encode())
|
||||
self._handle_mode_switch()
|
||||
elif path == '/api/laser':
|
||||
self._handle_laser()
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def _handle_mode_switch(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else b'{}'
|
||||
try:
|
||||
data = json.loads(body)
|
||||
mode = data.get('mode', '')
|
||||
except json.JSONDecodeError:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"error":"invalid json"}')
|
||||
return
|
||||
|
||||
valid_modes = ('auto', 'manual', 'debug')
|
||||
if mode not in valid_modes:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(
|
||||
f'{{"error":"invalid mode, must be one of {valid_modes}"}}'
|
||||
.encode()
|
||||
)
|
||||
return
|
||||
|
||||
if _mode_publisher is not None:
|
||||
msg = String()
|
||||
msg.data = mode
|
||||
_mode_publisher.publish(msg)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(f'{{"status":"ok","mode":"{mode}"}}'.encode())
|
||||
|
||||
def _handle_laser(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else b'{}'
|
||||
try:
|
||||
data = json.loads(body)
|
||||
action = data.get('action', '')
|
||||
except json.JSONDecodeError:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"error":"invalid json"}')
|
||||
return
|
||||
|
||||
if action == 'on':
|
||||
mode = 'laser_on'
|
||||
elif action == 'off':
|
||||
mode = 'laser_off'
|
||||
else:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"error":"action must be on or off"}')
|
||||
return
|
||||
|
||||
if _mode_publisher is not None:
|
||||
msg = String()
|
||||
msg.data = mode
|
||||
_mode_publisher.publish(msg)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(f'{{"status":"ok","laser":"{action}"}}'.encode())
|
||||
|
||||
def _serve_image(self, key):
|
||||
global _latest_jpeg
|
||||
jpeg = _latest_jpeg.get(key) if _latest_jpeg else None
|
||||
|
||||
Reference in New Issue
Block a user