发布时间:2023-07-01 19:00
ESP32S蓝牙07
继续ESP32S的BLE蓝牙学习。在上一篇中,我们完成了APP对BLE蓝牙设备的搜索,今天我们继续完成手机APP作为客户端时,怎样连接和读写。
我们还是以之前的手机APP控制BLE蓝牙小车为例子,ESP32S运行的是BLE蓝牙服务器的程序,对外广播服务消息,并开放了一个读和一个写的通道。 手机APP运行的是客户端程序,负责搜索蓝牙设备、连接设备、以及向开发板发送控制指令。 开发板在接收连接后,当接收到来自APP的消息时,把消息返回给APP。
我们还是先上源码吧,里面有注释。
这个是主程序 MainActivity. java
package com.example.bluetooth;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
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.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.view.MotionEvent;
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.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SuppressLint(\"NewApi\")
public class MainActivity extends AppCompatActivity {
//public final static UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(\"00001101-0000-1000-8000-00805F9B34FB\"); //通用蓝牙的标志
private UUID mServiceUUID = UUID.fromString(\"6E400001-B5A3-F393-E0A9-E50E24DCCA9E\");
private UUID mReadUUID = UUID.fromString(\"6E400003-B5A3-F393-E0A9-E50E24DCCA9E\");
private UUID mWriteUUID = UUID.fromString(\"6E400002-B5A3-F393-E0A9-E50E24DCCA9E\");
private Button scan_button, send_button, discon_button;
private TextView msgstr;
private EditText msgstr02;
private BluetoothAdapter bleadapter;
private BluetoothGatt bluetoothGatt;
private BluetoothGattService bluetoothGattServices;
private BluetoothGattCharacteristic character_read, character_write, character_notify;
private BluetoothDevice bledevice;
private List
private ArrayList list;
private ArrayAdapter adapter;
private ListView mListView;
private int inval=0;
private boolean connected = false;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
msgstr02.setText(msgstr02.getText().toString() + \"in: \" + (String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgstr = (TextView) findViewById(R.id.txt_msg);
msgstr02 = (EditText) findViewById(R.id.txt_msg02);
scan_button = (Button) findViewById(R.id.btn_scan);
send_button = (Button) findViewById(R.id.btn_send);
discon_button = (Button) findViewById(R.id.btn_discon);
mListView = (ListView) findViewById(R.id.listView1);
msgstr02.setFocusable(false);
//初始化ble设配器
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bleadapter = manager.getAdapter();
//判断蓝牙是否开启,若是关闭则请求打开蓝牙
if (bleadapter == null || !bleadapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
}
//搜索按钮
scan_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
msgstr.setText(\"扫描中,请稍候 ...\");
//初始化列表变量,一个用于存储自定义类,一个用于存储字符串
mDeviceList=new ArrayList
list = new ArrayList();
//把扫描过程放进一个线程里面进行
new Thread(new Runnable() {
@Override
public void run() {
//如果发现一个BLE设备,就会执行一次callback回调函数
bleadapter.startLeScan(callback);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bleadapter.stopLeScan(callback);
msgstr.setText(\"\");
}
}).start();
}
});
//发送消息按钮
send_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(connected == true) {
inval += 1;
String str = \"hello\" + inval + \"\\n\\r\";
character_write.setValue(str);
bluetoothGatt.writeCharacteristic(character_write);
msgstr02.setText(msgstr02.getText().toString() + \"ou: \" + str);
}
}
});
//断开连接按钮
discon_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//断开连接
if(connected == true) {
bluetoothGatt.disconnect();
connected = false;
}
}
});
//列表框侦听,当用户点击选择蓝牙时,连接蓝牙
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
//获取用户选中的蓝牙设备
bledevice = mDeviceList.get(position);
msgstr.setText(\"connect: \" + bledevice.getAddress());
//连接设备的方法,返回值为bluetoothgatt类型
//根据手机的版本,版本较高 或较低的时候
//回调函数gattcallback,管理蓝牙的连接、获取服务、读写消息
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
bluetoothGatt = bledevice.connectGatt(MainActivity.this, false, gattcallback, BluetoothDevice.TRANSPORT_LE);
else
bluetoothGatt = bledevice.connectGatt(MainActivity.this, false, gattcallback);
}
});
}
//这个是蓝牙扫描的回调函数,每当扫描到一个BLE设备时,就会运行一次这个函数
public BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
if (bluetoothDevice != null){
//这里给大家另外两种显示扫描结果的方法,可以用消息框或标签来显示
//Toast.makeText(MainActivity.this, bluetoothDevice.getName(), Toast.LENGTH_SHORT).show();
//showresult.append(bleDevice.getName() + \" \" + bleDevice.getMac() + \"\\n\");
if(!mDeviceList.contains(bluetoothDevice)) {
mDeviceList.add(bluetoothDevice);
//list是存储字符串的集合,adapter是连接字符串到列表框的工具
list.add(bluetoothDevice.getName() + \" \" + bluetoothDevice.getAddress());
adapter = new ArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, list);
mListView.setAdapter(adapter);
}
}
}
};
//这个是蓝牙管理的回调函数,管理BLE的连接、获取服务、读写消息
private BluetoothGattCallback gattcallback = new BluetoothGattCallback() {
//连接状态,当APP与BLE连接成功、或者连接断开时,都会触发这个事件
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, final int newState) {
super.onConnectionStateChange(gatt, status, newState);
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (newState) {
//已经连接
case BluetoothProfile.STATE_CONNECTED:
msgstr.setText(\"已连接\" + \"\\n\");
//当连接成功,就获取BLE的服务,并触发获取服务的事件
bluetoothGatt.discoverServices();
break;
//正在连接
case BluetoothProfile.STATE_CONNECTING:
msgstr.setText(\"正在连接\" + \"\\n\");
break;
//连接断开
case BluetoothProfile.STATE_DISCONNECTED:
msgstr.setText(\"已断开\" + \"\\n\");
bluetoothGatt.close();
msgstr02.setText(\"\");
break;
//正在断开
case BluetoothProfile.STATE_DISCONNECTING:
msgstr.setText(\"断开中\" + \"\\n\");
break;
}
}
});
}
//这个是获取BLE服务的事件,如果获取成功
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService gattService = bluetoothGatt.getService(mServiceUUID);
//获取指定的UUID服务不为空时
if(gattService != null){
//获取指定的UUID读写通道
bluetoothGattServices = gattService;
character_read = gattService.getCharacteristic(mReadUUID);
character_write = gattService.getCharacteristic(mWriteUUID);
//把读取通道设置为可侦听、可读取状态
if (character_read != null)
setCharacteristicNotification(character_read, true);
connected = true;
msgstr.setText(\"获取服务成功\");
}else{
msgstr.setText(\"获取服务失败\");
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
//这个是侦听事件,当有数据从BLE设备传入APP的时候,就会引发这个事件
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if(characteristic == character_read) {
Message mesg = new Message();
mesg.what = 1;
mesg.obj = new String(characteristic.getValue()) ;
MainActivity.this.handler.sendMessage(mesg);
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
}
} ;
//这个是把某个通道设置为可侦听状态
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
boolean isEnableNotification = bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
if(isEnableNotification) {
//一个读写通道里面,可能一次就传递多个类型的数值,每个类型数字都要设置侦听属性
List
if(descriptorList != null && descriptorList.size() > 0) {
for(BluetoothGattDescriptor descriptor : descriptorList) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
}
}
}
}
这个是界面设计 activity_main. xml
<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"
xmlns:tools=\"http://schemas.android.com/tools\"
android:layout_width=\"match_parent\"
android:layout_height=\"match_parent\"
android:orientation=\"vertical\"
android:paddingBottom=\"@dimen/activity_vertical_margin\"
android:paddingLeft=\"@dimen/activity_horizontal_margin\"
android:paddingRight=\"@dimen/activity_horizontal_margin\"
android:paddingTop=\"@dimen/activity_vertical_margin\"
tools:context=\".MainActivity\" >
<ListView
android:id=\"@+id/listView1\"
android:layout_width=\"wrap_content\"
android:layout_height=\"wrap_content\"
android:visibility=\"visible\" />
<Button
android:id=\"@+id/btn_scan\"
android:layout_width=\"wrap_content\"
android:layout_height=\"wrap_content\"
android:text=\"扫描蓝牙\" />
<Button
android:id=\"@+id/btn_send\"
android:layout_width=\"wrap_content\"
android:layout_height=\"wrap_content\"
android:text=\"发送消息\" />
<Button
android:id=\"@+id/btn_discon\"
android:layout_width=\"wrap_content\"
android:layout_height=\"wrap_content\"
android:text=\"断开蓝牙\" />
<TextView
android:id=\"@+id/txt_msg\"
android:layout_width=\"match_parent\"
android:layout_height=\"wrap_content\" />
<EditText
android:id=\"@+id/txt_msg02\"
android:minLines=\"12\"
android:gravity=\"top\"
android:layout_width=\"match_parent\"
android:layout_height=\"match_parent\" />
LinearLayout>
这个是安卓的版本号以及权限申请 在AndroidManifest. xml
<uses-sdk
android:minSdkVersion=\"11\"
android:targetSdkVersion=\"21\" />
<uses-permission android:name=\"android.permission.BLUETOOTH\" />
<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" />
<uses-feature android:name=\"android.hardware.location.gps\" />
<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>
<uses-permission-sdk-23 android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>
<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>
整体回顾一下:BLE蓝牙的控制主要还是“线程 + 回调”,当用户点击扫描按钮时,程序会启动一个扫描回调函数,每搜索到一个BLE蓝牙设备时,就触发运行一次回调函数,在回调函数中,我们把搜索到的设备添加到列表框中。
当用户选择列表框中的某一个BLE蓝牙设备时,程序会连接这个BLE蓝牙设备,并不断地触发BLE的管理回调函数。这个管理回调函数会被不断地触发运行:当连接成功、或者连接断开时,会触发里面的连接侦听事件;当获取到(读取)BLE蓝牙设备的广播服务消息时,会触发获取服务侦听事件;当用户进行写操作时,会触发写的事件(在这个程序中没有关于写侦听的代码);当有消息从BLE服务器(在这个例子中,指的是ESP32S开发板)传入时,APP会触发读的侦听事件,并获取传入的文本内容。