526 lines
18 KiB
Java
526 lines
18 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 ESPDevice espDevice;
|
|
private ProvisionCallback callback;
|
|
private boolean connecting;
|
|
private boolean connected;
|
|
private boolean provisioning;
|
|
private boolean eventBusRegistered;
|
|
private boolean ignoreNextDisconnectEvent;
|
|
private boolean expectDisconnected; // 标记是否预期断开(配网成功后)
|
|
private String currentDeviceName;
|
|
private String currentDeviceType = ProvisionConfig.TRANSPORT_BLE;
|
|
|
|
private final Runnable connectionTimeoutTask = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (connecting && !connected) {
|
|
connecting = false;
|
|
notifyFailed(ProvisionConfig.ERROR_CONNECT_TIMEOUT, "蓝牙连接超时");
|
|
disconnectDeviceOnly();
|
|
}
|
|
}
|
|
};
|
|
|
|
private final Runnable provisionTimeoutTask = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (provisioning) {
|
|
finishProvisionFailed(ProvisionConfig.ERROR_PROVISION_TIMEOUT, "配网超时");
|
|
}
|
|
}
|
|
};
|
|
|
|
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 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;
|
|
}
|
|
|
|
String primaryServiceUuid = TextUtils.isEmpty(serviceUuid) ? ProvisionConfig.SERVICE_UUID : serviceUuid;
|
|
String displayName = !TextUtils.isEmpty(deviceName) ? deviceName : bluetoothDevice.getName();
|
|
currentDeviceName = displayName;
|
|
currentDeviceType = ProvisionConfig.TRANSPORT_BLE;
|
|
|
|
ignoreNextDisconnectEvent = espDevice != null;
|
|
disconnectDeviceOnly();
|
|
ensureEventBusRegistered();
|
|
|
|
espDevice = provisionManager.createESPDevice(
|
|
ESPConstants.TransportType.TRANSPORT_BLE,
|
|
parseSecurityType(securityType)
|
|
);
|
|
espDevice.setBluetoothDevice(bluetoothDevice);
|
|
espDevice.setPrimaryServiceUuid(primaryServiceUuid);
|
|
espDevice.setDeviceName(displayName);
|
|
espDevice.setProofOfPossession(proofOfPossession == null ? "" : proofOfPossession);
|
|
espDevice.setUserName(userName == null ? "" : userName);
|
|
|
|
connecting = true;
|
|
connected = false;
|
|
provisioning = false;
|
|
mainHandler.removeCallbacks(connectionTimeoutTask);
|
|
mainHandler.postDelayed(connectionTimeoutTask, ProvisionConfig.CONNECTION_TIMEOUT);
|
|
notifyConnecting();
|
|
espDevice.connectBLEDevice(bluetoothDevice, primaryServiceUuid);
|
|
}
|
|
|
|
public void scanDeviceWifiList() {
|
|
if (!checkConnected()) {
|
|
notifyWifiListFailed("设备未连接");
|
|
return;
|
|
}
|
|
|
|
espDevice.scanNetworks(new WiFiScanListener() {
|
|
@Override
|
|
public void onWifiListReceived(ArrayList<WiFiAccessPoint> wifiList) {
|
|
Set<String> wifiNames = new LinkedHashSet<>();
|
|
if (wifiList != null) {
|
|
for (WiFiAccessPoint accessPoint : wifiList) {
|
|
if (accessPoint != null && !TextUtils.isEmpty(accessPoint.getWifiName())) {
|
|
wifiNames.add(accessPoint.getWifiName());
|
|
}
|
|
}
|
|
}
|
|
notifyWifiListReceived(wifiNames.toArray(new String[0]));
|
|
}
|
|
|
|
@Override
|
|
public void onWiFiScanFailed(Exception e) {
|
|
notifyWifiListFailed(getErrorMessage(e, "获取设备 WiFi 列表失败"));
|
|
}
|
|
});
|
|
}
|
|
|
|
public void provision(String ssid, String password) {
|
|
provisionWifi(ssid, password);
|
|
}
|
|
|
|
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;
|
|
mainHandler.removeCallbacks(provisionTimeoutTask);
|
|
mainHandler.postDelayed(provisionTimeoutTask, ProvisionConfig.PROVISION_TIMEOUT);
|
|
notifyProvisioning(0);
|
|
|
|
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() {
|
|
notifyProvisioning(30);
|
|
}
|
|
|
|
@Override
|
|
public void wifiConfigFailed(Exception e) {
|
|
notifyProvisioningRejected();
|
|
finishProvisionFailed(ProvisionConfig.ERROR_WIFI_CONFIG_FAILED, getErrorMessage(e, "发送 WiFi 信息失败"));
|
|
}
|
|
|
|
@Override
|
|
public void wifiConfigApplied() {
|
|
notifyProvisioning(60);
|
|
}
|
|
|
|
@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, "配网失败"));
|
|
}
|
|
});
|
|
}
|
|
|
|
public void disconnect() {
|
|
boolean shouldNotify = connected || connecting || provisioning;
|
|
disconnectDeviceOnly();
|
|
resetState();
|
|
if (shouldNotify) {
|
|
notifyDisconnected();
|
|
}
|
|
}
|
|
|
|
public void release() {
|
|
disconnect();
|
|
callback = null;
|
|
mainHandler.removeCallbacksAndMessages(null);
|
|
if (eventBusRegistered) {
|
|
EventBus.getDefault().unregister(this);
|
|
eventBusRegistered = false;
|
|
}
|
|
if (instance == this) {
|
|
instance = null;
|
|
}
|
|
}
|
|
|
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
public void onDeviceConnectionEvent(DeviceConnectionEvent event) {
|
|
if (event == null) {
|
|
return;
|
|
}
|
|
|
|
switch (event.getEventType()) {
|
|
case ESPConstants.EVENT_DEVICE_CONNECTED:
|
|
mainHandler.removeCallbacks(connectionTimeoutTask);
|
|
connecting = false;
|
|
connected = true;
|
|
notifyConnected();
|
|
break;
|
|
|
|
case ESPConstants.EVENT_DEVICE_CONNECTION_FAILED:
|
|
mainHandler.removeCallbacks(connectionTimeoutTask);
|
|
mainHandler.removeCallbacks(provisionTimeoutTask);
|
|
boolean wasConnecting = connecting;
|
|
connecting = false;
|
|
connected = false;
|
|
provisioning = false;
|
|
if (wasConnecting) {
|
|
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;
|
|
resetState();
|
|
if (shouldNotify) {
|
|
// 如果是配网成功后的预期断开,不发送断开回调
|
|
// 因为 onSuccess 已经通知过了
|
|
if (!expectDisconnected) {
|
|
notifyDisconnected();
|
|
}
|
|
expectDisconnected = false; // 重置标志
|
|
}
|
|
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() {
|
|
mainHandler.removeCallbacks(connectionTimeoutTask);
|
|
mainHandler.removeCallbacks(provisionTimeoutTask);
|
|
connecting = false;
|
|
connected = false;
|
|
provisioning = false;
|
|
ignoreNextDisconnectEvent = false;
|
|
// expectDisconnected 保持不变,用于断开回调判断
|
|
}
|
|
|
|
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);
|
|
provisioning = false;
|
|
expectDisconnected = true; // 标记为预期断开
|
|
notifyProvisioning(100);
|
|
notifySuccess();
|
|
}
|
|
|
|
private void finishProvisionFailed(int errorCode, String message) {
|
|
mainHandler.removeCallbacks(provisionTimeoutTask);
|
|
provisioning = false;
|
|
notifyFailed(errorCode, message);
|
|
}
|
|
|
|
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) {
|
|
runOnMainThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (callback != null) {
|
|
callback.onProvisioning(progress);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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 notifyWifiListReceived(final String[] wifiList) {
|
|
runOnMainThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (callback != null) {
|
|
callback.onWifiListReceived(wifiList);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void notifyWifiListFailed(final String message) {
|
|
runOnMainThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (callback != null) {
|
|
callback.onWifiListFailed(message);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void notifyProvisioningRejected() {
|
|
runOnMainThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (callback != null) {
|
|
callback.onProvisioningRejected();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|