【ROS2】中级:tf2-添加框架(Python)

目标:学习如何向 tf2 添加额外的框架。

教程级别:中级

 时间:15 分钟

 目录

  •  背景

  •  tf2 树

  •  任务

    • 1 编写固定帧广播器

    • 1.1 检查代码

    • 1.2 添加入口点

    • 1.3 编写启动文件

    •  1.4 构建

    •  1.5 运行

    • 2 编写动态帧广播器

    • 2.1 检查代码

    • 2.2 添加入口点

    • 2.3 编写启动文件

    •  2.4 构建

    •  2.5 运行

  •  摘要

 背景

之前的教程中,我们通过编写 tf2 广播器和 tf2 监听器重新创建了乌龟演示。本教程将教您如何向变换树添加额外的固定和动态帧。事实上,在 tf2 中添加帧与创建 tf2 广播器非常相似,但本示例将向您展示 tf2 的一些附加功能

对于许多与变换相关的任务,在局部坐标系中思考更容易。例如,最容易在激光扫描仪中心的坐标系中推理激光扫描测量值。tf2 允许您为系统中的每个传感器、链接或关节定义一个局部坐标系。当从一个坐标系变换到另一个坐标系时,tf2 将处理所有引入的隐藏中间坐标系变换

20d2e6602c0a7633ba11f8c43593a749.png

 任务

1 编写固定帧广播器

在我们的乌龟示例中,我们将添加一个新的框架 carrot1 ,它将是 turtle1 的子框架。这个框架将作为第二只乌龟的目标。

首先创建源文件。进入我们在前面的教程中创建的 learning_tf2_py 包。在 src/learning_tf2_py/learning_tf2_py 目录中,通过输入以下命令下载固定帧广播代码:

cxy@ubuntu2404-cxy:~$ cd ~/ros2_ws/src/learning_tf2_py/learning_tf2_py
cxy@ubuntu2404-cxy:~/ros2_ws/src/learning_tf2_py/learning_tf2_py$ wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_py/turtle_tf2_py/fixed_frame_tf2_broadcaster.py

现在打开名为 fixed_frame_tf2_broadcaster.py 的文件。

from geometry_msgs.msg import TransformStamped  # 从 geometry_msgs.msg 导入 TransformStamped 消息类型


import rclpy  # 导入 rclpy 库
from rclpy.node import Node  # 从 rclpy.node 导入 Node 类


from tf2_ros import TransformBroadcaster  # 从 tf2_ros 导入 TransformBroadcaster 类




class FixedFrameBroadcaster(Node):  # 定义一个名为 FixedFrameBroadcaster 的类,继承自 Node 类


    def __init__(self):  # 初始化方法
        super().__init__('fixed_frame_tf2_broadcaster')  # 调用父类的初始化方法,并设置节点名称为 'fixed_frame_tf2_broadcaster'
        self.tf_broadcaster = TransformBroadcaster(self)  # 创建一个 TransformBroadcaster 对象
        self.timer = self.create_timer(0.1, self.broadcast_timer_callback)  # 创建一个定时器,每 0.1 秒调用一次 broadcast_timer_callback 方法


    def broadcast_timer_callback(self):  # 定义定时器回调方法
        t = TransformStamped()  # 创建一个 TransformStamped 对象


        t.header.stamp = self.get_clock().now().to_msg()  # 设置时间戳为当前时间
        t.header.frame_id = 'turtle1'  # 设置父坐标系 ID 为 'turtle1'
        t.child_frame_id = 'carrot1'  # 设置子坐标系 ID 为 'carrot1'
        t.transform.translation.x = 0.0  # 设置平移变换的 x 坐标为 0.0
        t.transform.translation.y = 2.0  # 设置平移变换的 y 坐标为 2.0
        t.transform.translation.z = 0.0  # 设置平移变换的 z 坐标为 0.0
        t.transform.rotation.x = 0.0  # 设置旋转变换的 x 分量为 0.0
        t.transform.rotation.y = 0.0  # 设置旋转变换的 y 分量为 0.0
        t.transform.rotation.z = 0.0  # 设置旋转变换的 z 分量为 0.0
        t.transform.rotation.w = 1.0  # 设置旋转变换的 w 分量为 1.0


        self.tf_broadcaster.sendTransform(t)  # 发送变换




def main():  # 主函数
    rclpy.init()  # 初始化 rclpy
    node = FixedFrameBroadcaster()  # 创建一个 FixedFrameBroadcaster 节点
    try:
        rclpy.spin(node)  # 运行节点
    except KeyboardInterrupt:  # 捕捉键盘中断异常
        pass


    rclpy.shutdown()  # 关闭 rclpy

代码与 tf2 广播器教程示例非常相似,唯一的区别是这里的变换不会随时间变化。

1.1 检查代码

让我们看看这段代码中的关键行。这里我们创建了一个新的变换,从父 turtle1 到新的子 carrot1 。 carrot1 框架在 y 轴上相对于 turtle1 框架偏移了 2 米。

t = TransformStamped()


t.header.stamp = self.get_clock().now().to_msg()
t.header.frame_id = 'turtle1'
t.child_frame_id = 'carrot1'
t.transform.translation.x = 0.0
t.transform.translation.y = 2.0
t.transform.translation.z = 0.0
1.2 添加入口点

要允许 ros2 run 命令运行您的节点,您必须将入口点添加到 setup.py (位于 src/learning_tf2_py 目录中)。

在 'console_scripts': 括号之间添加以下行:

'fixed_frame_tf2_broadcaster = learning_tf2_py.fixed_frame_tf2_broadcaster:main',
1.3 编写启动文件

现在让我们为这个示例创建一个启动文件。使用您的文本编辑器,在 src/learning_tf2_py/launch 目录中创建一个名为 turtle_tf2_fixed_frame_demo_launch.py 的新文件,并添加以下几行代码:

import os  # 导入os模块,用于处理文件和目录


from ament_index_python.packages import get_package_share_directory  # 导入get_package_share_directory函数,用于获取包的共享目录


from launch import LaunchDescription  # 导入LaunchDescription类,用于描述启动配置
from launch.actions import IncludeLaunchDescription  # 导入IncludeLaunchDescription类,用于包含其他的启动描述
from launch.launch_description_sources import PythonLaunchDescriptionSource  # 导入PythonLaunchDescriptionSource类,用于从Python文件加载启动描述


from launch_ros.actions import Node  # 导入Node类,用于描述ROS节点




def generate_launch_description():  # 定义一个名为generate_launch_description的函数
    demo_nodes = IncludeLaunchDescription(  # 创建一个IncludeLaunchDescription对象
        PythonLaunchDescriptionSource([os.path.join(  # 使用os.path.join函数拼接路径
            get_package_share_directory('learning_tf2_py'), 'launch'),  # 获取'learning_tf2_py'包的共享目录,并加上'launch'
            '/turtle_tf2_demo_launch.py']),  # 加上'/turtle_tf2_demo_launch.py'
        )


    return LaunchDescription([  # 返回一个LaunchDescription对象
        demo_nodes,  # 包含demo_nodes
        Node(  # 创建一个Node对象
            package='learning_tf2_py',  # 设置包名为'learning_tf2_py'
            executable='fixed_frame_tf2_broadcaster',  # 设置可执行文件名为'fixed_frame_tf2_broadcaster'
            name='fixed_broadcaster',  # 设置节点名为'fixed_broadcaster'
        ),
    ])

此启动文件导入所需的包,然后创建一个 demo_nodes 变量,该变量将存储我们在上一个教程的启动文件中创建的节点。

代码的最后部分将使用我们的 fixed_frame_tf2_broadcaster 节点将固定的 carrot1 框架添加到 turtlesim 世界中。

Node(
    package='learning_tf2_py',
    executable='fixed_frame_tf2_broadcaster',
    name='fixed_broadcaster',
),
 1.4 构建 

在工作区的根目录运行 rosdep 以检查缺失的依赖项。

rosdep install -i --from-path src --rosdistro jazzy -y

仍在工作区的根目录下,构建您的包:

colcon build --packages-select learning_tf2_py

打开一个新的终端,导航到工作区的根目录,然后加载设置文件:

. install/setup.bash
 1.5 运行 

现在你可以开始乌龟广播演示了

a1b9a0c48d60c4857c7a8952c4b1990f.png

ros2 launch learning_tf2_py turtle_tf2_fixed_frame_demo_launch.py

您应该注意到新的 carrot1 框架出现在变换树中。

c8a73a778d8db8441f66b5955dfc0106.png

 ros2 run tf2_tools view_frames

如果你驾驶第一只乌龟,你应该注意到行为没有从以前的教程中改变,即使我们添加了一个新的框架。那是因为添加一个额外的框架不会影响其他框架,我们的监听器仍然使用先前定义的框架。

因此,如果我们希望第二只乌龟跟随carrot1而不是第一只乌龟,我们需要更改 target_frame 的值。这可以通过两种方式完成。一种方法是直接从控制台将 target_frame 参数传递给启动文件:

ee3e0a79e487556cf1c3003fe0fb07ef.png

ros2 launch learning_tf2_py turtle_tf2_fixed_frame_demo_launch.py target_frame:=carrot1

‍第二种方法是更新启动文件。为此,请打开 turtle_tf2_fixed_frame_demo_launch.py 文件,并通过 launch_arguments 参数添加 'target_frame': 'carrot1' 参数。

def generate_launch_description():
    demo_nodes = IncludeLaunchDescription(
        ...,
        launch_arguments={'target_frame': 'carrot1'}.items(),
        )

现在重新构建包,重启 turtle_tf2_fixed_frame_demo_launch.py ,你会看到第二只乌龟跟着胡萝卜carrot1而不是第一只乌龟!

2 编写动态帧广播器

我们在本教程中发布的额外帧是一个固定帧,它不会随时间相对于父帧发生变化。然而,如果您想发布一个移动帧,可以编写广播器代码使帧随时间变化。让我们更改我们的 carrot1 帧,使其随时间相对于 turtle1 帧发生变化。转到我们在上一个教程中创建的 learning_tf2_py 包。在 src/learning_tf2_py/learning_tf2_py 目录中,通过输入以下命令下载动态帧广播器代码:

cxy@ubuntu2404-cxy:~/ros2_ws/src/learning_tf2_py/learning_tf2_py$ wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_py/turtle_tf2_py/dynamic_frame_tf2_broadcaster.py

现在打开名为 dynamic_frame_tf2_broadcaster.py 的文件:

import math  # 导入 math 库


from geometry_msgs.msg import TransformStamped  # 从 geometry_msgs.msg 导入 TransformStamped 消息类型


import rclpy  # 导入 rclpy 库
from rclpy.node import Node  # 从 rclpy.node 导入 Node 类


from tf2_ros import TransformBroadcaster  # 从 tf2_ros 导入 TransformBroadcaster 类




class DynamicFrameBroadcaster(Node):  # 定义一个名为 DynamicFrameBroadcaster 的类,继承自 Node 类


    def __init__(self):  # 初始化方法
        super().__init__('dynamic_frame_tf2_broadcaster')  # 调用父类的初始化方法,并设置节点名称为 'dynamic_frame_tf2_broadcaster'
        self.tf_broadcaster = TransformBroadcaster(self)  # 创建一个 TransformBroadcaster 对象
        self.timer = self.create_timer(0.1, self.broadcast_timer_callback)  # 创建一个定时器,每 0.1 秒调用一次 broadcast_timer_callback 方法


    def broadcast_timer_callback(self):  # 定义定时器回调方法
        seconds, _ = self.get_clock().now().seconds_nanoseconds()  # 获取当前时间的秒数
        x = seconds * math.pi  # 计算 x 值


        t = TransformStamped()  # 创建一个 TransformStamped 对象
        t.header.stamp = self.get_clock().now().to_msg()  # 设置时间戳为当前时间
        t.header.frame_id = 'turtle1'  # 设置父坐标系 ID 为 'turtle1'
        t.child_frame_id = 'carrot1'  # 设置子坐标系 ID 为 'carrot1'
        t.transform.translation.x = 10 * math.sin(x)  # 设置平移变换的 x 坐标为 10 * sin(x)
        t.transform.translation.y = 10 * math.cos(x)  # 设置平移变换的 y 坐标为 10 * cos(x)
        t.transform.translation.z = 0.0  # 设置平移变换的 z 坐标为 0.0
        t.transform.rotation.x = 0.0  # 设置旋转变换的 x 分量为 0.0
        t.transform.rotation.y = 0.0  # 设置旋转变换的 y 分量为 0.0
        t.transform.rotation.z = 0.0  # 设置旋转变换的 z 分量为 0.0
        t.transform.rotation.w = 1.0  # 设置旋转变换的 w 分量为 1.0


        self.tf_broadcaster.sendTransform(t)  # 发送变换




def main():  # 主函数
    rclpy.init()  # 初始化 rclpy
    node = DynamicFrameBroadcaster()  # 创建一个 DynamicFrameBroadcaster 节点
    try:
        rclpy.spin(node)  # 运行节点
    except KeyboardInterrupt:  # 捕捉键盘中断异常
        pass


    rclpy.shutdown()  # 关闭 rclpy
2.1 检查代码 

相对于我们 x 和 y 偏移量的固定定义,我们使用当前时间的 sin() 和 cos() 函数,因此 carrot1 的偏移量在不断变化。

seconds, _ = self.get_clock().now().seconds_nanoseconds()
x = seconds * math.pi
...
t.transform.translation.x = 10 * math.sin(x)
t.transform.translation.y = 10 * math.cos(x)
2.2 添加入口点

要允许 ros2 run 命令运行您的节点,您必须将入口点添加到 setup.py (位于 src/learning_tf2_py 目录中)。

在 'console_scripts': 括号之间添加以下行:

'dynamic_frame_tf2_broadcaster = learning_tf2_py.dynamic_frame_tf2_broadcaster:main',
2.3 编写启动文件 

要测试此代码,请在 src/learning_tf2_py/launch 目录中创建一个新的启动文件 turtle_tf2_dynamic_frame_demo_launch.py ,并粘贴以下代码:

import os  # 导入 os 模块


from ament_index_python.packages import get_package_share_directory  # 从 ament_index_python.packages 导入 get_package_share_directory 函数


from launch import LaunchDescription  # 从 launch 导入 LaunchDescription 类
from launch.actions import IncludeLaunchDescription  # 从 launch.actions 导入 IncludeLaunchDescription 类
from launch.launch_description_sources import PythonLaunchDescriptionSource  # 从 launch.launch_description_sources 导入 PythonLaunchDescriptionSource 类


from launch_ros.actions import Node  # 从 launch_ros.actions 导入 Node 类




def generate_launch_description():  # 定义 generate_launch_description 函数
    demo_nodes = IncludeLaunchDescription(  # 创建 IncludeLaunchDescription 对象
        PythonLaunchDescriptionSource([os.path.join(  # 使用 PythonLaunchDescriptionSource 指定启动文件路径
            get_package_share_directory('learning_tf2_py'), 'launch'),  # 获取 'learning_tf2_py' 包的共享目录,并指定 'launch' 文件夹
            '/turtle_tf2_demo_launch.py']),  # 指定启动文件名
       launch_arguments={'target_frame': 'carrot1'}.items(),  # 设置启动参数 'target_frame' 为 'carrot1'
       )


    return LaunchDescription([  # 返回 LaunchDescription 对象
        demo_nodes,  # 包含 demo_nodes
        Node(  # 创建 Node 对象
            package='learning_tf2_py',  # 指定包名为 'learning_tf2_py'
            executable='dynamic_frame_tf2_broadcaster',  # 指定可执行文件名为 'dynamic_frame_tf2_broadcaster'
            name='dynamic_broadcaster',  # 设置节点名称为 'dynamic_broadcaster'
        ),
    ])
 2.4 构建 

在工作区的根目录运行 rosdep 以检查缺失的依赖项。

rosdep install -i --from-path src --rosdistro jazzy -y

仍在工作区的根目录下,构建您的包:

colcon build --packages-select learning_tf2_py

打开一个新的终端,导航到工作区的根目录,然后加载设置文件:

. install/setup.bash
 1.5 运行 

现在你可以开始动态框架演示了:

ros2 launch learning_tf2_py turtle_tf2_dynamic_frame_demo_launch.py

8e82ae0b35ed68673f0ea2527ad79a40.png

102f9ed5d92ffd4e02298e1fb4060109.png

你应该看到第二只乌龟正在跟随不断变化的胡萝卜的位置。

 摘要

在本教程中,您了解了 tf2 变换树、其结构及其特性。您还了解到,在本地框架内思考是最容易的,并学会了为该本地框架添加额外的固定和动态框架。

下列软件包有未满足的依赖关系: python3-rosdistro-modules : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 python3-rospkg-modules : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-ament-cmake-core : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-qt-gui : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-ros2doctor : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-ros2pkg : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-rqt-gui : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-rqt-msg : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-rqt-plot : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-rqt-publisher : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-humble-rqt-shell : 依赖: python3-catkin-pkg-modules 但是它将不会被安装 ros-rolling-turtlesim : 依赖: ros-rolling-geometry-msgs 但是它将不会被安装 依赖: ros-rolling-rcl-interfaces 但是它将不会被安装 依赖: ros-rolling-rclcpp 但是它将不会被安装 依赖: ros-rolling-rclcpp-action 但是它将不会被安装 依赖: ros-rolling-rosidl-default-runtime 但是它将不会被安装 依赖: ros-rolling-std-msgs 但是它将不会被安装 依赖: ros-rolling-std-srvs 但是它将不会被安装 E: 有未能满足的依赖关系。请尝试不指明软件包的名字来运行“apt --fix-broken install”(也可以指定一个解决办法)
07-05
<think>我们正在解决ROS安装过程中出现的依赖问题。根据引用,问题通常发生在Ubuntu系统上安装ROS时,具体表现为python3-catkin-pkg-modules等依赖包无法安装,导致其他ROS包安装失败。错误信息提示有未满足的依赖关系,并建议运行`apt --fix-broken install`来尝试修复。 根据引用[2]和[3],错误信息中提到了多个依赖包无法安装,例如python-catkin-pkg、python-rosdep-modules等都有依赖问题。引用[4]提供了一个参考链接,但我们可以先尝试通用的解决方法。 解决步骤: 1. **更新软件包列表**:确保软件包列表是最新的,以避免因旧索引导致的依赖问题。 2. **尝试修复损坏的依赖**:运行`sudo apt --fix-broken install`,这是错误信息中建议的命令。 3. **安装缺失的依赖**:如果上一步不能解决问题,尝试单独安装缺失的依赖包。例如,根据错误信息,可能需要安装`python3-catkin-pkg-modules`,但注意版本要求。 4. **检查软件源**:确保已正确设置ROS的软件源(sources.list)和密钥(key)。 5. **清除apt缓存**:有时候损坏的缓存文件会导致安装失败,可以尝试清除缓存。 6. **尝试安装完整的ROS桌面版**:如果单独安装包失败,可以尝试安装整个ROS桌面版(如`ros-melodic-desktop-full`),这可能会自动解决依赖问题。 具体操作: 步骤1:更新软件包列表 ```bash sudo apt update ``` 步骤2:修复损坏的依赖 ```bash sudo apt --fix-broken install ``` 步骤3:如果步骤2没有解决问题,尝试安装特定的依赖包(根据错误信息,可能需要安装`python3-catkin-pkg-modules`,但注意版本) ```bash sudo apt install python3-catkin-pkg-modules ``` 如果上述命令失败,可能是因为版本冲突或者软件源中没有合适的版本。此时,可以考虑添加正确的ROS软件源。 步骤4:检查软件源 确保你的软件源中包含ROS的源。例如,对于Melodic(Ubuntu 18.04),应该有以下行: ```bash sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' ``` 然后导入密钥: ```bash sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 ``` 然后再次更新: ```bash sudo apt update ``` 步骤5:清除apt缓存 ```bash sudo apt clean sudo apt autoclean sudo rm -rf /var/lib/apt/lists/* sudo apt update ``` 步骤6:安装完整的ROS桌面版(如果之前尝试安装的是部分包) ```bash sudo apt install ros-melodic-desktop-full ``` 注意:以上步骤中的`melodic`需要根据你安装的ROS版本替换(如noetic、kinetic等),Ubuntu版本也需要对应。 如果问题仍然存在,可能需要查看具体的错误日志,或者参考引用[4]中的博客链接,但注意该链接是针对Ubuntu 20.04的,如果你的系统版本不同,可能需要寻找对应版本的解决方案。 最后,如果依赖关系非常混乱,可以考虑使用`aptitude`工具,它有时能提供更好的依赖解决方案。 ```bash sudo apt install aptitude sudo aptitude install ros-melodic-desktop-full ``` 在aptitude的界面中,它会提供几个解决方案,你可以选择接受或拒绝。注意,它可能会建议卸载一些冲突的包,请谨慎选择。 请根据你的实际情况调整上述步骤中的ROS版本和Ubuntu版本。 §§相关问题§§ 1. 如何确定我应该安装哪个版本的ROS2. 在安装ROS时,如何正确配置软件源和密钥? 3. 除了`apt --fix-broken install`,还有哪些方法可以解决依赖问题? 4. 如何避免在安装ROS时出现依赖问题?</think>### 解决ROS软件包安装依赖问题的方法 #### 问题分析 根据引用内容[^1][^2][^3],ROS安装过程中出现的依赖问题通常表现为: ```bash 下列软件包有未满足的依赖关系: python3-catkin-pkg-modules : 依赖: ... 但是它将不会被安装 E: 有未能满足的依赖关系 ``` 主要原因是软件包版本冲突、系统源配置不当或安装流程不规范。 --- ### 解决方案(逐步操作) #### 步骤1:修复损坏的依赖 ```bash sudo apt --fix-broken install ``` 此命令会尝试自动修复引用[^2]中提示的依赖链断裂问题。如果提示需要删除某些包,请确认是否与ROS无关。 #### 步骤2:更新软件源 ```bash sudo apt update sudo apt upgrade ``` 确保所有软件包处于最新状态,解决引用[^4]提到的缓存文件冲突问题。 #### 步骤3:强制安装核心依赖 ```bash sudo apt install -f python3-catkin-pkg-modules python3-rospkg-modules ``` `-f`参数会强制解决依赖关系,适用于引用[^3]中"无法修正错误"的情况。 #### 步骤4:清理冲突包 ```bash sudo apt autoremove sudo apt purge $(dpkg -l | grep '^rc' | awk '{print $2}') ``` 移除残留配置文件,解决引用[^2]中多个软件包相互阻塞的问题。 #### 步骤5:完整安装ROS ```bash sudo apt install ros-melodic-desktop-full # 请替换melodic为你的ROS版本 ``` 完整安装会处理所有子依赖,避免部分安装导致的依赖缺失。 --- ### 预防措施 1. **版本匹配**:确保ROS版本与Ubuntu版本兼容(如Melodic→Ubuntu 18.04) 2. **源配置**:正确设置`/etc/apt/sources.list.d/ros-latest.list` 3. **安装顺序**: ```bash sudo apt install python3-rosdep sudo rosdep init rosdep update ``` 再执行主安装 > **注意**:若仍失败,可尝试更换软件源(如阿里云镜像)或参考引用[^4]的缓存清理方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值