一,简介
1.1 现在的手机设备基本上都支持蓝牙模块,蓝牙与蓝牙之前可以相互通信,所以只要物联网机器上配有蓝牙模块,就可以用手机蓝牙连接机器蓝牙,从而和机器通信
1.2 蓝牙按协议常见可以分为经典蓝牙和低功耗蓝牙,下面是主要区别:
对比 | 经典蓝牙 | 低功耗蓝牙 |
---|---|---|
协议 | 4.0以下 | 4.0以上 |
传输速度 | 慢,100ms | 快,3ms |
传输大小 | 大,可以传大文件 | 小,大文件需要分包 |
网络拓扑 | 点对点 | 点对点,广播,Mesh组网 |
应用领域 | 无线耳机,无线音箱 | 鼠标,单车,智能家居,监控系统,灯光组网 |
二,经典蓝牙
2.1 开启蓝牙
获取BluetoothAdapter对象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
2.2 判断设备是否支持蓝牙
/*** 设备是否支持蓝牙 true为支持* @return*/
public boolean isSupportBlue(){return mBluetoothAdapter != null;
}
2.3 判断蓝牙是否开启
/*** 蓝牙是否打开* @return*/
public boolean isBlueEnable(){return mBluetoothAdapter.isEnabled();
}
2.4 添加权限
<!-- 使用蓝牙的权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 扫描蓝牙设备或者操作蓝牙设置 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--模糊定位权限,仅作用于6.0+-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--精准定位权限,仅作用于6.0+-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2.5 动态请求权限
/*** 检查权限*/
private void checkPermissions() {String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};List<String> permissionDeniedList = new ArrayList<>();for (String permission : permissions) {int permissionCheck = ContextCompat.checkSelfPermission(this, permission);if (permissionCheck == PackageManager.PERMISSION_GRANTED) {onPermissionGranted(permission);} else {permissionDeniedList.add(permission);}}if (!permissionDeniedList.isEmpty()) {String[] deniedPermissions = permissionDeniedList.toArray(new String[permissionDeniedList.size()]);ActivityCompat.requestPermissions(this, deniedPermissions, REQUEST_CODE_PERMISSION_LOCATION);}
}/*** 权限回调* @param requestCode* @param permissions* @param grantResults*/
@Override
public final void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case REQUEST_CODE_PERMISSION_LOCATION:if (grantResults.length > 0) {//权限成功做其它任务}break;}
}
2.6 检查GPS是否打开
/*** 检查GPS是否打开* @return*/
private boolean checkGPSIsOpen() {LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);if (locationManager == null)return false;return locationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER);
}
2.7 前往设置里面开启GPS
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);startActivityForResult(intent, REQUEST_CODE_OPEN_GPS);
2.8 扫描蓝牙
//发现蓝牙
public void startBluetoothDiscovery() {//当前是否在扫描,如果是就取消当前的扫描,重新扫描if (mBluetoothAdapter.isDiscovering()){mBluetoothAdapter.cancelDiscovery();}mBluetoothAdapter.startDiscovery();
}
2.9 监听蓝牙扫描状态
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙开关状态
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//蓝牙开始搜索
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//蓝牙搜索结束
filter.addAction(BluetoothDevice.ACTION_FOUND);//蓝牙发现新设备(未配对的设备)
registerReceiver(scanBlueReceiver, filter);
/*** 扫描广播接收类*/public class ScanBlueReceiver extends BroadcastReceiver {private static final String TAG = ScanBlueReceiver.class.getName();private ScanBlueCallBack callBack;public ScanBlueReceiver(ScanBlueCallBack callBack){this.callBack = callBack;}//广播接收器,当远程蓝牙设备被发现时,回调函数onReceiver()会被执行@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();Log.d(TAG, "action:" + action);BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);switch (action){case BluetoothAdapter.ACTION_STATE_CHANGED:int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);switch (blueState) {case BluetoothAdapter.STATE_TURNING_ON:Log.e(TAG,"蓝牙正在打开中");break;case BluetoothAdapter.STATE_ON:Log.e(TAG,"蓝牙已经打开");mListener.stateOn();break;case BluetoothAdapter.STATE_TURNING_OFF:Log.e(TAG,"蓝牙正在关闭中");break;case BluetoothAdapter.STATE_OFF:Log.e(TAG,"蓝牙已经关闭");mListener.stateOff();break;}break;case BluetoothAdapter.ACTION_DISCOVERY_STARTED:Log.d(TAG, "开始扫描...");callBack.onScanStarted();break;case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:Log.d(TAG, "结束扫描...");callBack.onScanFinished();break;case BluetoothDevice.ACTION_FOUND:Log.d(TAG, "发现设备...");callBack.onScanning(device);break;}}
}
2.10 连接蓝牙
/** 连接蓝牙线程*/public class ConnectBlueTask extends AsyncTask<BluetoothDevice, Integer, BluetoothSocket> {private static final String TAG = ConnectBlueTask.class.getName();private BluetoothDevice bluetoothDevice;private ConnectBlueCallBack callBack;public ConnectBlueTask(ConnectBlueCallBack callBack){this.callBack = callBack;}@Overrideprotected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {bluetoothDevice = bluetoothDevices[0];BluetoothSocket socket = null;try{Log.d(TAG,"开始连接socket,uuid:" + ClassicsBluetooth.UUID);socket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(ClassicsBluetooth.UUID));if (socket != null && !socket.isConnected()){socket.connect();}mOutputStream = socket.getOutputStream();mInputStream = socket.getInputStream();}catch (IOException e){Log.e(TAG,"socket连接失败");try {socket.close();} catch (IOException e1) {e1.printStackTrace();Log.e(TAG,"socket关闭失败");}}return socket;}@Overrideprotected void onPreExecute() {Log.d(TAG,"开始连接");if (callBack != null) callBack.onStartConnect();}@Overrideprotected void onPostExecute(BluetoothSocket bluetoothSocket) {if (bluetoothSocket != null && bluetoothSocket.isConnected()){Log.d(TAG,"连接成功");if (callBack != null) callBack.onConnectSuccess(bluetoothDevice, bluetoothSocket);}else {Log.d(TAG,"连接失败");if (callBack != null) callBack.onConnectFail(bluetoothDevice, "连接失败");}}
}
2.11 判断蓝牙是否连接
/*** 当前设备与指定设备是否连接*/
public boolean isBlueToothConnected() {boolean connected = (bluetoothSocket != null && bluetoothSocket.isConnected());if (bluetoothDevice == null)return connected;return connected && bluetoothSocket.getRemoteDevice().equals(bluetoothDevice);
}
2.12 读取蓝牙消息
private class ReadThread extends Thread {@Overridepublic void run() {super.run();while (!isInterrupted()) {int size;try {byte[] buffer = new byte[512];if (mInputStream == null) return;size = mInputStream.read(buffer);if (size > 0) {String mReception=new String(buffer, 0, size);String msg = mReception.toString().trim();Log.e(TAG, "接收短消息:" + msg);}} catch (IOException e) {e.printStackTrace();return;}}}}
2.13 发送蓝牙指令
private class WriteRunnable implements Runnable {@Overridepublic void run() {try {String cmd="KZMT;";Log.e(TAG, "发送短消息:" + cmd);mOutputStream.write(cmd.getBytes());mOutputStream.flush();} catch (IOException e) {}}}
2.14 断开连接
/*** 关闭蓝牙Socket连接*/
public void closeBluetoothStream() {try { if (mOutputStream != null) {mOutputStream.close();mOutputStream = null;}if (mInputStream != null) {mInputStream.close();mInputStream = null;}if (bluetoothSocket != null) {if (bluetoothSocket.isConnected()) {bluetoothSocket.close();}bluetoothSocket = null;}} catch (Exception e) {e.printStackTrace();}
}
三,低功耗蓝牙
3.1 低功耗蓝牙扫描之前的步骤都一样,开启,请求权限等,从扫描开始有变化,所以扫描之前步骤可以参考经典蓝牙,下面是扫描的操作:
private BluetoothAdapter mBluetoothAdapter;private boolean isScanning;//是否正在搜索private Handler mHandler;//15秒搜索时间private static final long SCAN_PERIOD = 15000;private void scanLeDevice(final boolean enable) {if (enable) {//true//15秒后停止搜索mHandler.postDelayed(new Runnable() {@Overridepublic void run() {isScanning = false;mBluetoothAdapter.stopLeScan(mLeScanCallback);}}, SCAN_PERIOD);isScanning = true;mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索} else {//falseisScanning = false;mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜索}} private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {//这里是个子线程,下面把它转换成主线程处理runOnUiThread(new Runnable() {@Overridepublic void run() {//在这里可以把搜索到的设备保存起来//device.getName();获取蓝牙设备名字//device.getAddress();获取蓝牙设备mac地址//这里的rssi即信号强度,即手机与设备之间的信号强度。}});}};
3.2 停止扫描
mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜索
3.3 连接蓝牙
//这个方法需要三个参数:一个Context对象,自动连接(boolean值,表示只要BLE设备可用是否自动连接它),和BluetoothGattCallback调用。
BluetoothGatt mBluetoothGatt = device.connectGatt(this, false, mBluetoothGattCallback);BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {@Overridepublic void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {super.onPhyUpdate(gatt, txPhy, rxPhy, status);}@Overridepublic void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {super.onPhyRead(gatt, txPhy, rxPhy, status);}//当连接状态发生改变@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);}//发现新服务,即调用了mBluetoothGatt.discoverServices()后,返回的数据@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);}//调用mBluetoothGatt.readCharacteristic(characteristic)读取数据回调,在这里面接收数据@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);}//发送数据后的回调@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);}@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {//descriptor读super.onDescriptorRead(gatt, descriptor, status);}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {//descriptor写super.onDescriptorWrite(gatt, descriptor, status);}@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status) {super.onReliableWriteCompleted(gatt, status);}//调用mBluetoothGatt.readRemoteRssi()时的回调,rssi即信号强度@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {//读Rssisuper.onReadRemoteRssi(gatt, rssi, status);}@Overridepublic void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {super.onMtuChanged(gatt, mtu, status);}};
3.4 断开连接
mBluetoothGatt.disconnect(); //主动断开连接
3.5 写入数据,通过mBluetoothGatt.writeCharacteristic(characteristic)
写入的数据长度是有限制的,如果要改变这个限制,可以通过MTU来改变
/*** 向蓝牙发送数据*/
public void dataSend(){//byte[] send={(byte) 0xaa,0x01,0x01,(byte)0x81,(byte) 0xff};byte[] send = new byte[20];send = hexStringToBytes(et_send.getText().toString());byte[] sendData=new byte[send.length+2];sendData[0]=(byte) 0xaa;sendData[sendData.length-1]=(byte) 0xff;for(int i=1;i<sendData.length-1;i++){sendData[i]=send[i-1];}mCharacteristic.setValue(sendData);boolean status = mBluetoothGatt.writeCharacteristic(mCharacteristic);
}
3.6 读取数据,通过mBluetoothGatt.readCharacteristic(characteristic),在BluetoothGattCallback 回调方法onCharacteristicRead中获取到数据。
//读取数据时调用这个方法,数据会在回调接口中(BluetoothGattCallback )获取到
mBluetoothGatt.readCharacteristic(characteristic)BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() { //调用mBluetoothGatt.readCharacteristic(characteristic)读取数据回调,在这里面接收数据@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);//这里面就是数据characteristic.getValue();}
}
3.7 总结
第一步:我们要判断手机是否支持BLE,并且获得各种权限,才能让我们之后的程序能正常运行。
然后,我们去搜索BLE设备,得到它的MAC地址。
第二步:我们通过这个MAC地址去连接,连接成功后,去遍历得到Characteristic的uuid。
在我们需要发送数据的时候,通过这个uuid找到Characteristic,去设置其值,最后通过writeCharacteristic(characteristic)方法发送数据。
如果我们想知道手机与BLE设备的距离,则可以通过readRemoteRssi()去得到rssi值,通过这个信号强度,就可以换算得到距离。
第三步:连接上后,我们就可以用BluetoothGatt的各种方法进行数据的读取等操作
3.8 相关api
1、BluetoothManager
通过BluetoothManager来获取BluetoothAdapter。
2、BluetoothAdapter
代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作,一个Android系统只有一个BluetoothAdapter,通过BluetoothManager获取。
3、BluetoothDevice
扫描后发现可连接的设备,获取已经连接的设备,通过它可以获取到BluetoothGatt。
4、BluetoothGatt
继承BluetoothProfile,通过BluetoothGatt可以连接设备(connect),发现服务(discoverServices),并把相应地属性返回到BluetoothGattCallback,可以看成蓝牙设备从连接到断开的生命周期。
5、BluetoothGattService
服务,Characteristic的集合。
6、BluetoothGattCharacteristic
相当于一个数据类型,可以看成一个特征或能力,它包括一个value和0~n个value的描述(BluetoothGattDescriptor)。
7、BluetoothGattDescriptor
描述符,对Characteristic的描述,包括范围、计量单位等。
8、BluetoothProfile
一个通用的规范,按照这个规范来收发数据。
9、BluetoothGattCallback
已经连接上设备,对设备的某些操作后返回的结果。
10、总结:当我们扫描后发现多个设备BluetoothDevice,每个设备下会有很多服务BluetoothGattService,这些服务通过service_uuid(唯一标识符)来区分,每个服务下又会有很多特征BluetoothGattCharacteristic,这些特征通过uuid来区分的,它是手机与BLE终端设备交换数据的关键。而BluetoothGatt可以看成手机与BLE终端设备建立通信的一个管道,只有有了这个管道,才有了通信的前提