最近按导师的要求做了个小项目:用Android手机跟蓝牙血压计通信。在网上查了很多资料,发现有许多文章是讲解Android蓝牙开发,但是在中文社区缺少针对实现HDP profile的蓝牙健康设备的文章,所以整理了相关知识和代码做一个总结。做了一点微小的贡献,谢谢大家。
1、相关概念
HDP:Health Device Profile顾名思义是针对蓝牙健康设备(如蓝牙血压计、蓝牙体重秤)的一个profile,由蓝牙技术联盟(Bluetooth SIG)发布。并不是所有蓝牙设备都可以采用HDP,只有经典蓝牙(BR/EDR)设备才可以。低功耗蓝牙设备,即蓝牙4.0及以上,采用的是GATT等profile。
上图是HDP的协议栈。在通信时,蓝牙健康设备作为source,Android手机作为sink。图中L2CAP、SDP、MCAP是蓝牙通信的底层协议,中间是IEEE11073协议。11073是IEEE发布的一个健康设备的协议簇,其下包含了许多不同种类的健康设备协议,如11073-10407是血压计的协议。在数据传输时,手机与健康设备根据此协议来发送请求、解析数据等。最上层的application在本文中自然指的就是Android app。
Android蓝牙模块:android.bluetooth是蓝牙的开发包,里面包含了所有蓝牙相关的类,无论是经典蓝牙还是低功耗蓝牙。其中,本文重点关注的是BluetoothHealth这个类,还用到了一些蓝牙基础类BluetoothAdapter BluetoothDevice等,在这里就不展开介绍了。BluetoothHealth是在API14时引入的,所以系统版本高于14的Android手机都可以与采用HDP的健康设备通信。BluetoothHealth中包括了与HDP设备建立通信的API,具体请参见官方文档。在官方文档中有一个建立通信的流程:
1、调用getProfileProxy(Context, BluetoothProfile.ServiceListener, int)来获取代理对象的连接。
2、创建BluetoothHealthCallback回调,调用registerSinkAppConfiguration(String, int, BluetoothHealthCallback)注册一个sink端的应用程序配置。
3、将手机与健康设备配对,这一步一般在手机的“设置”中完成。
4、使用connectChannelToSource(BluetoothDevice, BluetoothHealthAppConfiguration)来建立一个与健康设备的通信channel。有的设备会自动建立通信,不需要在代码中调用这个方法。第二步中的回调会指示channel的状态变化。
5、用ParcelFileDescriptor来读取健康设备传来的数据,并根据IEEE 11073来解析数据。
6、通信结束后,关闭通信channel,注销应用程序配置。
看完这个流程是不是一脸懵逼?不要慌,这里官方文档实在太抽象了,必须要配合实际的代码才能看懂。
2、demo代码
项目的目标是与经典蓝牙血压计进行通信,获取血压计测量的数值和日期。这里血压计的型号是A&D UA-767PBT-C。项目中的代码是以github上这个项目为基础,根据需求进行修改而实现的。
项目结构
上图是项目的结构,非常简单,只有一个activity和一个service。activity与用户交互,service绑定在activity中与蓝牙血压计建立通信、交换数据。
首先AndroidManifest.xml中注册蓝牙权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
MainActivity
主要作用是显示血压计传过来的数据,以及绑定服务。
activity的布局如下:
上半部分是一个ArrayList,用于显示手机所配对的蓝牙设备的名称和Mac地址。下半部分会显示一次血压测量的参数:收缩压、舒张压、心率、测量时间。
在MainActivity中一些重要私有变量:
private static final int REQUEST_ENABLE_BT = 1; //用于打开手机蓝牙
private static final int HEALTH_PROFILE_SOURCE_DATA_TYPE = 0x1007; //IEEE 11073中规定的血压数据类型
private BluetoothAdapter mBluetoothAdapter;
private Messenger mHealthService; //用于与service通信
private boolean mHealthServiceBound; //用于判断service是否与此activity绑定
打开蓝牙:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
} else {
initialize();
}
启动服务:
// Sets up communication with HDPService.
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mHealthServiceBound = true;
Message msg = Message.obtain(null,
HDPService.MSG_REG_CLIENT);
msg.replyTo = mMessenger;
mHealthService = new Messenger(service);
try {
mHealthService.send(msg);
//register blood pressure data type
sendMessage(HDPService.MSG_REG_HEALTH_APP,
HEALTH_PROFILE_SOURCE_DATA_TYPE);
} catch (RemoteException e) {
Log.w(TAG, "Unable to register client to service.");
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName name) {
mHealthService = null;
mHealthServiceBound = false;
}
};
private void initialize() {
// Starts health service.
Intent intent = new Intent(this, HDPService.class);
//startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
用handler和message来实现activity与service的通信:
private Handler mIncomingHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
......
}
}
};
private final Messenger mMessenger = new Messenger(mIncomingHandler);
HDPService
这个service是核心部分,手机与蓝牙血压计的通信就是在此实现的。
service中部分重要私有变量:
private BluetoothHealthAppConfiguration mHealthAppConfig; //BluetoothHealthAppConfiguration
pr