多ros转mqtt

思路

基于paho包开发,支持多个话题输入,但需对应输入话题类型和需要的ros queue长度

参考

Python paho-mqtt使用心得 - 大叔明 - 博客园

isaac_mission_client的ros转mqtt包

调试

launch-》ros to mqtt node-》mqttx工具

代码

node

# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES
# Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

"""Implementation for RosToMqttNode."""
import json
import socket
import time

from isaac_ros_mqtt_bridge.MqttBridgeUtils import convert_dict_keys
from std_msgs.msg import String
from sensor_msgs.msg import LaserScan

import paho.mqtt.client as mqtt
import rclpy
from rclpy.node import Node

from rosbridge_library.internal import message_conversion
from rosbridge_library.internal import ros_loader


class RosToMqttNode(Node):
    """
    Bridge node that converts ROS messages to MQTT messages.

    Bridge node that subscribes to a ROS topic, translates the received message to a JSON,
    and publishes it to a MQTT channel.
    """

    def __init__(self, name='multy_ros_to_mqtt_bridge_node'):
        """Construct the RosToMqttBridge."""
        super().__init__(name)
        self.declare_parameters(
            namespace='',
            parameters=[
                ('interface_name', 'uagv'),
                ('major_version', 'v2'),
                ('manufacturer', 'RobotCompany'),
                ('serial_number', 'carter01'),
                ('mqtt_client_name', 'RosToMqttBridge'),
                ('mqtt_host_name', '192.168.9.97'),
                ('mqtt_port', 1883),
                ('mqtt_transport', 'tcp'),
                ('mqtt_ws_path', ''),
                ('mqtt_keep_alive', 100),
                # ('ros_subscriber_type', 'vda5050_msgs/NodeState'),
                # ('ros_subscriber_type', 'sensor_msgs/msg/LaserScan'),
                # ('ros_subscriber_queue', 10),
                ('convert_snake_to_camel', True),
                ('reconnect_period', 5),
                ('retry_forever', False),
                ('num_retries', 10),

                ('ros_subscriber_types', ['sensor_msgs/msg/LaserScan','std_msgs/msg/String']),
                ('ros_sub_topics', ['/scan','/test_string']),
                ('ros_subscriber_queues', [1,10]),
            ]
        )

        self.mqtt_client = mqtt.Client(
            self.get_parameter('mqtt_client_name').value,
            transport=self.get_parameter('mqtt_transport').value)
        self.mqtt_client.username_pw_set(username="admin", password="Softw0rk")
        
        if self.get_parameter('mqtt_transport').value == 'websockets' and \
                self.get_parameter('mqtt_ws_path').value != '':
            self.mqtt_client.ws_set_options(path=self.get_parameter('mqtt_ws_path').value)

        self.interface_name = self.get_parameter('interface_name').value
        self.major_version = self.get_parameter('major_version').value
        self.manufacturer = self.get_parameter('manufacturer').value
        self.serial_number = self.get_parameter('serial_number').value
        self.mqtt_topic_prefix = \
            f'{self.interface_name}/{self.major_version}/{self.manufacturer}/{self.serial_number}'

        def on_mqtt_connect(client, userdata, flags, rc):
            if rc == 0:
                self.get_logger().info(f"MQTT connected successfully! rc:{rc}")
            else:   
                self.get_logger().error(f"Failed to connect to MQTT: result code {rc}")

        def on_mqtt_disconnect(client, userdata, rc):
            if rc != 0:
                self.mqtt_client.loop_start()

                self.get_logger().error(f'Disconnected with result code {str(rc)}')

        self.mqtt_client.on_connect = on_mqtt_connect
        self.mqtt_client.on_disconnect = on_mqtt_disconnect

        # print("test1")
        
        self.my_subscriptions = []
        self.callbacks = {}  # 存储动态生成的回调函数
        self.generate_callbacks(self.get_parameter('ros_sub_topics').value) #  生成回调函数
        for i in range(len(self.get_parameter('ros_subscriber_types').value)):
            # print("test55")
            temp_sub = self.create_subscription(
                ros_loader.get_message_class(
                    self.get_parameter('ros_subscriber_types').value[i]),
                self.get_parameter('ros_sub_topics').value[i], 
                self.callbacks[self.get_parameter('ros_sub_topics').value[i]],
                # self.callbacks[self.get_parameter('ros_sub_topics').value[i]],
                self.get_parameter('ros_subscriber_queues').value[i])
            self.my_subscriptions.append(temp_sub)
        # -------------------------------
        # print("test6")

        """
            self.subscription = self.create_subscription(
            # ros_loader.get_message_class(
            #     self.get_parameter('ros_subscriber_type').value),
            # 'ros_sub_topic', self.__ros_subscriber_callback,
            # String,
            # '/test_string', self.__ros_subscriber_callback,
            LaserScan,
            '/scan', self.__ros_subscriber_callback,
            self.get_parameter('ros_subscriber_queue').value)
        """
        
        max_retries = self.get_parameter('num_retries').value
        retries = 0
        connected = False
        retry_forever = self.get_parameter('retry_forever').value
        while retries < max_retries or retry_forever:
            try:
                self.get_logger().info(f'try....{retries}')

                self.mqtt_client.connect(
                    self.get_parameter('mqtt_host_name').value,
                    self.get_parameter('mqtt_port').value,
                    self.get_parameter('mqtt_keep_alive').value)
                connected = True
                self.get_logger().info(f'connected....')
                break
            except ConnectionRefusedError as e:
                self.get_logger().error(f'Connection Error: {e}. Please check the mqtt_host_name.')
                time.sleep(self.get_parameter('reconnect_period').value)
                retries += 1
            except socket.timeout as e:
                self.get_logger().error(f'Connection Error: {e}. Please check the mqtt_host_name'
                                        ' and make sure it is reachable.')
                time.sleep(self.get_parameter('reconnect_period').value)
                retries += 1
            except socket.gaierror as e:
                self.get_logger().error(f'Connection Error: {e}. Could not resolve mqtt_host_name')
                time.sleep(self.get_parameter('reconnect_period').value)
                retries += 1
        if connected:
            self.mqtt_client.loop_start()
            self.get_logger().error(f'loop_start')

        else:
            self.get_logger().error('Failed to connect to MQTT broker, ending retries.')
    def generate_callbacks(self, ros_sub_topics):
        # for value in ros_sub_topics:
        for i in range(len(ros_sub_topics)):
            # 定义动态回调函数
            def callback_function(msg, value=ros_sub_topics[i]):
                try:
                    extracted = message_conversion.extract_values(msg)
                    msg_class = ros_loader.get_message_class(self.get_parameter('ros_subscriber_types').value[i])
                    value = value.replace("/", "_")
                    class_name = str(msg_class).replace('\'', '')
                    # self.get_logger().info(f"Message class name : {class_name}")
                    self.get_logger().info(f"publish mqtt:{self.mqtt_topic_prefix}/{value}")
                    if self.get_parameter('convert_snake_to_camel').value:
                        if class_name == '<class vda5050_msgs.msg._agv_state.AGVState>':
                            # self.get_logger().info(f"get agv state class")
                            value = "agv_state"
                            self.mqtt_client.publish(
                                f'{self.mqtt_topic_prefix}/{value}',
                                json.dumps(convert_dict_keys(extracted, 'snake_to_dromedary')))
                        else:
                            self.mqtt_client.publish(
                                f'{self.mqtt_topic_prefix}/{value}',
                                json.dumps(extracted))

                except (message_conversion.FieldTypeMismatchException, 
                        json.decoder.JSONDecodeError) as e:
                    self.get_logger().info(repr(e))

            # 存储到实例的回调字典中
            self.callbacks[ros_sub_topics[i]] = callback_function

def main(args=None):
    """Execute the RosToMqttNode."""
    rclpy.init(args=args)
    node = RosToMqttNode('multy_ros_to_mqtt_bridge_node')
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

launch

<think>好的,我现在需要回答用户关于ROS2MQTT的问题。首先,我需要明确用户的需求,他们可能想了解如何在ROS2中集成MQTT协议,或者两者的基本概念和结合使用的方法。 首先,我应该回顾ROS2的基本架构,特别是它的通信机制,比如话题、服务和动作。ROS2默认使用DDS作为中间件,但用户可能需要在特定场景下使用其他协议,比如MQTT,尤其是在与物联网设备通信时。 接下来,我需要解释MQTT是什么。MQTT是一种轻量级的发布-订阅消息协议,适合低带宽、高延迟或不可靠的网络环境。它在物联网中广泛应用,设备可以通过MQTT代理(broker)进行通信。 然后,用户可能想知道为什么要在ROS2中使用MQTT。可能的理由包括与现有IoT设备集成,或者在分布式系统中连接不同的部分,这些部分可能不使用ROS2。需要强调ROS2MQTT结合使用的场景,比如跨平台通信或云边端协同。 接下来,如何实现ROS2MQTT的桥接?这里需要分步骤说明。首先,安装必要的库,如eclipse-mosquitto作为MQTT代理,以及ROS2MQTT客户端库,比如ros2/mqtt_client或自定义的ROS2节点使用paho-mqtt库。 然后,创建桥接节点。这部分需要详细说明如何编写一个ROS2节点,该节点同时订阅ROS2话题和MQTT主题,并在两者之间发消息。可能需要代码示例,比如使用rclpy和paho-mqtt库的基本结构。 数据格式换也是一个关键点。ROS2使用自己的消息类型,而MQTT传输的是原始字节或JSON等格式。需要提醒用户注意序列化和反序列化,可能需要使用JSON或Protobuf进行换,并确保双方数据结构的兼容性。 安全方面,MQTT通常支持TLS加密和用户名/密码认证,这些配置需要在桥接节点中处理。同时,ROS2的安全机制(如SROS)也需要考虑,尤其是在敏感数据传递时。 测试和部署步骤同样重要。建议用户先单独测试MQTT代理,确保其正常运行,然后逐步测试桥接节点的功能,最后整合到整个系统中。部署时要注意网络配置,比如代理的IP和端口是否可达,防火墙设置等。 此外,可能需要提及现有的开源项目或包,如rosbridge_suite,或者针对ROS2的特定MQTT桥接实现,以节省开发时间。同时,提醒用户注意性能问题,比如消息延迟和带宽限制,尤其是在大量数据传输时。 最后,总结使用ROS2MQTT的优势,比如扩展性、灵活性和跨平台能力,以及可能的挑战,如维护数据一致性和处理不同协议的特性差异。可能还需要提供参考资料或官方文档链接,方便用户深入查阅。 在整理这些思路时,需要确保遵循用户的格式要求:行内公式用$...$,独立公式用$$...$$,虽然这个问题可能不涉及数学公式,但要注意其他格式要求,如中文回答、结构清晰等。同时,检查LaTeX语法是否正确,尽管在技术说明中可能用不到,但如果有数学表达式出现,必须正确格式化。 可能还需要验证一些技术细节,比如ROS2的接口是否支持异步处理MQTT消息,或者现有的库是否兼容当前的ROS2版本(如Humble或Iron)。如果有不确定的地方,建议用户参考官方文档或社区资源。 总之,回答需要结构清晰,分点说明ROS2MQTT的基本概念、结合原因、实现步骤、注意事项以及实际应用案例,确保用户能够逐步理解并实施集成方案。</think>以下是关于ROS2MQTT结合的详细介绍,结构清晰,分步说明: --- ### **1. ROS2MQTT的基本概念** - **ROS2**(Robot Operating System 2) 一种机器人开发框架,提供通信、硬件抽象和工具链,核心基于**DDS**(数据分发服务)实现节点间高效通信,支持话题(Topic)、服务(Service)和动作(Action)等通信模式。 - **MQTT**(Message Queuing Telemetry Transport) 轻量级发布-订阅协议,专为低带宽、高延迟网络设计。核心组件包括: - **代理(Broker)**:消息中服务器(如Mosquitto)。 - **客户端**:发布或订阅消息的设备。 --- ### **2. 为何结合ROS2MQTT?** - **应用场景** - 连接物联网设备(如传感器、执行器)至ROS2系统。 - 跨平台通信(如与云端、移动端或非ROS设备交互)。 - 低功耗网络环境下的数据传输。 - **优势** - **扩展性**:利用MQTT兼容海量IoT设备。 - **灵活性**:突破ROS2网络限制(如跨公网通信)。 - **标准化**:MQTT是IoT领域通用协议。 --- ### **3. 实现ROS2MQTT桥接的步骤** #### **步骤1:安装MQTT代理** ```bash sudo apt install mosquitto mosquitto-clients # 安装Mosquitto systemctl start mosquitto # 启动服务 ``` #### **步骤2:选择ROS2 MQTT客户端库** 常用选项: - **mqtt_client**(ROS2专用包) GitHub仓库:https://github.com/ika-rwth-aachen/mqtt_client - **自定义节点**:使用Python的`paho-mqtt`或C++的`Paho MQTT`库。 #### **步骤3:编写桥接节点** 以Python为例,创建一个同时订阅ROS2话题和MQTT主题的节点: ```python import rclpy from rclpy.node import Node import paho.mqtt.client as mqtt class MQTTBridge(Node): def __init__(self): super().__init__('mqtt_bridge') # ROS2订阅器 self.sub_ros = self.create_subscription(String, 'ros_topic', self.ros_callback, 10) # MQTT客户端 self.mqtt_client = mqtt.Client() self.mqtt_client.connect("localhost", 1883) self.mqtt_client.subscribe("mqtt_topic") self.mqtt_client.on_message = self.mqtt_callback def ros_callback(self, msg): # 将ROS2消息发至MQTT self.mqtt_client.publish("mqtt_topic", msg.data) def mqtt_callback(self, client, userdata, msg): # 将MQTT消息发至ROS2 ros_msg = String(data=msg.payload.decode()) self.pub_ros.publish(ros_msg) ``` #### **步骤4:数据格式换** - **ROS2消息** ↔ **MQTT负载** 常用方法: - **JSON序列化**:将ROS2消息为JSON字符串传输。 - **Protobuf**:高效二进制编码(需双方统一.proto文件)。 #### **步骤5:安全配置** - **MQTT**:启用TLS加密与用户名/密码认证。 - **ROS2**:配置SROS2管理节点权限与加密通信。 --- ### **4. 实际案例** - **智能仓储机器人** ROS2机器人通过MQTT接收云端调度指令,并上报实时状态至IoT平台。 - **农业物联网** 田间传感器(MQTT)将温湿度数据发至ROS2系统,触发灌溉机器人动作。 --- ### **5. 注意事项** - **网络延迟**:MQTT的QoS设置(如QoS 1确保消息到达)。 - **带宽限制**:避免高频大数据传输(如图像流)。 - **依赖管理**:确保ROS2MQTT库版本兼容。 --- ### **6. 参考资源** - ROS2官方文档:https://docs.ros.org/ - Eclipse Mosquitto:https://mosquitto.org/ - MQTT协议规范:https://mqtt.org/ 通过以上步骤,可实现ROS2MQTT的高效集成,扩展机器人系统与物联网的交互能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值