Kinova Gen3机器人:基于ROS2的MQTT数据上传完整实现指南

四足机器人 SLAM 导航实战

从零实现 Unitree Go2 的 SLAM 建图与 ROS2 导航,手把手集成 slam_toolbox

前言

随着机器人技术的快速发展,工业机器人与物联网(IoT)平台的集成变得越来越重要。Kinova Gen3作为一款高性能的协作机器人,在ROS2生态系统中有着广泛的应用。本文将详细介绍如何在Kinova Gen3机器人上通过ROS2框架使用MQTT协议实时上传机器人状态数据,实现机器人与云平台的无缝连接。

背景介绍

Kinova Gen3 机器人

Kinova Gen3是一款7自由度协作机器人,具有以下特点:

  • 高精度定位:重复定位精度达±0.1mm
  • 安全协作:内置力矩传感器,支持安全停止
  • 易于集成:支持多种通信协议和编程接口
  • ROS2原生支持:完整的ROS2驱动程序

ROS2 (Robot Operating System 2)

ROS2 Humble是ROS2的最新长期支持版本,具有以下优势:

  • 分布式架构:支持多机器人系统
  • 实时性能:改进的实时调度能力
  • 安全性增强:内置安全机制
  • 跨平台支持:支持Linux、Windows、macOS

MQTT协议

MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息传输协议,特别适合物联网应用:

  • 低带宽占用:最小化网络流量
  • QoS支持:三种服务质量等级
  • 双向通信:支持发布和订阅模式
  • 广泛支持:各大云平台均支持MQTT

系统架构设计

整体架构

┌─────────────────┐    ROS2 Topics    ┌──────────────────────┐    UDP     ┌──────────────────────┐    MQTT     ┌─────────────────┐
│   Kinova Gen3    │ ──────────────► │ gen3_pose_udp_bridge  │ ───────► │ gen3_mqtt_sender     │ ───────► │  IoT Platform   │
│   (IP:192.168.1.11)│                │ (ROS2 Subscriber)    │            │ (MQTT Publisher)     │            │                 │
│                   │                 │ • 订阅/joint_states   │            │ • 格式化数据         │            │ • 阿里云 IoT    │
│ • 关节状态        │                 │ • 订阅/end_effector   │            │ • 发布到MQTT         │            │ • EMQX          │
│ • 末端执行器位姿   │                 │ • 订阅/imu/data      │            │ • 多平台支持         │            │ • AWS IoT       │
│ • IMU传感器数据   │                 │ • JSON序列化         │            │ • 错误处理           │            │ • 本地Mosquitto │
└─────────────────┘                 └──────────────────────┘            └──────────────────────┘            └─────────────────┘

数据流说明

  1. 数据采集层:Kinova Gen3通过ROS2驱动发布状态数据
  2. 数据处理层:UDP桥接器订阅ROS2主题并进行数据格式转换
  3. 数据传输层:MQTT发送器将数据发布到指定的MQTT Broker
  4. 数据消费层:云平台接收数据并进行存储、分析、可视化

环境准备

系统要求

  • 操作系统:Ubuntu 22.04 LTS
  • ROS2版本:Humble Hawksbill
  • Python版本:3.8+
  • Kinova Gen3:固件版本2.4.0+

安装ROS2 Humble

# 添加ROS2仓库
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

sudo apt install software-properties-common
sudo add-apt-repository universe

sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

# 安装ROS2 Humble
sudo apt update
sudo apt upgrade
sudo apt install ros-humble-desktop

# 设置环境变量
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc

安装Kinova驱动

# 创建工作空间
mkdir -p ~/ros2_kortex_ws/src
cd ~/ros2_kortex_ws/src

# 克隆Kinova ROS2驱动
git clone https://github.com/Kinovarobotics/ros2_kortex.git
git clone https://github.com/Kinovarobotics/kortex_description.git

# 安装依赖
cd ~/ros2_kortex_ws
sudo apt install python3-colcon-common-extensions
rosdep install --from-paths src --ignore-src -r -y

# 编译
colcon build --symlink-install
echo "source ~/ros2_kortex_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

安装MQTT相关依赖

# 安装Mosquitto客户端
sudo apt install mosquitto mosquitto-clients

# 安装Python依赖
pip install paho-mqtt pyyaml numpy transforms3d

核心代码实现

1. ROS2 UDP桥接器 (gen3_pose_udp_bridge.py)

#!/usr/bin/env python3
"""
Gen3 机器人位置信息 UDP 桥接器
订阅 ROS2 主题并通过 UDP 发送 JSON 数据
"""

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState, Imu
from geometry_msgs.msg import PoseStamped
import socket
import json
import time
import argparse
import logging
from tf_transformations import euler_from_quaternion

class Gen3PoseUDPBridge(Node):
    """Gen3 机器人 UDP 桥接器"""

    def __init__(self, config):
        super().__init__('gen3_pose_udp_bridge')
        
        self.config = config
        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        
        # UDP 配置
        self.udp_ip = config.get('udp_ip', '127.0.0.1')
        self.udp_port = config.get('udp_port', 9999)
        
        # 数据存储
        self.latest_data = {
            'joint_states': None,
            'end_effector_pose': None,
            'imu_data': None
        }
        
        # 创建订阅者
        self.create_subscriptions()
        
        # 定时器
        self.timer = self.create_timer(0.1, self.publish_udp_data)  # 10Hz
        
        self.get_logger().info('Gen3 UDP 桥接器已初始化')

    def create_subscriptions(self):
        """创建 ROS2 主题订阅"""
        # 关节状态
        self.joint_sub = self.create_subscription(
            JointState,
            '/joint_states',
            self.joint_callback,
            10
        )
        
        # 末端执行器位姿
        self.pose_sub = self.create_subscription(
            PoseStamped,
            '/end_effector_pose',
            self.pose_callback,
            10
        )
        
        # IMU 数据
        self.imu_sub = self.create_subscription(
            Imu,
            '/imu/data',
            self.imu_callback,
            10
        )

    def joint_callback(self, msg):
        """关节状态回调"""
        self.latest_data['joint_states'] = {
            'timestamp': msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9,
            'names': list(msg.name),
            'position': list(msg.position),
            'velocity': list(msg.velocity),
            'effort': list(msg.effort)
        }

    def pose_callback(self, msg):
        """末端执行器位姿回调"""
        # 四元数转欧拉角
        quaternion = [
            msg.pose.orientation.x,
            msg.pose.orientation.y,
            msg.pose.orientation.z,
            msg.pose.orientation.w
        ]
        roll, pitch, yaw = euler_from_quaternion(quaternion)
        
        self.latest_data['end_effector_pose'] = {
            'timestamp': msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9,
            'position': {
                'x': msg.pose.position.x,
                'y': msg.pose.position.y,
                'z': msg.pose.position.z
            },
            'orientation': {
                'roll': roll,
                'pitch': pitch,
                'yaw': yaw,
                'quaternion': quaternion
            }
        }

    def imu_callback(self, msg):
        """IMU 数据回调"""
        self.latest_data['imu_data'] = {
            'timestamp': msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9,
            'orientation': {
                'x': msg.orientation.x,
                'y': msg.orientation.y,
                'z': msg.orientation.z,
                'w': msg.orientation.w
            },
            'angular_velocity': {
                'x': msg.angular_velocity.x,
                'y': msg.angular_velocity.y,
                'z': msg.angular_velocity.z
            },
            'linear_acceleration': {
                'x': msg.linear_acceleration.x,
                'y': msg.linear_acceleration.y,
                'z': msg.linear_acceleration.z
            }
        }

    def publish_udp_data(self):
        """发布 UDP 数据"""
        try:
            # 构建完整数据包
            data_packet = {
                'timestamp': time.time(),
                'robot_id': 'gen3_001',
                'joint_states': self.latest_data['joint_states'],
                'end_effector_pose': self.latest_data['end_effector_pose'],
                'imu_data': self.latest_data['imu_data']
            }
            
            # 转换为 JSON
            json_data = json.dumps(data_packet, ensure_ascii=False)
            
            # 发送 UDP 数据包
            self.udp_socket.sendto(
                json_data.encode('utf-8'), 
                (self.udp_ip, self.udp_port)
            )
            
            if self.config.get('verbose'):
                self.get_logger().info(f'UDP 数据已发送: {len(json_data)} 字节')
                
        except Exception as e:
            self.get_logger().error(f'UDP 发送失败: {e}')

def main():
    rclpy.init()
    
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Gen3 UDP 桥接器')
    parser.add_argument('--udp_ip', default='127.0.0.1', help='UDP 目标 IP')
    parser.add_argument('--udp_port', type=int, default=9999, help='UDP 目标端口')
    parser.add_argument('--verbose', action='store_true', help='详细输出')
    
    args = parser.parse_args()
    config = vars(args)
    
    # 创建节点
    bridge = Gen3PoseUDPBridge(config)
    
    try:
        rclpy.spin(bridge)
    except KeyboardInterrupt:
        pass
    finally:
        bridge.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

2. MQTT发送器 (gen3_mqtt_sender.py)

#!/usr/bin/env python3
"""
Gen3 机器人 MQTT 发送器
接收 UDP 数据并发布到 MQTT Broker
"""

import socket
import json
import time
import logging
import argparse
import paho.mqtt.client as mqtt
from datetime import datetime

class Gen3MQTTSender:
    """Gen3 机器人 MQTT 发送器"""

    def __init__(self, config):
        self.config = config
        
        # MQTT 配置
        self.broker = config.get('mqtt_broker', 'localhost')
        self.port = config.get('mqtt_port', 1883)
        self.username = config.get('username')
        self.password = config.get('password')
        self.topic_prefix = config.get('topic_prefix', 'gen3')
        
        # UDP 配置
        self.udp_ip = config.get('udp_ip', '127.0.0.1')
        self.udp_port = config.get('udp_port', 9999)
        
        # 统计信息
        self.stats = {
            'messages_received': 0,
            'messages_published': 0,
            'errors': 0,
            'start_time': time.time()
        }
        
        # MQTT 客户端
        self.mqtt_client = None
        self.udp_socket = None

    def connect_mqtt(self):
        """连接到 MQTT Broker"""
        try:
            self.mqtt_client = mqtt.Client()
            
            # 设置认证
            if self.username and self.password:
                self.mqtt_client.username_pw_set(self.username, self.password)
            
            # 设置回调
            self.mqtt_client.on_connect = self.on_connect
            self.mqtt_client.on_disconnect = self.on_disconnect
            
            # 连接
            self.mqtt_client.connect(self.broker, self.port, 60)
            self.mqtt_client.loop_start()
            
            return True
        except Exception as e:
            logging.error(f"MQTT 连接失败: {e}")
            return False

    def on_connect(self, client, userdata, flags, rc):
        """MQTT 连接回调"""
        if rc == 0:
            logging.info(f"✅ 成功连接到 MQTT Broker: {self.broker}:{self.port}")
        else:
            logging.error(f"❌ MQTT 连接失败,代码: {rc}")

    def on_disconnect(self, client, userdata, rc):
        """MQTT 断开回调"""
        logging.warning(f"MQTT 连接断开,代码: {rc}")

    def setup_udp(self):
        """设置 UDP 监听"""
        try:
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.udp_socket.bind((self.udp_ip, self.udp_port))
            self.udp_socket.settimeout(1.0)  # 1秒超时
            logging.info(f"✅ UDP 监听启动: {self.udp_ip}:{self.udp_port}")
            return True
        except Exception as e:
            logging.error(f"❌ UDP 设置失败: {e}")
            return False

    def process_udp_data(self, data):
        """处理 UDP 数据并发布到 MQTT"""
        try:
            # 解析 JSON 数据
            udp_data = json.loads(data)
            self.stats['messages_received'] += 1
            
            # 发布关节状态
            if udp_data.get('joint_states'):
                joint_topic = f"{self.topic_prefix}/joint_states"
                joint_payload = self.format_joint_states(udp_data['joint_states'])
                self.publish_message(joint_topic, joint_payload)
            
            # 发布末端执行器位姿
            if udp_data.get('end_effector_pose'):
                pose_topic = f"{self.topic_prefix}/end_effector_pose"
                pose_payload = self.format_pose(udp_data['end_effector_pose'])
                self.publish_message(pose_topic, pose_payload)
            
            # 发布 IMU 数据
            if udp_data.get('imu_data'):
                imu_topic = f"{self.topic_prefix}/imu"
                imu_payload = self.format_imu(udp_data['imu_data'])
                self.publish_message(imu_topic, imu_payload)
            
            # 发布状态信息
            status_topic = f"{self.topic_prefix}/status"
            status_payload = self.format_status()
            self.publish_message(status_topic, status_payload)
            
        except json.JSONDecodeError as e:
            logging.error(f"JSON 解析失败: {e}")
            self.stats['errors'] += 1
        except Exception as e:
            logging.error(f"数据处理失败: {e}")
            self.stats['errors'] += 1

    def format_joint_states(self, data):
        """格式化关节状态数据"""
        return {
            'timestamp': datetime.now().isoformat(),
            'robot_id': 'gen3_001',
            'joint_count': len(data.get('position', [])),
            'names': data.get('names', []),
            'position': data.get('position', []),
            'velocity': data.get('velocity', []),
            'effort': data.get('effort', [])
        }

    def format_pose(self, data):
        """格式化位姿数据"""
        return {
            'timestamp': datetime.now().isoformat(),
            'robot_id': 'gen3_001',
            'position': data.get('position', {}),
            'orientation': data.get('orientation', {})
        }

    def format_imu(self, data):
        """格式化 IMU 数据"""
        return {
            'timestamp': datetime.now().isoformat(),
            'robot_id': 'gen3_001',
            'orientation': data.get('orientation', {}),
            'angular_velocity': data.get('angular_velocity', {}),
            'linear_acceleration': data.get('linear_acceleration', {})
        }

    def format_status(self):
        """格式化状态数据"""
        uptime = time.time() - self.stats['start_time']
        return {
            'timestamp': datetime.now().isoformat(),
            'robot_id': 'gen3_001',
            'status': 'online',
            'uptime': round(uptime, 1),
            'messages_received': self.stats['messages_received'],
            'messages_published': self.stats['messages_published'],
            'errors': self.stats['errors']
        }

    def publish_message(self, topic, payload):
        """发布 MQTT 消息"""
        try:
            json_payload = json.dumps(payload, ensure_ascii=False)
            self.mqtt_client.publish(topic, json_payload, qos=1)
            self.stats['messages_published'] += 1
            
            if self.config.get('verbose'):
                logging.info(f"📤 发布到 {topic}: {len(json_payload)} 字节")
                
        except Exception as e:
            logging.error(f"消息发布失败: {e}")
            self.stats['errors'] += 1

    def run(self):
        """主循环"""
        logging.info("=" * 60)
        logging.info("  Gen3 机器人 MQTT 发送器")
        logging.info("=" * 60)
        logging.info(f"UDP: {self.udp_ip}:{self.udp_port}")
        logging.info(f"MQTT: {self.broker}:{self.port}")
        logging.info(f"主题前缀: {self.topic_prefix}")
        logging.info("=" * 60)
        
        # 连接 MQTT
        if not self.connect_mqtt():
            logging.error("无法连接到 MQTT Broker")
            return
        
        # 设置 UDP
        if not self.setup_udp():
            logging.error("无法设置 UDP 监听")
            return
        
        logging.info("✅ 系统启动完成,等待数据...")
        
        try:
            while True:
                try:
                    # 接收 UDP 数据
                    data, addr = self.udp_socket.recvfrom(65535)
                    data_str = data.decode('utf-8')
                    self.process_udp_data(data_str)
                    
                except socket.timeout:
                    continue
                    
        except KeyboardInterrupt:
            logging.info("\n⏹️ 收到中断信号,正在关闭...")
        finally:
            self.cleanup()

    def cleanup(self):
        """清理资源"""
        if self.mqtt_client:
            self.mqtt_client.loop_stop()
            self.mqtt_client.disconnect()
        if self.udp_socket:
            self.udp_socket.close()
        logging.info("✅ 资源已释放")

def main():
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format='[%(asctime)s] %(levelname)s - %(message)s'
    )
    
    parser = argparse.ArgumentParser(description='Gen3 MQTT 发送器')
    parser.add_argument('--mqtt_broker', default='localhost', help='MQTT Broker 地址')
    parser.add_argument('--mqtt_port', type=int, default=1883, help='MQTT Broker 端口')
    parser.add_argument('--username', help='MQTT 用户名')
    parser.add_argument('--password', help='MQTT 密码')
    parser.add_argument('--topic_prefix', default='gen3', help='MQTT 主题前缀')
    parser.add_argument('--udp_ip', default='127.0.0.1', help='UDP 监听 IP')
    parser.add_argument('--udp_port', type=int, default=9999, help='UDP 监听端口')
    parser.add_argument('--verbose', action='store_true', help='详细输出')
    
    args = parser.parse_args()
    config = vars(args)
    
    sender = Gen3MQTTSender(config)
    sender.run()

if __name__ == '__main__':
    main()

配置和使用

1. 本地Mosquitto测试

# 启动Mosquitto
sudo systemctl start mosquitto

# 终端1: 启动Gen3驱动
source /opt/ros/humble/setup.bash
source ~/ros2_kortex_ws/install/setup.bash
ros2 launch kortex_bringup gen3.launch.py robot_ip:=192.168.1.11

# 终端2: 启动UDP桥接器
python3 gen3_pose_udp_bridge.py --verbose

# 终端3: 启动MQTT发送器
python3 gen3_mqtt_sender.py --mqtt_broker localhost --verbose

# 终端4: 监听消息
mosquitto_sub -h localhost -p 1883 -t "gen3/#" -v

2. 云平台配置

阿里云IoT配置
python3 gen3_mqtt_sender.py \
    --mqtt_broker iot-xxx.aliyuncs.com \
    --mqtt_port 1883 \
    --username gen3_device@product_id \
    --password device_secret \
    --topic_prefix gen3
AWS IoT Core配置
python3 gen3_mqtt_sender.py \
    --mqtt_broker xxx-ats.iot.us-east-1.amazonaws.com \
    --mqtt_port 8883 \
    --topic_prefix gen3

测试和验证

1. 单元测试

# 测试MQTT连接
python3 -c "
import paho.mqtt.client as mqtt
client = mqtt.Client()
client.connect('localhost', 1883, 60)
print('MQTT连接成功')
client.disconnect()
"

2. 集成测试

# 发送测试数据
echo '{
  "timestamp": 1234567890,
  "robot_id": "gen3_test",
  "joint_states": {
    "position": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.0],
    "velocity": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.0]
  }
}' | nc -u 127.0.0.1 9999

3. 性能测试

# 监控消息频率
timeout 10 mosquitto_sub -h localhost -p 1883 -t "gen3/#" | wc -l
# 预期输出: 80-120 条消息(10秒内)

数据格式说明

关节状态数据

{
  "timestamp": "2024-01-15T10:30:45.123456",
  "robot_id": "gen3_001",
  "joint_count": 7,
  "names": ["joint_1", "joint_2", "joint_3", "joint_4", "joint_5", "joint_6", "joint_7"],
  "position": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.0],
  "velocity": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.0],
  "effort": [1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 0.0]
}

末端执行器位姿

{
  "timestamp": "2024-01-15T10:30:45.123456",
  "robot_id": "gen3_001",
  "position": {
    "x": 0.45,
    "y": 0.12,
    "z": 0.38
  },
  "orientation": {
    "roll": 0.05,
    "pitch": -0.02,
    "yaw": 0.15,
    "quaternion": [0.0, 0.0, 0.0, 1.0]
  }
}

故障排除

常见问题

  1. ROS2主题不存在

    ros2 topic list
    # 确保Gen3驱动正在运行
    
  2. MQTT连接失败

    # 检查Mosquitto状态
    sudo systemctl status mosquitto
    # 测试连接
    mosquitto_pub -h localhost -t test -m hello
    
  3. 网络连接问题

    # 检查Gen3连接
    ping 192.168.1.11
    # 检查网络配置
    ip addr show
    

    基于常见问题,快速诊断:

问题症状解决方案
Gen3连接失败Connection refusedping 192.168.1.11;检查网线/电源;sudo ufw disable防火墙。
ROS2话题缺失/joint_states重启驱动:ros2 launch kinova_gen3_bringup gen3.launch.pyros2 topic list验证。
MQTT连接失败Broker unreachablesudo systemctl start mosquitto;测试mosquitto_pub -h localhost -p 1883 -t test -m hello
无数据上传日志无Published检查UDP端口`netstat -tuln
YAML配置错误Syntax errorpython3 -c "import yaml; yaml.safe_load(open('config/default.yaml'))"验证缩进。

诊断脚本:运行./diagnose.sh获取报告。


性能优化

1. 调整发布频率

# 在UDP桥接器中修改定时器间隔
self.timer = self.create_timer(0.05, self.publish_udp_data)  # 20Hz

2. MQTT QoS设置

# 使用QoS 1确保消息送达
self.mqtt_client.publish(topic, payload, qos=1)

3. 数据压缩

# 启用数据压缩减少带宽
import gzip
compressed_data = gzip.compress(json_data.encode())

4. 云平台集成

  • 阿里云:编辑config/aliyun_iot.yaml,填入ProductKey/DeviceId/Secret;启动时选3。
  • AWS:上传证书到config/aws_iot_core.yaml,端口8883(TLS)。
  • 性能调优:修改publish.interval: 0.1(10Hz),支持5-50Hz。

5. 未来扩展

  • 双向控制:云端下发命令控制Gen3。
  • 数据可视化:集成Grafana仪表盘。
  • 多机器人:支持并发管理。

总结

本文详细介绍了在Kinova Gen3机器人上通过ROS2使用MQTT协议上传机器人状态的完整解决方案。通过UDP桥接的方式,我们实现了ROS2与MQTT之间的数据转换,支持多种云平台的集成。

核心优势

  1. 实时性强:10Hz的数据更新频率满足大多数应用需求
  2. 可靠性高:MQTT QoS机制确保数据可靠传输
  3. 扩展性好:支持多种云平台和自定义数据格式
  4. 易于维护:模块化设计,代码结构清晰

应用场景

  • 远程监控:实时监控机器人状态和运行情况
  • 数据分析:收集机器人运行数据进行性能分析
  • 故障诊断:基于状态数据进行 predictive maintenance
  • 人机协作:为协作机器人提供状态反馈

未来展望

随着机器人技术的不断发展,预计会有更多高级功能被集成:

  • 双向通信:支持云端控制指令下发
  • 边缘计算:在机器人端进行初步数据处理
  • AI集成:结合机器学习进行智能状态分析
  • 多机器人协调:支持多机器人系统的协同工作

这个解决方案为机器人与物联网平台的深度集成提供了可靠的技术基础,希望能为广大机器人开发者提供有价值的参考。

参考资源


完整代码和文档已开源至GitHubGen3-MQTT-Bridge

如果本文对你有帮助,记得点赞+收藏! 🚀
CSDN专栏:机器人开发与IoT实践
欢迎Star和Fork!如果您有任何问题或建议,欢迎在评论区交流。

四足机器人 SLAM 导航实战

从零实现 Unitree Go2 的 SLAM 建图与 ROS2 导航,手把手集成 slam_toolbox

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值