wjc
2026-03-12 ddc4ee25f8e4fd5a13f010488b6a11a501cca96a
2026年03月12日18:07:47 蓝牙配网开发中
2个文件已添加
1个文件已修改
766 ■■■■■ 已修改文件
app/src/main/AndroidManifest.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java 601 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml
@@ -89,6 +89,13 @@
    <!-- 添加 dataSync 前台服务权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <!-- Android 12+ 推荐的新权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <!-- Android 12+ 可使用 NEARBY_WIFI_DEVICES 替代位置权限(仅限特定场景) -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
    <queries>
        <package android:name="com.hdl.photovoltaic.services" />
    </queries>
app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java
New file
@@ -0,0 +1,158 @@
package com.hdl.photovoltaic.other;
import android.Manifest;
import android.bluetooth.BluetoothDevice;
import androidx.annotation.RequiresPermission;
import com.hdl.photovoltaic.HDLApp;
import com.hdl.photovoltaic.utils.BleWifiConfiguratorUtils;
/**
 * 蓝牙逻辑
 */
public class HdlBluetoothLogic {
    private static volatile HdlBluetoothLogic sHdlBluetoothLogic;
    private BleWifiConfiguratorUtils configurator;
    /**
     * 获取当前对象
     *
     * @return HdlAccountLogic
     */
    public static synchronized HdlBluetoothLogic getInstance() {
        if (sHdlBluetoothLogic == null) {
            synchronized (HdlBluetoothLogic.class) {
                if (sHdlBluetoothLogic == null) {
                    sHdlBluetoothLogic = new HdlBluetoothLogic();
                    // 在构造方法中初始化configurator
                    sHdlBluetoothLogic.initConfigurator();
                }
            }
        }
        return sHdlBluetoothLogic;
    }
    /**
     * 实例方法,初始化configurator
     */
    private void initConfigurator() {
        this.configurator = new BleWifiConfiguratorUtils(HDLApp.getInstance());
    }
    /**
     * 扫描设备
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void scanDevices() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.startScan(new BleWifiConfiguratorUtils.ScanListener() {
            @Override
            public void onDeviceFound(BluetoothDevice device, int rssi, byte[] scanRecord) {
            }
            @Override
            public void onScanFailed(int errorCode) {
            }
        });
    }
    /**
     * 连接设备
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    public void connect() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.connect("", new BleWifiConfiguratorUtils.ConnectListener() {
            @Override
            public void onConnected() {
            }
            @Override
            public void onDisconnected() {
            }
            @Override
            public void onConnectionFailed(String reason) {
            }
        });
    }
    /**
     * 发送配置信息
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    public void sendConfig() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.writeCredentials("", "", new BleWifiConfiguratorUtils.WriteListener() {
            @Override
            public void onSsidWriteSuccess() {
            }
            @Override
            public void onPasswordWriteSuccess() {
            }
            @Override
            public void onWriteComplete(boolean success) {
            }
            @Override
            public void onWriteFailed(int status) {
            }
            @Override
            public void onDeviceResponse(String response) {
            }
        });
    }
    /**
     * 停止扫描
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void stopScan() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.stopScan();
    }
    /**
     * 断开设备连接
     */
    public void disconnect() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.disconnect();
    }
    /**
     * 释放资源(在 Activity/Fragment 销毁时调用)
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void release() {
        if (this.configurator == null) {
            return;
        }
        this.configurator.release();
    }
}
app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java
New file
@@ -0,0 +1,601 @@
package com.hdl.photovoltaic.utils;
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.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.RequiresPermission;
import androidx.core.app.ActivityCompat;
import com.google.gson.Gson;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
 * 手机端通过 BLE 向设备写入 Wi-Fi SSID 和密码的工具类。
 * 典型用法:
 * 1. 创建实例 BleWifiConfigurator configurator = new BleWifiConfigurator(context);
 * 2. 设置自定义 UUID(可选,默认使用常见 UUID)
 * 3. 调用 startScan() 扫描设备
 * 4. 在 onDeviceFound 回调中选择目标设备,调用 connect() 连接
 * 5. 连接成功后调用 writeCredentials(ssid, password) 写入凭证
 * 6. 监听回调处理结果
 */
public class BleWifiConfiguratorUtils {
    private static final String TAG = "BleWifiConfigurator";
    // 默认的 UUID(设备端需要定义以下服务和特征)
    //这是主服务,包含所有配网相关的特征。
    public static final UUID DEFAULT_SERVICE_UUID = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
    //特征值 1: 0000fff1-0000-1000-8000-00805f9b34fb (SSID)
    //用途:写入 Wi-Fi 的 SSID 名称
    //属性:Write (可写)
    //数据类型:UTF-8 字符串
    //示例:"MyWiFi_5G"
    public static final UUID DEFAULT_SSID_CHAR_UUID = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
    //特征值 2: 0000fff2-0000-1000-8000-00805f9b34fb (密码)
    // 用途:写入 Wi-Fi 密码
    //属性:Write (可写)
    //数据类型:UTF-8 字符串
    //示例:"password123"
    public static final UUID DEFAULT_PWD_CHAR_UUID = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");
    //特征值 3: 0000fff3-0000-1000-8000-00805f9b34fb (通知,可选)
    //用途:接收设备端的响应消息
    //属性:Notify (通知)
    //数据类型:UTF-8 字符串
    //示例响应:"SUCCESS", "FAIL: Invalid SSID"
    public static final UUID DEFAULT_NOTIFY_CHAR_UUID = UUID.fromString("0000fff3-0000-1000-8000-00805f9b34fb"); // 可选,用于接收设备响应
    // 上下文
    private Context context;
    // 蓝牙适配器和扫描器
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScanner bleScanner;
    // GATT 相关
    private BluetoothGatt bluetoothGatt;
    private BluetoothGattService targetService;
    private BluetoothGattCharacteristic ssidCharacteristic;
    private BluetoothGattCharacteristic pwdCharacteristic;
    private BluetoothGattCharacteristic notifyCharacteristic; // 可选
    // 自定义 UUID(允许调用者修改)
    private UUID serviceUuid = DEFAULT_SERVICE_UUID;
    private UUID ssidCharUuid = DEFAULT_SSID_CHAR_UUID;
    private UUID pwdCharUuid = DEFAULT_PWD_CHAR_UUID;
    private UUID notifyCharUuid = DEFAULT_NOTIFY_CHAR_UUID;
    // 回调接口
    private ScanListener scanListener;
    private ConnectListener connectListener;
    private WriteListener writeListener;
    // 内部状态
    private boolean isScanning = false;
    private boolean isConnected = false;
    private int writeState = 0; // 0: idle, 1: writing ssid, 2: writing pwd
    private String ssidToWrite;
    private String pwdToWrite;
    // 用于将 BLE 回调抛到主线程
    private final Handler callbackHandler = new Handler(Looper.getMainLooper());
    // ==================== 构造函数和设置 ====================
    public BleWifiConfiguratorUtils(Context context) {
        this.context = context.getApplicationContext();
        initBluetooth();
    }
    private void initBluetooth() {
        BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        if (bluetoothManager != null) {
            bluetoothAdapter = bluetoothManager.getAdapter();
        }
    }
    /**
     * 设置自定义 Service UUID(可选)
     */
    public void setServiceUuid(UUID serviceUuid) {
        this.serviceUuid = serviceUuid;
    }
    /**
     * 设置自定义 SSID 特征 UUID(可选)
     */
    public void setSsidCharUuid(UUID ssidCharUuid) {
        this.ssidCharUuid = ssidCharUuid;
    }
    /**
     * 设置自定义密码特征 UUID(可选)
     */
    public void setPwdCharUuid(UUID pwdCharUuid) {
        this.pwdCharUuid = pwdCharUuid;
    }
    /**
     * 设置自定义通知特征 UUID(可选,用于接收设备响应)
     */
    public void setNotifyCharUuid(UUID notifyCharUuid) {
        this.notifyCharUuid = notifyCharUuid;
    }
    // ==================== 权限检查 ====================
    /**
     * 检查是否拥有必要的运行时权限(Android 12+ 需动态申请新权限)
     *
     * @return 缺失的权限列表,为空则表示全部拥有
     */
    public List<String> getMissingPermissions() {
        List<String> missing = new ArrayList<>();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (context.checkSelfPermission(android.Manifest.permission.BLUETOOTH_SCAN) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                missing.add(android.Manifest.permission.BLUETOOTH_SCAN);
                Log.d(TAG, "No Permissions: android.Manifest.permission.BLUETOOTH_SCAN");
            }
            if (context.checkSelfPermission(android.Manifest.permission.BLUETOOTH_CONNECT) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                missing.add(android.Manifest.permission.BLUETOOTH_CONNECT);
                Log.d(TAG, "No Permissions: android.Manifest.permission.BLUETOOTH_CONNECT");
            }
            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                missing.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_FINE_LOCATION");
            }
        } else {
            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                missing.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_FINE_LOCATION");
            }
            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                missing.add(android.Manifest.permission.ACCESS_COARSE_LOCATION);
                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_COARSE_LOCATION");
            }
        }
        return missing;
    }
    // ==================== 扫描相关 ====================
    /**
     * 开始扫描 BLE 设备,默认不设置过滤(由 ScanListener 自行判断)
     *
     * @param listener 扫描回调
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void startScan(ScanListener listener) {
        try {
            this.scanListener = listener;
            if (!checkBluetoothEnabled()) {
                notifyScanFailed(ScanListener.ERROR_BLUETOOTH_DISABLED);
                return;
            }
            if (bleScanner == null) {
                bleScanner = bluetoothAdapter.getBluetoothLeScanner();
                if (bleScanner == null) {
                    notifyScanFailed(ScanListener.ERROR_SCANNER_NULL);
                    return;
                }
            }
            if (isScanning) {
                stopScan();
            }
            ScanSettings settings = new ScanSettings.Builder()
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
//                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) //CALLBACK_TYPE_FIRST_MATCH 仅当首次匹配到设备时回调一次// 或 CALLBACK_TYPE_ALL_MATCHES 每次收到广播都回调。
//                    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
//                    .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
                    .build();
            // 可以添加基于 serviceUuid 的过滤,但保留由 listener 自行过滤的灵活性
            List<ScanFilter> filters = new ArrayList<>();
            // 可选:添加服务 UUID 过滤(如果需要)
            // filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuid)).build());
            if (!this.getMissingPermissions().isEmpty()) {
                callbackHandler.post(() -> this.scanListener.onScanFailed(ScanListener.ERROR_No_Permissions)); // 没有权限
                return;
            }
            bleScanner.startScan(filters, settings, scanCallback);
            isScanning = true;
            Log.d(TAG, "BLE scan started");
        } catch (Exception e) {
        }
    }
    /**
     * 停止扫描
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void stopScan() {
        if (isScanning && bleScanner != null) {
            if (!this.getMissingPermissions().isEmpty()) {
                callbackHandler.post(() -> this.scanListener.onScanFailed(ScanListener.ERROR_No_Permissions)); // 没有权限
                return;
            }
            bleScanner.stopScan(scanCallback);
            isScanning = false;
            Log.d(TAG, "BLE scan stopped");
        }
    }
    private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            BluetoothDevice device = result.getDevice();
            Log.d("===", "onDeviceFound: " + Objects.requireNonNull(result.getScanRecord()).getDeviceName());
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
//                if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
//                    // TODO: Consider calling
//                    //    ActivityCompat#requestPermissions
//                    // here to request the missing permissions, and then overriding
//                    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
//                    //                                          int[] grantResults)
//                    // to handle the case where the user grants the permission. See the documentation
//                    // for ActivityCompat#requestPermissions for more details.
//                    scanListener.onScanFailed(ScanListener.ERROR_No_Permissions);
//                    return;
//                }
//            }
            int rssi = result.getRssi();
            byte[] scanRecord = result.getScanRecord() != null ? result.getScanRecord().getBytes() : null;
            if (scanListener != null) {
                callbackHandler.post(() -> scanListener.onDeviceFound(device, rssi, scanRecord));
            }
        }
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            // 批量结果,可根据需要处理
            Log.d("=====", "onDeviceFound: " + new Gson().toJson(results));
        }
        @Override
        public void onScanFailed(int errorCode) {
            Log.e(TAG, "Scan failed, error: " + errorCode);
            notifyScanFailed(errorCode);
        }
    };
    private void notifyScanFailed(int errorCode) {
        if (this.scanListener != null) {
            callbackHandler.post(() -> this.scanListener.onScanFailed(errorCode));
        }
        isScanning = false;
    }
    // ==================== 连接相关 ====================
    /**
     * 连接指定的 BLE 设备(系统默认连接超时30秒        )
     *
     * @param deviceAddress 目标设备BluetoothDevice
     * @param listener      连接状态回调
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    public void connect(String deviceAddress, ConnectListener listener) {
        this.connectListener = listener;
        if (!checkBluetoothEnabled()) {
            notifyConnectionFailed("Bluetooth disabled");
            return;
        }
        if (!this.getMissingPermissions().isEmpty()) {
            callbackHandler.post(() -> this.connectListener.onConnectionFailed("No permissions")); // 没有权限
            return;
        }
        if (bluetoothGatt != null) {
            bluetoothGatt.close();
            bluetoothGatt = null;
        }
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
        // 自动连接 = false,避免系统缓存连接
        bluetoothGatt = device.connectGatt(context, false, gattCallback);
        Log.d(TAG, "Connecting to " + device.getAddress());
    }
    /**
     * 断开当前连接
     */
    public void disconnect() {
        if (bluetoothGatt != null) {
            if (ActivityCompat.checkSelfPermission(this.context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return;
            }
            bluetoothGatt.disconnect();
            bluetoothGatt.close();
            bluetoothGatt = null;
        }
        isConnected = false;
        targetService = null;
        ssidCharacteristic = null;
        pwdCharacteristic = null;
        notifyCharacteristic = null;
    }
    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            //如果连接超时系统会报133
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, "Connected to GATT server, attempting service discovery");
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                isConnected = false;
                if (connectListener != null) {
                    callbackHandler.post(() -> connectListener.onDisconnected());
                }
            }//
        }
        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                targetService = gatt.getService(serviceUuid);
                if (targetService == null) {
                    Log.e(TAG, "Service not found: " + serviceUuid);
                    notifyConnectionFailed("Service not found");
                    return;
                }
                ssidCharacteristic = targetService.getCharacteristic(ssidCharUuid);
                pwdCharacteristic = targetService.getCharacteristic(pwdCharUuid);
                notifyCharacteristic = targetService.getCharacteristic(notifyCharUuid);
                if (ssidCharacteristic == null || pwdCharacteristic == null) {
                    Log.e(TAG, "Required characteristics not found");
                    notifyConnectionFailed("Characteristics not found");
                    return;
                }
                // 如果支持通知,可以开启通知(可选)
                if (notifyCharacteristic != null) {
                    enableNotification(notifyCharacteristic);
                }
                isConnected = true;
                if (connectListener != null) {
                    callbackHandler.post(() -> connectListener.onConnected());
                }
            } else {
                Log.e(TAG, "Service discovery failed, status: " + status);
                notifyConnectionFailed("Service discovery failed");
            }
        }
        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            boolean success = status == BluetoothGatt.GATT_SUCCESS;
            if (!success) {
                Log.e(TAG, "Write failed, status: " + status);
                writeState = 0;
                if (writeListener != null) {
                    callbackHandler.post(() -> writeListener.onWriteFailed(status));
                }
                return;
            }
            if (writeState == 1 && characteristic.getUuid().equals(ssidCharUuid)) {
                // SSID 写入成功,开始写密码
                writeState = 2;
                if (writeListener != null) {
                    callbackHandler.post(() -> writeListener.onSsidWriteSuccess());
                }
                writeCharacteristic(pwdCharacteristic, pwdToWrite);
            } else if (writeState == 2 && characteristic.getUuid().equals(pwdCharUuid)) {
                // 密码写入成功
                writeState = 0;
                if (writeListener != null) {
                    callbackHandler.post(() -> writeListener.onPasswordWriteSuccess());
                    callbackHandler.post(() -> writeListener.onWriteComplete(true));
                }
            }
        }
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            if (characteristic.getUuid().equals(notifyCharUuid) && writeListener != null) {
                byte[] value = characteristic.getValue();
                String response = new String(value, StandardCharsets.UTF_8);
                callbackHandler.post(() -> writeListener.onDeviceResponse(response));
            }
        }
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            // 通知描述符写入完成,可以忽略
//            if (status == BluetoothGatt.GATT_SUCCESS) {
//                Log.d(TAG, "通知开启成功,可以接收设备响应了");
//            } else {
//                Log.e(TAG, "通知开启失败:" + status);
//                if (writeListener != null) {
//                    callbackHandler.post(() -> writeListener.onWriteFailed(status));
//                }
//            }
        }
    };
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    private void enableNotification(BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null) return;
        if (!this.getMissingPermissions().isEmpty()) {
            return;
        }
        // 第 1 步:在协议栈层面开启通知
        boolean enabled = bluetoothGatt.setCharacteristicNotification(characteristic, true);
        if (enabled) {
            // 第 2 步:获取客户端特征配置描述符 (CCCD)
            // ↑ 00002902 是标准的 CCCD 描述符 UUID
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                    UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
            if (descriptor != null) {
                // 第 3 步:设置描述符的值为 ENABLE_NOTIFICATION_VALUE,
                // ↑ 这个值是 byte[]{0x01, 0x00},告诉设备端"我要订阅通知"
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                // 第 4 步:写入描述符到设备
                //↑ 只有写入成功后,告诉设备端,设备才会开始发送通知,
                bluetoothGatt.writeDescriptor(descriptor);
            }
        }
    }
    private void notifyConnectionFailed(String reason) {
        if (connectListener != null) {
            callbackHandler.post(() -> connectListener.onConnectionFailed(reason));
        }
        disconnect();
    }
    // ==================== 写入凭证 ====================
    /**
     * 写入 Wi-Fi SSID 和密码(必须在连接成功后调用)
     *
     * @param ssid     Wi-Fi SSID
     * @param password Wi-Fi 密码
     * @param listener 写入结果回调
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    public void writeCredentials(String ssid, String password, WriteListener listener) {
        this.writeListener = listener;
        if (!isConnected || ssidCharacteristic == null || pwdCharacteristic == null) {
            if (listener != null) {
                callbackHandler.post(() -> listener.onWriteFailed(-1)); // 自定义错误码
            }
            return;
        }
        this.ssidToWrite = ssid;
        this.pwdToWrite = password;
        // 开始写 SSID
        writeState = 1;
        writeCharacteristic(ssidCharacteristic, ssid);
    }
    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
    private void writeCharacteristic(BluetoothGattCharacteristic characteristic, String value) {
        if (!this.getMissingPermissions().isEmpty()) {
            callbackHandler.post(() -> this.writeListener.onWriteFailed(-2)); // 没有权限
            return;
        }
        characteristic.setValue(value.getBytes(StandardCharsets.UTF_8));
        characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        bluetoothGatt.writeCharacteristic(characteristic);
    }
    // ==================== 工具方法 ====================
    /**
     * 检查蓝牙是否开启
     */
    private boolean checkBluetoothEnabled() {
        return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
    }
    /**
     * 释放资源(在 Activity/Fragment 销毁时调用)
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
    public void release() {
        stopScan();
        disconnect();
    }
    // ==================== 回调接口定义 ====================
    public interface ScanListener {
        /**
         * 蓝牙没有开启
         */
        int ERROR_BLUETOOTH_DISABLED = 1;
        int ERROR_SCANNER_NULL = 2;
        int ERROR_No_Permissions = 3;
        /**
         * 发现设备(可能多次回调)
         */
        void onDeviceFound(BluetoothDevice device, int rssi, byte[] scanRecord);
        /**
         * 扫描失败
         */
        void onScanFailed(int errorCode);
    }
    public interface ConnectListener {
        /**
         * 连接成功,服务已发现,特征已获取
         */
        void onConnected();
        /**
         * 连接断开(包括主动断开和意外断开)
         */
        void onDisconnected();
        /**
         * 连接失败(服务未找到、特征缺失、GATT 错误等)
         */
        void onConnectionFailed(String reason);
    }
    public interface WriteListener {
        /**
         * SSID 写入成功
         */
        void onSsidWriteSuccess();
        /**
         * 密码写入成功
         */
        void onPasswordWriteSuccess();
        /**
         * 全部写入完成(成功为 true)
         */
        void onWriteComplete(boolean success);
        /**
         * 写入失败
         */
        void onWriteFailed(int status);
        /**
         * 设备通过通知返回的响应(如果有)
         */
        void onDeviceResponse(String response);
    }
}