From 7e63101fc94a336fc37128d992321c8f80cadd23 Mon Sep 17 00:00:00 2001 From: fengjignqi <15192412330@163.com> Date: Thu, 4 Jun 2026 14:30:24 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E8=93=9D=E7=89=99=EF=BC=8C?= =?UTF-8?q?=E9=85=8D=E7=BD=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- .../water/device/AddDeviceActivity_copy.java | 203 +------ .../ckkj/water/device/SelectWifiActivity.java | 119 +++- .../water/espBle/EspProvisionManager.java | 517 ++++++++++++++++++ .../ckkj/water/espBle/ProvisionCallback.java | 61 +++ .../ckkj/water/espBle/ProvisionConfig.java | 125 +++++ .../utils/manager/WaterDeviceManager.java | 3 +- 7 files changed, 850 insertions(+), 180 deletions(-) create mode 100644 app/src/main/java/com/ckkj/water/espBle/EspProvisionManager.java create mode 100644 app/src/main/java/com/ckkj/water/espBle/ProvisionCallback.java create mode 100644 app/src/main/java/com/ckkj/water/espBle/ProvisionConfig.java diff --git a/app/build.gradle b/app/build.gradle index 87a7fa2..a47c978 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -121,6 +121,7 @@ dependencies { implementation project(path: ':imagepickerl') //消息传递 implementation 'de.greenrobot:eventbus:3.0.0-beta1' + implementation 'org.greenrobot:eventbus:3.3.1' implementation 'com.trello.rxlifecycle2:rxlifecycle:2.2.1' implementation 'com.trello.rxlifecycle2:rxlifecycle-android:2.2.1' implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1' @@ -234,4 +235,3 @@ dependencies { implementation 'com.github.espressif:esp-idf-provisioning-android:lib-2.2.3' } - diff --git a/app/src/main/java/com/ckkj/water/device/AddDeviceActivity_copy.java b/app/src/main/java/com/ckkj/water/device/AddDeviceActivity_copy.java index 5440073..1e27108 100644 --- a/app/src/main/java/com/ckkj/water/device/AddDeviceActivity_copy.java +++ b/app/src/main/java/com/ckkj/water/device/AddDeviceActivity_copy.java @@ -138,6 +138,32 @@ public class AddDeviceActivity_copy extends BaseActivity { - Log.e("TEST", "设备响应: " + Arrays.toString(bytes)); - runOnUiThread(() -> { - // TODO: 处理设备返回的数据,比如 WiFi 配置响应 - handleDeviceResponse(bytes); - }); - }); - - // 连接设备 - waterDeviceManager.connect(device) - .retry(3, 100) // 重试3次,间隔100ms - .done(bluetoothDevice -> { - Log.e("TEST", "连接成功: " + deviceName); - runOnUiThread(() -> { - ToastUtil.showShort(mContext, "连接成功: " + deviceName); - // TODO: 连接成功后,发送 WiFi 配置 - // sendWifiConfig("SSID", "PASSWORD"); - }); - }) - .fail((bluetoothDevice, status) -> { - Log.e("TEST", "连接失败: " + status); - runOnUiThread(() -> { - ToastUtil.showShort(mContext, "连接失败,请重试"); - }); - }) - .enqueue(); - - waterDeviceManager.setConnectionObserver(new ConnectionObserver() { - - @Override - public void onDeviceConnecting(@NonNull BluetoothDevice device) { - Log.e("TEST", "onDeviceConnecting"); - } - - @Override - public void onDeviceConnected(@NonNull BluetoothDevice device) { - Log.e("TEST", "onDeviceConnected"); - } - - @Override - public void onDeviceFailedToConnect(@NonNull BluetoothDevice device, int reason) { - Log.e("TEST", "onDeviceFailedToConnect"); - } - - @Override - public void onDeviceReady(@NonNull BluetoothDevice device) { - Log.e("TEST", "onDeviceReady"); - } - - @Override - public void onDeviceDisconnecting(@NonNull BluetoothDevice device) { - Log.e("TEST", "onDeviceDisconnecting"); - } - - @Override - public void onDeviceDisconnected( - @NonNull BluetoothDevice device, - int reason) { - - Log.e("TEST", "onDeviceDisconnected reason=" + reason); - } - }); - } - - /** - * 发送 WiFi 配置到设备 - */ - public void sendWifiConfig(String ssid, String password) { - if (waterDeviceManager == null || !waterDeviceManager.isBlConnected()) { - Log.e("TEST", "设备未连接,无法发送WiFi配置"); - return; - } - - // TODO: 根据设备协议组装数据 - // 示例:假设协议是 [header(1) + ssid_len(1) + ssid(n) + pwd_len(1) + pwd(m)] - byte[] ssidBytes = ssid.getBytes(); - byte[] pwdBytes = password.getBytes(); - - byte[] data = new byte[2 + ssidBytes.length + 2 + pwdBytes.length]; - int index = 0; - data[index++] = 0x01; // header - data[index++] = (byte) ssidBytes.length; - System.arraycopy(ssidBytes, 0, data, index, ssidBytes.length); - index += ssidBytes.length; - data[index++] = (byte) pwdBytes.length; - System.arraycopy(pwdBytes, 0, data, index, pwdBytes.length); - - waterDeviceManager.send(data); - Log.e("TEST", "发送WiFi配置: SSID=" + ssid + ", PWD=" + password); - } - - /** - * 处理设备响应数据 - */ - private void handleDeviceResponse(byte[] data) { - if (data == null || data.length == 0) { - return; - } - - // TODO: 根据设备协议解析响应数据 - // 示例:假设响应格式是 [type(1) + status(1) + ...] - Log.e("TEST", "处理设备响应: " + Arrays.toString(data)); - - if (data.length >= 2) { - byte type = data[0]; - byte status = data[1]; - - switch (type) { - case 0x01: // WiFi配置响应 - if (status == 0x00) { - ToastUtil.showShort(mContext, "WiFi配置成功"); - // TODO: 下一步操作 - } else { - ToastUtil.showShort(mContext, "WiFi配置失败,错误码: " + status); - } - break; - default: - Log.e("TEST", "未知响应类型: " + type); - break; - } - } - } /** * 初始化蓝牙适配器 @@ -405,34 +290,6 @@ public class AddDeviceActivity_copy extends BaseActivity implements DeviceContract.DeviceView { @@ -45,6 +51,11 @@ public class SelectWifiActivity extends BaseActivity wifiList) { + Set 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) { + 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() { + mainHandler.removeCallbacks(connectionTimeoutTask); + mainHandler.removeCallbacks(provisionTimeoutTask); + connecting = false; + connected = false; + provisioning = false; + ignoreNextDisconnectEvent = false; + } + + 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; + 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(); + } + } + }); + } +} diff --git a/app/src/main/java/com/ckkj/water/espBle/ProvisionCallback.java b/app/src/main/java/com/ckkj/water/espBle/ProvisionCallback.java new file mode 100644 index 0000000..063c08c --- /dev/null +++ b/app/src/main/java/com/ckkj/water/espBle/ProvisionCallback.java @@ -0,0 +1,61 @@ +package com.ckkj.water.espBle; + +/** + * @author fengxiaoyang + * @date 2026/6/4 + * @description 蓝牙设备配网回调接口 + */ +public interface ProvisionCallback { + + /** + * 正在连接设备 + */ + void onConnecting(); + + /** + * 设备连接成功 + */ + void onConnected(); + + /** + * 正在配网中 + * @param progress 配网进度 (0-100) + */ + void onProvisioning(int progress); + + /** + * 配网成功 + * @param deviceName 设备名称 + * @param deviceType 设备类型 + */ + void onSuccess(String deviceName, String deviceType); + + /** + * 配网失败 + * @param errorCode 错误码 + * @param message 错误信息 + */ + void onFailed(int errorCode, String message); + + /** + * 设备断开连接 + */ + void onDisconnected(); + + /** + * WiFi列表获取成功 + * @param wifiList WiFi列表 + */ + void onWifiListReceived(String[] wifiList); + + /** + * 获取WiFi列表失败 + * @param message 错误信息 + */ + void onWifiListFailed(String message); + + /** + * 配网被拒绝(设备端拒绝配网请求) + */ + void onProvisioningRejected(); +} diff --git a/app/src/main/java/com/ckkj/water/espBle/ProvisionConfig.java b/app/src/main/java/com/ckkj/water/espBle/ProvisionConfig.java new file mode 100644 index 0000000..3d9cda0 --- /dev/null +++ b/app/src/main/java/com/ckkj/water/espBle/ProvisionConfig.java @@ -0,0 +1,125 @@ +package com.ckkj.water.espBle; + +import android.os.Build; + +import com.espressif.provisioning.ESPConstants; + +/** + * @author fengxiaoyang + * @date 2026/6/4 + * @description 蓝牙配网配置管理类 + */ +public class ProvisionConfig { + + /** + * ESP BLE Service UUID + */ + public static final String SERVICE_UUID = + "0000ffff-0000-1000-8000-00805f9b34fb"; + + /** + * ESP BLE Characteristic UUID for writes + */ + public static final String CHARACTERISTIC_UUID = + "0000ff01-0000-1000-8000-00805f9b34fb"; + + /** + * ESP BLE Characteristic UUID for notifications + */ + public static final String NOTIFY_UUID = + "0000ff02-0000-1000-8000-00805f9b34fb"; + + /** + * 设备传输的协议类型 + */ + public static final String TRANSPORT_BLE = "ble"; + + /** + * 设备前缀 (用于过滤设备) + */ + public static final String DEVICE_PREFIX = "PROV_"; + + /** + * 安全类型 - SECURITY_0 (无安全) + */ + public static final int SECURITY_0 = ESPConstants.SecurityType.SECURITY_0.ordinal(); + + /** + * 安全类型 - SECURITY_1 (推荐) + */ + public static final int SECURITY_1 = ESPConstants.SecurityType.SECURITY_1.ordinal(); + + /** + * 默认安全类型 + */ + public static final int DEFAULT_SECURITY = SECURITY_1; + + /** + * 连接超时时间 (毫秒) + */ + public static final int CONNECTION_TIMEOUT = 30000; + + /** + * 配网超时时间 (毫秒) + */ + public static final int PROVISION_TIMEOUT = 120000; + + public static final int ERROR_NOT_INITIALIZED = 1000; + + public static final int ERROR_INVALID_DEVICE = 1001; + + public static final int ERROR_CONNECT_TIMEOUT = 1002; + + public static final int ERROR_CONNECT_FAILED = 1003; + + public static final int ERROR_NOT_CONNECTED = 1004; + + public static final int ERROR_SESSION_FAILED = 1005; + + public static final int ERROR_WIFI_CONFIG_FAILED = 1006; + + public static final int ERROR_WIFI_APPLY_FAILED = 1007; + + public static final int ERROR_PROVISION_FAILED = 1008; + + public static final int ERROR_PROVISION_TIMEOUT = 1009; + + public static final int ERROR_WIFI_SCAN_FAILED = 1010; + + /** + * 配网版本 (可根据实际固件调整) + */ + public static final String VERSION = "v1.1"; + + /** + * 生成蓝牙扫描过滤器 + * @return 服务UUID数组 + */ + public static String[] getScanServiceUuids() { + return new String[]{SERVICE_UUID}; + } + + /** + * 生成设备前缀过滤器 (用于过滤非ESP设备) + * @return 设备前缀 + */ + public static String getDevicePrefix() { + return DEVICE_PREFIX; + } + + /** + * 获取当前Android版本号 + * @return Android版本号 + */ + public static int getAndroidVersion() { + return Build.VERSION.SDK_INT; + } + + /** + * 检查是否为Android 10及以上版本 + * @return true 表示支持 + */ + public static boolean isAndroid10OrAbove() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; + } +} diff --git a/app/src/main/java/com/ckkj/water/utils/manager/WaterDeviceManager.java b/app/src/main/java/com/ckkj/water/utils/manager/WaterDeviceManager.java index 4ff479a..d50bc10 100644 --- a/app/src/main/java/com/ckkj/water/utils/manager/WaterDeviceManager.java +++ b/app/src/main/java/com/ckkj/water/utils/manager/WaterDeviceManager.java @@ -133,8 +133,7 @@ public class WaterDeviceManager extends BleManager { ? notifyCharacteristic.getUuid() : "null")); - return writeCharacteristic != null - && notifyCharacteristic != null; + return writeCharacteristic != null; } @Override