diff --git a/src/fc_interfaces/CMakeLists.txt b/src/fc_interfaces/CMakeLists.txt index d86950e..36b3500 100644 --- a/src/fc_interfaces/CMakeLists.txt +++ b/src/fc_interfaces/CMakeLists.txt @@ -9,13 +9,15 @@ endif() find_package(ament_cmake REQUIRED) find_package(rosidl_default_generators REQUIRED) find_package(std_msgs REQUIRED) +find_package(builtin_interfaces REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/AttitudeRaw.msg" + "msg/GnssRaw.msg" "srv/MavPing.srv" "srv/MavCommandLong.srv" "srv/MavPositionTargetGlobalInt.srv" - DEPENDENCIES std_msgs + DEPENDENCIES std_msgs builtin_interfaces ) ament_package() diff --git a/src/fc_interfaces/msg/GnssRaw.msg b/src/fc_interfaces/msg/GnssRaw.msg new file mode 100644 index 0000000..2ab69bc --- /dev/null +++ b/src/fc_interfaces/msg/GnssRaw.msg @@ -0,0 +1,8 @@ +builtin_interfaces/Time stamp +float64 latitude +float64 longitude +float64 altitude +uint8 fix_type +uint8 satellites_visible +float32 eph +float32 epv diff --git a/src/fc_interfaces/package.xml b/src/fc_interfaces/package.xml index 5dcd350..a215b5c 100644 --- a/src/fc_interfaces/package.xml +++ b/src/fc_interfaces/package.xml @@ -17,6 +17,8 @@ std_msgs std_msgs + builtin_interfaces + builtin_interfaces ament_cmake diff --git a/src/fc_network_adapter/fc_network_adapter/mainOrchestrator.py b/src/fc_network_adapter/fc_network_adapter/mainOrchestrator.py index dd0b36b..37c8b42 100644 --- a/src/fc_network_adapter/fc_network_adapter/mainOrchestrator.py +++ b/src/fc_network_adapter/fc_network_adapter/mainOrchestrator.py @@ -1145,7 +1145,7 @@ class Orchestrator: self.fc_ros_manager = mros.ros2_manager self.fc_ros_manager.initialize() self.fc_ros_manager.status_publisher.rate_controller.topic_intervals = { - 'position': 1.0, + 'position_gnss': 1.0, 'position_ned': 1.0, 'attitude': 1.0, 'velocity': 0.0, diff --git a/src/fc_network_adapter/fc_network_adapter/mavlinkObject.py b/src/fc_network_adapter/fc_network_adapter/mavlinkObject.py index 15dcef6..ab3e80f 100644 --- a/src/fc_network_adapter/fc_network_adapter/mavlinkObject.py +++ b/src/fc_network_adapter/fc_network_adapter/mavlinkObject.py @@ -109,6 +109,7 @@ class mavlink_bridge: """初始化訊息處理器映射表,提高處理效率""" self.message_handlers = { 0: self._handle_heartbeat, # HEARTBEAT + 24: self._handle_gps_raw_int, # GPS_RAW_INT 30: self._handle_attitude, # ATTITUDE 32: self._handle_local_position, # LOCAL_POSITION_NED 33: self._handle_global_position, # GLOBAL_POSITION_INT @@ -239,6 +240,14 @@ class mavlink_bridge: component.status.position.relative_altitude = msg.relative_alt / 1000.0 # 轉換為公尺 component.status.position.timestamp = timestamp + def _handle_gps_raw_int(self, vehicle, component, msg, timestamp): + """處理 GPS_RAW_INT 訊息 (msg_id: 24)""" + component.status.gps.fix_type = msg.fix_type + component.status.gps.satellites_visible = msg.satellites_visible + component.status.gps.eph = None if msg.eph == 65535 else msg.eph + component.status.gps.epv = None if msg.epv == 65535 else msg.epv + component.status.gps.timestamp = timestamp + def _handle_vfr_hud(self, vehicle, component, msg, timestamp): """處理 VFR_HUD 訊息 (msg_id: 74)""" component.status.vfr.airspeed = msg.airspeed @@ -386,7 +395,9 @@ class mavlink_object: self.MAVLink = mav_common.MAVLink(self.mavlinkPipeline, srcSystem=254, srcComponent=191) # 記錄訊息過濾類型 (可選) - self.bridge_msg_types = set([0, 30, 32, 33, 74, 147]) # 0 HEARTBEAT, 30 ATTITUDE, 33 GLOBAL_POSITION_INT, 74 VFR_HUD, 147 BATTERY_STATUS, 32 LOCAL_POSITION_NED + # 0 HEARTBEAT, 24 GPS_RAW_INT, 30 ATTITUDE, + # 32 LOCAL_POSITION_NED, 33 GLOBAL_POSITION_INT, 74 VFR_HUD, 147 BATTERY_STATUS + self.bridge_msg_types = set([0, 24, 30, 32, 33, 74, 147]) self.return_msg_types = set([]) # 轉發到別的 mavlink object 作為目標端口 的列表 diff --git a/src/fc_network_adapter/fc_network_adapter/mavlinkROS2Nodes.py b/src/fc_network_adapter/fc_network_adapter/mavlinkROS2Nodes.py index 96455fc..5e03fc2 100644 --- a/src/fc_network_adapter/fc_network_adapter/mavlinkROS2Nodes.py +++ b/src/fc_network_adapter/fc_network_adapter/mavlinkROS2Nodes.py @@ -61,7 +61,7 @@ class PublishRateController: # 注意 這邊是定義區 不要把參數寫在這裡 所以預設全部關閉 # 以這個專案 請看 mainOrchestrator.py 的 Orchestrator 初始化階段 self.topic_intervals = { - 'position': 0.0, # GNSS位置 + 'position_gnss': 0.0, # GNSS位置 'position_ned': 0.0, # LOCAL_POSITION_NED (位置+速度) 'attitude': 0.0, # 姿態 (pitch yaw row 與其加速狀態) 'velocity': 0.0, # 速度 (已經包含在 vfr_hud 未來移除) @@ -171,7 +171,7 @@ class VehicleStatusPublisher(Node): # 例如:self._publish_ekf_status(sysid, status) # ═══════════════════════════════════════════════════════════════ # 發布各種狀態(通過頻率控制器判斷是否發布) - self._publish_position(sysid, status) + self._publish_position_gnss(sysid, status) self._publish_position_ned(sysid, status) self._publish_attitude(sysid, status) self._publish_velocity(sysid, status) @@ -202,9 +202,9 @@ class VehicleStatusPublisher(Node): logger.info(f"Created publisher: {topic_name}") return self.fc_publishers[key] - def _publish_position(self, sysid: int, status: mvv.ComponentStatus): + def _publish_position_gnss(self, sysid: int, status: mvv.ComponentStatus): """發布 GPS 位置""" - if not self.rate_controller.should_publish(sysid, 'position'): + if not self.rate_controller.should_publish(sysid, 'position_gnss'): return pos = status.position @@ -212,22 +212,27 @@ class VehicleStatusPublisher(Node): return publisher = self._get_or_create_publisher( - sysid, 'position', sensor_msgs.msg.NavSatFix + sysid, 'position_gnss', fcmsg.GnssRaw ) # 檢查是否有訂閱者 if publisher.get_subscription_count() == 0: return - msg = sensor_msgs.msg.NavSatFix() - msg.latitude = pos.latitude - msg.longitude = pos.longitude - msg.altitude = pos.altitude if pos.altitude is not None else 0.0 + msg = fcmsg.GnssRaw() + msg.stamp = self.get_clock().now().to_msg() + msg.latitude = float(pos.latitude) + msg.longitude = float(pos.longitude) + msg.altitude = float(pos.altitude) if pos.altitude is not None else 0.0 # GPS 狀態資訊 gps = status.gps if gps.fix_type is not None: - msg.status.status = gps.fix_type - 1 # MAVLink fix_type 轉 NavSatStatus + msg.fix_type = int(gps.fix_type) + if gps.satellites_visible is not None: + msg.satellites_visible = int(gps.satellites_visible) + msg.eph = float(gps.eph) / 100.0 if gps.eph is not None else float('nan') + msg.epv = float(gps.epv) / 100.0 if gps.epv is not None else float('nan') publisher.publish(msg) diff --git a/src/fc_network_adapter/tests/test_vehicleStatusPublisher.py b/src/fc_network_adapter/tests/test_vehicleStatusPublisher.py index 6fd1914..af2ebfc 100644 --- a/src/fc_network_adapter/tests/test_vehicleStatusPublisher.py +++ b/src/fc_network_adapter/tests/test_vehicleStatusPublisher.py @@ -17,6 +17,7 @@ import std_msgs.msg import sensor_msgs.msg import geometry_msgs.msg import mavros_msgs.msg +import fc_interfaces.msg # 專案 imports from ..fc_network_adapter.mavlinkROS2Nodes import ( @@ -39,7 +40,7 @@ class TestSubscriber(Node): self.sysid = sysid self.received_messages = { - 'position': [], + 'position_gnss': [], 'attitude': [], 'velocity': [], 'battery': [], @@ -60,9 +61,9 @@ class TestSubscriber(Node): # Position self.create_subscription( - sensor_msgs.msg.NavSatFix, - f'{base_topic}/position', - lambda msg: self._on_message('position', msg), + fc_interfaces.msg.GnssRaw, + f'{base_topic}/position_gnss', + lambda msg: self._on_message('position_gnss', msg), 10 ) @@ -121,7 +122,7 @@ class TestSubscriber(Node): def _format_msg(self, topic_name: str, msg) -> str: """格式化消息以便顯示""" - if topic_name == 'position': + if topic_name == 'position_gnss': return f"lat={msg.latitude:.6f}, lon={msg.longitude:.6f}, alt={msg.altitude:.2f}m" elif topic_name == 'attitude': return f"quat=({msg.orientation.x:.3f}, {msg.orientation.y:.3f}, {msg.orientation.z:.3f}, {msg.orientation.w:.3f})" @@ -264,7 +265,7 @@ def test_basic_publishing(): # 檢查收到的消息 print("\n--- 消息統計 ---") total_messages = 0 - for topic in ['position', 'attitude', 'velocity', 'battery', 'vfr_hud', 'mode', 'summary']: + for topic in ['position_gnss', 'attitude', 'velocity', 'battery', 'vfr_hud', 'mode', 'summary']: count = test_sub.get_message_count(topic) total_messages += count print(f" {topic:15s}: {count:3d} 條消息") @@ -304,7 +305,7 @@ def test_frequency_control(): # 修改頻率設定 publisher_node = ros2_manager.status_publisher publisher_node.rate_controller.topic_intervals = { - 'position': 1.5, + 'position_gnss': 1.5, 'attitude': 1.0, 'velocity': 1.0, 'battery': 1.0, @@ -329,7 +330,7 @@ def test_frequency_control(): print("預期:position 約 2 條 (0.67Hz),attitude/battery/velocity 約 3 條 (1Hz),vfr_hud 約 6 條 (2Hz) mode/summary 不發布") print("2Hz Topics (預期 ~6 條):") - for topic in ['position', 'attitude', 'velocity', 'vfr_hud']: + for topic in ['position_gnss', 'attitude', 'velocity', 'vfr_hud']: count = test_sub.get_message_count(topic) print(f" {topic:15s}: {count:3d} 條") for topic in ['battery', 'mode', 'summary']: