#!/usr/bin/env python3 """Publish one flight_intent JSON to a ROS1 std_msgs/String topic (for flight bridge). Run after sourcing ROS, e.g.: source /opt/ros/noetic/setup.bash && python3 -m voice_drone.tools.publish_flight_intent_ros_once /tmp/intent.json Or from repo root(须在已 source ROS 的 shell 中,且勿覆盖 ROS 的 PYTHONPATH;应 prepend): export PYTHONPATH="$PWD:$PYTHONPATH" && python3 -m voice_drone.tools.publish_flight_intent_ros_once ... """ from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any import rospy from std_msgs.msg import String def _load_payload(path: str | None) -> dict[str, Any]: if path and path != "-": raw = Path(path).read_text(encoding="utf-8") else: raw = sys.stdin.read() data = json.loads(raw) if not isinstance(data, dict): raise ValueError("root must be a JSON object") return data def main() -> None: ap = argparse.ArgumentParser(description="Publish flight_intent JSON once to ROS String topic") ap.add_argument( "json_file", nargs="?", default="-", help="Path to JSON file, or - for stdin (default: stdin)", ) ap.add_argument("--topic", default="/input", help="ROS topic (default: /input)") ap.add_argument( "--wait-subscribers", type=float, default=0.0, help="Seconds to wait for at least one subscriber (default: 0)", ) args = ap.parse_args() payload = _load_payload(args.json_file if args.json_file != "-" else None) json_str = json.dumps(payload, ensure_ascii=False, separators=(",", ":")) rospy.init_node("flight_intent_ros_publisher_once", anonymous=True) pub = rospy.Publisher(args.topic, String, queue_size=1, latch=False) deadline = rospy.Time.now() + rospy.Duration(args.wait_subscribers) while args.wait_subscribers > 0 and pub.get_num_connections() < 1 and not rospy.is_shutdown(): if rospy.Time.now() > deadline: break rospy.sleep(0.05) rospy.sleep(0.15) pub.publish(String(data=json_str)) rospy.sleep(0.25) if __name__ == "__main__": main()