Files
Android-WaterProject/app/src/main/java/com/ckkj/water/espBle/EspProvisionManager.java
fengjignqi d8d546c90a 优化
2026-07-04 10:47:05 +08:00

647 lines
22 KiB
Java

package com.ckkj.water.espBle;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.ckkj.water.AppConfig;
import com.espressif.provisioning.DeviceConnectionEvent;
import com.espressif.provisioning.ESPConstants;
import com.espressif.provisioning.ESPDevice;
import com.espressif.provisioning.WiFiAccessPoint;
import com.espressif.provisioning.listeners.ProvisionListener;
import com.espressif.provisioning.listeners.WiFiScanListener;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
/**
* @author fengxiaoyang
* @date 2026/6/4
* @description 鎿嶄綔钃濈墮绠$悊绫?
*/
public class EspProvisionManager {
private static volatile EspProvisionManager instance;
private final Context context;
private final Handler mainHandler;
private final com.espressif.provisioning.ESPProvisionManager provisionManager;
private static final long CONNECT_COOLDOWN = 1200L;
private static final long QUICK_DISCONNECT_WINDOW = 3000L;
private static final long RETRY_DELAY = 1200L;
private static final long PROGRESS_UPDATE_INTERVAL = 500L;
private static final int MAX_CONNECT_RETRY = 2;
private static final int MAX_AUTO_PROGRESS = 99;
private ESPDevice espDevice;
private ProvisionCallback callback;
private boolean connecting;
private boolean connected;
private boolean provisioning;
private int provisionProgress;
private int provisionTargetProgress;
private boolean eventBusRegistered;
private boolean ignoreNextDisconnectEvent;
private boolean expectDisconnected; // 标记是否预期断开(配网成功后)
private String currentDeviceName;
private String currentDeviceType = ProvisionConfig.TRANSPORT_BLE;
private BluetoothDevice pendingBluetoothDevice;
private String pendingDeviceName;
private String pendingServiceUuid;
private String pendingProofOfPossession;
private String pendingUserName;
private int pendingSecurityType;
private int connectRetryCount;
private long lastDisconnectTime;
private long connectStartTime;
private final Runnable connectionTimeoutTask = new Runnable() {
@Override
public void run() {
if (connecting && !connected) {
connecting = false;
disconnectDeviceOnly();
lastDisconnectTime = System.currentTimeMillis();
if (retryConnectIfAllowed()) {
return;
}
resetState(true);
clearPendingConnect();
notifyFailed(ProvisionConfig.ERROR_CONNECT_TIMEOUT, "蓝牙连接超时");
disconnectDeviceOnly();
}
}
};
private final Runnable provisionTimeoutTask = new Runnable() {
@Override
public void run() {
if (provisioning) {
finishProvisionFailed(ProvisionConfig.ERROR_PROVISION_TIMEOUT, "配网超时");
}
}
};
private final Runnable connectRetryTask = new Runnable() {
@Override
public void run() {
startPendingConnect(false);
}
};
private final Runnable provisionProgressTask = new Runnable() {
@Override
public void run() {
if (!provisioning) {
return;
}
if (provisionProgress < provisionTargetProgress) {
notifyProvisioning(provisionProgress + 1);
}
if (provisionProgress < MAX_AUTO_PROGRESS) {
mainHandler.postDelayed(provisionProgressTask, PROGRESS_UPDATE_INTERVAL);
}
}
};
private EspProvisionManager(Context context) {
this.context = context.getApplicationContext();
this.mainHandler = new Handler(Looper.getMainLooper());
this.provisionManager = com.espressif.provisioning.ESPProvisionManager.getInstance(this.context);
ensureEventBusRegistered();
}
public static EspProvisionManager init(Context context) {
if (instance == null) {
synchronized (EspProvisionManager.class) {
if (instance == null) {
instance = new EspProvisionManager(context);
}
}
}
return instance;
}
public static EspProvisionManager get() {
if (instance == null) {
throw new RuntimeException("Please call init()");
}
return instance;
}
public EspProvisionManager setCallback(ProvisionCallback callback) {
this.callback = callback;
return this;
}
public ESPDevice getEspDevice() {
return espDevice;
}
public boolean isConnected() {
return connected;
}
public boolean isProvisioning() {
return provisioning;
}
public int getProvisionProgress() {
return provisionProgress;
}
public void connect(String deviceAddress, String deviceName) {
connect(deviceAddress, deviceName, AppConfig.BLUETOOTH_DEVICE_UUID);
}
public void connect(String deviceAddress, String deviceName, String serviceUuid) {
connect(deviceAddress, deviceName, serviceUuid, "", ProvisionConfig.DEFAULT_SECURITY);
}
public void connect(String deviceAddress, String deviceName, String serviceUuid,
String proofOfPossession, int securityType) {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null || TextUtils.isEmpty(deviceAddress)) {
notifyFailed(ProvisionConfig.ERROR_INVALID_DEVICE, "蓝牙设备无效");
return;
}
try {
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(deviceAddress);
connect(bluetoothDevice, deviceName, serviceUuid, proofOfPossession, securityType);
} catch (IllegalArgumentException e) {
notifyFailed(ProvisionConfig.ERROR_INVALID_DEVICE, "蓝牙设备地址无效");
}
}
public void connect(BluetoothDevice bluetoothDevice, String serviceUuid) {
connect(bluetoothDevice, null, serviceUuid, "", ProvisionConfig.DEFAULT_SECURITY);
}
public void connect(BluetoothDevice bluetoothDevice, String deviceName, String serviceUuid,
String proofOfPossession, int securityType) {
connect(bluetoothDevice, deviceName, serviceUuid, proofOfPossession, "", securityType);
}
public void connect(BluetoothDevice bluetoothDevice, String deviceName, String serviceUuid,
String proofOfPossession, String userName, int securityType) {
if (bluetoothDevice == null) {
notifyFailed(ProvisionConfig.ERROR_INVALID_DEVICE, "蓝牙设备无效");
return;
}
if (connecting && pendingBluetoothDevice != null
&& TextUtils.equals(pendingBluetoothDevice.getAddress(), bluetoothDevice.getAddress())) {
return;
}
pendingBluetoothDevice = bluetoothDevice;
pendingDeviceName = deviceName;
pendingServiceUuid = TextUtils.isEmpty(serviceUuid) ? ProvisionConfig.SERVICE_UUID : serviceUuid;
pendingProofOfPossession = proofOfPossession == null ? "" : proofOfPossession;
pendingUserName = userName == null ? "" : userName;
pendingSecurityType = securityType;
connectRetryCount = 0;
startPendingConnect(true);
}
private void startPendingConnect(boolean respectCooldown) {
if (pendingBluetoothDevice == null) {
notifyFailed(ProvisionConfig.ERROR_INVALID_DEVICE, "蓝牙设备无效");
return;
}
if (provisioning) {
return;
}
mainHandler.removeCallbacks(connectRetryTask);
long elapsedSinceDisconnect = System.currentTimeMillis() - lastDisconnectTime;
if (respectCooldown && lastDisconnectTime > 0 && elapsedSinceDisconnect < CONNECT_COOLDOWN) {
mainHandler.postDelayed(connectRetryTask, CONNECT_COOLDOWN - elapsedSinceDisconnect);
return;
}
String displayName = !TextUtils.isEmpty(pendingDeviceName)
? pendingDeviceName : pendingBluetoothDevice.getName();
currentDeviceName = displayName;
currentDeviceType = ProvisionConfig.TRANSPORT_BLE;
boolean hadActiveDevice = espDevice != null && (connecting || connected || provisioning);
ignoreNextDisconnectEvent = hadActiveDevice;
disconnectDeviceOnly();
ensureEventBusRegistered();
espDevice = provisionManager.createESPDevice(
ESPConstants.TransportType.TRANSPORT_BLE,
parseSecurityType(pendingSecurityType)
);
espDevice.setBluetoothDevice(pendingBluetoothDevice);
espDevice.setPrimaryServiceUuid(pendingServiceUuid);
espDevice.setDeviceName(displayName);
espDevice.setProofOfPossession(pendingProofOfPossession);
espDevice.setUserName(pendingUserName);
connecting = true;
connected = false;
provisioning = false;
connectStartTime = System.currentTimeMillis();
mainHandler.removeCallbacks(connectionTimeoutTask);
mainHandler.postDelayed(connectionTimeoutTask, ProvisionConfig.CONNECTION_TIMEOUT);
notifyConnecting();
espDevice.connectBLEDevice(pendingBluetoothDevice, pendingServiceUuid);
}
private boolean retryConnectIfAllowed() {
if (pendingBluetoothDevice == null || provisioning || connected) {
return false;
}
if (connectRetryCount >= MAX_CONNECT_RETRY) {
return false;
}
connectRetryCount++;
resetState(true);
mainHandler.removeCallbacks(connectRetryTask);
mainHandler.postDelayed(connectRetryTask, RETRY_DELAY * connectRetryCount);
return true;
}
private void clearPendingConnect() {
pendingBluetoothDevice = null;
pendingDeviceName = null;
pendingServiceUuid = null;
pendingProofOfPossession = null;
pendingUserName = null;
pendingSecurityType = 0;
connectRetryCount = 0;
connectStartTime = 0;
}
public void provisionWifi(String ssid, String password) {
if (!checkConnected()) {
notifyFailed(ProvisionConfig.ERROR_NOT_CONNECTED, "设备未连接");
return;
}
if (TextUtils.isEmpty(ssid)) {
notifyFailed(ProvisionConfig.ERROR_WIFI_CONFIG_FAILED, "WiFi 名称不能为空");
return;
}
provisioning = true;
provisionProgress = 0;
provisionTargetProgress = 29;
mainHandler.removeCallbacks(provisionTimeoutTask);
mainHandler.postDelayed(provisionTimeoutTask, ProvisionConfig.PROVISION_TIMEOUT);
notifyProvisioning(0);
startProvisionProgressTask();
//wifi 密码 回调
espDevice.provision(ssid, password == null ? "" : password, new ProvisionListener() {
@Override
public void createSessionFailed(Exception e) {
finishProvisionFailed(ProvisionConfig.ERROR_SESSION_FAILED, getErrorMessage(e, "创建配网会话失败"));
}
@Override
public void wifiConfigSent() {
advanceProvisionProgress(30, 59);
}
@Override
public void wifiConfigFailed(Exception e) {
notifyProvisioningRejected();
finishProvisionFailed(ProvisionConfig.ERROR_WIFI_CONFIG_FAILED, getErrorMessage(e, "发送 WiFi 信息失败"));
}
@Override
public void wifiConfigApplied() {
advanceProvisionProgress(60, MAX_AUTO_PROGRESS);
}
@Override
public void wifiConfigApplyFailed(Exception e) {
finishProvisionFailed(ProvisionConfig.ERROR_WIFI_APPLY_FAILED, getErrorMessage(e, "应用 WiFi 信息失败"));
}
@Override
public void provisioningFailedFromDevice(ESPConstants.ProvisionFailureReason failureReason) {
finishProvisionFailed(ProvisionConfig.ERROR_PROVISION_FAILED, getProvisionFailureMessage(failureReason));
}
@Override
public void deviceProvisioningSuccess() {
finishProvisionSuccess();
}
@Override
public void onProvisioningFailed(Exception e) {
finishProvisionFailed(ProvisionConfig.ERROR_PROVISION_FAILED, getErrorMessage(e, "配网失败"));
}
});
}
/**
* 通过蓝牙请求 ESP32 扫描周围 WiFi
* @param scanListener 扫描结果监听器
*/
public void scanWifiFromDevice(WiFiScanListener scanListener) {
if (!checkConnected()) {
if (scanListener != null) {
scanListener.onWiFiScanFailed(new Exception("设备未连接"));
}
return;
}
if (espDevice == null) {
if (scanListener != null) {
scanListener.onWiFiScanFailed(new Exception("设备实例为空"));
}
return;
}
espDevice.scanNetworks(scanListener);
}
public void disconnect() {
boolean shouldNotify = connected || connecting || provisioning;
mainHandler.removeCallbacks(connectRetryTask);
disconnectDeviceOnly();
lastDisconnectTime = System.currentTimeMillis();
resetState(true);
clearPendingConnect();
if (shouldNotify) {
notifyDisconnected();
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onDeviceConnectionEvent(DeviceConnectionEvent event) {
if (event == null) {
return;
}
switch (event.getEventType()) {
case ESPConstants.EVENT_DEVICE_CONNECTED:
mainHandler.removeCallbacks(connectionTimeoutTask);
mainHandler.removeCallbacks(connectRetryTask);
connecting = false;
connected = true;
connectRetryCount = 0;
notifyConnected();
break;
case ESPConstants.EVENT_DEVICE_CONNECTION_FAILED:
mainHandler.removeCallbacks(connectionTimeoutTask);
mainHandler.removeCallbacks(provisionTimeoutTask);
boolean wasConnecting = connecting;
connecting = false;
connected = false;
provisioning = false;
lastDisconnectTime = System.currentTimeMillis();
if (wasConnecting && retryConnectIfAllowed()) {
break;
}
if (wasConnecting) {
resetState(true);
clearPendingConnect();
notifyFailed(ProvisionConfig.ERROR_CONNECT_FAILED, "蓝牙连接失败");
} else {
notifyFailed(ProvisionConfig.ERROR_PROVISION_FAILED, "设备通信失败");
}
break;
case ESPConstants.EVENT_DEVICE_DISCONNECTED:
if (ignoreNextDisconnectEvent) {
ignoreNextDisconnectEvent = false;
return;
}
mainHandler.removeCallbacks(connectionTimeoutTask);
mainHandler.removeCallbacks(provisionTimeoutTask);
boolean shouldNotify = connected || connecting || provisioning;
boolean expectedDisconnect = expectDisconnected;
boolean wasConnecting2 = connecting;
long connectDuration = System.currentTimeMillis() - connectStartTime;
lastDisconnectTime = System.currentTimeMillis();
resetState(true);
if (wasConnecting2 && !expectedDisconnect
&& connectDuration < QUICK_DISCONNECT_WINDOW
&& retryConnectIfAllowed()) {
break;
}
if (wasConnecting2 && !expectedDisconnect) {
clearPendingConnect();
notifyFailed(ProvisionConfig.ERROR_CONNECT_FAILED, "蓝牙连接失败");
break;
}
if (shouldNotify) {
// 如果是配网成功后的预期断开,不发送断开回调
// 因为 onSuccess 已经通知过了
if (!expectedDisconnect) {
notifyDisconnected();
}
}
break;
default:
break;
}
}
private boolean checkConnected() {
return espDevice != null && connected;
}
private void ensureEventBusRegistered() {
if (!eventBusRegistered) {
EventBus.getDefault().register(this);
eventBusRegistered = true;
}
}
private void disconnectDeviceOnly() {
if (espDevice != null) {
try {
espDevice.disconnectDevice();
} catch (Exception ignored) {
}
}
}
private void resetState(boolean clearDevice) {
mainHandler.removeCallbacks(connectionTimeoutTask);
mainHandler.removeCallbacks(provisionTimeoutTask);
stopProvisionProgressTask();
connecting = false;
connected = false;
provisioning = false;
provisionProgress = 0;
provisionTargetProgress = 0;
ignoreNextDisconnectEvent = false;
expectDisconnected = false;
if (clearDevice) {
espDevice = null;
}
}
private ESPConstants.SecurityType parseSecurityType(int securityType) {
if (securityType == ProvisionConfig.SECURITY_0) {
return ESPConstants.SecurityType.SECURITY_0;
} else if (securityType == ProvisionConfig.SECURITY_1) {
return ESPConstants.SecurityType.SECURITY_1;
} else {
return ESPConstants.SecurityType.SECURITY_2;
}
}
private void finishProvisionSuccess() {
mainHandler.removeCallbacks(provisionTimeoutTask);
stopProvisionProgressTask();
provisioning = false;
expectDisconnected = true; // 标记为预期断开
notifyProvisioning(100);
notifySuccess();
}
private void finishProvisionFailed(int errorCode, String message) {
mainHandler.removeCallbacks(provisionTimeoutTask);
stopProvisionProgressTask();
provisioning = false;
notifyFailed(errorCode, message);
}
private void advanceProvisionProgress(int minProgress, int targetProgress) {
int nextProgress = Math.max(provisionProgress, minProgress);
provisionTargetProgress = Math.max(nextProgress, Math.min(targetProgress, MAX_AUTO_PROGRESS));
notifyProvisioning(nextProgress);
startProvisionProgressTask();
}
private void startProvisionProgressTask() {
mainHandler.removeCallbacks(provisionProgressTask);
mainHandler.postDelayed(provisionProgressTask, PROGRESS_UPDATE_INTERVAL);
}
private void stopProvisionProgressTask() {
mainHandler.removeCallbacks(provisionProgressTask);
}
private String getProvisionFailureMessage(ESPConstants.ProvisionFailureReason failureReason) {
if (failureReason == ESPConstants.ProvisionFailureReason.AUTH_FAILED) {
return "WiFi 密码错误";
} else if (failureReason == ESPConstants.ProvisionFailureReason.NETWORK_NOT_FOUND) {
return "未找到指定 WiFi";
} else if (failureReason == ESPConstants.ProvisionFailureReason.DEVICE_DISCONNECTED) {
return "设备已断开连接";
} else {
return "设备配网失败";
}
}
private String getErrorMessage(Exception e, String defaultMessage) {
if (e != null && !TextUtils.isEmpty(e.getMessage())) {
return e.getMessage();
}
return defaultMessage;
}
private void runOnMainThread(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
runnable.run();
} else {
mainHandler.post(runnable);
}
}
private void notifyConnecting() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onConnecting();
}
}
});
}
private void notifyConnected() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onConnected();
}
}
});
}
private void notifyProvisioning(final int progress) {
provisionProgress = Math.max(0, Math.min(progress, 100));
runOnMainThread(new Runnable() {
@Override
public void run() {
provisionProgress = Math.max(0, Math.min(progress, 100));
if (callback != null) {
callback.onProvisioning(provisionProgress);
}
}
});
}
private void notifySuccess() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onSuccess(currentDeviceName, currentDeviceType);
}
}
});
}
private void notifyFailed(final int errorCode, final String message) {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onFailed(errorCode, message);
}
}
});
}
private void notifyDisconnected() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onDisconnected();
}
}
});
}
private void notifyProvisioningRejected() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onProvisioningRejected();
}
}
});
}
}