如何在同一个界面页面读取两个服务的两个BLE特性?

Posted

技术标签:

【中文标题】如何在同一个界面页面读取两个服务的两个BLE特性?【英文标题】:How to read two BLE characteristics of two services at the same interface page? 【发布时间】:2017-10-30 13:17:59 【问题描述】:

我正在尝试在同一个 android 界面上管理我的 BLE 设备的温度服务和 LED 服务。但是,每当我尝试打开 LED 时,应用程序就会死机,我不知道如何解决这个问题(我不擅长 Java 或 BLE)。

我拥有所需的特征和服务的所有 UUID。我正在尝试创建两个 BluetoothGattCharacteristic

private BluetoothGattCharacteristic characteristic;
private BluetoothGattCharacteristic characteristicCTRL2;

当我调用温度特性并进行温度测量时,它工作正常。但是,每当我尝试打开 LED/调用 LED 特性时,应用程序就会死机。

10-30 08:48:33.026 1033-1033/com.example.android.bluetoothlegatt E/AndroidRuntime: FATAL EXCEPTION: main
                                                                               Process: com.example.android.bluetoothlegatt, PID: 1033
                                                                               java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.bluetooth.BluetoothGattCharacteristic.setValue(byte[])' on a null object reference
                                                                                   at com.example.android.bluetoothlegatt.AutoConnectActivity.turnLEDon(AutoConnectActivity.java:71)
                                                                                   at com.example.android.bluetoothlegatt.AutoConnectActivity$6.onClick(AutoConnectActivity.java:264)
                                                                                   at android.view.View.performClick(View.java:4756)
                                                                                   at android.view.View$PerformClick.run(View.java:19754)
                                                                                   at android.os.Handler.handleCallback(Handler.java:739)
                                                                                   at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                   at android.os.Looper.loop(Looper.java:135)
                                                                                   at android.app.ActivityThread.main(ActivityThread.java:5219)
                                                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                                                   at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

以下代码是我如何将所有特征和服务一起传输。

 // Loops through available Characteristics.
        for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) 
            charas.add(gattCharacteristic);
            if ( gattCharacteristic.getUuid().equals(UUID.fromString(SampleGattAttributes.CTRL_2))) //CTRL_2 is the UUID of my LED characteristic
                Log.e(TAG, "GattCharacteristics= " + gattCharacteristic.getUuid());
                Intent intent = new Intent(DeviceControlActivity.this, AutoConnectActivity.class);
                intent.putExtra("character_id", "F000AA01-0451-4000-B000-000000000000"); //UUID of my temperature characteristic
                intent.putExtra("service_id", "F000AA00-0451-4000-B000-000000000000");//UUID of my temperature service
                intent.putExtra("address", mDeviceAddress);
                intent.putExtra("ctrl2_character_id", gattCharacteristic.getUuid().toString()); //UUID of my LED characteristics
                intent.putExtra("ctrl2_service_id", gattCharacteristic.getService().getUuid().toString());//UUID of my LED service
                startActivity(intent);
              

我失败的部分是我尝试打开 LED 的功能。

public boolean turnLEDon()
    

    byte[] value = (byte)1;
    characteristicCTRL2.setValue(value);//This is the line I failed

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return status;
;

以下是我尝试在同一界面操作两个特征的完整代码。

package com.example.android.bluetoothlegatt;



public class AutoConnectActivity extends Activity 
private BluetoothGattCharacteristic characteristic;
private BluetoothGattCharacteristic characteristicCTRL2;
private String address;
private UUID serviceId;
private UUID charId;
private UUID ctrl2ServiceId;
private UUID ctrl2CharId;
private TextView debugText;
private Button startBtn;
private Button stopBtn;
private Button ctrlStartBtn;
private Button ctrlStopBtn;
private Timer timer = new Timer();
private boolean started = false;
private ArrayAdapter<String> adapter;
private String Entry;
private File file;
private boolean check= true;


private BluetoothLeService mBluetoothLeService;



public boolean turnLEDon()


    byte[] value = (byte)1;
    characteristicCTRL2.setValue(value);//This is the line I failed

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return status;
;
public boolean turnLEDoff()


    byte[] value = (byte)0;
    characteristicCTRL2.setValue(value);

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return !status;
;

private TimerTask timerTask = new TimerTask() 
    @Override
    public void run() 

        mBluetoothLeService.connect(address);
    
;


// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() 

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder service) 
        mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
        if (!mBluetoothLeService.initialize()) 
            Log.e(TAG, "Unable to initialize Bluetooth");
            finish();
        
        mBluetoothLeService.connect(address);
        List<BluetoothGattService> list = mBluetoothLeService.getSupportedGattServices();
        for(BluetoothGattService s : list)
            if(s.getUuid().compareTo(serviceId) == 0)
                if (check==true) 
                    characteristic = s.getCharacteristic(charId);
                    mBluetoothLeService.disconnect();
                


                return;
            
            if(s.getUuid().compareTo(ctrl2ServiceId) == 0)

                if(check==false) 
                    characteristicCTRL2 = s.getCharacteristic(ctrl2CharId);
                

                return;
            
        
        mBluetoothLeService.disconnect();
        Log.e(TAG, "not find device");
        debugText.setText("not find device");
    

    @Override
    public void onServiceDisconnected(ComponentName componentName) 
        mBluetoothLeService = null;
    
;

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() 
    @Override
    public void onReceive(Context context, Intent intent) 
        final String action = intent.getAction();
        debugText.setText(action);
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) 
            // if temperature measurement
            if (check==true)
            mBluetoothLeService.readCharacteristic(characteristic);
            // if control LED
            if(check==false)
            mBluetoothLeService.readCharacteristic(characteristicCTRL2);
                turnLEDon();

            

         else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) 
         else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) 
         else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action))

        
            if (check==false) 
            String CTRL_Status = intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
            adapter.add("CTRL Status: " + CTRL_Status + "     " + new Date(System.currentTimeMillis()));

            turnLEDoff();
                mBluetoothLeService.disconnect();
            
        if(check==true) 
            String temperature = intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
            adapter.add("Temperature: " + temperature + "°C     " + new Date(System.currentTimeMillis()));

                Entry = address + "," + new Date(System.currentTimeMillis()).toString() + "," + temperature.toString() + "\n";

                try 
                    FileOutputStream out = new FileOutputStream(file, true);
                    out.write(Entry.getBytes());
                    out.close();
                 catch (FileNotFoundException e) 
                    e.printStackTrace();
                 catch (IOException e) 
                    e.printStackTrace();
                
                mBluetoothLeService.disconnect();
            
        
    
;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.autoconnect);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    Intent intent = getIntent();
    address = intent.getStringExtra("address");
    serviceId = UUID.fromString(intent.getStringExtra("service_id"));
    charId = UUID.fromString(intent.getStringExtra("character_id"));
    ctrl2ServiceId = UUID.fromString(intent.getStringExtra("ctrl2_service_id"));
    Log.e(TAG, " CTRL2 SERVICE: " + ctrl2ServiceId);
    ctrl2CharId = UUID.fromString(intent.getStringExtra("ctrl2_character_id"));
    Log.e(TAG, " CTRL2 CHARAC: " + ctrl2CharId);
    ((TextView)findViewById(R.id.address_txt)).setText(address);
    ((TextView)findViewById(R.id.service_txt)).setText(serviceId.toString());
    ((TextView)findViewById(R.id.char_txt)).setText(charId.toString());
    debugText = (TextView)findViewById(R.id.debug_txt);
    startBtn = (Button)findViewById(R.id.start_btn);
    stopBtn = (Button)findViewById(R.id.stop_btn);
    ctrlStartBtn = (Button)findViewById(R.id.ctrl_start_btn);
    ctrlStopBtn = (Button)findViewById(R.id.ctrl_stop_btn);
    ListView listView = (ListView)findViewById(R.id.result_list);
    adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
    listView.setAdapter(adapter);

    startBtn.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            if(started) return;
            started = true;
            check=true;

            //External Storage
            String state;
            state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) 
                File root = Environment.getExternalStorageDirectory();
                File Dir = new File(root.getAbsolutePath() + "/MeasurementDataFile");
                if (!Dir.exists()) 
                    Dir.mkdir();
                
                file = new File(Dir, "Temperature.csv");

            
            if (check==true)
            timer.schedule(timerTask, 0, 1000 * 5); 
        
    );

    stopBtn.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            if(!started) return;
            started = false;
            check=true;

            timer.cancel();
        
    );

    ctrlStartBtn.setOnClickListener(new View.OnClickListener()
    

        @Override
        public void onClick(View v)
        
            mBluetoothLeService.connect(address);
            check=false;
            if(started) return;
            started = true;



            Toast.makeText (getBaseContext(), "Turn CTRL 1 ON", Toast.LENGTH_SHORT).show();

            Log.e(TAG, " On?: " + turnLEDon());

        
    );

    ctrlStopBtn.setOnClickListener(new View.OnClickListener() 
        //mBluetoothLeService.connect(address);
        @Override

        public void onClick(View v) 
            if(!started) return;
            started = false;
            check=false;

            Log.e(TAG, " Off?: " + turnLEDoff());
            Toast.makeText (getBaseContext(), "Turn CTRL 1 OFF", Toast.LENGTH_SHORT).show();

        
    );

    Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
    bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);


@Override
protected void onResume() 
    super.onResume();
    registerReceiver(mGattUpdateReceiver, DeviceControlActivity.makeGattUpdateIntentFilter());
    if (mBluetoothLeService != null) 
        final boolean result = mBluetoothLeService.connect(address);
        Log.d(TAG, "Connect request result=" + result);
    


@Override
protected void onPause() 
    super.onPause();
    unregisterReceiver(mGattUpdateReceiver);

【问题讨论】:

【参考方案1】:

    所有 BLE 调用都是异步的。这真的很让人头疼,但我们拥有我们所拥有的。所以你不能只写:

    布尔状态 = writeCharacteristic(特征); 返回状态;

希望一切都会好起来。在这种情况下,您获得的只是 writeCharacteristic 指令的结果,而不是真正的写操作结果!最后,您需要将部分代码注册为 BluetoothGattCallback 后代并读取 OnCharacteristicWrite 事件,您可以在其中真正了解写入操作的结果。

    所有 BLE 读写操作都应严格按照一对一的顺序完成,并且只能从主应用线程进行。

    不同的设备具有不同的功能和生产力,因此您可以获得不同的结果 - 恕我直言,这比 Google 决定以这种方式实现 BLE 堆栈更奇怪:)

可在此处找到适用于 Android 的漂亮 BLE 指南:https://droidcon.de/sites/global.droidcon.cod.newthinking.net/files/media/documents/practical_bluetooth_le_on_android_0.pdf

编辑

上面的链接已损坏,但可以在此处找到另一个链接: https://speakerdeck.com/erikhellman/practical-bluetooth-low-energy-on-android

【讨论】:

不确定这与“为什么会出现空指针异常?”这个问题有什么关系?但是你写的大部分是正确的,除了你需要在主线程上执行 gatt 操作(没有这样的要求)。 @Emil,是的,这不是要求,只是建议,这可以帮助避免一些丑陋的问题,例如错误 133(例如 ***.com/questions/38136103/…) 我想我仍然不确定如何处理我的代码。当我只调用一个 LED 特性一次时,我的 turnLEDon 和 turnLEDoff 功能可以工作,但我不想使用一个按钮来触发我的 LED 特性和温度特性。我的代码基于示例代码 BluetoothLeGatt(从 Android Studio 导入),所以我基本上将所有内容都包含在 PDF 文件中。你能帮我澄清一下我应该做的更多吗? BLE 指南链接已损坏。

以上是关于如何在同一个界面页面读取两个服务的两个BLE特性?的主要内容,如果未能解决你的问题,请参考以下文章

BLE如何从设备下载所有数据?

Android BLE 读取特性

BLE入门 19 ATT MTU 提升BLE数据传输率

GATT特性BLE读取速度慢

Android BLE - 连接到多个设备似乎失败并且两个连接的 GATT 响应相同?

如何确保 UUID 对于我的 BLE 服务或特性是唯一的?