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("000000ff-0000-1000-8000-00805f9b34fb"); //特征值 1: 0000fff1-0000-1000-8000-00805f9b34fb (SSID) //用途:写入 Wi-Fi 的 SSID 名称 //属性:Write (可写) //数据类型:UTF-8 字符串 //示例:"MyWiFi_5G" public static final UUID DEFAULT_CHAR_UUID = UUID.fromString("0000ff01-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("0000ff01-0000-1000-8000-00805f9b34fb"); // 可选,用于接收设备响应 // 上下文 private Context context; // 蓝牙适配器和扫描器 private BluetoothAdapter bluetoothAdapter; private BluetoothLeScanner bleScanner; // GATT 相关 private BluetoothGatt bluetoothGatt; private BluetoothGattService targetService; private BluetoothGattCharacteristic characteristic; private BluetoothGattCharacteristic notifyCharacteristic; // 可选 // 自定义 UUID(允许调用者修改) private UUID serviceUuid = DEFAULT_SERVICE_UUID; private UUID charUuid = DEFAULT_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 String dataToWrite; // 待写入的数据 // 用于将 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; } /** * 设置自定义特征 UUID(可选) */ public void setCharUuid(UUID charUuid) { this.charUuid = charUuid; } /** * 设置自定义通知特征 UUID(可选,用于接收设备响应) */ public void setNotifyCharUuid(UUID notifyCharUuid) { this.notifyCharUuid = notifyCharUuid; } /** * 检查蓝牙连接状态 */ public boolean getBluetoothStatus() { return this.isConnected; } // ==================== 权限检查 ==================== /** * 检查是否拥有必要的运行时权限(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) { Log.d(TAG, "BLE scan fail"); } } /** * 停止扫描 */ @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(); // // 关键:获取设备的蓝牙类型 // int bluetoothType = device.getType(); // switch (bluetoothType) { // case BluetoothDevice.DEVICE_TYPE_CLASSIC: // Log.d(TAG, "📞 经典蓝牙设备:" + device.getName()); // // 例如:蓝牙耳机、车载音响 // break; // // case BluetoothDevice.DEVICE_TYPE_LE: // Log.d(TAG, "💡 低功耗蓝牙设备:" + device.getName()); // // 例如:手环、智能灯、你的 HDL 设备 // break; // // case BluetoothDevice.DEVICE_TYPE_DUAL: // Log.d(TAG, "🔄 双模设备(支持两种):" + device.getName()); // // 例如:高端蓝牙耳机 // break; // // case BluetoothDevice.DEVICE_TYPE_UNKNOWN: // Log.d(TAG, "❓ 未知类型"); // break; // } 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, result)); } } @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.disconnect(); // 先断开连接 bluetoothGatt.close(); bluetoothGatt = null; Log.d(TAG, "Connecting to 先断开旧蓝牙连接"); } 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; characteristic = 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 (bluetoothGatt != null) { // bluetoothGatt.close(); // bluetoothGatt = null; // } if (connectListener != null) { callbackHandler.post(() -> connectListener.onDisconnected()); } Log.d(TAG, "Connected to GATT server, 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; } characteristic = targetService.getCharacteristic(charUuid); notifyCharacteristic = targetService.getCharacteristic(notifyCharUuid); boolean mtuRequestResult = gatt.requestMtu(512); // 默认设置请求最大 MTU,有设备可能不支持 Log.d(TAG, "requestMtu(512) 结果:" + mtuRequestResult); if (characteristic == null) { Log.e(TAG, "Required characteristics not found"); notifyConnectionFailed("Characteristics not found"); return; } Log.e(TAG, "Required characteristics find"); // 如果支持通知,可以开启通知(可选) 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); if (writeListener != null) { callbackHandler.post(() -> writeListener.onWriteFailed(status)); } return; } Log.d(TAG, "onCharacteristicWrite: 写入成功回复"); if (characteristic.getUuid().equals(charUuid)) { //写入成功 if (writeListener != null) { callbackHandler.post(() -> writeListener.onWriteSuccess()); callbackHandler.post(() -> writeListener.onWriteComplete(true)); } } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.d(TAG, "onCharacteristicChanged: " + characteristic.getUuid()); //收到蓝牙回复的数 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) { Log.d(TAG, "onDescriptorWrite: status=" + status + ", descriptor=" + descriptor.getUuid()); // 通知描述符写入完成,可以忽略 if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "通知开启成功,可以接收设备响应了"); } else { Log.e(TAG, "通知开启失败:" + status); if (writeListener != null) { callbackHandler.post(() -> writeListener.onWriteFailed(status)); } } } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { //todo 后面兼用mtc分包的,目前默认设置512字节 if (status == BluetoothGatt.GATT_SUCCESS) { // currentMtu = mtu; Log.d(TAG, "✅ MTU 协商成功:当前 MTU = " + mtu + " 字节"); Log.d(TAG, " 有效数据长度 = " + (mtu - 5) + " 字节"); // // MTU 设置成功后,标记连接成功 // isConnected = true; // if (connectListener != null) { // callbackHandler.post(() -> { // Log.d(TAG, "通知连接成功回调"); // connectListener.onConnected(); // }); // } } else { // Log.w(TAG, "⚠️ MTU 协商失败,status: " + status + ",使用默认 MTU=23"); //// currentMtu = 23; // // 即使 MTU 失败,也认为连接成功(使用默认 MTU) // isConnected = true; // if (connectListener != null) { // callbackHandler.post(() -> connectListener.onConnected()); // } } } }; @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 步:设置描述符的值 // // 先检查特征属性,决定使用 NOTIFY 还是 INDICATE // int charProp = characteristic.getProperties(); // if ((charProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { // descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); // Log.d(TAG, "使用 NOTIFICATION 模式"); // } else if ((charProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { // descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); // Log.d(TAG, "使用 INDICATION 模式"); // } else { // Log.e(TAG, "该特征不支持通知/指示"); // return; // } // 第 3 步:设置描述符的值为 ENABLE_NOTIFICATION_VALUE, // ↑ 这个值是 byte[]{0x01, 0x00},告诉设备端"我要订阅通知" descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); // 第 4 步:写入描述符到设备 //↑ 只有写入成功后,告诉设备端,设备才会开始发送通知, bluetoothGatt.writeDescriptor(descriptor); Log.d(TAG, "writeDescriptor success"); } } } private void notifyConnectionFailed(String reason) { if (connectListener != null) { callbackHandler.post(() -> connectListener.onConnectionFailed(reason)); } disconnect(); } // ==================== 写入凭证 ==================== /** * 写入数据(必须在连接成功后调用) * * @param data 数据 * @param listener 写入结果回调 */ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public void writeCredentials(String data, WriteListener listener) { Log.d(TAG, "writeCredentials :准备写入"); this.writeListener = listener; if (!isConnected || characteristic == null) { if (listener != null) { callbackHandler.post(() -> listener.onWriteFailed(-1)); // 自定义错误码 } return; } // 开始写 writeCharacteristic(characteristic, data); } @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) private void writeCharacteristic(BluetoothGattCharacteristic characteristic, String value) { Log.d(TAG, "writeCharacteristic :开始写入"); 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); Log.d(TAG, "writeCharacteristic :开始完成"); } // ==================== 工具方法 ==================== /** * 检查蓝牙是否开启 */ public 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, ScanResult scanResult); /** * 扫描失败 */ void onScanFailed(int errorCode); } public interface ConnectListener { /** * 连接成功,服务已发现,特征已获取 */ void onConnected(); /** * 连接断开(包括主动断开和意外断开) */ void onDisconnected(); /** * 连接失败(服务未找到、特征缺失、GATT 错误等) */ void onConnectionFailed(String reason); } public interface WriteListener { /** * 写入成功 */ void onWriteSuccess(); /** * 全部写入完成(成功为 true) */ void onWriteComplete(boolean success); /** * 写入失败 */ void onWriteFailed(int status); /** * 设备通过通知返回的响应(如果有) */ void onDeviceResponse(String response); } }