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