当前位置:   article > 正文

ROS-机器人操作系统 - - 带你一文入门_机器人与ros概述

机器人与ros概述

ROS-机器人操作系统----带你一文入门

1.1.ROS发展史

  • 2000 ROS起源: 斯坦福大学机器人软件系统框架
  • 2007 ROS正式发布:柳树车库(willow Garage) 发起
  • 2010 ROS 1.0 发布
  • ROS逐渐流行:ROS,Indigo,ROS kinetic(LTS)
    在这里插入图片描述

1.2.什么是ROS

  • ROS 实质上并不是操作系统,而是中间件/类操作系统
    • 硬件抽象
    • 底层设备控制
    • 常用函数实现
    • 进程间消息传递
    • 包管理
  • ROS 官方解释:框架+工具+功能+社区
    • 框架:
      • 分布式
        • 节点: ROS中用节点表示进程,即Node
        • 分布式架构优点:扩展性好,软件复用率高
      • 进程管理
      • 进程通信
      • 框架示意图如下:
    • 工具:
      • 仿真
      • 数据可视化
      • 图形界面
      • 数据记录
    • 功能:
      • 控制
      • 规划
      • 视觉
      • 建图
    • 社区:
      • 软件包管理
      • 教程
      • 文档

1.3 ROS 安装

  • ROS:官方网站:www.ros.org
  • 打开wiki
  • 点击 install
  • 选择 ROS kinetic版本
  • 自己搭建虚拟机ubuntu,更新软件源
  • 按照手册在终端操作即可
  • 教程代码包:重德重能教学代码包(github网站)
  • 安装开发IDE:Roboware(实质是基于vscode)

2.1 ROS 工程结构

在这里插入图片描述

  • catkin 工作空间
    • catkin: ROS定制的编译构建系统,对CMake的扩展
    • 组织和管理功能包的文件夹,以catkin工具编译
    • 操作:
      • 建立工作空间:
        • mkdir -p ~/catkin_ws/src
        • cd ~/catkin_ws/
        • catkin_make
      • 编译:
        • cd ~/catkin_ws #注意,一定要切换到此处进行编译
        • catkin_make
        • source ~/catkin_ws/devel/setup.bash # 编译完成后需要source刷新环境
    • catkin worksapce:
      • src:package 源代码,真正写代码的地方
        • src通常放置功能包package,Eg.package1,package2.etc
        • package是catkin编译的基本单元,catkin编译的对象就是一个又一个的package
        • 编译系统会递归的查询每一个package
        • package
          • ROS软件的基本组织形式,
          • catkin编译的基本单元
          • 一个package可以包含多个可执行文件(节点)
          • 判断package的方式:至少包含CMakeLists.txt,package.xml
            • CMakeLists.txt:
              • 规定catkin编译的规则,例如,源文件,依赖项,目标文件
            • package.xml
              • 定义package的属性,例如:包名,版本号.作者,依赖项
            • scripts:
              • 脚本 (python 文件,shell文件)
            • include
              • C++头文件
            • src
              • C++源文件
              • python modue
            • srv(服务)
              • *.srv
            • msg(消息)
              • *.msg
            • 动作(action)
              • *.action
            • launch:*.launch
            • config:*.yaml
      • build: cmake & catkin 缓存和中间文件
      • devel: 目标文件
        • 头文件
        • 动态链接库
        • 静态链接库
        • 可执行文件
    • rosbuild:ROS早期采用的编译系统,目前主流的ROS项目已经不采用
    • 常用指令:
      • rospack
        • 查找某个pkg的地址
          • rospack find package_name
        • 列出本地所有pkg
          • rospack list
      • roscd
        • 跳转到某个pkg路径下
          • roscd package_name
      • rosls
        • 列举某个pkg下的文件信息
          • rosls package_name
      • rosed
        • 编辑pkg的文件
          • rosed package_name file_name
      • catkin_create_pkg
        • 创建一个pkg
          • catkin_create_pkg <pkg_nmae>[deps]
      • rosdep
        • 安装某个pkg所需的依赖
        • rosdep install [pkg_name]
    • Metapackge
      • 虚包:无实质内容,但是依赖其他软件包,使得安装更加方便
      • Stack:软件包集,现已被Metapackage代替
      • 常见metapackgae
        • navigation:导航相关的功能包集
        • moveit:运动规划相关(主要是机械臂)
        • image_pipeline(图像获取,处理相关的功能包集)
        • vision_opencv(ROS与OpenCV相关的功能包)
        • turtlebot
        • pr2_robot

3.1 ROS 通信架构(计算图级)

  • PR2 机器人
    • roslaunch pr2_bringup pr2.launch
      在这里插入图片描述
  • master(节点管理器)
    • 每个node启动都要向master注册
    • 管理node之间的通信
    • roscore
      • 启动master
      • master:节点管理器
      • rosout:日志输出
      • parameter server:参数服务器
        • 进行参数配置
  • node
    • ROS的进程
    • pkg里的可执行文件运行的实例
    • 启动一个node节点
      • rosrun [pkg_name][node_name]
    • rosnode
      • 列出当前运行的node信息
        • rosnode list
      • 显示某个node的详细信息
        • rosnode info [node_name]
      • 结束某个node
        • rosnode kill[node_name]
    • roslaunch
      • 启动master和多个node
      • roslaunch[pkg_name][file_name.launch]
  • ROS 通信方式:
    • Topic
      • ROS中的异步通信方式
      • Node之间通过publish-subscribe机制通信
      • 多对多
      • Message
        • topic内容的数据类型(类),定义在*.msg文件中
        • 基本msg包括:bool,int8,int16,int32,int64(以及uint),float32,float64,string,time,duration,header,可变长数组array[],固定长度数组array[C]
      • 指令
        • 列出所有topic
          • rostopic list
        • 显示某个topic的属性信息
          • rostopic info /topic_nmae
        • 显示某个topic的内容
          • rostopic echo /topic_name
        • 向某个topic发布内容
          • rostopic pub /topic_name …
        • rosmsg
          • 列出系统所有msg
            • rosmsg list
          • 显示某个msg内容
            • rosmsg show /msg_name
    • Service
      • ROS中的同步通信方式
      • Node之间可以通过request-reply方式通信
      • 多对一
      • 远程过程调用
      • srv
        • Service 通信的数据格式
        • 定义在*.srv文件中
      • 指令:
        • rosservice<----->rostopic
          • 列出当前所有活跃的service
            • rosservice list
          • 显示某个service的属性信息
            • rosservice info service_name
          • 调用某个service
            • rosservice call service_name args
        • rossrv<------>rosmsg
          • 列出系统上所有srv
            • rossrv list
          • 显示某个srv内容
            • rossrv show srv_name
    • Parameter Service(参数服务器)
      • 存储各种参数的字典(<key,value>)
      • 可用命令行,launch文件和node(API)读写
      • 命令
        • 列出当前所有参数
          • rosparam list
        • 显示某个参数的值
          • rosparam get param_key
        • 设置某个参数的值
          • rosparam set param_key param_value
        • 保存参数到文件
          • rosparam dump file_name
        • 从文件读取参数
          • rosparam load file_name
        • 删除文件参数
          • rosparam delete param_key
    • Action
      - 类似Service,带有状态反馈的通信方式
      - 通常在长时间,可抢占的任务中
      - action
      - action 通信的数据格式
      - 定义在*.action中
      - goal:clien发起,result:server发起,feedback:server发起

5.1 ROS常用工具

  • Gazebo
    • 机器人仿真工具:类似的模拟器有V-Rep,Carsim(用于无人驾驶)
    • ODE物理引擎
    • 用于动力学,导航,感知等任务的模拟
  • RViz
    • The Robot Visualization tool 可视化工具
    • 方便监控和调试
  • rqt
    • 可视化工具
    • 常用的rqt_graph,rqt_plot,rqt_console
      • rqt_graph: 显示通信架构
      • rqt_plot:查看原始数据,绘制曲线,查看里程计,码盘
      • rqt_console: 查看日志
  • rosbag
    • ROS 命令行工具
    • 记录和回放数据流
    • 命令
      • 记录某些topic到bag中
        • rosbag record <topic_names>
      • 记录所有topic到bag中
        • rosbag record -a
      • 回放bag
        • rosbag play
  • 专用工具: Moveit(常用于机械臂领域)

6.1 Client Libray(客户端库,类似于API)&&roscpp

  • Client Library
    • 提供ROS编程的库
    • 例如:建立node,发布消息,调用服务
    • 常用的库:
      • roscpp库:对应C++
      • rospy库: 对应python
      • roslisp
  • roscpp:
    • 由ROS提供的由C++与topic,service,param,timer交互的接口
    • ros主要包括:
      • ros::init(): 解析传入的ROS参数,使用roscpp第一步需要用到的函数
        • void ros::init() //解析ROS函数,为本node命名
      • ros::NodeHandle():和topic,service,param等交互的接口
        • NodeHandler 实质上为class类
        • 常用成员函数:
          • 创建话题的publisher
            • ros::Publisher advertise(const string &topic,uint32_t queue_size)
          • 创建话题的subscriber
            • ros::Subscriber subscribe(const string &topic.uint32_t queue_size,void(*)M)
          • 创建服务的Server
          • 创建服务的client
          • 查询某个参数的值
          • 给某个参数赋值
      • ros::master:包含从master查询信息的函数
        • master实质上为Namespace 命名空间
      • ros::this_node:包含查询这个进程(Node)的函数
      • ros::service:包含查询服务的函数
        • service为命名空间
      • ros::param:包含查询参数服务器的函数,而不需要用到NodeHandler
        • param为命名空间
      • ros::name:包含处理ROS图资源名称的函数
  • 6.2 roscpp中topic的常用(模板)写法:
topic_demo:
    
功能描述:
       两个node,一个发布模拟的GPS信息(格式为自定义,包括坐标和工作状态),另一个接收并处理该信息(计算到原点的距离)
步骤:   
     1.package
     2.msg
     3.talker.cpp
     4.listener.cpp
     5.CMakeList.txt&package.xml
实现:
    1.package
     $ cd ~/catkin_ws/src
     $ catkin_create_pkg topic_demo roscpp rospy std_msgs
    2.msg
     $ cd topic_demo/
     $ mdkir msg
     $ vi gps.msg
     gps.msg
        float32 x
        float32 y
        string state
    catkin_make  ++++++> ~/catkin_ws/devel/include/topic_demo/gps.h 使用时只需
    #include<topic_demo/gps.h>
    topic_demo::gps msg;
   3. talker.cpp
    #include<ros/ros.h>
    #include<topic_demo/gps.h>
    int main(int argc char** argv)
    {
        // 解析参数,命名节点
        ros(argc,argv,"talker")
        // 创建句柄,实例化node
        ros::NodeHandler nh;
        // 创建gps信息
        topic_demo::gps msg; 
        msg.x=1.0;
        msg.y=1.0;
        msg.state="working";
        // 创建publisher
        ros::Publisher pub=nh.advertise<topic_demo::gps>("gps_info",1);
        // 定义循环发布的频率
        ros::Rate loop_rate(1.0);
        while(ros::OK()){
        // 以指数增长,每隔1s
         msg.x=1.03*msg.x;
         msg.y=1.01*msg.y;
        // 输出当前msg
         ROS INFO("Talker:GPS:x=%f,y=%f",msg.x,mgs.y)
        // 发布消息
        pub.publish(msg);
        // 根据定义的发布频率,sleep,从而达到周期性发布消息的结果
        loop_rate.sleep();
        }
        return 0;
    }
 4.listener.cpp
   #include<ros/ros.h>
   #include<topic_demo/gps.h>
   #include<std_msgs/Float332.h>
   void gpsCallback(const topic_demo::ggps::ConstPtr&msg)
   {
       std::msgs:Float32 distance;
       distacne.data=sqrt(pow(msg->x,2),pow(msg->y,2))
       ROS_INFO("Listener:Distance to togin=%f,state=%s",distance.data,msg->state.c_str());
   }
   int main(int argc,char**argv)
   {
       ros::init(argc,argv,"listener");
       ros::NodeHandler n;
       // 创建subscriber(订阅者)
       ros::SubScriber sub=n.subscribe("gps_info",gpsCallback)
       // 反复调用当前可触发的回调函数,阻塞
       // ros::spin() 函数作用: 清空当前队列
       // ros::spinOnce() 非阻塞
       ros::spin();
       return 0;
   }
    5.修改CMakeList.txt&package.xml
    6.编译catkin_make,执行rosrun
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 6.3 roscpp中service的常用(模板)写法:
service_demo:

功能描述: 两个node,一个发布请求(格式自定义),另一个接收处理信息,并返回信息

步骤:
      1. package
      2. 定义传输数据格式:srv
      3. server.cpp
      4. client.cpp
      5. CMakeList.txt&package.xml
实现:
      1.package
      $ cd ~/catkin_ws/src
      $ catkin_create_pkg service_demo roscpp rospy std_msgs
      2. srv
      $ cd service_demo/
      $ mkdir srv
      $ vi Greeting.srv
      Greeting.srv
      string name
      int32 age
      ---
      strig feedback
      ++++++++> ~/catkin_ws/devel/include/service_demo/Greeting.h
                                         .../GreetingRequest.h
                                         .../GreetingResponse.h
    3.server.cpp
    #include<ros/ros.h>
    #include<service_demo/Greeting.h>
    bool handler_function(service_demo::Greeting::Request &req,service::demo::Greeting::Response &res)
    {
        // 显示请求信息
        ROS_INFO("Request from %s with age %d",req.name.c_str(),req.age);
        // 处理请求,写入response
        res.feedback="Hi"+req.name+"I am server!";
        // 返回true,正确处理了请求
        return true;
    }
    int main(int argc,char**argv)
    {
        // 解析参数,命名节点
        ros::init(argc,argv,"greeting_server");
        // 创建句柄,实例化节点
        ros::NodeHandler nh;
        ros::ServiceServer service=nh.advertiseService("greetings",handler_function);
        ros::spin();
        return 0;
    }
    4.client.cpp
    #include<ros/ros.h>
    #include<service_demo/Greeting.h>
    int main(int argc,char**argv)
    {
        // 解析参数,命名节点
        ros::init(argc,argv,"greetings_server");
        // 创建节点,实例化节点
        ros::NodeHandler nh;
        ros::ServiceCLient client=nh.serviceClient<service_demo::Greeting>("greetings");
        service::demo::Greeting srv;
        service.request.name="Wu";
        service.request.age="20";
        if(client.call(srv)){
            ROS_INFO("Feedback from server: %s",srv.response.feedback);
        }else{
            ROS_ERROR("Failed to call  service greetings.");
            return 1;
        }
        return 0;
    }
     5.修改CMakeList.txt&package.xml
     6.编译catkin_make,执行rosrun
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 6.4 roscpp中Parameter Server的常用(模板)写法:
param_demo:
     同一功能的两种API: ros::param和ros::NodeHandler
实现:
   #include<ros/ros.h>
   int main(int argc,char **argv)
   {
       ros::init(argc,argv,"greeting_server");
       ros::NodeHandler nh;
       int parameter1,parameter2,parameter3,parameter4,parameter5;
       // 获取参数
       ros::param::get("param1",parameter1);
       nh.getParam("param2",parameter2);
       // 这里123是默认值
       nh.param("param3",parameter3,123)
       // 设置参数
       ros::parameter::set("param4",parameter4);
       nh.setParam("param",parameter5);
       // 检查参数是否存在
       ros::param::has("param5");
       nh.hasParam("param5");
       // 删除参数
       ros::param::del("param5");
       // 删除参数
       ros::param::del("param5");
       nh.deleteParam("param6");

       return 0;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

7.1 Client Libray&&rospy

  • rospy:
    • Node,Topic,Service,Param,Time
    • rospy-Node 相关函数
    • rospy-Topic相关函数
    • rospy-Service 相关函数
    • rospy-Param相关函数
    • rospy-Time相关
  • 7.2 rospy中topic的常用(模板)写法:
topic_demo:
    
功能描述:
       两个node,一个发布模拟的GPS信息(格式为自定义,包括坐标和工作状态),另一个接收并处理该信息(计算到原点的距离)
步骤:   
     1.package
     2.msg
     3.talker.py
     4.listener.py
     5.CMakeList.txt&package.xml
实现:
    1.package
     $ cd ~/catkin_ws/src
     $ catkin_create_pkg topic_demo roscpp rospy std_msgs
    2.msg
     $ cd topic_demo/
     $ mdkir msg
     $ vi gps.msg
     gps.msg
        float32 x
        float32 y
        string state
    catkin_make  ++++++> ~/catkin_ws/devel/include/topic_demo/msg/_init_.py
    topic_demo::gps msg;
    3. talker.py
    #!/usr/bin/env python
    import rospy
    from topic_demo.msg import msg
    def talker():
        pub=rospy.Publisher('gps_info',gps,queue_size=10)
        ros.init_node('pytalker',anonymous=True)
        rate=rospy.Rate(1)
        x=1.0
        y=2.0
        state='Working'
        while not rospy is_shutdown():
         ros.loginfo('Ta;ker:GPS:x=%,y=%f)
         x=1.03*x
         y=1.01*y
         rate.sleep()
    if __name__=='__main__':
      talker()
    4.listener.py
    #!/usr/bin/env python
    import rospy
    import math
    from topic_demo.msg import gps
    def callback(gps):
        distance=math.sqrt(math.pow(gps.x,2),math.pow(gps.y,2))
    def listener():
         ros.init_node('pylistener')
         rospy.Subscriber('gps_info',gps,callback)
         rospy.spin()
    if __name__=='__main__':
         listener()
    5.修改CMakeList.txt&package.xml
    6.编译catkin_make,执行rosrun
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 7.3 rospy中service的常用(模板)写法:
service_demo:

功能描述: 两个node,一个发布请求(格式自定义),另一个接收处理信息,并返回信息

步骤:
      1. package
      2. 定义传输数据格式:srv
      3. server.py
      4. client.py
      5. CMakeList.txt&package.xml
实现:
      1.package
      $ cd ~/catkin_ws/src
      $ catkin_create_pkg service_demo roscpp rospy std_msgs
      2. srv
      $ cd service_demo/
      $ mkdir srv
      $ vi Greeting.srv
      Greeting.srv
      string name
      int32 age
      ---
      strig feedback
      ++++++++> ~/catkin_ws/devel/lib/python2.7/dis-packages/service_demo/srv/__init__.py
    3.server.py
     #!/usr/bin/env python
     import rospy
     from service_demo.srv import *
     def server_srv():
        rospy.init_node('greeting_server')
        # 定义程序的server端
        s=rospy.Service('greetings',Greeting,handle_function)
        rospy.info('Ready to handle the request:')
        rospy.spin()
     def handle_function(req):
        rospy.loginfo('Request from',req.name,'with age',req.age)
        return GreetingResponse('Hi %s I'm server!,%server.name)
     if __name__='__main__':
         server_srv()
    4.client.py
     import rospy
     from service_demo.srv import *
     def client_srv:
        rospy.init_node('greeting_client')
        rospy.wait_for_service('greetings')
        try:
        greetings_client=rospy.ServiceProxy('greetings',Greeting)
        rosp=greetings_client('HAN',20)
        rospy.loginfo('Message From Server:%s',%rosp.feedback)
        except rospy.ServiceException,e:
            rospy.logwarn('Service call failed:%s'%e)
     if __name__=='__main__':
       client_srv()
     5.修改CMakeList.txt&package.xml
     6.编译catkin_make,执行rosrun
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

8.1.ROS之TF&URDF

  • TF
    • TF(TransForm)
      • 坐标变换(位置+姿态)
      • 坐标系数据维护的工具
      • 坐标转换的标准,话题,工具,接口
      • tf tree
      • TransformStamped.msg
    • TF in C++
      • 基本数据类型
      • tf::TransormBroadcaster类
      • tf::TransformListener类
    • TF in python
      • 基本数据类型
      • tf::TransormBroadcaster类
      • tf::TransformListener类
    • 指令:
      • 根据当前的tf树创建一个pdf图
        • rosrun tf view_frames
      • 查看当前的tf树
        • rosrun rqt_tf_tree rqt_tf_tree
      • 查看两个frame之间的坐标关系
        • rosrun tf tf_echo [reference_frame][target_frame]
  • urdf:unifed Robot Description Format 统一机器人描述格式

9.1.SLAM与Map

  • SLAM:Simutaneous Localization And Mpping
  • Gamapping SLAM包
  • Karto SLAM包

10.1 Navaigation-NavigationStack (路径规划,导航)

  • movebase 与插件
    • 全局规划(静态),局部规划(动态),处理异常行为
  • costmap(代价地图)
    • 给导航使用,用于路径规划
    • 两张,2维,多层
      • static layer(map)
        • 静态,不变
      • obstacle layser
        • 动态,支持3D点云的投影
      • infaltion layer
        • 膨胀
  • MapServer
    • 建立地图之后,提供地图
    • 命令
      • 启动map_server 发布地图:
        • rosrun map_server map_server my_map.yaml
      • 保存地图
        • rosrun map_server amp_saver [-f my_map]
  • AMCL(蒙特卡洛自适应定位)
    • 定位
      在这里插入图片描述

(源于mooc 中科院软件所video note) 学习总结,欢迎r读者批评指正!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/999746
推荐阅读
相关标签
  

闽ICP备14008679号