在Windows mobile 6.0模拟器中实现蓝牙数据采集

一、Windows mobile 6.0中蓝牙驱动的加载

大家知道,Windows Mobile 6.0 SDK自带了cellular emulator和fake GPS,使得我们可以在模拟器上调试radio级的phone call和基于GPS的地理位置应用。

我们来看看实现Bluetooth的架构,参考下图(来源于Bluetooth for Microsoft Device Emulator),主要包括emulator端和PC端:

1.1 软硬件条件

1. Emulator端

Bluetooth HCI Transport Driver

Serial Port Driver

Microsoft Remote Tools Framework remote agent

2.  PC端

Microsoft Remote Tools Framework desktop plugin

FreeBT USB Driver runtime

FreeBT USB Driver

Connected Bluetooth USB device

    注意:作者采用了FreeBT这个开源的工程来控制Bluetooth的接入。

    在Windows Mobile模拟器上使用蓝牙的必要条件有:

1. Visual studio 2005 with SP1 或者 Visual studio 2008

2. Windows Mobile 5.0 Pocket PC/Smartphone SDK and/or Windows Mobile 6 Standard/Professional SDK emulator images

3. Device Emulator 3.0

4. Microsoft Remote Tools Framework 1.00

5. 一个具有USB接口的Bluetooth dongle

1.2 模拟环境搭建   
(一)安装FreeBT蓝牙驱动

在Windows Mobile模拟器上使用蓝牙,即在PC端安装下载的FreeBT的蓝牙驱动,方法自然是通过"windows设备管理器",为新的硬件设备(也就是连接的Bluetooth USB device)添加下载过来的驱动(主要是找到fbtusb.inf文件)。具体如下:

1、打开Windows设备管理器。

2、在设备管理器,找到蓝牙设备将设备用作FreeBT USB接口。

3、点击鼠标右键并选择"更新驱动程序..."在弹出的菜单。

4、选择"否,暂时不",然后点击"下一步>"。

5、选择"安装从列表或指定位置",然后点击"下一步>"。

6、选择"不要搜索。我将选择要安装的驱动",然后点击"下一步>"。

7、选择设备驱动程序,然后点击"从磁盘安装... ".

8、在查找文件对话框中,浏览到fbtusb.inf文件,然后点击"下一步>"。

9、当一个"硬件安装警告"出现后,点击"仍然继续"。

10、一旦安装完毕后,点击"完成"。

11、设备管理器现在应显示"FreeBT USB驱动"在名单的USB控制器。

(二)安装加载Bluetooth for Microsoft Device Emulator

1、安装FreeBT USB驱动中描述了蓝牙USB驱动安装部分。

2、安装要求如描述的安装步骤。

3、 在Remote Tools Framework plugin中运行BthEmulManager.cetool,将其和模拟器建立连接。

4、从列表中选择一个仿真图像。

5、等待模拟器连接。

6、选择"Bluetooth for Microsoft Device Emulator"节点。

7、如果你有一个蓝牙设备连接成功,那么蓝牙设备信息(地址,制造商,人机交互版本和LMP版本)将被显示。现在,蓝牙应该运行在你的模拟器上。如果有一个错误代码,则将被显示。如图9所示。

8、要清除通讯记录,点击鼠标右键,选择"清除"。

9、当前设备信息复制到剪贴板,选择"蓝牙为微软装置模拟器"节点,点击鼠标右键,选择"拷贝到剪贴板"。

10、关掉蓝牙的模拟器,点击"连接"菜单,选择"脱离…模拟器"。

11、微软远程工具结构允许你在同一时间启动两个装置模拟器。如果您已经安装了两个或两个以上FreeBT USB设备,点击"窗口"菜单并选择"切分窗口查看"。重复步骤3-6。你将得到两个同时运行的蓝牙驱动仿真器。

12、很可能要启用/禁用设备端日志记录。如果您启用"设备日志记录"复选框,然后将启用远程登录在模拟器上。关注\ \ Temp的模拟器目录。应该有建立btd_bthemul_0.txt,btd_BthEmulAgent_0.txt,btd_bthemulcom_0.txt文件。

13、很可能要启用/禁用桌面端日志记录。如果您启用"桌面日志记录"复选框,那么就将启用本地记录。查看你的安装目录,应该有建立BthEmulManager.txt文件。

14、很可能要启用/禁用通讯记录。通讯记录可以看到仿真器和蓝牙设备之间的通信活动。

(三)连通测试

连接建立以后,蓝牙设备的信息(Address, Manufacturer, HCI Version, LMP Version)就会显示在界面上。

在程序启动后,首先搜索附近的蓝牙设备,搜索过程完成以后,将其设备的名字和蓝牙地址显示在下拉列表中,然后就可以进行双向的消息发送和接收了。程序运行界面如下图10所示:

需要注意的是,在设备蓝牙的时候,如果没有将"对其他设备可见"选项打上勾,即只是将蓝牙打开,如下图所示。

可能无法正常通信,应用程序会提示无法发送消息。选中"对其他设备可见"之后,就能够正常通信了,如下图12所示:

在蓝牙配对中,当Windows Mobile 找到了蓝牙设备金瓯,金瓯设置的蓝牙连接密码是1234。如图13所示。

成功后,选择打开蓝牙时不会出错

正在搜索设备过程中

搜索完成

建立连接过程中要求输入连接密码

 

选择蓝牙服务——串口连接服务

创建成功COM端口

二、Bluetooth操作的软件实现

由于蓝牙无线协议在手机中的应用较为广泛,因此,无论是Windows Mobile SDK,还是其他第三方商业或开源软件组织,都对蓝牙程序设计有较好的支持。如著名的OpenNETCF,在其早期的版本中就提供了OpenNETCF.Bluetooth.WidComm库,而微软则有官方版本的Microsoft Bluetooth Stack提供,目前,比较流行的库有32feet.net。三方的地址列表如下:

  1. InTheHand.NET项目(即32feet.net)

在.NET Compact Framework下进行Bluetooth开发有几个可选解决方案

  • 可以P/Invoke直接调用Bluetooth的API(btdrt.dll)
  • 使用MS的Windows Embedded Source Tools for Bluetooth
  • 使用32feet.NET库

而这些库里,对蓝牙串口服务的支持均存在一定的缺陷,如Feet32.Net的作者甚至声称其中建立串口连接的代码是不安全的。这个问题在模拟器下更显突出,原因是模拟器下,对串口的支持仅是从COM0~COM2,而在使用SDK中列举时,得到的结果却是COM0~COM10,这样,如果随机选择了大于COM2的虚拟端口,程序便会报错。而这个问题,在手动创建蓝牙虚拟串口服务时也会不时发生。由于暂时没有真实的Mobile设备进行测试,不能完全证明,是由于模拟器的问题还是SDK使用不当的问题,留待购置了Mobile设备后再来研究。

OpenNETCF的新版本中,不知何故去除了WidComm的包,但仍有网友在其源代码上进行部分的改造利用,下文就针对上面所列的两种库的使用进行简要的说明。

2.1 利用32feet.net实现Windows Mobile上的蓝牙数据传输
(一)32feet.NET简介

32feet.net是由InTheHand公司所属的一个开源项目组织,专门针对基于.NET平台的无线数据传输(如蓝牙,红外等)提供技术支持。目前已经发布的组件有:

  • Bluetooth(蓝牙)
  • IrDA(红外)
  • ObjectExchange(OBEX文件交换)

对蓝牙的支持需要设备兼容 Microsoft Bluetooth协议栈。CE.NET4.2XP以上的操作系统。

(二)32feet.NET类库结构

表1:InTheHand类库结构

名字空间

描述

InTheHand

The InTheHand namespace contains classes for working with the Uri class for personal area networks.

InTheHand.Net

The InTheHand.Net namespace contains classes for working with addressing on personal area networks.

InTheHand.Net.Bluetooth

The InTheHand.Net.Bluetooth namespace contains classes for working with Bluetooth functionality such as Radio hardware.

InTheHand.Net.Bluetooth.AttributeIds

The InTheHand.Net.Bluetooth.AttributeIds namespace contains definitions of Service Discovery Protocol attributes.

InTheHand.Net.IrDA

The InTheHand.Net.IrDA namespace contains classes for working with Infrared functionality.

InTheHand.Net.Mime

The InTheHand.Net.Mime namespace holds types that are used to represent Multipurpose Internet Mail Exchange (MIME) headers

InTheHand.Net.Ports

The InTheHand.Net.Ports namespace contains classes for working with legacy virtual COM ports over Bluetooth.

InTheHand.Net.Sockets

The InTheHand.Net.Sockets namespace provides added functionality for working with IrDA and Bluetooth Sockets.

InTheHand.Windows.Forms

The InTheHand.Windows.Forms namespace contains forms related to networking functionality.

现仅列举32feet.NET中的Buletooth及Sockets名字空间,以供参考。

表2:InTheHand.Net.Bluetooth名字空间

描述

AttributeIdLookup

Retrieves the name of the SDP Attribute ID with the given value in the specified Attribute ID class sets. Implementing Enum-like behaviour.

BluetoothProtocolDescriptorType

Configures what type of element will be added by the ServiceRecordBuilder for the ProtocolDescriptorList attribute.

BluetoothRadio

Represents a Bluetooth Radio device.

BluetoothSecurity

Handles security between bluetooth devices.

BluetoothService

Standard Bluetooth Profile identifiers.

BluetoothWin32Authentication

Provides Bluetooth authentication services on desktop Windows.

BluetoothWin32AuthenticationEventArgs

Provides data for an authentication event.

ClassOfDevice

Describes the device and service capabilities of a device.

DeviceClass

Class of Device flags as assigned in the Bluetooth specifications.

ElementType

Represents the types that an SDP element can hold.

ElementTypeDescriptor

Represents the type of the element in the SDP record binary format, and is stored as the higher 5 bits of the header byte.

HardwareStatus

Specifies the current status of the Bluetooth hardware.

LanguageBaseItem

Represents a member of the SDP LanguageBaseAttributeIdList, Attribute which provides for multi-language strings in a record.

LinkPolicy

Flags to describe Link Policy.

Manufacturer

Manufacturer codes.

MapServiceClassToAttributeIdList

Gets a list of enum-like classes containing SDP Service Attribute Id definitions for a particular Service Class.

RadioMode

Determine all the possible modes of operation of the Bluetooth radio.

ServiceAttribute

Holds an attribute from an SDP service record.

ServiceAttributeId

A Service Attribute Id identifies each attribute within an SDP service record.

ServiceClass

 

ServiceElement

Holds an SDP data element.

ServiceRecord

Holds an SDP service record.

ServiceRecordBuilder

Provides a simple way to build a ServiceRecord, including ServiceClassIds and ServiceNames attributes etc.

ServiceRecordCreator

Creates a Service Record byte array from the given ServiceRecord object.

ServiceRecordHelper

Some useful methods for working with a SDP ServiceRecord including creating and accessing the ProtocolDescriptorList for an RFCOMM service.

ServiceRecordParser

Parses an array of bytes into the contained SDP ServiceRecord.

ServiceRecordUtilities

Utilities method working on SDP ServiceRecords, for instance to produce a 'dump' of the record's contents.

SizeIndex

Represents the size of the SDP element in the record binary format, and is stored as the lower 3 bits of the header byte.

StringWithLanguageBaseAttribute

Indicates that the field to which it is applied represents an SDP Attribute that can exist in multiple language instances and thus has a language base offset applied to its numerical ID when added to a record.

表3: InTheHand.Net.Sockets名字空间

 

Type

Description

AddressFamily32

Specifies additional addressing schemes that an instance of the Socket class can use.

BluetoothClient

Provides client connections for Bluetooth network services.

BluetoothDeviceInfo

Provides information about an available device obtained by the client during device discovery.

BluetoothListener

Listens for connections from Bluetooth network clients.

BluetoothProtocolType

Specifies additional protocols that the Socket class supports.

BluetoothSocketOptionLevel

Defines additional Bluetooth socket option levels for the SetSocketOption(SocketOptionLevel, SocketOptionName, Int32) and GetSocketOption(SocketOptionLevel, SocketOptionName) methods.

BluetoothSocketOptionName

Defines Socket configuration option names for the Socket class.

IrDACharacterSet

Describes the character sets supported by the device.

IrDAClient

Makes connections to services on peer IrDA devices.

IrDADeviceInfo

Provides information about remote devices connected by infrared communications.

IrDAHints

Describes an enumeration of possible device types, such as Fax.

IrDAListener

Places a socket in a listening state to monitor infrared connections from a specified service or network address.

IrDASocketOptionLevel

Defines additional IrDA socket option levels for the SetSocketOption(SocketOptionLevel, SocketOptionName, Int32) and GetSocketOption(SocketOptionLevel, SocketOptionName) methods.

IrDASocketOptionName

Socket option constants to set IrDA specific connection modes, and get/set IrDA specific features.

2.2 基于32feet.NET的蓝牙程序设计

Bluetooth的应用十分广泛,基于Bluetooth的通信程序开发主要有以下几个步骤:

  1. 服务端
  • 设置本设备为可发现。
  • 公开服务给其他Bluetooth设备访问。
  • 接受其他Bluetooth设备的链接。
  • 与链接上的Bluetooth设备进行通信。
  1. 客户端
  • 发现周边Bluetooth设备。
  • 主动与被发现的设备发起连接。
  • 与链接上的Bluetooth设备进行通信。
  1. 蓝牙功能设置

(1)服务端

开启蓝牙

public
					static
					void DisplayBluetoothRadio()

{

    BluetoothRadio myRadio = BluetoothRadio.PrimaryRadio;


					if (myRadio == null)

    {

        WriteMessage("No radio hardware or unsupported software stack");


					return;

    }


					// Enable discoverable mode
				

    myRadio.Mode = RadioMode.Discoverable;

}

开启服务

private
					static
					void StartService()

{

    BluetoothListener listener = new BluetoothListener(BluetoothService.SerialPort);

    listener.Start();

    WriteMessage("Service started!");

    BluetoothClient client = listener.AcceptBluetoothClient();

    WriteMessage("Got a request!");

    Stream peerStream = client.GetStream();


					string dataToSend = "Hello from service!";


					// Convert dataToSend into a byte array
				


					byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(dataToSend);


					// Output data to stream
				

    peerStream.Write(dataBuffer, 0, dataBuffer.Length);


					byte[] buffer = new
					byte[2000];


					while (true)

    {


					if (peerStream.CanRead)

        {

            peerStream.Read(buffer, 0, 50);


					string data = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, 50);

            WriteMessage("Receiving data: " + data);

        }

    }

}

DisplayBluetoothRadio用来展现本端设备的信息,以及把本端bluetooth设备设置为可发现。如果使用32feet.NET 2.3,也就是当前最新的release版本,wince不支持读取和设置radio mode。

服务端的侦听BluetoothListener还是使用winsock实现和Windows Embedded Source Tools for Bluetooth的实现类似。

(2)客户端

private
					static
					void ConnectService()

{

    BluetoothClient client = new BluetoothClient();

    BluetoothDeviceInfo[] devices = client.DiscoverDevices();

    BluetoothDeviceInfo device = null;


					foreach (BluetoothDeviceInfo d in devices)

    {


					if (d.DeviceName == "BLUETOOTH_DEVICE")

        {

            device = d;


					break;

        }

    }


					if (device != null)

    {

        WriteMessage(String.Format("Name:{0} Address:{1:C}", device.DeviceName, device.DeviceAddress));

        client.Connect(device.DeviceAddress, BluetoothService.SerialPort);

        Stream peerStream = client.GetStream();


					// Create storage for receiving data
				


					byte[] buffer = new
					byte[2000];


					// Read Data
				

        peerStream.Read(buffer, 0, 50);


					// Convert Data to String
				


					string data = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, 50);

        WriteMessage("Receiving data: " + data);


					int i = 0;


					while (true)

        {

            WriteMessage("Writing: " + i.ToString());


					byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(i.ToString());

            peerStream.Write(dataBuffer, 0, dataBuffer.Length);

            ++i;


					if (i >= int.MaxValue)

            {

                i = 0;

            }

            System.Threading.Thread.Sleep(500);

        }


					// Close network stream
				

        peerStream.Close();

    }

}

 

32feet.NET 2.3提供了自发现功能,通过client.DiscoverDevices()寻找附近的bluetooth设备。在上述例子中指定连接名字为"BLUETOOTH_DEVICE"的设备。Winsock通信和Windows Embedded Source Tools for Bluetooth类似。client.Connect(device.DeviceAddress, BluetoothService.SerialPort)用于连接名字为"BLUETOOTH_DEVICE"的设备,同样指定串口服务。传输的数据为字节流(byte[]),因此可以传输任意类型的数据。客户端在接收回应信息后,不断的往服务端发送数据。

  1. 蓝牙配对

Connect() 函数的第二个参数是服务类型,十分重要,通信双方必须使用同样的服务类型。如果通信双方的程序都是由我们负责开发,可以使用通用的服务类型,例如BluetoothService.SerialPort。但是如果要与第三方设备进行通信,需要查出该设备的服务类型,在设备端的通信服务类型一般编写在Firmware(固件)里面,而且会按照规范编写,例如Bluetooth耳机会使用BluetoothService.Handsfree。

try

{

BluetoothAddress deviceAddress = deviceAddresses[listBoxDevices.SelectedItem.ToString()];

client.SetPin(deviceAddress, textBoxPin.Text.Trim());

//client.Connect(deviceAddress, BluetoothService.Handsfree); //if connect ot Hands free.

client.Connect(deviceAddress, BluetoothService.SerialPort); //if connect to cell phone and so forth.

MessageBox.Show("Pair successful.");

//transfer data..

}

catch (Exception ex)

{

MessageBox.Show(ex.Message);

}

SetPin()的功能是设置连接密码的,其函数原型如下:

[DllImport("btdrt.dll", SetLastError=true)]

public static extern int BthSetPIN(byte[] pba, int cPinLength, byte[] ppin);

(三) 虚拟串口

所谓Bluetooth Virtual Serial Port,其实是从软件的角度看,把Bluetooth的通信转化成Serial Port(串口)。经过这样的转换后,使用Bluetooth的Client程序可以像使用串口一样操作Bluetooth。这个应用方式的出现是为了支持现有应用(Legacy system,遗产应用)。举个例子,在Bluetooth出现以前,大部分移动设备都是通过串口连接的GPS receiver的,基于GPS应用程序的开发也就通过的串口通信取出NMEA data。

所谓Bluetooth Virtual Serial Port,其实是从软件的角度看,把Bluetooth的通信转化成Serial Port(串口)。经过这样的转换后,使用Bluetooth的Client程序可以像使用串口一样操作Bluetooth。这个应用方式的出现是为了支持现有应用(Legacy system,遗产应用)。举个例子,在Bluetooth出现以前,大部分移动设备都是通过串口连接的GPS receiver的,基于GPS应用程序的开发也就通过的串口通信取出NMEA data。随着Bluetooth的普及,移动设备可以通过Bluetooth来连接GPS receiver了,那么原先基于GPS的应用程序需要重新开发通信部分去读取NMEA data,这为现有应用带来很多麻烦,所有的现有应用都需要重写通信部分,因此人们想出解决方法,把Bluetooth的通信转化成Serial Port(串口)。硬件上使用Bluetooth来进行通信,在软件上虚拟一个串口给应用程序,应用程序不需要任何的修改就可以支持Bluetooth的GPS Receiver了。这就像设计模式里面的Adapter模式,但是这里是为新设备提供原有的接口,使得原先的Client不需要更改。

由于Bluetooth Virtual Serial Port的出现基于对现有系统(Legacy System)支持的需求,所以对于新的系统,MS不推荐使用Bluetooth Virtual Serial Port,而是直接使用Winsock进行通信。在使用Winsock进行Bluetooth通信需要指定服务,因此可以指定使用串口服务进行通信。Bluetooth Virtual Serial Port和Winsock的Bluetooth通信都是使用RFCOMM协议,所以两者等同。使用Winsock的Bluetooth通信比Bluetooth Virtual Serial Port更简单,不需要配置。而且更强壮(robust),因为使用Winsock的Bluetooth通信可以直接监听到蓝牙设备关闭或者离开通信范围,而Bluetooth Virtual Serial Port只能通过Timeout来检查。

由于支持现有系统(Legacy System),Bluetooth Virtual Serial Port还是有存在的价值,下面讲述Bluetooth Virtual Serial Port的开发。在Windows Mobile下有两种方法可以建立Bluetooth Virtual Serial Port:调用API建立Bluetooth Virtual Serial Port和修改注册表建立Bluetooth Virtual Serial Port。

  1. 使用 32feet.net API

在32feet.net里面,创建虚拟串口这些API封装在InTheHand.Net.Ports.BluetoothSerialPort,可以直接使用。但是32feet.net的作者提醒这些API是不是可信赖的(unreliable),所以在使用之前请要谨慎考虑和详细测试。我在wince 5下测试过,不能成功建立Bluetooth Virtual Serial Port。

  • 使用32feet.net建立服务端口

public static void CreateIncomingPort()

{

BluetoothSerialPort port = BluetoothSerialPort.CreateServer(BluetoothService.SerialPort);

Console.WriteLine(port.PortName);

}

  • 使用32feet.net建立客户端口

public static void CreateIncomingPort()

{

BluetoothClient client = new BluetoothClient();

BluetoothDeviceInfo[] devices = client.DiscoverDevices();

BluetoothDeviceInfo device = null;

foreach (BluetoothDeviceInfo d in devices)

{

if (d.DeviceName == "BLUETOOTH_DEVICE")

{

device = d;

break;

}

}

BluetoothEndPoint endPoint = new BluetoothEndPoint(device.DeviceAddress, BluetoothService.SerialPort);

BluetoothSerialPort port = BluetoothSerialPort.CreateClient(endPoint);

Console.WriteLine(port.PortName);

}

(2)使用修改注册表法创建虚拟串口

if (state)

{

//write registry settings for WM5 Serial Port support

//get available ports

Microsoft.Win32.RegistryKey rkPorts = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Bluetooth\\Serial\\Ports", true);

string[] supportedPorts = (string[])rkPorts.GetValue("SupportedPorts");

System.Collections.ArrayList alPorts = new System.Collections.ArrayList(supportedPorts);

//check availability

foreach (string deviceid in rkPorts.GetSubKeyNames())

{

Microsoft.Win32.RegistryKey rkDevice = rkPorts.OpenSubKey(deviceid);

//remove port from arraylist if unavailable

string port = rkDevice.GetValue("Port").ToString();

int nullPos = port.IndexOf('\0');

if (nullPos > -1)

{

port = port.Substring(0, nullPos);

}

if (alPorts.Contains(port))

{

alPorts.Remove(port);

}

rkDevice.Close();

}

if (alPorts.Count == 0)

{

throw new InvalidOperationException("No ports available");

}

//write port details to registry

Microsoft.Win32.RegistryKey rkNewPort = rkPorts.CreateSubKey(this.DeviceAddress.ToString("8"));

rkNewPort.SetValue("KeepDCB", 0);

rkNewPort.SetValue("RemoteDCB", 0);

rkNewPort.SetValue("Encryption", 0);

rkNewPort.SetValue("Authentication", 0);

rkNewPort.SetValue("Port", alPorts[0]);

rkNewPort.SetValue("Server", 0);

rkNewPort.Close();

rkPorts.Close();

//try open port now

try

{

InTheHand.Net.Ports.BluetoothSerialPort.CreateClient(alPorts[0].ToString(), new BluetoothEndPoint(this.DeviceAddress, BluetoothService.SerialPort));

}

catch

{

}

}

else

{

//find and remove registry entries

Microsoft.Win32.RegistryKey rkPorts = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Bluetooth\\Serial\\Ports", true);

foreach (string deviceAddress in rkPorts.GetSubKeyNames())

{

if (deviceAddress == this.DeviceAddress.ToString("8"))

{

rkPorts.DeleteSubKeyTree(deviceAddress);

break;

}

}

    rkPorts.Close();

}

当state为true时为注册,当state为false时为反注册。

posted on 2010-04-22 16:16  易学 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/wantfei/archive/2010/04/22/1718097.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值