在 Android 低功耗蓝牙上读取 GATT 属性的问题

Posted

技术标签:

【中文标题】在 Android 低功耗蓝牙上读取 GATT 属性的问题【英文标题】:Issue with reading GATT Attribute on Android Bluetooth Low Energy 【发布时间】:2015-10-20 07:44:41 【问题描述】:

我正在尝试从 GATT 服务器读取一个值并将其显示在我的屏幕上。我已经阅读了各种教程并试图找到各种方法来解决这个问题,但我在某个地方磕磕绊绊!

我们将不胜感激。

布局 XML:

<?xml version="1.0" encoding="utf-8"?>

<TextView android:text="Key : " android:layout_
    android:layout_
    android:id="@+id/textView2" />

<TextView
    android:layout_
    android:layout_
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:text="Medium Text"
    android:id="@+id/pubkey"
    android:layout_alignTop="@+id/textView2"
    android:layout_toEndOf="@+id/textView2"
    android:layout_marginStart="30dp" />
</RelativeLayout>

MainActivity.java

public class MainActivity extends Activity implements  BluetoothAdapter.LeScanCallback 
private static final String TAG = "BluetoothGattActivity";
private static final String DEVICE_NAME = "PUNE\u0005\u0012\b";

/*
SECURITY SERVICE
 */
private static final UUID SECURITY_SERVICE = UUID.fromString("3E099914-293F-11E4-93BD-AFD0FE6D1DFD");
private static final UUID SECURITY_PUBLICKEY = UUID.fromString("3E099915-293F-11E4-93BD-AFD0FE6D1DFD");


private BluetoothAdapter mBluetoothAdapter;
private SparseArray<BluetoothDevice> mDevices;

private BluetoothGatt mConnectedGatt;

private  TextView publicKEY;

private ProgressDialog mProgress;


@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.activity_main);
    setProgressBarIndeterminate(true);

    publicKEY = (TextView) findViewById(R.id.pubkey);


    BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
    mBluetoothAdapter = manager.getAdapter();

    mDevices = new SparseArray<BluetoothDevice>();

    mProgress = new ProgressDialog(this);
    mProgress.setIndeterminate(true);
    mProgress.setCancelable(false);



@Override
protected void onResume() 
    super.onResume();
    /*
     * We need to enforce that Bluetooth is first enabled, and take the
     * user to settings to enable it if they have not done so.
     */
    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) 
        //Bluetooth is disabled
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivity(enableBtIntent);
        finish();
        return;
    

    /*
     * Check for Bluetooth LE Support.  In production, our manifest entry will keep this
     * from installing on these devices, but this will allow test devices or other
     * sideloads to report whether or not the feature exists.
     */
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) 
        Toast.makeText(this, "No LE Support.", Toast.LENGTH_SHORT).show();
        finish();
        return;
    

    clearDisplayValues();



@Override
protected void onPause() 
    super.onPause();
    //Make sure dialog is hidden
    mProgress.dismiss();
    //Cancel any scans in progress
    mHandler.removeCallbacks(mStopRunnable);
    mHandler.removeCallbacks(mStartRunnable);
    mBluetoothAdapter.stopLeScan(this);


@Override
protected void onStop() 
    super.onStop();
    //Disconnect from any active tag connection
    if (mConnectedGatt != null) 
        mConnectedGatt.disconnect();
        mConnectedGatt = null;
    



@Override
public boolean onCreateOptionsMenu(Menu menu) 
    // Add the "scan" option to the menu
    getMenuInflater().inflate(R.menu.main, menu);
    //Add any device elements we've discovered to the overflow menu
    for (int i=0; i < mDevices.size(); i++) 
        BluetoothDevice device = mDevices.valueAt(i);
        menu.add(0, mDevices.keyAt(i), 0, device.getName());
    

    return true;


@Override
public boolean onOptionsItemSelected(MenuItem item) 
    switch (item.getItemId()) 
        case R.id.action_scan:
            mDevices.clear();
            startScan();
            return true;
        default:
            //Obtain the discovered device to connect with
            BluetoothDevice device = mDevices.get(item.getItemId());
            Log.i(TAG, "Connecting to "+device.getName());
            /*
             * Make a connection with the device using the special LE-specific
             * connectGatt() method, passing in a callback for GATT events
             */
            mConnectedGatt = device.connectGatt(this, false, mGattCallback);
            //Display progress UI
            mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Connecting to "+device.getName()+"..."));
            return super.onOptionsItemSelected(item);
    


private void clearDisplayValues() 
    publicKEY.setText("---");



private Runnable mStopRunnable = new Runnable() 
    @Override
    public void run() 
        stopScan();
    
;
private Runnable mStartRunnable = new Runnable() 
    @Override
    public void run() 
        startScan();
    
;

private void startScan() 
    mBluetoothAdapter.startLeScan(this);
    setProgressBarIndeterminateVisibility(true);

    mHandler.postDelayed(mStopRunnable, 2500);


private void stopScan() 
    mBluetoothAdapter.stopLeScan(this);
    setProgressBarIndeterminateVisibility(false);

我所有的回调都在这里

/* BluetoothAdapter.LeScanCallback */


@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) 
    Log.i(TAG, "New LE Device: " + device.getName() + " @ " + rssi);
    /*
     * We are looking for SensorTag devices only, so validate the name
     * that each device reports before adding it to our collection
     */
    if (DEVICE_NAME.equals(device.getName())) 
        mDevices.put(device.hashCode(), device);
        //Update the overflow menu
        invalidateOptionsMenu();
    



private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() 


    private int mState = 0;

    private void reset()
    
     mState= 0;
    

    private void advance()
    
        mState++;
    

    private void enabled(BluetoothGatt gatt) 
        BluetoothGattCharacteristic characteristic;

        switch(mState) 
            case 0:
                Log.d(TAG, "Enable Public Key");
                characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);

                break;

            default:
                mHandler.sendEmptyMessage(MSG_DISMISS);
                Log.i(TAG, "All Sensors Enabled");
                return;
        


    

    private void readData(BluetoothGatt gatt)
    

        BluetoothGattCharacteristic characteristic;

        switch (mState) 
            case 0:
                Log.d(TAG, "Reading Public Key");
                characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
                break;

            default:
                mHandler.sendEmptyMessage(MSG_DISMISS);
                Log.i(TAG, "All Sensors Enabled");
                return;
        
        gatt.readCharacteristic(characteristic);


    

    private void enableNotif(BluetoothGatt gatt) 
        BluetoothGattCharacteristic characteristic;
        switch (mState) 
            case 0:
                Log.d(TAG, "Setting Notification Public Key");
                characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
                break;

            default:
                mHandler.sendEmptyMessage(MSG_DISMISS);
                Log.i(TAG, "All Sensors Enabled");
                return;
        

        gatt.setCharacteristicNotification(characteristic, true);

    

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) 
        Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState));
        if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) 
            /*
             * Once successfully connected, we must next discover all the services on the
             * device before we can read and write their characteristics.
             */
            gatt.discoverServices();
            mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Discovering Services..."));
         else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED) 
            /*
             * If at any point we disconnect, send a message to clear the weather values
             * out of the UI
             */
            mHandler.sendEmptyMessage(MSG_CLEAR);
         else if (status != BluetoothGatt.GATT_SUCCESS) 
            /*
             * If there is a failure at any stage, simply disconnect
             */
            gatt.disconnect();
        
    

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) 
        Log.d(TAG, "Services Discovered: " + status);
        mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Enabling Public Key..."));

        reset();
        enabled(gatt);

    

    @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status ) 
        if(SECURITY_PUBLICKEY.equals(characteristic.getUuid())) 
            mHandler.sendMessage(Message.obtain(null,MSG_PUBLICKEY,characteristic));
        
        enableNotif(gatt);
        readData(gatt);

    


    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 
        //After writing the enable flag, next we read the initial value
        readData(gatt);
    

    @Override
    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) 
        Log.d(TAG, "Remote RSSI: " + rssi);
    

    private String connectionState(int status) 
        switch (status) 
            case BluetoothProfile.STATE_CONNECTED:
                return "Connected";
            case BluetoothProfile.STATE_DISCONNECTED:
                return "Disconnected";
            case BluetoothProfile.STATE_CONNECTING:
                return "Connecting";
            case BluetoothProfile.STATE_DISCONNECTING:
                return "Disconnecting";
            default:
                return String.valueOf(status);
        
    

;



private static final int MSG_PROGRESS = 201;
private static final int MSG_DISMISS = 202;
private static final int MSG_CLEAR = 301;
private static final int MSG_PUBLICKEY = 101;

private Handler mHandler = new Handler() 
    @Override
    public void handleMessage(Message msg) 
        BluetoothGattCharacteristic characteristic;
        switch (msg.what) 
            case MSG_PUBLICKEY:
                characteristic = (BluetoothGattCharacteristic) msg.obj;
                if (characteristic.getValue() == null) 
                    Log.w(TAG, "Error obtaining humidity value");
                    return;
                
                updatePubKey(characteristic);
                break;

            case MSG_PROGRESS:
                mProgress.setMessage((String) msg.obj);
                if (!mProgress.isShowing()) 
                    mProgress.show();
                
                break;
            case MSG_DISMISS:
                mProgress.hide();
                break;
            case MSG_CLEAR:
                clearDisplayValues();
                break;
        
    
;

private void updatePubKey(BluetoothGattCharacteristic characteristic) 

    byte[] data = characteristic.getValue();
    final StringBuilder stringBuilder = new StringBuilder(data.length);
    for(byte byteChar : data)
        stringBuilder.append(String.format("%02X ", byteChar));
    publicKEY.setText(new String(data)+" "+stringBuilder.toString());





这是日志

Timeline: Timeline: Activity_idle id: android.os.BinderProxy@41cc5460         time:10882957
BluetoothAdapter: startLeScan(): null
BluetoothAdapter: onClientRegistered() - status=0 clientIf=5
BluetoothGattActivity: New LE Device: null @ -83
BluetoothAdapter: stopLeScan()
BluetoothAdapter: startLeScan(): null
BluetoothAdapter: onClientRegistered() - status=0 clientIf=5
BluetoothGattActivity: New LE Device: PUNE @ -38
BluetoothAdapter: stopLeScan()
BluetoothGattActivity: Connecting to PUNE
BluetoothGatt: connect() - device: B0:B4:48:BA:40:84, auto: false
BluetoothGatt: registerApp()
BluetoothGatt: registerApp() - UUID=0bdf35a7-d4d0-4048-be15-a6cb030626f5
BluetoothGatt: onClientRegistered() - status=0 clientIf=5
BluetoothGatt: onClientConnectionState() - status=0 clientIf=5                device=B0:B4:48:BA:40:84
BluetoothGattActivity: Connection State Change: 0 -> Connected
BluetoothGatt: discoverServices() - device: B0:B4:48:BA:40:84

我正在寻找的服务在这里..

onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099919-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099915-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099916-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099917-293f-11e4-93bd-afd0fe6d1dfd

这就是问题所在。

 D/BluetoothGattActivity: Services Discovered: 0
 D/BluetoothGattActivity: Enable Public Key
 W/BluetoothGatt: Unhandled exception in callback
 W/BluetoothGatt: java.lang.NullPointerException
 W/BluetoothGatt:     at gune.blegune.MainActivity$3.enabled(MainActivity.java:232)
 W/BluetoothGatt:     at gune.blegune.MainActivity$3.onServicesDiscovered(MainActivity.java:314)
 W/BluetoothGatt:     at android.bluetooth.BluetoothGatt$1.onSearchComplete(BluetoothGatt.java:295)
 W/BluetoothGatt:     at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:215)
 W/BluetoothGatt:     at android.os.Binder.execTransact(Binder.java:404)
 W/BluetoothGatt:     at dalvik.system.NativeStart.run(Native Method)
 D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=B0:B4:48:BA:40:84
 D/BluetoothGattActivity: Connection State Change: 0 -> Disconnected
 D/BluetoothAdapter: stopLeScan()

请。一些帮助将不胜感激。非常感谢!

【问题讨论】:

您发现的服务列表是什么样的?您显示的日志表明特征 UUID 是有效的,但没有任何迹象表明您的服务 UUID 是正确的。 【参考方案1】:

我猜这是抛出 NPE 的那一行:

characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);

所以要么

gatt 为空或

gatt.getService(SECURITY_SERVICE)

正在返回 null。

你能添加一些日志来找出它是什么吗?

我的猜测是第二种情况,在这种情况下我建议您在 BluetoothGatt 对象上调用 getServices() 并在结果列表中记录每个 BluetoothGattService 对象的 UUID,以仔细检查您是否拥有您认为应该拥有的服务 UUID。

getService 的规范显示“如果支持,则为 BluetoothGattService,如果远程设备未提供请求的服务,则为 null。”

【讨论】:

以上是关于在 Android 低功耗蓝牙上读取 GATT 属性的问题的主要内容,如果未能解决你的问题,请参考以下文章

低功耗蓝牙 GATT 配置文件测量解析值

Android BLE低功耗蓝牙开发极简系列(二)之读写操作

蓝牙低功耗profile:ATT和GATT(转载)

GATT特性BLE读取速度慢

Android 蓝牙低功耗 (BLE) API 尚未准备好迎接黄金时段

从蓝牙低功耗 GATT 特性中检索大的 32 位无符号整数