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 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 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()); } } }