目录
前言
准备工作
新建.net Framework 4.6 工程
微软BLE方法介绍
构建代码
如何连接小米体重秤
小米体重秤历史记录解析
结语
前言
最近天天再减肥,每天起床第一件事就是去小米体重秤上测下体重,每天都要打开小米运动APP去连接BLE蓝牙,感觉好繁琐......
于是我自己写了个程序去获取体重秤的历史数据,画个折线图体重就一目了然,省去了手机上的操作
结合之前再Unity中使用Unity Bluetooth LE Plugin for Android去使用 维特BWT901系列的九轴陀螺仪 过几天再写这个文章
本文只做记录,内容、为什么还请自行研究
准备工作
笔记本自带蓝牙,台式机没有蓝牙怎么办,直接TB买个蓝牙5.0的适配器就行了,长得如图所示如何判断低功耗蓝牙是否可以使用?打开设备管理器有下图标识就代表你的蓝牙可以使用低功耗蓝牙
稍微用“C# 蓝牙”这样的关键词百度、Google下,就会发现.Personal.dll这个支持库,貌似对低功耗蓝牙很不友好,暂时就没使用了,后续研究下在写
官网网址(将在7月1日之后关闭)
Git网址笔记本有蓝牙,打开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 >= -80Watcher.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 advertisementsWatcher.Received += OnAdvertisementReceived;// wait 5 seconds to make sure the device is really out of rangeWatcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);Watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);// starting watching for advertisementsWatcher.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 == pleted){GattCommunicationStatus a = asyncInfo.GetResults();Console.WriteLine("发送数据:" + BitConverter.ToString(data) + " State : " + a);}};}}/// 获取蓝牙服务/// </summary>public void FindService(){this.CurrentDevice.GetGattServicesAsync().Completed = (asyncInfo, asyncStatus) =>{if (asyncStatus == pleted){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 == pleted){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 == pleted){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 == pleted){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 == pleted){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 == pleted){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为一条数据
第1-2位为体重数据,小端存储。单位为斤(磅)
float weight_kg = Convert.ToInt32(BinaryConversion(date[2], date[1]), 16) / 100f / 2;
最后得出的就是体重
结语
由于我不知道小米运动的底层如何是做稳定连接的,所以我们获取完之后会,过一阵子就会主动断开连接,所以需要手动去做断线重连
在CurrentDevice_ConnectionStatusChanged中把注释掉的注回来就行了
如果觉得《C# BLE蓝牙开发之使用Windows.Devices.Bluetooth获取小米体重秤的体重》对你有帮助,请点赞、收藏,并留下你的观点哦!