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