最近做了一個(gè)智能硬件開(kāi)發(fā)(針灸儀)的項(xiàng)目她按,有一部分涉及到低功耗藍(lán)牙的開(kāi)發(fā)渠旁,就是通過(guò)藍(lán)牙和設(shè)備進(jìn)行數(shù)據(jù)的交互风响,比如控制改設(shè)備的LED的開(kāi)關(guān),設(shè)備的開(kāi)關(guān)機(jī)羡亩,設(shè)置設(shè)備的時(shí)間和溫度等摩疑,下面就項(xiàng)目中遇到的坑一一說(shuō)明:首先給出官網(wǎng)對(duì)于BLE開(kāi)發(fā)的講解,https://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#terms官方demo:https://github.com/googlesamples/android-BluetoothLeGatt,demo也比較好理解畏铆,主要是四個(gè)類雷袋,其中,DeviceControlActivity通過(guò)啟動(dòng)BluetoothLeService用來(lái)進(jìn)行與藍(lán)牙外圍設(shè)備的交互辞居。(注意楷怒,因?yàn)楸救耸菍I做了更改,并放到了fragment中瓦灶,所以部分代碼跟demo不一致鸠删,請(qǐng)主動(dòng)忽略,關(guān)注藍(lán)牙核心代碼)
BLE開(kāi)發(fā)所需要的知識(shí)倚搬,通過(guò)官方demo冶共,我們會(huì)發(fā)現(xiàn)很多service,點(diǎn)擊service后每界,每個(gè)service下面是Characteristic,每個(gè)service和Characteristic都對(duì)應(yīng)一個(gè)唯一的UUID家卖。所以眨层,在做BLE時(shí)候,首先你應(yīng)該找出你的藍(lán)牙外圍設(shè)備uuid上荡,不然會(huì)很頭疼趴樱,這個(gè)UUID也可能是硬件給你的,也可以你自己試出來(lái)酪捡,當(dāng)然自己試出來(lái)是個(gè)很煩的過(guò)程叁征。自己試的方法就是根據(jù)demo,加上一份讀寫的協(xié)議逛薇,然后捺疼,排著點(diǎn)擊,顯示出來(lái)的藍(lán)牙列表進(jìn)行測(cè)試永罚,看是否和協(xié)議對(duì)應(yīng)啤呼。另外,BluetoothLeService類不用做太多的更改呢袱。
一官扣,藍(lán)牙設(shè)備的掃描
這一部分基本上很簡(jiǎn)單,只要設(shè)備上電以后羞福,這部分代碼執(zhí)行后惕蹄,便可以掃描出設(shè)備,并獲得BluetoothDevice對(duì)象
public void scanLeDevice(final boolean enable) {
LogUtils.debug(TAG, "-----------開(kāi)始掃描藍(lán)牙=" + enable);
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
stopScanOuter();
}
}, SCAN_PERIOD);
LogUtils.debug("----------startLeScan--");
mScanning = true;
mBluetoothAdapter.startLeScan(this);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(this);
}
}
上面代碼為開(kāi)始掃描周圍已上電的設(shè)備,當(dāng)發(fā)現(xiàn)設(shè)備后卖陵,BluetoothAdapter.LeScanCallback會(huì)執(zhí)行onLeScan回調(diào)恋昼,將BluetoothDevice返回,
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("MyDeviceFragment");
if(dcfrag != null && dcfrag.isVisible()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
LogUtils.debug(TAG, "---------獲得設(shè)備" + device);
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
}
到此赶促,我們便可以得到掃描到的藍(lán)牙設(shè)備液肌,但是目前僅僅是掃描到,并不代表已經(jīng)連接上藍(lán)牙設(shè)備鸥滨。
二嗦哆,藍(lán)牙設(shè)備的連接
1,綁定service
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
LogUtils.debug(TAG, "開(kāi)始綁定service onServiceConnected"+device+"---name="+device.getName()+"--address="+device.getAddress());
mDeviceAddress = device.getAddress();
mDeviceName = device.getName();
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.i(TAG, "Unable to initialize Bluetooth");
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mDeviceAddress);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
LogUtils.debug(TAG, "--------onServiceDisconnected service無(wú)法綁定了");
mBluetoothLeService = null;
}
};
public void mybindService(){
LogUtils.debug(TAG, "---------開(kāi)始執(zhí)行onCreate---bindservice");
Intent gattServiceIntent = new Intent(getActivity(), BluetoothLeService.class);
getActivity().bindService(gattServiceIntent, mServiceConnection, getActivity().BIND_AUTO_CREATE);
}
2婿滓,連接藍(lán)牙
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
LogUtils.debug(TAG, "--mGattUpdateReceiver ACTION_GATT_CONNECTED");
mConnected = true;
LogUtils.debug(TAG, "--------ACTION_GATT_CONNECTED devicename"+mDeviceName);
Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
if((dcfrag != null && dcfrag.isVisible())){
//暫時(shí)寫死在這可以連接的BLE設(shè)備
if(mDeviceName == null || !deviceFilter(mDeviceName)){
connectService.switchFragment(false, mDeviceName);
}else{
connectService.switchFragment(true, mDeviceName);
}
}
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
LogUtils.debug(TAG, "--mGattUpdateReceiver ACTION_GATT_DISCONNECTED");
mConnected = false;
Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
if((dcfrag != null && dcfrag.isVisible()))
connectService.switchFragment(false, mDeviceName);
Fragment contfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ControlorFragment");
if((contfrag != null && contfrag.isVisible())){
transfertoControler.closeButton(false, false);
}
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
LogUtils.debug(TAG, "--mGattUpdateReceiver ACTION_GATT_SERVICES_DISCOVERED");
initMoxibustionService(
mBluetoothLeService.getSupportedGatteService(
SampleGattAttributes.SERVIECE_NOTIFY_DATA));
initMoxibustionService(
mBluetoothLeService.getSupportedGatteService(
SampleGattAttributes.SERVIECE_WRITE_DATA));
// Show all the supported services and characteristics on the user interface.
// displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
LogUtils.debug(TAG, "--mGattUpdateReceiver ACTION_DATA_AVAILABLE");
// byte[] data = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
// StringBuilder stringBuilder = new StringBuilder(data.length);
// for(byte byteChar : data)
// stringBuilder.append(String.format("0x%02X ", byteChar));
// String log = stringBuilder.toString();
// LogUtils.debug(TAG, "---字節(jié)數(shù)組為="+ log);
parsedata(intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA));
} else if (BluetoothLeService.ACTION_DATA_SEND_CONFIRM.equals(action)) {
// ECHO from android
LogUtils.debug(TAG, "write ok!");
}
}
};
public void myConnetService(){
getActivity().registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
LogUtils.debug(TAG, "------mBluetoothLeService myConnetService" + mBluetoothLeService);
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
LogUtils.debug(TAG, "Connect request result=" + result);
}
}
當(dāng)連接上藍(lán)牙后老速,我們會(huì)得到ACTION_GATT_CONNECTED的廣播,然后是ACTION_GATT_SERVICES_DISCOVERED凸主,這個(gè)時(shí)候我們需要對(duì)service進(jìn)行初始化橘券,以便能夠讀寫數(shù)據(jù),以下為初始化代碼(注意卿吐,初始化時(shí)候我們需要用到讀寫service的UUID)
private void initMoxibustionService(BluetoothGattService gattService) {
String uuid = "";
if (gattService == null)
{
LogUtils.debug(TAG, "gattService is null");
return;
}
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
uuid = gattCharacteristic.getUuid().toString();
if (SampleGattAttributes.CHARACTER_NOTIFY_DATA.substring(0,8).equals(uuid.substring(0, 8))) {
mNotifyCharacteristic = gattCharacteristic;
mBluetoothLeService.setCharacteristicNotification(
mNotifyCharacteristic, true);
LogUtils.debug(TAG, "NOTIFY_DATA");
LogUtils.debug(TAG, "getProperties()=" + mNotifyCharacteristic.getProperties());
} else if (SampleGattAttributes.CHARACTER_WRITE_DATA.substring(0,8).equals(uuid.subSequence(0, 8))) {
// mCommandCharacteristic = gattCharacteristic;
//寫數(shù)據(jù)的服務(wù)和characteristic
mCommandCharacteristic = mBluetoothLeService.getSupportedGatteService(SampleGattAttributes.SERVIECE_WRITE_DATA)
.getCharacteristic(UUID.fromString(SampleGattAttributes.CHARACTER_WRITE_DATA));
LogUtils.debug(TAG, "WRITE_CMD");
LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
mCommandCharacteristic.setWriteType(
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
}
}
}
CHARACTER_NOTIFY_DATA和CHARACTER_WRITE_DATA為讀和寫數(shù)據(jù)的CHARACTER的UUID旁舰,如下,注意這四個(gè)UUID
3嗡官,解析數(shù)據(jù)
至此箭窜,如果順利的話,我們就可以得到ACTION_DATA_AVAILABLE的廣播衍腥,也就拿到了從藍(lán)牙設(shè)備獲得的byte數(shù)組磺樱,大多數(shù)協(xié)議里,每個(gè)字節(jié)代表一個(gè)命令婆咸。這里涉及到Java中byte值與int值的轉(zhuǎn)換竹捉。因?yàn)镴ava中,所有的值都是singed性的尚骄,最高位為符號(hào)位块差,所以,大家請(qǐng)自行補(bǔ)下該部分的知識(shí)乖仇,對(duì)于有符號(hào)數(shù)憾儒,它的值相當(dāng)于取補(bǔ)碼,此處將不詳述乃沙,
上面為讀數(shù)據(jù)起趾,下面我們說(shuō)寫數(shù)據(jù),比如警儒,如下的開(kāi)關(guān)機(jī)命令训裆,
// 01 10 01 01 92 07 17
public void controlpower(boolean isOpen){
if (mCommandCharacteristic == null) return;
if (mBluetoothLeService == null) return;
byte[] setDataAfter;
if(isOpen){
byte[] setDataBefore = {0x01, 0x10, 0x01, 0x01};
byte[] trans = inttobyte(integrityCheck(setDataBefore));
byte[] transV = Arrays.copyOfRange(trans, 2, 4);
byte[] setData = byteMerger(setDataBefore, reverse(transV));
byte[] dd = {0x17};
setDataAfter = byteMerger(setData, dd);
LogUtils.debug(TAG, "---setDataAfter[4]="+setDataAfter[4]+",setDataAfter[5]="+setDataAfter[5]);
}else{
byte[] setDataBefore = {0x01, 0x10, 0x01, 0x00};
byte[] trans = inttobyte(integrityCheck(setDataBefore));
byte[] transV = Arrays.copyOfRange(trans, 2, 4);
byte[] setData = byteMerger(setDataBefore, reverse(transV));
byte[] dd = {0x17};
setDataAfter = byteMerger(setData, dd);
printDataHex(setDataAfter);
}
mCommandCharacteristic.setValue(setDataAfter);
mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);
}
mCommandCharacteristic.setValue(setDataAfter);
mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);
這兩行代碼眶根,是核心代碼,但是边琉,我們要進(jìn)行字節(jié)數(shù)組的正確傳遞属百,這里,給大家貼出來(lái)幾個(gè)很有可能用到的方法变姨,
CRC算法Java版:
//crc java
public int integrityCheck(byte[] bytes) {
int wCrc = 0xffff;
for (byte srcData : bytes) {
int data = byteToInt(srcData);
for(int j = 0; j < 8; j++) {
if ((((wCrc & 0x8000) >> 8) ^ ((data << j) & 0x80)) != 0) {
wCrc = (wCrc << 1) ^ 0x1021;
} else {
wCrc = wCrc << 1;
}
}
}
wCrc = (wCrc << 8) | (wCrc >> 8 & 0xff);
return wCrc & 0xffff;
}
public static int byteToInt(byte b) {
return b & 0xff;
}
// int to byte
public static byte[] inttobyte(int value) {
byte b0 = (byte) ((value >> 24) & 0xFF);
byte b1 = (byte) ((value >> 16) & 0xFF);
byte b2 = (byte) ((value >> 8) & 0xFF);
byte b3 = (byte) (value & 0xFF);
byte[] bytes = { b0, b1, b2, b3 };
return bytes;
}
//打印字節(jié)數(shù)組
public void printDataHex(byte[] data) {
if(SENTLOG){
StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("0x%02X ", byteChar));
String log = stringBuilder.toString();
LogUtils.debug(TAG, "---發(fā)送到藍(lán)牙的字節(jié)數(shù)組為="+ log);
}
}
//數(shù)組倒序
public byte[] reverse(byte[] rt){
for (int i = 0; i < rt.length / 2; i++) {
byte temp = rt[i];
rt[i] = rt[rt.length - 1 - i];
rt[rt.length - 1 - i] = temp;
}
return rt;
}
//java 合并兩個(gè)byte數(shù)組
public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
byte[] byte_3 = new byte[byte_1.length+byte_2.length];
System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
return byte_3;
}
寫的比較簡(jiǎn)單族扰,但是關(guān)鍵代碼都有了,大家可以參考下定欧!~