如何在同一个界面页面读取两个服务的两个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特性?的主要内容,如果未能解决你的问题,请参考以下文章