本文已参与「新人创作礼」活动,一起开启掘金创作之路。
全部源码可发邮件 305591377@qq.com
1.项目需求:实现Ble蓝牙客户端,可复用。
2.项目时间:2020.11.01-2022.3.12
3.项目环境: 软件:AndroidStuio3.2 grade4.6 汇承蓝牙串口助手HID(黄色) 硬件:汇承HC-42蓝牙5.0模块。 连接:使用模块自带小开发板进行连接,插入电脑,打开助手和app即可。 经测试,战舰开发板和串口助手+蓝牙模块也可以实现通讯。
手机是荣耀/vivo/oppo,如果搜索不到设备,打开手机设置,应用,权限,找到app,打开位置权限即可。
4.项目功能:本项目是基于物联网的控制——蓝牙技术,进行开发的。app主要实现蓝牙的扫描、连接、发送数据、接收数据功能。 蓝牙模块插入电脑,通过串口助手进行通讯。 最终实现app与模块之间的数据传输。
5.项目遇到的关键问题: 1.接收数据:可以使用Locat调试工具 2.接收数据显示到列表:使用适配器。 3.发送数据:获取GATT的写,然后发送,具体看代码。 4.定位权限:在旧版本手机下,可以默认打开。在荣耀手机下,很多时候需要手动打开。这可能是个BUG,暂时不影响使用。刚开始测试的时候,荣耀手机总是不能扫描,点击没反应,排查了很久都没有发现问题,最终发现是应用定位的问题。根据惯性,我们手动打开蓝牙和位置信息,但是app的定位默认是静止的,所以一直没有找到问题。 6.实现效果:
7.项目技术实现细节: 对于蓝牙开发,大致以下步骤 1.打开蓝牙 2.蓝牙扫描,列出可用设备 3.关闭蓝牙扫描(不关闭会一直扫描) 4.找到目标蓝牙设备进行连接 5.连接成功,进行通信 6.关闭蓝牙释放资源 接下来我们要根据上面6个步骤进行API的说明: (1)Service蓝牙功能集合,每一个Service都有一个UUID, (2)Characteristic 在service中也有好多个Characteristic 独立数据项,其中也有独立UUID 上面的两个uuid需要从硬件工程师中获取,我才用南京沁恒微电子股份有限公司的蓝牙助手获取模块的UUID,此处说明一下,人家这个在不知道UUID的情况下可以连接并且发送数据,原理暂时还没有弄懂。 (3)BluetoothAdapter 蓝牙的打开关闭等基本操作 (4)BluetoothDevice 蓝牙设备,扫描到的 (5)BluetoothGatt 蓝牙连接重连断开连接等操作的类 (6)BluetoothGattCharacteristic 数据通信操作类,读写等操作 `
activity_main.xml:
里面定义了两个列表,1个编辑框,3个按钮,使用线性布局。
array_item.xml:
这里面是接收列表的布局,暂时只用了一个控件。
MainActivity.java
1.首先定义一个函数,此函数是进行所有控件的初始化,还有搜索列表的点击事件。
Init_Item();
2.蓝牙初始化。
Init_Bluetooth();
mAdapter.enable();打开蓝牙
mAdapter.startDiscovery();开始扫描
扫描到设备之后,添加到list view,然后列表点击事件。
此时可以进行bluetoothGatt.getServices();操作了,此时连接就OK了。
通过bluetoothGatt可以获取设备的通知和读写。
setNotify(notifyCharacteristic);
setEnableNotify(notifyCharacteristic, true);
通过10.11行两个函数判断是否具有写的功能,如果是,传值
writeCharacteristic.setValue(str);
bluetoothGatt.writeCharacteristic(writeCharacteristic);
通过13.14行进行给模块发送数据,此时发送数据就OK了
接收数据函数onCharacteristicChanged
新建一个类bytesHexStrTranslate,用来处理value值,反正不处理就打印不出来。
接收到值 bytesHexStrTranslate.bytesToHexFun1(value)
然后把接收到的这个值放到list view里面打印出来,这样接收就OK了
至于接收的数据处理过程,暂时就理解为格式处理把。
`
`
**8.项目心得:**历时时间最长的一个项目。经过反思,一是自己基础薄弱,二是每次找网上的。 从没有想着自己写。 这次根据看的资料,一步一步实现,先实现打开关闭蓝牙,然后搜索,然后传输数据。
**9.项目代码:**
(使用方法:直接使用即可)
**MainActivity**
```java
package com.example.augenstern.ble_04;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String servicesUUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
private static final String writeUUID = "0000ffe1-0000-1000-8000-00805f9b34fb";
private static final String notifyUUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
public SimpleAdapter adapter;
private static final String TAG = "TAG";
public ListView mListView;
public Button disConnected,scan,sendButton;
public EditText sendDataEdit;
public TextView statusConnected;
public ListView receiveList;
public BluetoothDevice device;
public BluetoothManager mManager;
public BluetoothAdapter mAdapter;
private BluetoothGatt bluetoothGatt;
public ArrayAdapter<String> mStringArrayAdapter;
public List<String> mStringList = new ArrayList<>();
//2021.03.12 listview接收
private List<String> listDevices = new ArrayList<String>();
private ArrayAdapter<String> adtDevices;//显示搜索到的设备信息
private BytesHexStrTranslate bytesHexStrTranslate;
private BluetoothGattService bluetoothGattService;
private BluetoothGattCharacteristic writeCharacteristic;
private BluetoothGattCharacteristic notifyCharacteristic;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Init_Item();
Init_Bluetooth();
}
//控件初始化
private void Init_Item(){
disConnected = findViewById(R.id.disConnected);
disConnected.setOnClickListener(this);
scan = findViewById(R.id.scan);
scan.setOnClickListener(this);
statusConnected = findViewById(R.id.status);
statusConnected.setText("未连接...");
sendDataEdit = findViewById(R.id.sendDataEdit);
sendButton = findViewById(R.id.sendButton);
sendButton.setOnClickListener(this);
mListView = findViewById(R.id.devicesList);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if(mAdapter.isDiscovering()) mAdapter.cancelDiscovery();
String info = ((TextView)view).getText().toString();
Log.d(TAG, "onCreate: info = "+info);
String adress = info.substring(info.length() - 17);
Log.d(TAG, "onCreate: adress = "+adress);
device = mAdapter.getRemoteDevice(adress);
Log.d(TAG, "onItemClick: device = "+adress);
bluetoothGatt = device.connectGatt(MainActivity.this, false, bluetoothGattCallback);
}
});
}
//蓝牙初始化
private void Init_Bluetooth(){
mManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mAdapter = mManager.getAdapter();
if(!mAdapter.isEnabled()){
mAdapter.disable();
}
mAdapter.enable();
Log.d(TAG, "on Init_Bluetooth: 蓝牙已经打开.....");
if(mAdapter.isDiscovering()){
mAdapter.cancelDiscovery();
Log.d(TAG, "on Init_Bluetooth: 关闭搜索设备.....");
}
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},0);
// if(Build.VERSION.SDK_INT >= 23){
// if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.ACCESS_COARSE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
// ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},0);
// }else {
// //权限已打开直接开始扫描
// Log.d(TAG, "Init_Bluetooth: 定位权限已开启.....");
// Toast.makeText(MainActivity.this,"定位权限已开启",Toast.LENGTH_SHORT).show();
// }
// }else {
// //API小于23的直接开始扫描
// Log.d(TAG, "Init_Bluetooth: API小于23,定位权限不用开启");
// Toast.makeText(MainActivity.this," API小于23,定位权限不用开启",Toast.LENGTH_SHORT).show();
// }
}
//扫描附近设备
private void scanDevices(){
mStringArrayAdapter = new ArrayAdapter<>(MainActivity.this,android.R.layout.simple_list_item_1,mStringList);
mListView.setAdapter(mStringArrayAdapter);
IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver,intentFilter);
mStringList.clear();
mStringArrayAdapter.notifyDataSetChanged();
Set pairedDevice = mAdapter.getBondedDevices();
mStringList.add("已配对的设备:");
if(pairedDevice.size() > 0){
for(Iterator it = pairedDevice.iterator(); it.hasNext();){
BluetoothDevice pDevice = (BluetoothDevice) it.next();
mStringList.add("设备名称:"+pDevice.getName()+"\n设备地址:"+pDevice.getAddress());
Log.d(TAG, "onReceive: 已配对的设备:"+pDevice.getName()+"\t"+pDevice.getAddress());
mStringArrayAdapter.notifyDataSetChanged();
}
}else {
mStringList.add("无");
Log.d(TAG, "on scanDevices: 没有已配对的设备.....");
}
mStringList.add("附近的设备:");
mAdapter.startDiscovery();
Log.d(TAG, "on scanDevices: 开始扫描.....");
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals(BluetoothDevice.ACTION_FOUND)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mStringList.add("设备名称:"+device.getName()+"\n设备地址:"+device.getAddress());
Log.d(TAG, "onReceive: 搜索到的设备:"+device.getName()+"\t"+device.getAddress());
mStringArrayAdapter.notifyDataSetChanged();
}
}
};
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d(TAG, "onConnectionStateChange: 连接成功.....");
//gatt.setCharacteristicNotification(notifyCharacteristic,true);
gatt.discoverServices();
//跳转到通信界面
/* Intent intent = new Intent(MainActivity.this,SocketActivity.class);
intent.putExtra("msg",status);
startActivity(intent);*/
}
else {
Log.d(TAG, "onConnectionStateChange: 连接断开.....");
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.d(TAG, "onCharacteristicWrite: 写入成功.....");
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
List<BluetoothGattService> supportedGattServices = bluetoothGatt.getServices();
for (BluetoothGattService BluetoothGattService : gatt.getServices()) {
Log.e(TAG, "--->BluetoothGattService" + BluetoothGattService.getUuid().toString());
Log.d(TAG, "onItemClick: BluetoothGattServer 服务 -----> " + BluetoothGattService.getUuid().toString());
//遍历所有特征
for (BluetoothGattCharacteristic bluetoothGattCharacteristic : BluetoothGattService.getCharacteristics()) {
Log.e("---->gattCharacteristic", bluetoothGattCharacteristic.getUuid().toString());
Log.d(TAG, "onItemClick: bluetoothGattCharacteristic 特征 -----> " + bluetoothGattCharacteristic.getUuid().toString());
String str = bluetoothGattCharacteristic.getUuid().toString();
Log.d(TAG, "onServicesDiscovered: notifyUUID = "+notifyUUID);
Log.d(TAG, "onServicesDiscovered: bluetoothGattCharacteristic = "+bluetoothGattCharacteristic);
notifyCharacteristic = bluetoothGattCharacteristic;
Log.d(TAG, "onServicesDiscovered: notifyCharacteristic 特征 = "+notifyCharacteristic);
writeCharacteristic = bluetoothGattCharacteristic;
Log.d(TAG, "onServicesDiscovered: writeCharacteristic 特征 = "+writeCharacteristic);
}
}
}
if ((null == writeCharacteristic) || (null == notifyCharacteristic)) {
//连接失败
Log.d(TAG, "onServicesDiscovered: 失败.....");
}else {
setNotify(notifyCharacteristic);
setEnableNotify(notifyCharacteristic, true);
Log.d(TAG, "onServicesDiscovered: 成功.....");
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
final byte[] value = characteristic.getValue();
Log.d(TAG, "onCharacteristicChanged: 收到数据: "+bytesHexStrTranslate.bytesToHexFun1(value));
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
ListView arraylistview=(ListView)findViewById(R.id.receiveList);
adtDevices=new ArrayAdapter<String>(MainActivity.this,R.layout.array_item,listDevices);
String str=bytesHexStrTranslate.bytesToHexFun1(value);
int n=0;
listDevices.add(n,str);
arraylistview.setAdapter(adtDevices);
n++;
}
});
}
};
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.disConnected:
if(bluetoothGatt.connect()){
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(TAG, "onClick: 手动断开连接.....");
scanDevices();
}
break;
case R.id.scan:
scanDevices();
break;
case R.id.sendButton:
String str = sendDataEdit.getText().toString();
// Intent intent = new Intent(MainActivity.this,SocketActivity.class);
//
// startActivity();
writeCharacteristic.setValue(str);
bluetoothGatt.writeCharacteristic(writeCharacteristic);
Log.d(TAG, "onClick: 数据 %d "+str+"发送成功.....");
break;
}
}
public boolean setNotify(BluetoothGattCharacteristic data_char) {
// 查看是否带有可通知属性
if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) {
bluetoothGatt.setCharacteristicNotification(data_char, true);
BluetoothGattDescriptor descriptor = data_char.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
} else if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {
bluetoothGatt.setCharacteristicNotification(data_char, true);
BluetoothGattDescriptor descriptor = data_char.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
return true;
}
public boolean setEnableNotify(BluetoothGattCharacteristic data_char, boolean enable) {
// 没有打开蓝
bluetoothGatt.setCharacteristicNotification(data_char, enable);
return true;
}
}
class BytesHexStrTranslate {
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static String bytesToHexFun1(byte[] bytes) {
// 一个byte为8位,可用两个十六进制位标识
char[] buf = new char[bytes.length * 2];
int a = 0;
int index = 0;
for (byte b : bytes) { // 使用除与取余进行转换
if (b < 0) {
a = 256 + b;
} else {
a = b;
}
buf[index++] = HEX_CHAR[a / 16];
buf[index++] = HEX_CHAR[a % 16];
}
return new String(buf);
}
public static String bytesToHexFun2(byte[] bytes) {
char[] buf = new char[bytes.length * 2];
int index = 0;
for (byte b : bytes) { // 利用位运算进行转换,可以看作方法一的变种
buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
buf[index++] = HEX_CHAR[b & 0xf];
}
return new String(buf);
}
public static String bytesToHexFun3(byte[] bytes) {
StringBuilder buf = new StringBuilder(bytes.length * 2);
for (byte b : bytes) { // 使用String的format方法进行转换
buf.append(String.format("%02x", new Integer(b & 0xff)));
}
return buf.toString();
}
public static byte[] StringtoBytes(String str) {
if (str == null || str.trim().equals("")) {
return new byte[0];
}
byte[] bytes = new byte[str.length() / 2];
for (int i = 0; i < str.length() / 2; i++) {
String subStr = str.substring(i * 2, i * 2 + 2);
bytes[i] = (byte) Integer.parseInt(subStr, 16);
}
return bytes;
}
}
`
`<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ListView
android:id="@+id/devicesList"
android:layout_width="match_parent"
android:layout_height="200dp" />
<TextView
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"/>
<Button
android:id="@+id/scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="扫描设备" />
<Button
android:id="@+id/disConnected"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="断开连接" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/sendDataEdit"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="0x100"/>
<Button
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
android:textSize="20sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<ListView
android:id="@+id/receiveList"
android:layout_width="250dp"
android:layout_height="200dp" />
</LinearLayout>
</LinearLayout>
`