Android 藍牙和BLE應用開發經驗參考

Android 藍牙和BLE應用開發經驗參考

傳統藍牙和BLE的區別

技術規範

經典藍牙(2.1 &3.0)

低功耗藍牙(4.0)

無線電頻率

2.4GHz

2.4GHz

距離

10米/100米

30米

數據速率

1-3Mbps

1Mbps

應用吞吐量

0.7-2.1Mbps

0.2Mbps

發送數據的總時間

100ms

<6ms

耗電量

1

0.01至0.5

最大操作電流

<30mA

<15mA(最高運行時爲15 mA)

主要用途

手機,遊戲機,耳機,立體聲音頻流,汽車和PC等

手機,遊戲機,PC,表,體育和健身,醫療保健,汽車,家用電子,自動化和工業等

 

藍牙的應用開發參考

藍牙基礎

l  藍牙是一種無線技術標準,可實現固定設備、移動設備和樓宇個人域網之間的短距離數據交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍牙技術最初由電信巨頭愛立信公司於1994年創制,當時是作爲RS232數據線的替代方案。藍牙可連接多個設備,克服了數據同步的難題。

l  如今藍牙由藍牙技術聯盟(Bluetooth SpecialInterest Group,簡稱SIG)管理

l  所有的藍牙標準版本都支持向下兼容

l  藍牙最新的版本號是藍牙4.2,還有未發佈的藍牙5.0

l  一個藍牙主設備最能多和7個藍牙從設備進行通信

l  藍牙的數據傳輸速率在1Mbps以內

l  藍牙的理論最大通信距離是100米,傳輸距離越大,功耗越高,現在市面上的流行藍牙設備的傳輸距離大約在30米以內,傳輸距離在10米左右的藍牙設備最多。

l  藍牙使用的頻率是2.4GHZ

藍牙應用場景

l  藍牙應用在手機上

l  藍牙應用在PC上,現在很多PC都帶有藍牙模塊

l  藍牙應用於其它數字設備,如數字照相機、數字攝像機等

l  籃牙技術構成的電子錢包和電子鎖,這方面現在慢慢被主流的NFC所替代

l  籃牙技術在嵌入式設備上的應用如藍牙音箱,藍牙耳機,微波爐、洗衣機、電冰箱、空調機等

藍牙開發基礎

常用的藍牙術語

l  掃描

l  綁定

l  配對

l  連接

藍牙的通訊模型

藍牙和普通的網絡通信一樣,是基於socket進行通訊的,採用的是典型的C/S模型

常用的藍牙協議(Profile)

Profile名稱

主要用途

SPP即Serial Port Profile串口通訊協議

主要用於藍牙基礎數據流的傳輸

A2DP即Advanced Audio Distribution Profile 藍牙音頻傳輸模型協議

主要用來播放高品質的音樂,主要應用場景是藍牙音箱,藍牙耳機

AVRCP即Audio Video Remote Control Profile音頻/視頻遠程控制協議

主要用於藍牙設備的遠程控件,比如控制藍牙耳機的播放,暫停,繼續等

HFP即Hands Free Profile 免提協議

主要用於車載藍牙中,可以實現免提功能,可以控制電話的接聽,掛斷,拒接,音頻撥號等

HDP即Health Device Profile 健康設備協議

主要用於藍牙血壓計,藍牙體重稱等

OPP即Object Push Profile 對象推送協議

主要用於手機與手機或者手機與電腦之間通過藍牙進行文件操作,比如通過藍牙發送文件

 

藍牙開發核心類

類名

解釋

BluetoothAdapter

藍牙適配器類

BluetoothDevice

藍牙設備信息類

BluetoothSocket

藍牙客戶端Socket類

BluetoothServerSocket

藍牙服務端Socket類

BluetoothHeadset

藍牙HFP協議支持類

BluetoothA2dp

藍牙A2DP協議支持類

BluetoothHealth

藍牙HDP協議支持類

 

藍牙開發示例

public static String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";//SPP Profile UUID,這個UUID是藍牙SIG組織定義的官方ID

BluetoothSocketclientSocket = null;
BluetoothServerSocket serverSocket = null;

InputStreamis;
OutputStream os;

Android藍牙API歷史

Google從Android 3.0開始提供傳統藍牙相關的開發API,藍牙API在android.bluetooth包下面

藍牙權限聲明

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"/>

打開藍牙

通過代碼打開
BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
if(adapter.isEnabled())
{
   adapter.disable();//關閉設備藍牙
}
else{
   adapter.enable();//打開設備藍牙
}
通過系統藍牙對話框打開

public static void openBluetoothWithDialog(Activity activity, int seconds) {
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
   activity.startActivityForResult(intent, REQUEST_ENABLE);
}

註冊藍牙狀態監聽器

public void registerBluetoothReceiver() {
   IntentFilter filter = new IntentFilter();
   filter.addAction(BluetoothDevice.ACTION_FOUND);
   filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
   // filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
   // filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
   filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
   mActivity.registerReceiver(receiver, filter);
}

private BroadcastReceiver receiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      if (BluetoothDevice.ACTION_FOUND.equals(action)) {
         BluetoothDevice device = intent
               .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
         deviceList.add(device);
         createBond(device);
      } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
         BluetoothDevice device = intent
               .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
         if(device.getBondState()==BluetoothDevice.BOND_BONDED){
            try {
               clientSocket = BluetoothHelper.connect(device);
               remoteDevice = clientSocket.getRemoteDevice();
               showToast("綁定成功");
               is = clientSocket.getInputStream();
               os = clientSocket.getOutputStream();
               sendMessage("hello boy");
               startMessageLoop();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
      } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
            .equals(action)) {
         showToast("掃描完成");
      }
   }
};

建立藍牙客戶端Socket

public static BluetoothSocket createBluetoothSocket(BluetoothDevice device) {
        BluetoothSocket socket = null;
        UUID uuid = UUID.fromString(SPP_UUID);
        try {
//       Method m =device.getClass().getMethod("createRfcommSocket", newClass[]{int.class});
//       socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));
           
socket =device.createRfcommSocketToServiceRecord(uuid);
        } catch (Exception e) {
            try {
                socket =device.createInsecureRfcommSocketToServiceRecord(uuid);
            } catch (Exception ex) {
                socket = null;
            }
        }
        return socket;
    }

建立藍牙服務端Socket

public static BluetoothServerSocket createServerSocket() {
      BluetoothServerSocket socket = null;
      UUID uuid = UUID.fromString(SPP_UUID);
      BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
      String name=adapter.getName();
      try {
//       Method listenMethod = adapter.getClass().getMethod("listenUsingRfcommOn", new Class[]{int.class});
//       socket = ( BluetoothServerSocket) listenMethod.invoke(adapter, new Object[]{ 29});
         socket = adapter.listenUsingRfcommWithServiceRecord(name, uuid);
      } catch (Exception e) {
         e.printStackTrace();
      }
      return socket;
   }

 

藍牙掃描

BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
if(adapter.isDiscovering())
{
   adapter.cancelDiscovery();//取消掃描
}
else{
   adapter.startDiscovery();//開始掃描
}

綁定藍牙設備

public static boolean createBond(BluetoothDevice device) {
        if (device.getBondState() == BluetoothDevice.BOND_NONE) {
         return device.createBond();
//            Method createBondMethod = null;
//            try {
//                createBondMethod =BluetoothDevice.class.getMethod("createBond");
//                Object objResult = createBondMethod.invoke(device);
//                if (objResult != null)
//                    returnBoolean.parseBoolean(objResult.toString());
//                return false;
//            } catch (Exception e) {
//                e.printStackTrace();
//                return false;
//            }
       
}
        return true;
    }

獲取系統藍牙綁定的設備列表

public static ArrayList<BluetoothDevice> getBondedDevices()
{
   ArrayList<BluetoothDevice>deviceList=new ArrayList<BluetoothDevice>();
   BluetoothAdapter bluetoothAdapter =BluetoothAdapter.getDefaultAdapter();
   Object[] devices =bluetoothAdapter.getBondedDevices().toArray();
   for (int i = 0; i < devices.length; i++) {
      BluetoothDevice device =(BluetoothDevice) devices[i];
      if(!deviceList.contains(device))
      {
         deviceList.add(device);
      }
   }
   return deviceList;
}

判斷藍牙A2DP的連接狀態

public static boolean isA2DPDeviceConnected(Context context)
{
   BluetoothAdapteradapter=BluetoothAdapter.getDefaultAdapter();
   int state=adapter.getProfileConnectionState(BluetoothProfile.A2DP);
   return state==BluetoothProfile.STATE_CONNECTED;
}

藍牙連接

public static BluetoothSocket connect(BluetoothDevice device) {
    BluetoothSocket socket = null;
    try {
        socket = createBluetoothSocket(device);
        if (socket == null) return null;
        socket.connect();
    } catch (Exception e) {
        socket = null;
        e.printStackTrace();
    }
    return socket;
}

建立服務端消息循環

serverSocket =createServerSocket();

private void startServerListening() {
   new Thread(new Runnable() {
      @Override
     
public void run() {
         while (true) {
            try {
               clientSocket = serverSocket.accept();
               is = clientSocket.getInputStream();
               os = clientSocket.getOutputStream();
               startMessageLoop();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
      }
   }).start();
}

建立客戶端消息循環

private void startMessageLoop() {
      new Thread(new Runnable() {
         @Override
        
public void run() {
            while (true) {
               byte[] buffer = new byte[1024];
               try {
                  int readed = is.read(buffer);
                  String msg = new String(buffer, 0, readed);
                  Stringresult=HexHelper.encodeHexStr(buffer);
                  if(TextUtils.isEmpty(result)) return;
                  if(result.equalsIgnoreCase("aa12040042")){
                     //do something
                 
}
              
} catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      }).start();
   }

發送藍牙消息

//aa12040042

byte[]cmd_get_pid=new byte[]
           {
              (byte)0xaa,
              (byte)0x12,
              (byte)0x04,
              (byte)0x00,
              (byte)0x42
          
};

os.write(cmd_get_pid);
os.flush();

接收藍牙消息

int readed = is.read(buffer);
String msg = new String(buffer, 0,readed);
String result=HexHelper.encodeHexStr(buffer);
if(TextUtils.isEmpty(result)) return;
if(result.equalsIgnoreCase("aa12040042")){
   //do something
}

BLE的應用開發參考

BLE基礎

l  BLE技術是低成本、短距離、可互操作的魯棒性無線技術,工作在免許可的2.4GHz ISM射頻頻段。它從一開始就設計爲超低功耗(ULP)無線技術。它利用許多智能手段最大限度地降低功耗。藍牙低功耗技術採用可變連接時間間隔,這個間隔根據具體應用可以設置爲幾毫秒到幾秒不等。另外,因爲BLE技術採用非常快速的連接方式,因此平時可以處於“非連接”狀態(節省能源),此時鏈路兩端相互間只是知曉對方,只有在必要時纔開啓鏈路,然後在儘可能短的時間內關閉鏈路。

l  BLE是藍牙4.0的核心Profile,主打功能是快速搜索,快速連接,超低功耗保持連接和傳輸數據,弱點是數據傳輸速率低,由於BLE的低功耗特點,因此普遍用於穿戴設備

l  低功耗

l  傳輸距離短

l  傳輸速度慢

BLE應用場景

l  智能穿戴設備,比如智能手環,智能手錶等

l  智能家居,智能燈,智能空調,藍牙電子稱等

l  健康設備,心率監測儀等

BLE開發基礎

BLE調試工具

BLE Scanner

BLE Explorer

拼接完整的藍牙設備UUID
public static final String BASE_ADDRESS="00000000-0000-1000-8000-00805F9B34FB"; //藍牙UUID的基地址

如上圖,假如我們要拼接DeviceInformation Service服務的完整的UUID,我們可以拿藍牙UUID的基地址的首段+0x180A,

所以我們得到的完整的DeviceInformation Service的UUID是0000180A-0000-1000-8000-00805F9B34FB,我們可以把這個拼接後的UUID用在我們的代碼中

BLE服務,特性,特性掃描器的UUID拼接也是這個道理

常用的BLE術語

l  GATT

l  服務

l  特性

l  特性描述符

l  中央設備

l  外圍設備

關鍵術語和概念

l  Generic Attribute Profile(GATT)—GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲“屬性”的數據塊。目前所有的BLE應用都基於GATT。 藍牙SIG規定了許多低功耗設備的配置文件。配置文件是設備如何在特定的應用程序中工作的規格說明。注意一個設備可以實現多個配置文件。例如,一個設備可能包括心率監測儀和電量檢測。

l  Attribute Protocol(ATT)—GATT在ATT協議基礎上建立,也被稱爲GATT/ATT。ATT對在BLE設備上運行進行了優化,爲此,它使用了儘可能少的字節。每個屬性通過一個唯一的的統一標識符(UUID)來標識,每個String類型UUID使用128 bit標準格式。屬性通過ATT被格式化爲characteristics和services。

l  Characteristic 一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor,characteristic可以被認爲是一個類型,類似於類。

l  Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變量可接受的範圍,或者一個characteristic變量特定的測量單位。

l  Service service是characteristic的集合。例如,你可能有一個叫“Heart Rate Monitor(心率監測儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測量)”等。你可以在bluetooth.org 找到一個目前支持的基於GATT的配置文件和服務列表。

角色和責任

l  中央 VS 外圍設備。 適用於BLE連接本身。中央設備掃描,尋找廣播;外圍設備發出廣播。

l  GATT 服務端 VS GATT 客戶端。決定了兩個設備在建立連接後如何互相交流。

BLE核心類

類名

解釋

BluetoothAdapter

藍牙適配器類

BluetoothDevice

藍牙設備類

BluetoothGatt

作爲中央設備來使用和處理數據,是BLE的核心工具類,可以通過這個類來獲取BLE設備提供的服務

BluetoothGattService

BLE服務類

BluetoothGattCharacteristic

BLE特性類

BluetoothGattDescriptor

BLE特性描述器類

BluetoothGattCallback

BLE核心通訊回調類

LeScanCallback

BLE掃描回調類

 

BLE核心類關係圖

 

BLE開發示例

List<BluetoothGattService> mGattServiceList=null;
BluetoothDevice mRemoteDevice;
BluetoothGatt mBluetoothGatt;

 

public static final String DEVICE_INFO_SERVICE="00001800-0000-1000-8000-00805F9B34FB";
public static final String SYSTEM_ID_CHAR="00002a04-0000-1000-8000-00805F9B34FB";

 

BLE開發API歷史

Google從Android 4.3開始提供BLE的開發API,所以BLE開發只支持Android 4.3以上的手機

藍牙權限聲明

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"/>

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

掃描BLE設備

BLE設備掃描回調
LeScanCallback leScanCallback=new LeScanCallback()
{
   /**
    * BLE設備掃描結果回調函數
    * @param device 掃描到的BLE設備
    * @param rssi BLE設備的信號強度
    * @param scanRecord BLE設備發出的特定信息,可能通過這個信息來判斷掃描到的BLE設備是不是指定類型的BLE設備
    */
   @Override
   public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
      mActivity.runOnUiThread(new Runnable()
      {
         @Override
         public void run() {
            if(!mDeviceList.contains(device))
            {
               mDeviceList.add(device);
               mAdapter.notifyDataSetChanged();
            }
         }
      });
   }
};
開始BLE設備掃描
public static void startLeScan(UUID[] serviceUuids,LeScanCallback callback)
   {
      BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
      if(serviceUuids==null)
      {
         adapter.startLeScan(callback);
      }
      else
      {
         adapter.startLeScan(serviceUuids, callback);
      }
   }
停止BLE設備掃描
public static void stopLeScan(LeScanCallback callback)
{
   BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
   adapter.stopLeScan(callback);
}

判斷特性的屬性

判斷特性是否可讀
public static boolean isBluetoothGattCharacteristicRead(BluetoothGattCharacteristic characteristic)
{
   return (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ)== BluetoothGattCharacteristic.PROPERTY_READ;
}
判斷特性是否可寫
public static boolean isBluetoothGattCharacteristicRead(BluetoothGattCharacteristic characteristic)
{
   return (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ)== BluetoothGattCharacteristic.PROPERTY_READ;
}
判斷特性是否是Notify特性
public static boolean isBluetoothGattCharacteristicNotify(BluetoothGattCharacteristic characteristic)
{
   return (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY;
}

連接BLE設備

和BLE設備通訊的回調
BluetoothGattCallback mGattCallback=new BluetoothGattCallback()
{
   @Override
   public void onConnectionStateChange(BluetoothGatt gatt, int status,
         int newState) {
      if(newState==BluetoothGatt.STATE_CONNECTED)
      {
         boolean success=gatt.discoverServices();//查找BLE設備提供的所有服務
         showToast(gatt.getDevice().getName()+" connected");
      }
      else if(newState==BluetoothGatt.STATE_DISCONNECTED)
      {
         if(mGattServiceList!=null)
            mGattServiceList.clear();
         showToast(gatt.getDevice().getName()+" disconnected");
      }
      super.onConnectionStateChange(gatt, status, newState);
   }
   
   @Override
   public void onServicesDiscovered(BluetoothGatt gatt, int status) {
      if(status==BluetoothGatt.GATT_SUCCESS)
      {
         mGattServiceList=gatt.getServices();//獲取BLE設備提供的所有服務列表
         BluetoothGattService devInfoService=gatt.getService(UUID.fromString(DEVICE_INFO_SERVICE));
         BluetoothGattCharacteristic systemID=devInfoService.getCharacteristic(UUID.fromString(SYSTEM_ID_CHAR));
         gatt.readCharacteristic(systemID);
      }
      
      super.onServicesDiscovered(gatt, status);
   }
   
   @Override
   public void onCharacteristicChanged(BluetoothGatt gatt,
         BluetoothGattCharacteristic characteristic) {
      try
      {
         byte[]value=characteristic.getValue();
         if(value!=null)
         {
            String str=HexHelper.encodeHexStr(value);
            Log.i("hello", str);
         }
      }
      catch(Exception e)
      {
         Log.i("hello", e.getMessage());
      }
      super.onCharacteristicChanged(gatt, characteristic);
   }

   @Override
   public void onCharacteristicRead(BluetoothGatt gatt,
         BluetoothGattCharacteristic characteristic, int status) {
      if(status==BluetoothGatt.GATT_SUCCESS)
      {
         try
         {
            byte[]value=characteristic.getValue();
            if(value!=null)
            {
               String str=HexHelper.encodeHexStr(value);
               Log.i("hello", str);
            }
         }
         catch(Exception e)
         {
            Log.i("hello", e.getMessage());
         }
      }
      
      super.onCharacteristicRead(gatt, characteristic, status);
   }

   @Override
   public void onCharacteristicWrite(BluetoothGatt gatt,
         BluetoothGattCharacteristic characteristic, int status) {
      super.onCharacteristicWrite(gatt, characteristic, status);
   }

   @Override
   public void onDescriptorRead(BluetoothGatt gatt,
         BluetoothGattDescriptor descriptor, int status) {
      super.onDescriptorRead(gatt, descriptor, status);
   }

   @Override
   public void onDescriptorWrite(BluetoothGatt gatt,
         BluetoothGattDescriptor descriptor, int status) {
      super.onDescriptorWrite(gatt, descriptor, status);
   }
};
異步連接BLE設備
public void connectGatt(BluetoothDevice device)
{
   mBluetoothGatt=device.connectGatt(App.Instance, false, mGattCallback);
}
斷開和BLE設備的連接
public void disconnect() {
    if (mBluetoothGatt != null) {
       mBluetoothGatt.disconnect();
       mBluetoothGatt.close();
       mBluetoothGatt=null;
    }
}

設置BLE通知特性

public void setCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, UUID uuid, boolean enable) {
   if (gatt == null || characteristic==null || uuid==null) return;
   gatt.setCharacteristicNotification(characteristic, enable);
   BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid);
   descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
   gatt.writeDescriptor(descriptor);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章