| New file |
| | |
| | | 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); |
| | | } |
| | | } |