2026-04-14 09:54:26 +08:00

95 lines
3.2 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.

#!/usr/bin/env python3
"""
ROS1 伴飞桥节点:订阅 JSONstd_msgs/String校验 flight_intent v1 后调用 MAVROS 执行。
话题(默认):
全局 /inputstd_msgs/String与 rostopic pub、语音端 ROCKET_FLIGHT_BRIDGE_TOPIC 一致。
可通过私有参数 ~input_topic 覆盖(须带前导 / 才是全局名)。
示例:
rostopic pub -1 /input std_msgs/String \\
'{data: "{\"is_flight_intent\":true,\"version\":1,\"actions\":[{\"type\":\"land\",\"args\":{}}],\"summary\":\"\"}"}'
前提是:已 roslaunch mavros px4.launch …,且 /mavros/state connected。
"""
from __future__ import annotations
import json
import threading
import rospy
from std_msgs.msg import String
from voice_drone.core.flight_intent import parse_flight_intent_dict
from voice_drone.flight_bridge.ros1_mavros_executor import MavrosFlightExecutor
def _coerce_flight_intent_dict(raw: dict) -> dict:
"""允许仅传 {actions, summary?},补全顶层字段。"""
if raw.get("is_flight_intent") is True and raw.get("version") == 1:
return raw
actions = raw.get("actions")
if isinstance(actions, list) and actions:
summary = str(raw.get("summary") or "bridge").strip() or "bridge"
return {
"is_flight_intent": True,
"version": 1,
"actions": actions,
"summary": summary,
}
raise ValueError("JSON 须为完整 flight_intent 或含 actions 数组")
class FlightIntentBridgeNode:
def __init__(self) -> None:
self._exec = MavrosFlightExecutor()
self._busy = threading.Lock()
# 默认用绝对名 /input若用相对名 "input"anonymous 节点下会变成 /flight_intent_mavros_bridge_*/input与 rostopic pub /input 不一致。
topic = rospy.get_param("~input_topic", "/input")
self._sub = rospy.Subscriber(
topic,
String,
self._on_input,
queue_size=1,
)
rospy.loginfo("flight_intent_bridge 就绪:订阅 %s", topic)
def _on_input(self, msg: String) -> None:
data = (msg.data or "").strip()
if not data:
return
if not self._busy.acquire(blocking=False):
rospy.logwarn("上一段 flight_intent 仍在执行,忽略本条")
return
def _run() -> None:
try:
try:
raw = json.loads(data)
except json.JSONDecodeError as e:
rospy.logerr("JSON 解析失败: %s", e)
return
if not isinstance(raw, dict):
rospy.logerr("顶层须为 JSON object")
return
raw = _coerce_flight_intent_dict(raw)
parsed, errors = parse_flight_intent_dict(raw)
if errors or parsed is None:
rospy.logerr("flight_intent 校验失败: %s", errors)
return
self._exec.execute(parsed)
finally:
self._busy.release()
threading.Thread(target=_run, daemon=True, name="flight-intent-exec").start()
def main() -> None:
FlightIntentBridgeNode()
rospy.spin()
if __name__ == "__main__":
main()