bluetooth模組學習總結
Android系統中的bluetooth模組
1. 藍芽是什麼
藍芽是一種低功耗的無線連線技術,是一種裝置間短距離的無線通訊方式,這句話表明藍芽以下幾個特性:
- 藍芽是一種無線通訊方式,即表示該通訊需要有對應的協議支援(藍芽無線通訊協議標準) 。
- 藍芽跨裝置使用。
- 低耗能技術。
- 藍芽屬於短距離通訊方式。
2. 藍芽有什麼
藍芽技術有兩種型別:
- Basic Rate/Enhanced Data Rate (BR/EDR)基本速率/增強資料速率即所謂的傳統藍芽技術(藍芽版本2.0/2.1):僅支援P2P一種通訊方式,即1:1裝置間通訊,具有持續無線連線、優化音訊流的特點,所以是藍芽耳機、藍芽揚聲器等音訊傳輸的理想方案
- Low Energy (LE)低功耗即所謂的新型的低功耗藍芽技術(藍芽版本4.0/4.1/4.2/4.3)
所以藍芽模組可以分為經典藍芽模組(v1.1/1.2/2.0/2.1/3.0),低功耗藍芽模組(v4.0/4.1/4.2),以及藍芽雙模模組(支援藍芽所有版本,相容低功耗藍芽及經典藍芽)。
藍芽技術4種通訊方式:
- P2P通訊方式(舊版本):1:1裝置間通訊,具有持續無線連線、優化音訊流的特點,所以是藍芽耳機、藍芽揚聲器等音訊傳輸的理想方案
- P2P(point-to-point)(點對點):1:1支援短時間無限連線,優化了資料傳輸能量消耗,可用於無線鍵盤、無線滑鼠等
- broadcast(廣播資訊):1:m。可以實現本地化資訊共享。廣播資訊顧名思義,一裝置廣播資訊,其他對該資訊感興趣的裝置接受該資訊並進行處理。比如beacon
- mesh(網格):m:m
藍芽常用協議:
. | 含義 | 作用 | 舉例 |
---|---|---|---|
OppProfile | Object Push Profie | 檔案傳輸協議:用於藍芽裝置間的檔案傳輸 | 手機間的檔案傳輸 |
PbapServerProfile | Phone Book Access Profile(PSE) | 讀取聯絡人協議:作為server,本裝置的聯絡人可共享給其他裝置 | 提供聯絡人列表 |
PbapClientProfile | Phone Book Access Profile(PCE) | 讀取聯絡人協議:作為client角色,本裝置可讀取server端的聯絡人 | 讀取聯絡人列表 |
A2dpProfile | Advanced Audio Distribution Profile(SRC:Source) | 高階音訊分發協議:作為server提供音訊源 | 例如可以提供音訊源的手機 |
A2dpSinkProfile | Advanced Audio Distribution Profile(SINK) | 高階音訊分發協議:作為client播放接收到的音訊 | 車載藍芽,藍芽音響 |
HeadsetProfile | Headset Profile | 耳機協議:提供手機音訊 | 連線藍芽耳機 |
HfpClientProfile | Hands-Free Profile | 擴音裝置:播放音訊 | 藍芽耳機 |
HidProfile | Human Interface Device | 人機介面裝置 | 藍芽滑鼠,藍芽鍵盤 |
MapProfile | Message Access Profile | 讀取短訊息協議 | |
SapProfile | SIM Access Profile | 讀取sim卡協議 |
3. 藍芽需要改什麼
藍芽與Android關係:
- Google推出的各Android系統:所支援的藍芽協議profile均是開啟狀態
- 晶片提供商(常見的諸如高通、mtk)修改後的Android原始碼–開發中稱之為base程式碼:新增或者修改某些藍芽profile
- 開發商拿到base程式碼進行進一步加工:新增或者修改某些profile
藍芽堆疊的常規結構:
* 應用框架:
處於應用框架級別的是應用程式碼,它利用 android.bluetooth API 與藍芽硬體進行互動。此程式碼在內部通過 Binder IPC 機制呼叫藍芽程序。
- 藍芽系統服務
藍芽系統服務(位於 packages/apps/Bluetooth 中)被打包為 Android 應用,並在 Android 框架層實現藍芽服務和配置檔案。該應用通過 JNI 呼叫 HAL 層。 - JNI
與 android.bluetooth 相關聯的 JNI 程式碼位於 packages/apps/Bluetooth/jni 中。當發生特定藍芽操作時(例如發現裝置時),JNI 程式碼會呼叫 HAL 層並從 HAL 接收回調。 - HAL
硬體抽象層定義了 android.bluetooth API 和藍芽程序會呼叫的標準介面,並且您必須實現該接口才能使藍芽硬體正常工作。藍芽 HAL 的標頭檔案是 hardware/libhardware/include/hardware/bluetooth.h。另外,請檢視所有 hardware/libhardware/include/hardware/bt_*.h 檔案。 - 藍芽堆疊
系統為您提供了預設藍芽堆疊(位於 system/bt 中)。該堆疊會實現常規藍芽 HAL,並通過擴充套件程式和更改配置對其進行自定義。 - 供應商擴充套件程式
要新增自定義擴充套件程式和用於跟蹤的 HCI 層,一般可以建立一個 libbt-vendor 模組並指定這些元件。
藍芽核心架構:
藍芽程式碼的實現主要包括3個方面:
- 介面UI
- 設定應用中藍芽的ui
- 藍芽本身這個系統應用中的ui
- 藍芽開關預設值
- 協議配置開關:手機是否要支援各種協議
藍芽程式碼分佈:
系統應用設定Settings中的藍芽相關,包括藍芽開關,藍芽掃描,藍芽配對框,藍芽重新命名框,藍芽選擇框等等。
系統中有個藍芽應用Bluetooth,包含藍芽檔案傳入傳出歷史記錄,藍芽配對框,藍芽檔案傳輸框等等。
藍芽協議的具體實現
整合的一些藍芽介面
4. 對藍芽功能新增/修改
開發一個測試工具,主要對bluetooth的Channel進行測試
- 通過暗碼進行開啟這個測試工具
- 可以測試不通的Channel
關於這個工具的開發其實系統已經提供了相應的介面,介面的實現都是不用管的,只要將介面層層包裝,並在工具apk中呼叫包裝好的介面即可。
4.1 首先,看看系統什麼地方定義了該介面:
在檔案hardware/libhardware/include/hardware/bluetooth.h
中有個如下結構體:
/** Represents the standard Bluetooth DM interface. */
typedef struct {
/** set to sizeof(bt_interface_t) */
size_t size;
/**
* Opens the interface and provides the callback routines
* to the implemenation of this interface.
*/
int (*init)(bt_callbacks_t* callbacks );
/** Enable Bluetooth. */
int (*enable)(bool guest_mode);
/** Disable Bluetooth. */
int (*disable)(void);
/*此處省略*/
/** Get Bluetooth profile interface */
const void* (*get_profile_interface) (const char *profile_id);
/** Bluetooth Test Mode APIs - Bluetooth must be enabled for these APIs */
/* Configure DUT Mode - Use this mode to enter/exit DUT mode */
int (*dut_mode_configure)(uint8_t enable);
/* Send any test HCI (vendor-specific) command to the controller. Must be in DUT Mode */
int (*dut_mode_send)(uint16_t opcode, uint8_t *buf, uint8_t len);
/** BLE Test Mode APIs */
/* opcode MUST be one of: LE_Receiver_Test, LE_Transmitter_Test, LE_Test_End */
int (*le_test_mode)(uint16_t opcode, uint8_t *buf, uint8_t len);
/* 該結構體中的函式指標dut_mode_configure && le_test_mode就是提供給上層使用的負責藍芽的開關及基本控制標準介面,本次開發主要就使用這2個介面。*/
} bt_interface_t;
4.2 其次,問題就轉換為如何呼叫這個2介面了。 在 apk中呼叫c/c++層的介面需要通過JNI層將這2介面個進行包裝
- 在檔案
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
檔案中新增對應方法
#define HCI_LE_TRANSMITTER_TEST_OPCODE 0x201E
#define HCI_LE_END_TEST_OPCODE 0x201F
static jint setTestModeNative(JNIEnv *env, jobject object, jint mode) {
ALOGD("%s setTestModeNative mode = %d",__FUNCTION__, mode);
#if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
ALOGI("%s mode = %d",__FUNCTION__, mode);
if (!sBluetoothInterface) return -1;
return sBluetoothInterface->dut_mode_configure(mode);/*呼叫藍芽hal層的介面*/
#endif
return -1;
}
static jint setBtChannelNative(JNIEnv *env, jobject object, jint position) {
ALOGD("%s setBtChannelNative position = %d",__FUNCTION__, position);
#if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
ALOGI("%s position = %d",__FUNCTION__, position);
if (!sBluetoothInterface) return -1;
unsigned char buf[3];
memset(buf, 0, sizeof(buf));
if (position < 0) {
return sBluetoothInterface->le_test_mode(HCI_LE_END_TEST_OPCODE, buf, 0);
}
buf[0] = position; /* tx_channel 0-39*/
buf[1] = 0x25; /* length of test data 0-37*/
buf[2] = 0; /* packet payload <9*/
return sBluetoothInterface->le_test_mode(HCI_LE_TRANSMITTER_TEST_OPCODE, buf, 3);
#endif
return -1;
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"setSocketOptNative", "(III[BI)I", (void*) setSocketOptNative}
/*在陣列sMethods中註冊新加的2個方法為native方法供java層使用*/
,{"setTestModeNative", "(I)I", (void *)setTestModeNative},
{"setBtChannelNative","(I)I",(void *)setBtChannelNative}
/**/
};
將新增的2個方法包裝到AdapterService.java服務中,執行不同程序的應用呼叫。
a. 在檔案frameworks/base/core/java/android/bluetooth/IBluetooth.aidl中新增新方法的呼叫介面,具體如下:
interface IBluetooth
{
/*此處省略*/
int setBtTestMode(int mode);
int setBtChannel(int position);
/*此處省略*/
}
在packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java中實現IBluetooth中的2個介面,這樣對於任何持有AdapterService控制代碼的物件都可以使用新加的2個方法。
import android.bluetooth.IBluetooth;/*匯入aidl介面,實現方法跨程序呼叫*/
public class AdapterService extends Service {
/*此處省略*/
static {
System.loadLibrary("bluetooth_jni");/*load jni靜態庫*/
classInitNative();/*呼叫native中的init方法,為結構體bt_interface_t賦值*/
}
private native int setTestModeNative(int mode);
private native int setBtChannelNative(int position);
/**
* Handlers for incoming service callsH
*/
private AdapterServiceBinder mBinder;
/**
* The Binder implementation must be declared to be a static class, with
* the AdapterService instance passed in the constructor. Furthermore,
* when the AdapterService shuts down, the reference to the AdapterService
* must be explicitly removed.
*
* Otherwise, a memory leak can occur from repeated starting/stopping the
* service...Please refer to android.os.Binder for further details on
* why an inner instance class should be avoided.
*
*/
private static class AdapterServiceBinder extends IBluetooth.Stub {
/*此處省略*/
public int setBtTestMode(int mode) {
Log.d(TAG, "setBtTestMode mode " + mode);
AdapterService service = getService();
if (service == null) return -1;
return service.setTestModeNative(mode);
}
public int setBtChannel(int position) {
Log.d(TAG, "setBtChannel position " + position);
AdapterService service = getService();
if (service == null) return -1;
return service.setBtChannelNative(position);
}
}
/*此處省略*/
}
- 到目前為止,新增的2個方法還處在packages/apps/Bluetooth模組中,繼續將新加的方法包裝到BluetoothAdapter.java中,方便應用程式呼叫,具體修改如下。
public final class BluetoothAdapter {
/*此處省略*/
public int setBtTestMode(int mode) {
Log.d(TAG, "setBtTestMode mode : " + mode);
//IBluetooth service = mBluetoothAdapter.mService;
if (mService == null) {
return -1;
}
try {
return mService.setBtTestMode(mode);
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception: " + ex);
}
return -1;
}
public int setBtChannel(int position) {
Log.d(TAG, "setBtChannel position : " + position);
//IBluetooth service = mBluetoothAdapter.mService;
if (mService == null) {
return -1;
}
try {
return mService.setBtChannel(position);
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception: " + ex);
}
return -1;
}
/*此處省略*/
}
4.3 最後,寫個測試apk,呼叫封裝好的方法。
package com.android.BluetoothTestMode;
import com.android.BluetoothTestMode.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Toast;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Spinner;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;
public class BluetoothTestMode extends Activity implements AdapterView.OnItemSelectedListener {
private Context mContext;
private BluetoothAdapter mBtAdapter;
private final String TAG = "BluetoothTestMode";
private static final int ENABLE_BT_TEST_MODE_DELAY = 3;
private Button mButton01 = null;
private Button mButton02 = null;
private Spinner mSpinner = null;
private int mBluetoothChannel = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
setContentView(R.layout.main);
mButton01 = (Button) findViewById(R.id.Button01);
mButton02 = (Button) findViewById(R.id.Button02);
//bluetooth test mode channel setting
mSpinner = (Spinner) findViewById(R.id.Bluetooth_Channel_Spinner);
mSpinner.setSelection(0, true);
mSpinner.setOnItemSelectedListener(this);
mButton01.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mButton01.setEnabled(false);
mButton02.setEnabled(true);
if (!mBtAdapter.isEnabled()) {
mBtAdapter.enable();
synchronized(mHandler) {
if(mHandler.hasMessages(ENABLE_BT_TEST_MODE_DELAY)){
mHandler.removeMessages(ENABLE_BT_TEST_MODE_DELAY);
}
mHandler.sendEmptyMessageDelayed(ENABLE_BT_TEST_MODE_DELAY, 5000);
}
Toast.makeText(BluetoothTestMode.this, "BT is turning on, please wait...", Toast.LENGTH_SHORT).show();
}else {
int ret = mBtAdapter.setBtTestMode(1);
if (ret == -1) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
}
Log.d(TAG, "BT already ON! enableBtTestMode " + ret);
}
}
});
mButton02.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
if (mBtAdapter.isEnabled()) {
mBtAdapter.setBtChannel(-1);
mBtAdapter.setBtTestMode(0);
mBtAdapter.disable();
Toast.makeText(BluetoothTestMode.this, "BT disabled...", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(BluetoothTestMode.this, "BT already OFF!", Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* Called when the activity will start interacting with the user.
*/
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
/**
* Called when the system is about to start resuming a previous activity.
*/
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
/**
* The final call you receive before your activity is destroyed.
*/
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (mBtAdapter.isEnabled()) {
mBtAdapter.setBtChannel(-1);
mBtAdapter.setBtTestMode(0);
mBtAdapter.disable();
}
}
//bluetooth test mode channel setting
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == mSpinner) {
mBluetoothChannel = position;
if (!mBtAdapter.isEnabled()) {
mBtAdapter.enable();
}
int ret = mBtAdapter.setBtChannel(-1);
Log.d(TAG,"setBtChannel end test " + ret);
ret = mBtAdapter.setBtChannel(mBluetoothChannel);
Log.d(TAG,"setBtChannel start test " + ret);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
//
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == ENABLE_BT_TEST_MODE_DELAY){
int ret = mBtAdapter.setBtTestMode(1);
if (ret == -1) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
}
Log.d(TAG, "enableBtTestMode " + ret);
}
}
};
}