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<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) {
|
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<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.disconnect(); // 先断开连接
|
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;
|
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 (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;
|
}
|
characteristic = targetService.getCharacteristic(charUuid);
|
notifyCharacteristic = targetService.getCharacteristic(notifyCharUuid);
|
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;
|
}
|
|
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) {
|
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);
|
Log.e(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) {
|
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) {
|
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);
|
}
|
|
// ==================== 工具方法 ====================
|
|
/**
|
* 检查蓝牙是否开启
|
*/
|
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);
|
}
|
}
|