package com.shlb.comb.activity;
|
|
import android.bluetooth.BluetoothGatt;
|
import android.bluetooth.BluetoothGattCallback;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattService;
|
import android.bluetooth.BluetoothGattDescriptor;
|
import android.bluetooth.BluetoothProfile;
|
import android.os.Bundle;
|
import android.text.TextUtils;
|
import android.view.View;
|
import android.view.ViewGroup;
|
import android.widget.Button;
|
import android.widget.EditText;
|
import android.widget.TextView;
|
import android.widget.Toast;
|
|
import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager;
|
import com.blakequ.bluetooth_manager_lib.connect.ConnectState;
|
import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener;
|
import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice;
|
import com.blakequ.bluetooth_manager_lib.util.ByteUtils;
|
import com.qmuiteam.qmui.util.QMUIDisplayHelper;
|
import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView;
|
import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView;
|
import com.shlb.comb.R;
|
import com.shlb.comb.base.BaseActivity;
|
import com.shlb.comb.event.UpdateEvent;
|
import com.shlb.comb.util.Singletion;
|
|
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.ThreadMode;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.UUID;
|
|
public class TestActivity extends BaseActivity {
|
|
private TextView tvStatus;
|
private TextView tvSelectedService;
|
private QMUIGroupListView mGroupListView;
|
private EditText etCommand;
|
private Button btnSend;
|
private TextView tvLog;
|
|
private BluetoothConnectManager connectManager;
|
private BluetoothLeDevice mDevice;
|
private BluetoothGatt mGatt;
|
private BluetoothGattCharacteristic selectedCharacteristic;
|
|
// Store pairs of (Characteristic, UI Item) if needed, or just list
|
private List<BluetoothGattCharacteristic> writeableCharacteristics = new ArrayList<>();
|
|
@Override
|
protected void contentView() {
|
setContentView(R.layout.activity_test);
|
}
|
|
@Override
|
protected void initView() {
|
super.initHead();
|
tv_center.setText("蓝牙测试");
|
if (iv_left != null) {
|
iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back));
|
iv_left.setOnClickListener(v -> finish());
|
}
|
|
tvStatus = findViewById(R.id.tv_status);
|
tvSelectedService = findViewById(R.id.tv_selected_service);
|
mGroupListView = findViewById(R.id.groupListView);
|
etCommand = findViewById(R.id.et_command);
|
btnSend = findViewById(R.id.btn_send);
|
tvLog = findViewById(R.id.tv_log);
|
}
|
|
@Override
|
protected void initData() {
|
mDevice = Singletion.getInstance().mDevice;
|
if (mDevice == null) {
|
appendLog("未选择设备!");
|
return;
|
}
|
initConn(mDevice);
|
}
|
|
@Override
|
protected void initEvent() {
|
btnSend.setOnClickListener(v -> {
|
String cmd = etCommand.getText().toString().trim();
|
if (TextUtils.isEmpty(cmd)) {
|
Toast("请输入指令");
|
return;
|
}
|
if (selectedCharacteristic == null) {
|
Toast("请选择一个服务/特征值");
|
return;
|
}
|
sendCmd(cmd);
|
});
|
}
|
|
private void initConn(BluetoothLeDevice device) {
|
appendLog("正在连接 " + device.getAddress() + "...");
|
connectManager = BluetoothConnectManager.getInstance(this);
|
connectManager.addConnectStateListener(stateListener);
|
connectManager.setBluetoothGattCallback(new BluetoothGattCallback() {
|
@Override
|
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
super.onConnectionStateChange(gatt, status, newState);
|
EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_STATU, newState, "conn_statu"));
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
mGatt = gatt;
|
}
|
}
|
|
@Override
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
super.onServicesDiscovered(gatt, status);
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
runOnUiThread(() -> processServices(gatt.getServices()));
|
}
|
}
|
|
@Override
|
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
super.onCharacteristicWrite(gatt, characteristic, status);
|
String msg = (status == BluetoothGatt.GATT_SUCCESS) ? "写入成功" : "写入失败";
|
String uuid = characteristic.getUuid().toString().substring(0, 8);
|
runOnUiThread(() -> appendLog(msg + " (" + uuid + ")"));
|
}
|
|
@Override
|
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
super.onCharacteristicChanged(gatt, characteristic);
|
byte[] value = characteristic.getValue();
|
String hex = bytesToHexString(value);
|
String uuid = characteristic.getUuid().toString().substring(0, 8);
|
runOnUiThread(() -> appendLog("收到(" + uuid + "): " + hex));
|
|
}
|
});
|
connectManager.connect(device.getAddress());
|
}
|
|
private ConnectStateListener stateListener = new ConnectStateListener() {
|
@Override
|
public void onConnectStateChanged(String address, ConnectState state) {
|
runOnUiThread(() -> {
|
String stateStr = "";
|
switch (state) {
|
case CONNECTED: stateStr = "已连接"; break;
|
case CONNECTING: stateStr = "连接中"; break;
|
case NORMAL: stateStr = "已断开"; break;
|
default: stateStr = state.toString();
|
}
|
tvStatus.setText("状态: " + stateStr);
|
appendLog("状态变更: " + stateStr);
|
});
|
}
|
};
|
|
private void processServices(List<BluetoothGattService> services) {
|
writeableCharacteristics.clear();
|
int size = QMUIDisplayHelper.dp2px(this, 20);
|
QMUIGroupListView.Section section = QMUIGroupListView.newSection(this)
|
.setTitle("可写服务列表")
|
.setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
for (BluetoothGattService service : services) {
|
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
|
int props = characteristic.getProperties();
|
// Filter for Write properties
|
if ((props & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 ||
|
(props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
|
|
writeableCharacteristics.add(characteristic);
|
|
String propertyStr = getPropertyString(props);
|
String uuidFull = characteristic.getUuid().toString();
|
|
QMUICommonListItemView item = mGroupListView.createItemView(null,
|
"UUID: " + uuidFull,
|
propertyStr,
|
QMUICommonListItemView.VERTICAL,
|
QMUICommonListItemView.ACCESSORY_TYPE_NONE);
|
|
item.setTag(characteristic);
|
|
section.addItemView(item, v -> {
|
if (v.getTag() instanceof BluetoothGattCharacteristic) {
|
handleCharacteristicSelection((BluetoothGattCharacteristic) v.getTag());
|
}
|
});
|
}
|
}
|
}
|
|
section.addTo(mGroupListView);
|
|
if (!writeableCharacteristics.isEmpty()) {
|
// Default select first
|
handleCharacteristicSelection(writeableCharacteristics.get(0));
|
} else {
|
appendLog("未找到可写特征值.");
|
}
|
}
|
|
private void handleCharacteristicSelection(BluetoothGattCharacteristic characteristic) {
|
selectedCharacteristic = characteristic;
|
tvSelectedService.setText("当前选择服务: " + selectedCharacteristic.getUuid().toString());
|
appendLog("已选择特征值: " + selectedCharacteristic.getUuid());
|
Toast("已选择该服务");
|
|
// Attempt to find and enable notification on a characteristic in the same service
|
BluetoothGattService service = characteristic.getService();
|
boolean foundNotify = false;
|
for (BluetoothGattCharacteristic c : service.getCharacteristics()) {
|
int props = c.getProperties();
|
if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0 ||
|
(props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
|
appendLog("尝试启用通知: " + c.getUuid());
|
enableNotification(c);
|
foundNotify = true;
|
// Found one, stop searching to avoid concurrent descriptor writes
|
break;
|
}
|
}
|
|
if (!foundNotify) {
|
appendLog("当前服务未找到可通知的特征值");
|
}
|
}
|
|
private String getPropertyString(int property) {
|
StringBuilder sb = new StringBuilder();
|
if ((property & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
sb.append("读 ");
|
}
|
if ((property & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0
|
|| (property & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
|
sb.append("写 ");
|
}
|
if ((property & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
|
sb.append("通知 ");
|
}
|
if ((property & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
|
sb.append("指示 ");
|
}
|
return sb.toString();
|
}
|
|
private void enableNotification(BluetoothGattCharacteristic characteristic) {
|
if (mGatt == null || characteristic == null) return;
|
|
boolean success = mGatt.setCharacteristicNotification(characteristic, true);
|
if (success) {
|
appendLog("启用通知监听成功");
|
|
// Write Descriptor for Notify/Indicate
|
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
|
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
|
if (descriptor != null) {
|
int props = characteristic.getProperties();
|
byte[] value = null;
|
if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
|
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
} else if ((props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
|
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
}
|
|
if (value != null) {
|
descriptor.setValue(value);
|
boolean writeDesc = mGatt.writeDescriptor(descriptor);
|
appendLog("写入Descriptor: " + writeDesc);
|
}
|
}
|
} else {
|
appendLog("启用通知监听失败!");
|
}
|
}
|
|
private void sendCmd(String hexCmd) {
|
if (selectedCharacteristic == null || mGatt == null) return;
|
|
byte[] data = hexStringToBytes(hexCmd);
|
if (data == null) {
|
Toast("Hex指令格式错误");
|
return;
|
}
|
|
selectedCharacteristic.setValue(data);
|
boolean status = mGatt.writeCharacteristic(selectedCharacteristic);
|
appendLog("发送: " + hexCmd + ", 结果: " + status);
|
}
|
|
private void appendLog(String msg) {
|
tvLog.append(msg + "\n");
|
((View)tvLog.getParent()).post(() -> ((View)tvLog.getParent()).scrollTo(0, tvLog.getBottom()));
|
}
|
|
public static byte[] hexStringToBytes(String hexString) {
|
if (hexString == null || hexString.equals("")) {
|
return null;
|
}
|
hexString = hexString.toUpperCase();
|
hexString = hexString.replace(" ", "");
|
if (hexString.length() % 2 != 0) {
|
hexString = "0" + hexString;
|
}
|
|
int length = hexString.length() / 2;
|
char[] hexChars = hexString.toCharArray();
|
byte[] d = new byte[length];
|
for (int i = 0; i < length; i++) {
|
int pos = i * 2;
|
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
|
}
|
return d;
|
}
|
|
private static byte charToByte(char c) {
|
return (byte) "0123456789ABCDEF".indexOf(c);
|
}
|
|
public static String bytesToHexString(byte[] src) {
|
StringBuilder stringBuilder = new StringBuilder("");
|
if (src == null || src.length <= 0) {
|
return null;
|
}
|
for (int i = 0; i < src.length; i++) {
|
int v = src[i] & 0xFF;
|
String hv = Integer.toHexString(v);
|
if (hv.length() < 2) {
|
stringBuilder.append(0);
|
}
|
stringBuilder.append(hv);
|
}
|
return stringBuilder.toString().toUpperCase();
|
}
|
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
public void onEventRefresh(UpdateEvent event) {
|
// Handle global events if necessary
|
}
|
|
@Override
|
protected void onDestroy() {
|
super.onDestroy();
|
if (connectManager != null && mDevice != null) {
|
connectManager.removeConnectStateListener(stateListener);
|
connectManager.disconnect(mDevice.getAddress());
|
}
|
}
|
}
|