C# BLE蓝牙开发之使用Windows.Devices.Bluetooth获取小米体重秤的体重

本文介绍了如何使用C#通过Windows自带的库连接小米体重秤,获取并解析体重秤的历史记录数据。作者创建了一个BleCore类来处理蓝牙设备的搜索、连接、服务和特征的获取。在获取到特定特征后,通过写入特定数据来触发体重秤发送历史记录。接收到的数据经过解析后,可以用于绘制体重变化的折线图,从而简化了通过手机APP查看数据的步骤。
摘要由CSDN通过智能技术生成

目录

 

前言

准备工作

新建.net Framework 4.6 工程

微软BLE方法介绍

构建代码

如何连接小米体重秤

小米体重秤历史记录解析

结语


前言

最近天天再减肥,每天起床第一件事就是去小米体重秤上测下体重,每天都要打开小米运动APP去连接BLE蓝牙,感觉好繁琐......

于是我自己写了个程序去获取体重秤的历史数据,画个折线图体重就一目了然,省去了手机上的操作

结合之前再Unity中使用Unity Bluetooth LE Plugin for Android去使用 维特BWT901系列的九轴陀螺仪  过几天再写这个文章

本文只做记录,内容、为什么还请自行研究


准备工作

  1. 笔记本自带蓝牙,台式机没有蓝牙怎么办,直接TB买个蓝牙5.0的适配器就行了,长得如图所示
  2. 如何判断低功耗蓝牙是否可以使用?打开设备管理器有下图标识就代表你的蓝牙可以使用低功耗蓝牙
  3. 稍微用“C# 蓝牙”这样的关键词百度、Google下,就会发现InTheHand.Net.Personal.dll这个支持库,貌似对低功耗蓝牙很不友好,暂时就没使用了,后续研究下在写
      官网网址(将在2021年7月1日之后关闭)
      Git网址
  4. 笔记本有蓝牙,打开window自带的蓝牙管理页面,发现是能扫描到BLE蓝牙设备,比如小米手环、小米体重秤、fitbit手环等等
    那我们是不是可以直接用window自带的库去使用呢?答案是肯定的

新建.net Framework 4.6 工程

在右键解决方案->添加->引用->浏览
C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.19041.0\Facade\windows.winmd把这个引用进去
可以开心的瞎搞了


微软BLE方法介绍

BluetoothGATTGetServices

BluetoothGATTGetIncludedServices

BluetoothGATTGetCharacteristics

BluetoothGATTGetDescriptors

BluetoothGATTGetCharacteristicValue

BluetoothGATTGetDescriptorValue

BluetoothGATTBeginReliableWrite

BluetoothGATTSetCharacteristicValue

BluetoothGATTEndReliableWrite

BluetoothGATTAbortReliableWrite

BluetoothGATTSetDescriptorValue

BluetoothGATTRegisterEvent

BluetoothGATTUnregisterEvent

自行研读下把


构建代码

  • 此类包含连接、扫描、发送、获取服务/特性。
class BleCore
    {
        private bool asyncLock = false;

        /// <summary>
        /// 当前连接的服务
        /// </summary>
        public GattDeviceService CurrentService { get; private set; }

        /// <summary>
        /// 当前连接的蓝牙设备
        /// </summary>
        public BluetoothLEDevice CurrentDevice { get; private set; }

        /// <summary>
        /// 写特征对象
        /// </summary>
        public GattCharacteristic CurrentWriteCharacteristic { get; private set; }

        /// <summary>
        /// 通知特征对象
        /// </summary>
        public GattCharacteristic CurrentNotifyCharacteristic { get; private set; }

        /// <summary>
        /// 存储检测到的设备
        /// </summary>
        public List<BluetoothLEDevice> DeviceList { get; private set; }

        /// <summary>
        /// 特性通知类型通知启用
        /// </summary>
        private const GattClientCharacteristicConfigurationDescriptorValue CHARACTERISTIC_NOTIFICATION_TYPE = GattClientCharacteristicConfigurationDescriptorValue.Notify;

        /// <summary>
        /// 定义搜索蓝牙设备委托
        /// </summary>
        public delegate void DeviceWatcherChangedEvent(BluetoothLEDevice bluetoothLEDevice);

        /// <summary>
        /// 搜索蓝牙事件
        /// </summary>
        public event DeviceWatcherChangedEvent DeviceWatcherChanged;

        /// <summary>
        /// 获取服务委托
        /// </summary>
        public delegate void CharacteristicFinishEvent(int size);

        /// <summary>
        /// 获取服务事件
        /// </summary>
        public event CharacteristicFinishEvent CharacteristicFinish;

        /// <summary>
        /// 获取特征委托
        /// </summary>
        public delegate void CharacteristicAddedEvent(GattCharacteristic gattCharacteristic);

        /// <summary>
        /// 获取特征事件
        /// </summary>
        public event CharacteristicAddedEvent CharacteristicAdded;

        /// <summary>
        /// 接受数据委托
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        public delegate void RecDataEvent(GattCharacteristic sender, byte[] data);

        /// <summary>
        /// 接受数据事件
        /// </summary>
        public event RecDataEvent Recdate;

        /// <summary>
        /// 当前连接的蓝牙Mac
        /// </summary>
        private string CurrentDeviceMAC { get; set; }

        private BluetoothLEAdvertisementWatcher Watcher = null;

        public BleCore()
        {
            DeviceList = new List<BluetoothLEDevice>();
        }

        /// <summary>
        /// 搜索蓝牙设备
        /// </summary>
        public void StartBleDeviceWatcher()
        {
            Watcher = new BluetoothLEAdvertisementWatcher();

            Watcher.ScanningMode = BluetoothLEScanningMode.Active;

            // only activate the watcher when we're recieving values >= -80
            Watcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;

            // stop watching if the value drops below -90 (user walked away)
            Watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;

            // register callback for when we see an advertisements
            Watcher.Received += OnAdvertisementReceived;

            // wait 5 seconds to make sure the device is really out of range
            Watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
            Watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);

            // starting watching for advertisements
            Watcher.Start();

            Console.WriteLine("自动发现设备中..");
        }

        /// <summary>
        /// 停止搜索蓝牙
        /// </summary>
        public void StopBleDeviceWatcher()
        {
            if (Watcher != null)
                this.Watcher.Stop();
        }

        /// <summary>
        /// 主动断开连接
        /// </summary>
        /// <returns></returns>
        public void Dispose()
        {
            CurrentDeviceMAC = null;
            CurrentService?.Dispose();
            CurrentDevice?.Dispose();
            CurrentDevice = null;
            CurrentService = null;
            CurrentWriteCharacteristic = null;
            CurrentNotifyCharacteristic = null;
            Console.WriteLine("主动断开连接");
        }

        /// <summary>
        /// 匹配
        /// </summary>
        /// <param name="Device"></param>
        public void StartMatching(BluetoothLEDevice Device)
        {
            this.CurrentDevice = Device;
        }

        /// <summary>
        /// 发送数据接口
        /// </summary>
        /// <returns></returns>
        public void Write(byte[] data)
        {
            if (CurrentWriteCharacteristic != null)
            {
                CurrentWriteCharacteristic.WriteValueAsync(CryptographicBuffer.CreateFromByteArray(data), GattWriteOption.WriteWithResponse).Completed = (asyncInfo, asyncStatus) =>
                {
                    if (asyncStatus == AsyncStatus.Completed)
                    {
                        GattCommunicationStatus a = asyncInfo.GetResults();
                        Console.WriteLine("发送数据:" + BitConverter.ToString(data) + " State : " + a);
                    }
                };
            }

        }

        /// 获取蓝牙服务
        /// </summary>
        public void FindService()
        {
            this.CurrentDevice.GetGattServicesAsync().Completed = (asyncInfo, asyncStatus) =>
           {
               if (asyncStatus == AsyncStatus.Completed)
               {
                   var services = asyncInfo.GetResults().Services;
                   Console.WriteLine("GattServices size=" + services.Count);
                   foreach (GattDeviceService ser in services)
                   {
                       FindCharacteristic(ser);
                   }
                   CharacteristicFinish?.Invoke(services.Count);
               }
           };

        }

        /// <summary>
        /// 按MAC地址直接组装设备ID查找设备
        /// </summary>
        public void SelectDeviceFromIdAsync(string MAC)
        {
            CurrentDeviceMAC = MAC;
            CurrentDevice = null;
            BluetoothAdapter.GetDefaultAsync().Completed = (asyncInfo, asyncStatus) =>
            {
                if (asyncStatus == AsyncStatus.Completed)
                {
                    BluetoothAdapter mBluetoothAdapter = asyncInfo.GetResults();
                    byte[] _Bytes1 = BitConverter.GetBytes(mBluetoothAdapter.BluetoothAddress);//ulong转换为byte数组
                    Array.Reverse(_Bytes1);
                    string macAddress = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();
                    string Id = "BluetoothLE#BluetoothLE" + macAddress + "-" + MAC;
                    Matching(Id);
                }
            };
        }

        /// <summary>
        /// 获取操作
        /// </summary>
        /// <returns></returns>
        public void SetOpteron(GattCharacteristic gattCharacteristic)
        {
            byte[] _Bytes1 = BitConverter.GetBytes(this.CurrentDevice.BluetoothAddress);
            Array.Reverse(_Bytes1);
            this.CurrentDeviceMAC = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();

            string msg = "正在连接设备<" + this.CurrentDeviceMAC + ">..";
            Console.WriteLine(msg);

            if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Write)
            {
                this.CurrentWriteCharacteristic = gattCharacteristic;
            }
            if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Notify)
            {
                this.CurrentNotifyCharacteristic = gattCharacteristic;
            }
            if ((uint)gattCharacteristic.CharacteristicProperties == 26)
            {

            }

            if (gattCharacteristic.CharacteristicProperties == (GattCharacteristicProperties.Write | GattCharacteristicProperties.Notify))
            {
                this.CurrentWriteCharacteristic = gattCharacteristic;
                this.CurrentNotifyCharacteristic = gattCharacteristic;
                this.CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;
                this.CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;
                this.CurrentDevice.ConnectionStatusChanged += this.CurrentDevice_ConnectionStatusChanged;
                this.EnableNotifications(CurrentNotifyCharacteristic);
            }

        }

        private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
        {
            BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress).Completed = (asyncInfo, asyncStatus) =>
            {
                if (asyncStatus == AsyncStatus.Completed)
                {
                    if (asyncInfo.GetResults() == null)
                    {
                        //Console.WriteLine("没有得到结果集");
                    }
                    else
                    {
                        BluetoothLEDevice currentDevice = asyncInfo.GetResults();

                        if (DeviceList.FindIndex((x) => { return x.Name.Equals(currentDevice.Name); }) < 0)
                        {
                            this.DeviceList.Add(currentDevice);
                            DeviceWatcherChanged?.Invoke(currentDevice);
                        }

                    }

                }
            };
        }

        /// <summary>
        /// 获取特性
        /// </summary>
        private void FindCharacteristic(GattDeviceService gattDeviceService)
        {
            this.CurrentService = gattDeviceService;
            this.CurrentService.GetCharacteristicsAsync().Completed = (asyncInfo, asyncStatus) =>
            {
                if (asyncStatus == AsyncStatus.Completed)
                {
                    var services = asyncInfo.GetResults().Characteristics;
                    foreach (var c in services)
                    {
                        this.CharacteristicAdded?.Invoke(c);
                    }

                }
            };
        }

        /// <summary>
        /// 搜索到的蓝牙设备
        /// </summary>
        /// <returns></returns>
        private void Matching(string Id)
        {
            try
            {
                BluetoothLEDevice.FromIdAsync(Id).Completed = (asyncInfo, asyncStatus) =>
               {
                   if (asyncStatus == AsyncStatus.Completed)
                   {
                       BluetoothLEDevice bleDevice = asyncInfo.GetResults();
                       this.DeviceList.Add(bleDevice);
                       Console.WriteLine(bleDevice);
                   }

                   if (asyncStatus == AsyncStatus.Started)
                   {
                       Console.WriteLine(asyncStatus.ToString());
                   }
                   if (asyncStatus == AsyncStatus.Canceled)
                   {
                       Console.WriteLine(asyncStatus.ToString());
                   }
                   if (asyncStatus == AsyncStatus.Error)
                   {
                       Console.WriteLine(asyncStatus.ToString());
                   }
               };
            }
            catch (Exception e)
            {
                string msg = "没有发现设备" + e.ToString();
                Console.WriteLine(msg);
                this.StartBleDeviceWatcher();
            }
        }


        private void CurrentDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args)
        {
            if (sender.ConnectionStatus == BluetoothConnectionStatus.Disconnected && CurrentDeviceMAC != null)
            {
                if (!asyncLock)
                {
                    asyncLock = true;
                    Console.WriteLine("设备已断开");
                    //this.CurrentDevice?.Dispose();
                    //this.CurrentDevice = null;
                    //CurrentService = null;
                    //CurrentWriteCharacteristic = null;
                    //CurrentNotifyCharacteristic = null;
                    //SelectDeviceFromIdAsync(CurrentDeviceMAC);
                }
            }
            else
            {
                if (!asyncLock)
                {
                    asyncLock = true;
                    Console.WriteLine("设备已连接");
                }
            }
        }

        /// <summary>
        /// 设置特征对象为接收通知对象
        /// </summary>
        /// <param name="characteristic"></param>
        /// <returns></returns>
        private void EnableNotifications(GattCharacteristic characteristic)
        {
            Console.WriteLine("收通知对象=" + CurrentDevice.Name + ":" + CurrentDevice.ConnectionStatus);
            characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(CHARACTERISTIC_NOTIFICATION_TYPE).Completed = (asyncInfo, asyncStatus) =>
           {
               if (asyncStatus == AsyncStatus.Completed)
               {
                   GattCommunicationStatus status = asyncInfo.GetResults();
                   if (status == GattCommunicationStatus.Unreachable)
                   {
                       Console.WriteLine("设备不可用");
                       if (CurrentNotifyCharacteristic != null && !asyncLock)
                       {
                           this.EnableNotifications(CurrentNotifyCharacteristic);
                       }
                       return;
                   }
                   asyncLock = false;
                   Console.WriteLine("设备连接状态" + status);
               }
           };
        }

        /// <summary>
        /// 接受到蓝牙数据
        /// </summary>
        private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
        {
            byte[] data;
            CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out data);
            Recdate?.Invoke(sender, data);
        }

    }
  • 我们如何去使用呢?
    class Progame
    {
        private static BleCore bleCore = null;
        
        private static List<GattCharacteristic> characteristics = new List<GattCharacteristic>();

        public static void Main(string[] args)
        {
            bleCore = new BleCore();
            bleCore.DeviceWatcherChanged += DeviceWatcherChanged;
            bleCore.CharacteristicAdded += CharacteristicAdded;
            bleCore.CharacteristicFinish += CharacteristicFinish;
            bleCore.Recdate += Recdata;
            bleCore.StartBleDeviceWatcher();

            Console.ReadKey(true);

            bleCore.Dispose();
            bleCore = null;
        }

        private static void CharacteristicFinish(int size)
        {
            if (size <= 0)
            {
                Console.WriteLine("设备未连上");
                return;
            }
        }

        private static void Recdata(GattCharacteristic sender, byte[] data)
        {
            string str = BitConverter.ToString(data);
            Console.WriteLine(sender.Uuid + "             " + str);
        }

        private static void CharacteristicAdded(GattCharacteristic gatt)
        {
            Console.WriteLine(
                "handle:[0x{0}]  char properties:[{1}]  UUID:[{2}]",
                gatt.AttributeHandle.ToString("X4"),
                gatt.CharacteristicProperties.ToString(),
                gatt.Uuid);
            characteristics.Add(gatt);
        }

        private static void DeviceWatcherChanged(BluetoothLEDevice currentDevice)
        {
            byte[] _Bytes1 = BitConverter.GetBytes(currentDevice.BluetoothAddress);
            Array.Reverse(_Bytes1);
            string address = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();
            Console.WriteLine("发现设备:<" + currentDevice.Name + ">  address:<" + address + ">");

            //指定一个对象,使用下面方法去连接设备
            //ConnectDevice(currentDevice);
        }

        private static void ConnectDevice(BluetoothLEDevice Device)
        {
            characteristics.Clear();
            bleCore.StopBleDeviceWatcher();
            bleCore.StartMatching(Device);
            bleCore.FindService();
        }
    }
  • 那么运行成功并且蓝牙设备,如下图所示

  • 如果运行成功没有蓝牙设备,如下图所示

  • PC上如何判断蓝牙是否开启呢?

    打开cmd,输入ipconfig -all,如果有 “以太网适配器 蓝牙网络连接” 的字样, 证明你的蓝牙是开启的
    从代码层面如何来实现?
    NetworkInterface[] network = NetworkInterface.GetAllNetworkInterfaces()
    用这个方法去检查有没有蓝牙或者去匹配你的蓝牙适配器的mac地址

如何连接小米体重秤

在DeviceWatcherChanged的事件中去调用ConnectDevice方法,入参就是指定的BLE蓝牙设备
if (bluetoothLEDevice.Name.Equals("MI SCALE2") || bluetoothLEDevice.DeviceId.Contains("c8:47:8c:a0:2b:db"))
{
       ConnectDevice(bluetoothLEDevice);
}

小米体重秤的特征:
handle: 0x0002, char properties: 0x0a, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, char properties: 0x0a, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: 00002a02-0000-1000-8000-00805f9b34fb
handle: 0x0008, char properties: 0x02, char value handle: 0x0009, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x000b, char properties: 0x22, char value handle: 0x000c, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x000f, char properties: 0x02, char value handle: 0x0010, uuid: 00002a25-0000-1000-8000-00805f9b34fb
handle: 0x0011, char properties: 0x02, char value handle: 0x0012, uuid: 00002a27-0000-1000-8000-00805f9b34fb
handle: 0x0013, char properties: 0x02, char value handle: 0x0014, uuid: 00002a28-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a23-0000-1000-8000-00805f9b34fb
handle: 0x0017, char properties: 0x02, char value handle: 0x0018, uuid: 00002a50-0000-1000-8000-00805f9b34fb
handle: 0x001a, char properties: 0x0a, char value handle: 0x001b, uuid: 00002a2b-0000-1000-8000-00805f9b34fb
handle: 0x001c, char properties: 0x02, char value handle: 0x001d, uuid: 00002a9e-0000-1000-8000-00805f9b34fb
handle: 0x001e, char properties: 0x20, char value handle: 0x001f, uuid: 00002a9d-0000-1000-8000-00805f9b34fb
handle: 0x0021, char properties: 0x18, char value handle: 0x0022, uuid: 00002a2f-0000-3512-2118-0009af100700
handle: 0x0025, char properties: 0x18, char value handle: 0x0026, uuid: 00001531-0000-3512-2118-0009af100700
handle: 0x0028, char properties: 0x04, char value handle: 0x0029, uuid: 00001532-0000-3512-2118-0009af100700
handle: 0x002a, char properties: 0x1a, char value handle: 0x002b, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x002d, char properties: 0x1a, char value handle: 0x002e, uuid: 00001542-0000-3512-2118-0009af100700
handle: 0x0030, char properties: 0x1a, char value handle: 0x0031, uuid: 00001543-0000-3512-2118-0009af100700

这里有很多的特征,我们不知道这些特征代表什么,不过git上有人对这些做过详细说明,这里我们只要找到历史记录的特征码
我们在特征都获取完的时候去找到指定的特征
GattCharacteristic gattCharacteristic = characteristics.Find((x) =>{return x.Uuid.Equals(new Guid("00002a2f-0000-3512-2118-0009af100700")); });
bleCore.SetOpteron(gattCharacteristic);
bleCore.Write(new byte[] { 0x01, 0xFF, 0xFF, 0xFF, 0xFF, });
bleCore.Write(new byte[] { 0x02 });

PS:写入的0x01, 0xFF, 0xFF, 0xFF, 0xFF以及 0x02不用管为什么要写这玩意


小米体重秤历史记录解析

我们接收到的消息:
Notification handle = 0x0022 value: 22 36 33 e3 07 0b 09 09 19 28 22 18 33 e3 07 0b 09 09 29 07 
Notification handle = 0x0022 value: 22 16 3a e3 07 0b 09 09 2a 1d 22 ba 31 e3 07 0b 09 09 2a 28 
Notification handle = 0x0022 value: 22 56 31 e3 07 0b 09 09 2a 2e 22 16 3a e3 07 0b 09 09 2a 36 
Notification handle = 0x0022 value: 22 20 08 e3 07 0b 09 0f 02 21 22 96 0a e3 07 0b 09 0f 06 1a 
Notification handle = 0x0022 value: 22 62 0c e3 07 0b 09 0f 08 20 22 40 33 e3 07 0b 09 0f 11 0d 
Notification handle = 0x0022 value: 22 c6 39 e3 07 0b 09 0f 1c 0d 22 5e 33 e3 07 0b 09 0f 27 1f 
Notification handle = 0x0022 value: 22 d0 39 e3 07 0b 09 0f 31 18 22 18 33 e3 07 0b 09 0f 31 35 
Notification handle = 0x0022 value: 22 16 3a e3 07 0b 09 10 02 0a 22 4e 39 e3 07 0b 0d 0b 04 2e 
Notification handle = 0x0022 value: 22 62 39 e3 07 0b 0e 0a 2c 35 
Notification handle = 0x0022 value: 03 

03是结尾符,每10个byte为一条数据

bytefunction
0status byte: 
- Bit 0: lbs unit
- Bit 1-3: unknown
- Bit 4: jin unit
- Bit 5: stabilized
- Bit 6: unknown
- Bit 7: weight removed
1-2weight (little endian)
3-4year (little endian)
5month
6day
7hour
8minute
9second

第1-2位为体重数据,小端存储。单位为斤(磅)

float weight_kg = Convert.ToInt32(BinaryConversion(date[2], date[1]), 16) / 100f / 2;

最后得出的就是体重


结语

由于我不知道小米运动的底层如何是做稳定连接的,所以我们获取完之后会,过一阵子就会主动断开连接,所以需要手动去做断线重连
在CurrentDevice_ConnectionStatusChanged中把注释掉的注回来就行了

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jerrt-J

希望我创作能给你带来有用的帮助

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值