Unity3D蓝牙连接Mi Band 5手环获取实时心率
直接上代码
using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;publicclassBLEMiBand:MonoBehaviour{publicclassUUIDS{publicstaticreadonlystring miband1 ="0000fee0-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring miband2 ="0000fee1-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring alert ="00001802-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring devinfo ="0000180a-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring heartrate ="0000180d-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring notifications ="00001811-0000-1000-8000-00805f9b34fb";}publicclassCHAR_UUIDS{publicstaticreadonlystring devicename ="00002a00-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring hz ="00000002-0000-3512-2118-0009af100700";publicstaticreadonlystring sensor ="00000001-0000-3512-2118-0009af100700";publicstaticreadonlystring auth ="00000009-0000-3512-2118-0009af100700";publicstaticreadonlystring alert ="00002a06-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring current_time ="00002a2b-0000-1000-8000-00805f9b34fb";// unknown= "00002a23-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring serial ="00002a25-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring hrdw_revision ="00002a27-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring revision ="00002a28-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring heartrate_measure ="00002a37-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring heartrate_control ="00002a39-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring notifications ="00002a46-0000-1000-8000-00805f9b34fb";// unknown= "00002a50-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring age ="00002a80-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring le_params ="0000ff09-0000-1000-8000-00805f9b34fb";publicstaticreadonlystring configuration ="00000003-0000-3512-2118-0009af100700";publicstaticreadonlystring fetch ="00000004-0000-3512-2118-0009af100700";publicstaticreadonlystring activity_data ="00000005-0000-3512-2118-0009af100700";publicstaticreadonlystring battery ="00000006-0000-3512-2118-0009af100700";publicstaticreadonlystring steps ="00000007-0000-3512-2118-0009af100700";publicstaticreadonlystring user_settings ="00000008-0000-3512-2118-0009af100700";publicstaticreadonlystring music_notification ="00000010-0000-3512-2118-0009af100700";publicstaticreadonlystring deviceevent ="00000010-0000-3512-2118-0009af100700";publicstaticreadonlystring chunked_transfer ="00000020-0000-3512-2118-0009af100700";}publicclassNOTIFICATION_TYPES{publicstaticreadonlybyte[] auth ={0x02,0x00};publicstaticreadonlybyte[] msg ={0x01,0x01};publicstaticreadonlybyte[] call ={0x03,0x01};publicstaticreadonlybyte[] missed ={0x04,0x01};publicstaticreadonlybyte[] sms ={0x05,0x01};}publicclassAUTH_TYPES{publicstaticreadonlybyte[] received ={0x10,0x01,0x01};publicstaticreadonlybyte[] requestcode ={0x10,0x02,0x01};publicstaticreadonlybyte[] authed ={0x10,0x03,0x01};publicstaticreadonlybyte[] authfail ={0x10,0x03,0x08};}publicclassHEARTRATECONTROL_TYPES{publicstaticreadonlybyte[] stop_heartrate_manual ={0x15,0x01,0x00};publicstaticreadonlybyte[] stop_heartrate_auto ={0x15,0x02,0x00};publicstaticreadonlybyte[] start_heartrate_manual ={0x15,0x01,0x01};publicstaticreadonlybyte[] start_heartrate_auto ={0x15,0x02,0x01};publicstaticreadonlybyte[] ping ={0x16};}publicstring DeviceName ="Mi Smart Band 5";publicstring AuthKey ="50a2c4668b3e565c0f495885251c4346";publicText txtMiBandLabel;publicText txtMiBandStatus;publicText txtBluetoothStatus;publicGameObject PanelMiddle;publicText txtHeartRate;enum States
{
None,
Scan,
Connect,
Auth,
Subscribe,
Unsubscribe,
Disconnect,
Communication,}[SerializeField]privatebool bConnected =false;[SerializeField]privatebool bAuthed =false;[SerializeField]privatebool bSubscribed =false;[SerializeField]privatestring device;[SerializeField]bool foundChartAuth =false;[SerializeField]bool foundChartHeartrateControl =false;[SerializeField]bool foundChartHeartrateMeasurement =false;[SerializeField]bool foundChartSensor =false;voidReset(){
bConnected =false;
bAuthed =false;
device =null;
foundChartAuth =false;
foundChartHeartrateControl =false;
foundChartHeartrateMeasurement =false;
foundChartSensor =false;
PanelMiddle.SetActive(false);}voidInitBLE(){
txtMiBandStatus.text ="";
txtBluetoothStatus.text ="Initializing...";Reset();
BluetoothLEHardwareInterface.Initialize(true,false,()=>{StartCoroutine(delayScan(0.1f));
txtBluetoothStatus.text ="Initialized";},(error)=>{
BluetoothLEHardwareInterface.Log("Error: "+ error);});}// Use this for initializationvoidStart(){InitBLE();}// Update is called once per framevoidUpdate(){}IEnumeratordelayScan(float delay){yieldreturnnewWaitForSeconds(delay);
txtBluetoothStatus.text ="Scanning devices["+ DeviceName +"]";
BluetoothLEHardwareInterface.ScanForPeripheralsWithServices(null,(address, name)=>{// we only want to look at devices that have the name we are looking for// this is the best way to filter out devicesif(name.Contains(DeviceName)){
DeviceName = name;// it is always a good idea to stop scanning while you connect to a device// and get things set up
BluetoothLEHardwareInterface.StopScan();
txtBluetoothStatus.text ="";// add it to the list and set to connect to it
device = address;
txtMiBandLabel.text = DeviceName +"["+ device +"]";
txtMiBandStatus.text ="Found "+ DeviceName;StartCoroutine(delayConnect(0.5f));}},null,false,false);}IEnumeratordelayConnect(float delay){yieldreturnnewWaitForSeconds(delay);// set these flags
txtMiBandStatus.text ="Connecting to "+ DeviceName;// note that the first parameter is the address, not the name. I have not fixed this because// of backwards compatiblity.// also note that I am note using the first 2 callbacks. If you are not looking for specific characteristics you can use one of// the first 2, but keep in mind that the device will enumerate everything and so you will want to have a timeout// large enough that it will be finished enumerating before you try to subscribe or do any other operations.
BluetoothLEHardwareInterface.ConnectToPeripheral(device,null,null,(address, serviceUUID, characteristicUUID)=>{if(IsEqual(serviceUUID, UUIDS.miband2)&&IsEqual(characteristicUUID, CHAR_UUIDS.auth)){
foundChartAuth =true;}elseif(IsEqual(serviceUUID, UUIDS.heartrate)&&IsEqual(characteristicUUID, CHAR_UUIDS.heartrate_control)){
foundChartHeartrateControl =true;}elseif(IsEqual(serviceUUID, UUIDS.heartrate)&&IsEqual(characteristicUUID, CHAR_UUIDS.heartrate_measure)){
foundChartHeartrateMeasurement =true;}elseif(IsEqual(serviceUUID, UUIDS.miband1)&&IsEqual(characteristicUUID, CHAR_UUIDS.sensor)){
foundChartSensor =true;}// all need characteristics foundif(foundChartAuth && foundChartHeartrateControl && foundChartHeartrateMeasurement && foundChartSensor){
bConnected =true;
txtMiBandStatus.text ="Connected to "+ DeviceName;// if we have found the characteristic that we are waiting for// authenticate. make sure there is enough timeout that if the// device is still enumerating other characteristics it finishes// before we try to subscribeStartCoroutine(delayAuthenticate(12f));}},(disconnectedAddress)=>{
BluetoothLEHardwareInterface.Log("Device disconnected: "+ disconnectedAddress);
txtMiBandStatus.text ="Disconnected";
bConnected =false;
bAuthed =false;
bSubscribed =false;});}IEnumeratordelayAuthenticate(float delay){
PanelMiddle.SetActive(true);yieldreturnnewWaitForSeconds(delay);SubscribeAuth();yieldreturnnewWaitForSeconds(0.5f);SendAuth();}IEnumeratordelaySubscribeHeartrate(float delay){yieldreturnnewWaitForSeconds(delay);
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_control,
HEARTRATECONTROL_TYPES.stop_heartrate_auto, HEARTRATECONTROL_TYPES.stop_heartrate_auto.Length,false,(charUUID)=>{});yieldreturnnewWaitForSeconds(0.5f);
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_control,
HEARTRATECONTROL_TYPES.stop_heartrate_manual, HEARTRATECONTROL_TYPES.stop_heartrate_manual.Length,false,(charUUID)=>{});yieldreturnnewWaitForSeconds(0.5f);
BluetoothLEHardwareInterface.SubscribeCharacteristicWithDeviceAddress(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_measure,null,(address, characteristicUUID, bytes)=>{
txtMiBandStatus.text ="Received Subscribe["+ bytes.Length.ToString()+"]["+ Time.time.ToString("F2")+"]"+ Utility.Bytes2Hex(bytes)+"";if(2== bytes.Length){byte[] heartrate ={ bytes[1], bytes[0]};
txtHeartRate.text = System.BitConverter.ToInt16(heartrate,0).ToString("D3")+"BPM";}elseif(1== bytes.Length){
txtHeartRate.text =((int)(bytes[0])).ToString("D3")+"BPM";}});yieldreturnnewWaitForSeconds(0.5f);
txtMiBandStatus.text ="Starting manual heart rate detection...";
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_control,
HEARTRATECONTROL_TYPES.start_heartrate_manual, HEARTRATECONTROL_TYPES.start_heartrate_manual.Length,false,(charUUID)=>{
txtMiBandStatus.text ="Startedmanual heart rate detection...";});while(bSubscribed){yieldreturnnewWaitForSeconds(12f);
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_control,
HEARTRATECONTROL_TYPES.ping, HEARTRATECONTROL_TYPES.ping.Length,false,(charUUID)=>{});}}IEnumeratordelayUnsubscribeHeartrate(float delay){yieldreturnnewWaitForSeconds(delay);
BluetoothLEHardwareInterface.UnSubscribeCharacteristic(device, UUIDS.heartrate, CHAR_UUIDS.heartrate_measure,null);
bSubscribed =false;StartCoroutine(delayDisconnect(4f));}IEnumeratordelayDisconnect(float delay){yieldreturnnewWaitForSeconds(delay);if(bConnected){
BluetoothLEHardwareInterface.DisconnectPeripheral(device,(address)=>{
BluetoothLEHardwareInterface.DeInitialize(()=>{
bConnected =false;});});}else{
BluetoothLEHardwareInterface.DeInitialize(()=>{});}}publicvoidSubscribeAuth(){
txtMiBandStatus.text ="Subscribe Characteristic[CHAR_UUIDS.auth]";
BluetoothLEHardwareInterface.SubscribeCharacteristic(device, UUIDS.miband2, CHAR_UUIDS.auth,null,(characteristicUUID, bytes)=>{string msg ="Received Serial[CHAR_UUIDS.auth]";if(bytes.Length >=3){byte[] code ={ bytes[0], bytes[1], bytes[2]};if(IsEqual(code, AUTH_TYPES.received)){
msg ="Authenticate Received";}elseif(bytes.Length ==19&&IsEqual(code, AUTH_TYPES.requestcode)){
msg ="Authenticate Request Code";
List<byte> serial =newList<byte>();
serial.AddRange(bytes);
serial.RemoveRange(0,3);if(serial.Count !=16){
txtMiBandStatus.text ="Unknown Request Code["+ Utility.Bytes2Hex(serial.ToArray())+"]";
msg ="";}else{try{byte[] key = Utility.Hex2Bytes(AuthKey);if(key.Length !=16){
txtMiBandStatus.text ="AuthKey must be 16 bytes["+ AuthKey +"]";
msg ="";}else{byte[] iv =newbyte[16];byte[] aes =AesEncrypt(serial.ToArray(), key, iv);
List<byte> ls =newList<byte>();
ls.Add(0x03);
ls.Add(0x00);
ls.AddRange(aes);byte[] authKey = ls.ToArray();
txtMiBandStatus.text ="Writing Auth Code["+ Utility.Bytes2Hex(authKey)+"]";
msg ="";
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.miband2, CHAR_UUIDS.auth, authKey, authKey.Length,false,(charUUID)=>{
txtMiBandStatus.text ="Writed Auth Code["+ Utility.Bytes2Hex(authKey)+"]";});}}catch(System.Exception E){
msg ="Unknown Exception["+ E.Message +"]";}}}elseif(IsEqual(code, AUTH_TYPES.authfail)){
msg ="Authenticate Failed";Invoke("SendAuth",5);// retry after 5 seconds}elseif(IsEqual(code, AUTH_TYPES.authed)){
msg ="Authenticate Successful";
bAuthed =true;StartCoroutine(delaySubscribeHeartrate(0.5f));}else{
msg ="Authenticate Received Unknown Message";}}if(!string.IsNullOrEmpty(msg)){
txtMiBandStatus.text = msg +"["+ bytes.Length.ToString()+"]"+ Utility.Bytes2Hex(bytes);}});}publicvoidSendAuth(){
txtMiBandStatus.text ="Sending Auth Request["+ Utility.Bytes2Hex(NOTIFICATION_TYPES.auth)+"]";if(bConnected){if(!bAuthed){
BluetoothLEHardwareInterface.WriteCharacteristic(device, UUIDS.miband2, CHAR_UUIDS.auth, NOTIFICATION_TYPES.auth, NOTIFICATION_TYPES.auth.Length,false,(charUUID)=>{
txtMiBandStatus.text ="Sent Auth Request["+ Utility.Bytes2Hex(NOTIFICATION_TYPES.auth)+"]["+ charUUID +"]";});}}}publicvoidUnsubscribe(){
txtMiBandStatus.text ="Unsubscribe";StartCoroutine(delayUnsubscribeHeartrate(1.0f));}stringFullUUID(string uuid){return"0000"+ uuid +"-0000-1000-8000-00805F9B34FB";}boolIsEqual(string uuid1,string uuid2){if(uuid1.Length ==4)
uuid1 =FullUUID(uuid1);if(uuid2.Length ==4)
uuid2 =FullUUID(uuid2);return(uuid1.ToUpper().Equals(uuid2.ToUpper()));}boolIsEqual(byte[] data1,byte[] data2){if(data1.Length != data2.Length){returnfalse;}for(int i =0; i < data1.Length;++i){if(data1[i]!= data2[i]){returnfalse;}}returntrue;}// AES CBCpublicstaticbyte[]AesEncrypt(byte[] byteContnet,byte[] byteKEY,byte[] byteIV){var aes =newSystem.Security.Cryptography.RijndaelManaged();;
aes.Padding = System.Security.Cryptography.PaddingMode.None;
aes.Mode = System.Security.Cryptography.CipherMode.CBC;
aes.Key = byteKEY;
aes.IV = byteIV;var crypto = aes.CreateEncryptor(byteKEY, byteIV);byte[] decrypted = crypto.TransformFinalBlock(byteContnet,0, byteContnet.Length);
crypto.Dispose();return decrypted;}publicstaticbyte[]Hex2Bytes(string src){
System.Collections.Generic.List<byte> ls =newSystem.Collections.Generic.List<byte>();char[] chs = src.ToCharArray();for(int i =0; i < chs.Length; i +=2){byte b = System.Convert.ToByte(newstring(chs, i,2),16);
ls.Add(b);}return ls.ToArray();}publicstaticstringBytes2Hex(byte[] src){
System.Collections.Generic.List<char> ls =newSystem.Collections.Generic.List<char>();char[] hexDigits ={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};for(int i =0; i < src.Length; i++){
ls.Add(hexDigits[src[i]>>4&0x0f]);
ls.Add(hexDigits[src[i]&0x0f]);}returnnewstring(ls.ToArray());}}
以上代码适用于MiBand 3,4,5,不适用于MiBand 6,Auth验证方法应该有调整了
参考:
Unity3D BLE插件
蓝牙通信插件最新版Bluetooth LE for iOS tvOS and Android.unitypackage
MiBand AuthKey获取
Your Soul, Your Beats! —— 小米手环实时心率采集
GitHub项目
Mi Band 4/5 Heart Rate Monitor
版权归原作者 foenix66 所有, 如有侵权,请联系我们删除。