Android S Wi-Fi 打开流程(一)

该文章详细分析了Android系统中开启Wi-Fi功能的代码流程,从Settings应用的WifiEnabler类开始,经过WifiManager的setWifiEnabled方法,到WifiServiceImpl和ActiveModeWarden,再到状态机的处理。在开机初始化时,系统会根据之前Wi-Fi的状态决定初始状态。当尝试开启Wi-Fi时,会触发一系列操作,包括创建和启动ClientModeManager,以及通过状态机进行状态切换,最终实现Wi-Fi的开启并广播Wi-Fi状态变化。

基于 原生的Android S代码: http://aospxref.com/android-13.0.0_r3/

packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java

183      public boolean onSwitchToggled(boolean isChecked) {
184          //Do nothing if called as a result of a state machine event
185          if (mStateMachineEvent) {
186              return true;
187          }
188          // Show toast message if Wi-Fi is not allowed in airplane mode
189          if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {
190              Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
191              // Reset switch to off. No infinite check/listener loop.
192              mSwitchWidget.setChecked(false);
193              return false;
194          }
195  
196          if (isChecked) {
197              mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_WIFI_ON);
198          } else {
199              // Log if user was connected at the time of switching off.
200              mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_WIFI_OFF,
201                      mConnected.get());
202          }
203          if (!mWifiManager.setWifiEnabled(isChecked)) {    ---> 打开 Wi-Fi
204              // Error
205              mSwitchWidget.setEnabled(true);
206              Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
207          }
208          return true;
209      }
210  }

具体的可以看下代码,主要流程如下:
packages/modules/Wifi/framework/java/android/net/wifi/WifiManager.java
packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
setWifiEnabled —> setWifiEnabledInternal —> mActiveModeWarden.wifiToggled(new WorkSource(callingUid, packageName));

packages/modules/Wifi/service/java/com/android/server/wifi/ActiveModeWarden.java

668      /** Wifi has been toggled. */
669      public void wifiToggled(WorkSource requestorWs) {
670          mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED, requestorWs);
671      }

看下状态机:

1595          WifiController() {
1598              DefaultState defaultState = new DefaultState();
1599              addState(defaultState); {
1600                  addState(mDisabledState, defaultState);
1601                  addState(mEnabledState, defaultState);
1602              }

再看下初始的是哪个状态,这里我们看到这个地方有个初始化,很容易会根据这个误以为初始化状态是 setInitialState(mEnabledState)
需要仔细看下,这个start()方法是何时被调用的,追下代码发现是在:SystemService.PHASE_SYSTEM_SERVICES_READY阶段,也就是开机初始化的时候会根据之前的Wi-Fi状态去做些判断,而我们此时默认未开启,那初始状态就是 setInitialState(mDisabledState)

1656          public void start() {
1657              boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
1658              boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
1659              boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
1660              boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled();
1661  
1662              log("isAirplaneModeOn = " + isAirplaneModeOn
1663                      + ", isWifiEnabled = " + isWifiEnabled
1664                      + ", isScanningAvailable = " + isScanningAlwaysAvailable
1665                      + ", isLocationModeActive = " + isLocationModeActive);
1666  
1667              // Initialize these values at bootup to defaults, will be overridden by API calls
1668              // for further toggles.
1669              mLastPrimaryClientModeManagerRequestorWs = mFacade.getSettingsWorkSource(mContext);
1670              mLastScanOnlyClientModeManagerRequestorWs = INTERNAL_REQUESTOR_WS;   ---> 指的是 Process.WIFI_UID
1671              ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
1672              if (role == ROLE_CLIENT_PRIMARY) {   --->Wi-Fi 模式,然后构造相应的 ClientModeManager
1673                  startPrimaryClientModeManager(mLastPrimaryClientModeManagerRequestorWs);
1674                  setInitialState(mEnabledState);
1675              } else if (role == ROLE_CLIENT_SCAN_ONLY) {   ---> 这个理解仅仅扫描模式
1676                  startScanOnlyClientModeManager(mLastScanOnlyClientModeManagerRequestorWs);
1677                  setInitialState(mEnabledState);
1678              } else {   ---> 不属于上面两种的任何一种
1679                  setInitialState(mDisabledState);
1680              }
1681              mWifiMetrics.noteWifiEnabledDuringBoot(mSettingsStore.isWifiToggleEnabled());
1682  
1683              // Initialize the lower layers before we start.
1684              mWifiNative.initialize();
1685              super.start();
1686          }

			// 看下处理这个消息的地方:
1936          class DisabledState extends BaseState {
1952              @Override
1953              public boolean processMessageFiltered(Message msg) {
1954                  switch (msg.what) {
1955                      case CMD_WIFI_TOGGLED:  ---> 处理这个消息
1956                      case CMD_SCAN_ALWAYS_MODE_CHANGED:
1957                          handleStaToggleChangeInDisabledState((WorkSource) msg.obj);
1958                          break;
				}
				
1909          private void handleStaToggleChangeInDisabledState(WorkSource requestorWs) {
1910              if (shouldEnableSta()) {
1911                  startPrimaryOrScanOnlyClientModeManager(requestorWs);
1912                  transitionTo(mEnabledState);
1913              }
1914          }

1112      private boolean startPrimaryOrScanOnlyClientModeManager(WorkSource requestorWs) {
1113          ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
1114          if (role == ROLE_CLIENT_PRIMARY) {   ---> 开启Wi-Fi 是这个角色
1115              return startPrimaryClientModeManager(requestorWs);
1116          } else if (role == ROLE_CLIENT_SCAN_ONLY) {
1117              return startScanOnlyClientModeManager(requestorWs);
1118          } else {
1119              return false;
1120          }
1121      }

1097      /** 
1098       * Method to enable a new primary client mode manager in connect mode.
1099       */
1100      private boolean startPrimaryClientModeManager(WorkSource requestorWs) {
1101          Log.d(TAG, "Starting primary ClientModeManager in connect mode");
              // 主要是去构造一个clientModeManager,其实就是[ new ConcreteClientModeManager]
1102          ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(
1103                  new ClientListener(), requestorWs, ROLE_CLIENT_PRIMARY, mVerboseLoggingEnabled);
1104          mClientModeManagers.add(manager);
1105          mLastPrimaryClientModeManagerRequestorWs = requestorWs;
1106          return true;
1107      }

/packages/modules/Wifi/service/java/com/android/server/wifi/ConcreteClientModeManager.java
接着上面的地方看下new出来的:

151      ConcreteClientModeManager(
152              Context context, @NonNull Looper looper, Clock clock,
153              WifiNative wifiNative, @NonNull Listener<ConcreteClientModeManager> listener,
154              WifiMetrics wifiMetrics,
155              WakeupController wakeupController, WifiInjector wifiInjector,
156              SelfRecovery selfRecovery, WifiGlobals wifiGlobals,
157              DefaultClientModeManager defaultClientModeManager, long id,
158              @NonNull WorkSource requestorWs, @NonNull ClientRole role,
159              @NonNull ClientModeManagerBroadcastQueue broadcastQueue,
160              boolean verboseLoggingEnabled) {
161          mContext = context;
162          mClock = clock;
163          mWifiNative = wifiNative;
164          mModeListener = listener;
165          mWifiMetrics = wifiMetrics;
166          mWakeupController = wakeupController;
167          mWifiInjector = wifiInjector;
168          mStateMachine = new ClientModeStateMachine(looper);
169          mDeferStopHandler = new DeferStopHandler(looper);
170          mSelfRecovery = selfRecovery;
171          mWifiGlobals = wifiGlobals;
172          mDefaultClientModeManager = defaultClientModeManager;
173          mId = id;
174          mTargetRoleChangeInfo = new RoleChangeInfo(role, requestorWs, listener);
175          mBroadcastQueue = broadcastQueue;
176          enableVerboseLogging(verboseLoggingEnabled);
			 // 这个消息会在初始 mIdleState 状态里处理
177          mStateMachine.sendMessage(ClientModeStateMachine.CMD_START, mTargetRoleChangeInfo);  
178      }

825          private class IdleState extends State {
826              @Override
827              public void enter() {
828                  Log.d(getTag(), "entering IdleState");
829                  mClientInterfaceName = null;
830                  mIfaceIsUp = false;
831              }

843              @Override
844              public boolean processMessage(Message message) {
845                  switch (message.what) {
846                      case CMD_START:
847                          // Always start in scan mode first.
848                          RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
                             // 这个就是比较熟悉的方法了,去底层初始化加载驱动等,待会我们再看
849                          mClientInterfaceName = mWifiNative.setupInterfaceForClientInScanMode(
850                                  mWifiNativeInterfaceCallback, roleChangeInfo.requestorWs);
851                          if (TextUtils.isEmpty(mClientInterfaceName)) {
852                              Log.e(getTag(), "Failed to create ClientInterface. Sit in Idle");
853                              takeBugReportInterfaceFailureIfNeeded(
854                                      "Wi-Fi scan STA interface HAL failure");
855                              mModeListener.onStartFailure(ConcreteClientModeManager.this);
856                              break;
857                          }
858                          if (roleChangeInfo.role instanceof ClientConnectivityRole) {
859                              sendMessage(CMD_SWITCH_TO_CONNECT_MODE, roleChangeInfo);  ---> 发送消息
860                              transitionTo(mStartedState);   ---> 切换到这个状态里处理消息
861                          } else {
862                              mScanRoleChangeInfoToSetOnTransition = roleChangeInfo;
863                              transitionTo(mScanOnlyModeState);
864                          }
865                          break;

874          private class StartedState extends State {
892              public void enter() {
893                  Log.d(getTag(), "entering StartedState");
894                  mIfaceIsUp = false;
895                  mIsStopped = false;
896                  onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
897              }

899              @Override
900              public boolean processMessage(Message message) {
901                  switch (message.what) {
902                      case CMD_START:
903                          // Already started, ignore this command.
904                          break;
905                      case CMD_SWITCH_TO_CONNECT_MODE: {   ---> 处理这个消息
906                          RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
907                          updateConnectModeState(roleChangeInfo.role,   ---> 广播告知状态
908                                  WifiManager.WIFI_STATE_ENABLING,
909                                  WifiManager.WIFI_STATE_DISABLED);
910                          if (!mWifiNative.switchClientInterfaceToConnectivityMode(
911                                  mClientInterfaceName, roleChangeInfo.requestorWs)) {
912                              updateConnectModeState(roleChangeInfo.role,
913                                      WifiManager.WIFI_STATE_UNKNOWN,
914                                      WifiManager.WIFI_STATE_ENABLING);
915                              updateConnectModeState(roleChangeInfo.role,
916                                      WifiManager.WIFI_STATE_DISABLED,
917                                      WifiManager.WIFI_STATE_UNKNOWN);
918                              takeBugReportInterfaceFailureIfNeeded(
919                                      "Wi-Fi STA interface HAL failure");
920                              mModeListener.onStartFailure(ConcreteClientModeManager.this);
921                              break;
922                          }
923                          // Role set in the enter of ConnectModeState.
924                          mConnectRoleChangeInfoToSetOnTransition = roleChangeInfo;
925                          transitionTo(mConnectModeState);  ---> 转到这个状态机
926                          break;
927                      }

主要看下这个就知道广播的内容,新状态和当前的状态,这里有如下几个状态:
[ WIFI_STATE_DISABLING、 WIFI_STATE_DISABLED
WIFI_STATE_ENABLING、 WIFI_STATE_ENABLED
WIFI_STATE_UNKNOWN ]

657      private void updateConnectModeState(ClientRole role, int newState, int currentState) {
671          final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
672          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
673          intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
674          intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
675          String summary = "broadcast=WIFI_STATE_CHANGED_ACTION"
676                  + " EXTRA_WIFI_STATE=" + newState
677                  + " EXTRA_PREVIOUS_WIFI_STATE=" + currentState;
678          if (mVerboseLoggingEnabled) Log.d(getTag(), "Queuing " + summary);
}

接下来看下 状态机切换到:ConnectModeState 中:

1031      private class ConnectModeState extends State {
1032              @Override
1033              public void enter() {
1034                  Log.d(getTag(), "entering ConnectModeState, starting ClientModeImpl");
1035                  if (mClientInterfaceName == null) {
1036                      Log.e(getTag(), "Supposed to start ClientModeImpl, but iface is null!");
1037                  } else {
1038                      if (mClientModeImpl != null) {
1039                          Log.e(getTag(), "ConnectModeState.enter(): mClientModeImpl is already "
1040                                  + "instantiated?!");
1041                      }
1042                      mClientModeImpl = mWifiInjector.makeClientModeImpl(
1043                              mClientInterfaceName, ConcreteClientModeManager.this,
1044                              mVerboseLoggingEnabled);
1045                      mClientModeImpl.setShouldReduceNetworkScore(mShouldReduceNetworkScore);
1046                  }
1047                  if (mConnectRoleChangeInfoToSetOnTransition == null
1048                          || !(mConnectRoleChangeInfoToSetOnTransition.role
1049                          instanceof ClientConnectivityRole)) {
1050                      Log.wtf(TAG, "Unexpected mConnectRoleChangeInfoToSetOnTransition: "
1051                              + mConnectRoleChangeInfoToSetOnTransition);
1052                      // Should never happen, but fallback to primary to avoid a crash.
1053                      mConnectRoleChangeInfoToSetOnTransition =
1054                              new RoleChangeInfo(ROLE_CLIENT_PRIMARY);
1055                  }
1056                  // 成功后更新
1057                  // Could be any one of possible connect mode roles.
1058                  setRoleInternalAndInvokeCallback(mConnectRoleChangeInfoToSetOnTransition);
1059                  updateConnectModeState(mConnectRoleChangeInfoToSetOnTransition.role,
1060                          WIFI_STATE_ENABLED, WIFI_STATE_ENABLING);
1061              }
1062  

至此第一部分暂时记录到这里,接下来会记录 上面提到的 wifiNative到底层。

<think>我们正在处理个关于在ESP8266-01S上实现多个功能的请求。根据用户需求,我们需要实现: 1. Wi-Fi配网(带强制门户Captive Portal) 2. LED状态指示(不同状态不同闪烁模式) 3. OTA升级 4. 设备密钥验证 5. Blinker语音控制 6. 配网界面设计 由于ESP8266-01S只有有限的GPIO(通常只有两个可用的GPIO:GPIO0和GPIO2),我们需要合理分配资源。同时,实现这些功能需要结合多个库和技巧。 ### 步骤概述 1. **使用Arduino框架和ESP8266核心库**:这是最常用的开发方式。 2. **使用WiFiManager库实现Captive Portal**:该库可以简化强制门户的实现,并支持保存WiFi密码和自动重连。 3. **设计LED状态指示**:使用GPIO2(板载LED)来指示不同状态(如快闪表示配网中,慢闪表示连接中,常亮表示已连接等)。 4. **集成OTA升级**:使用ArduinoOTA库。 5. **设备密钥验证**:可以在配网过程中要求输入设备密钥(在WiFiManager中设置自定义参数),并在连接服务器时验证。 6. **Blinker语音控制**:需要集成Blinker库,并实现语音控制功能(可能需要额外的语音识别服务,这里我们假设使用Blinker提供的语音控制功能)。 7. **配网界面设计**:WiFiManager允许自定义界面,我们可以修改HTML内容来美化界面。 ### 硬件准备 - ESP8266-01S模块 - 个LED(如果使用板载LED,则GPIO2已连接,否则需要外接) - 可能需要个USB转TTL模块进行烧录 ### 代码结构 我们将使用以下库(需要提前安装): - WiFiManager(用于配网) - ArduinoOTA(用于OTA升级) - Blinker(用于语音控制等) 注意:ESP8266-01S的GPIO0和GPIO2在启动时需要处于正确状态(GPIO0拉高,GPIO2拉高),因此我们使用GPIO2作为LED输出,但要避免在启动时影响其状态(通常板载LED在GPIO2,上电时为低电平会点亮,但启动后我们可以控制)。 ### 实现细节 #### 1. 初始化设置 - 设置串口通信。 - 初始化LED引脚(GPIO2)。 - 初始化WiFiManager。 - 设置OTA。 - 设置Blinker。 #### 2. WiFiManager配置 - 设置自定义参数(例如设备密钥)。 - 设置AP名称和超时时间。 - 设置强制门户(Captive Portal)功能(WiFiManager内置支持)。 - 保存WiFi凭证到Flash,支持自动重连。 #### 3. LED状态指示 - 定义不同的闪烁模式(配网模式、连接模式、已连接、OTA升级中等)。 - 使用非阻塞方式(millis)控制LED状态,避免使用delay。 #### 4. OTA升级 - 在setup中初始化ArduinoOTA,设置密码(可选)。 - 在loop中处理OTA请求。 #### 5. 设备密钥验证 -WiFiManager的自定义参数中获取用户输入的设备密钥。 - 在连接Blinker服务器时使用该密钥进行验证。 #### 6. Blinker语音控制 - 初始化Blinker,使用设备密钥(从WiFiManager获取)进行认证。 - 注册语音控制回调函数。 #### 7. 配网界面设计 - 使用WiFiManager的setCustomHeadElement和setCustomHeadElement等方法修改配网页面。 ### 代码示例 以下是个简化的示例代码框架,具体实现需要根据实际情况调整。 ```cpp #include <ESP8266WiFi.h> #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> // 安装版本:0.16.0 #include <ArduinoOTA.h> #include <Blinker.h> // 定义LED引脚 #define LED_PIN 2 // GPIO2,板载LED(低电平点亮) // 全局变量 WiFiManager wifiManager; char blinkerKey[34] = "default"; // 设备密钥,34字节足够存储32字符密钥+结束符 // LED状态变量 unsigned long previousMillis = 0; int ledState = LOW; int ledMode = 0; // 0:常关, 1:慢闪(连接中), 2:快闪(配网中), 3:常亮(已连接) void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); // 初始关闭LED(因为低电平点亮,所以高电平关闭) // 设置LED为快闪模式(配网中) setLEDMode(2); // WiFiManager配置 WiFiManagerParameter custom_blinker_key("key", "Blinker设备密钥", blinkerKey, 34); wifiManager.addParameter(&custom_blinker_key); wifiManager.setTimeout(180); // 3分钟超时 wifiManager.setAPCallback([](WiFiManager *myWiFiManager) { Serial.println("进入配网模式"); setLEDMode(2); // 快闪 }); // 尝试连接已保存的WiFi或启动配置AP if (!wifiManager.autoConnect("ESP8266-01S_AP")) { Serial.println("配网超时,重启设备"); delay(3000); ESP.restart(); } // 连接成功,获取自定义参数 strcpy(blinkerKey, custom_blinker_key.getValue()); Serial.print("获取到的Blinker密钥: "); Serial.println(blinkerKey); // 设置LED为慢闪(连接中) setLEDMode(1); // 初始化OTA ArduinoOTA.setHostname("ESP8266-01S"); ArduinoOTA.setPassword("admin"); // 设置OTA密码 ArduinoOTA.begin(); // 初始化Blinker Blinker.begin(blinkerKey, WiFi.SSID().c_str(), WiFi.psk().c_str()); Blinker.attachVoiceControl(voiceControlCallback); // 注册语音控制回调 // 连接成功,LED常亮 setLEDMode(3); } void loop() { // 处理OTA ArduinoOTA.handle(); // 处理Blinker Blinker.run(); // 处理LED状态 handleLED(); } // 设置LED模式 void setLEDMode(int mode) { ledMode = mode; if (mode == 0) { digitalWrite(LED_PIN, HIGH); // 关 } else if (mode == 3) { digitalWrite(LED_PIN, LOW); // 开(常亮) } // 对于闪烁模式,在handleLED中处理 } // 处理LED闪烁(非阻塞) void handleLED() { unsigned long currentMillis = millis(); if (ledMode == 1) { // 慢闪:1秒周期 if (currentMillis - previousMillis >= 500) { previousMillis = currentMillis; ledState = !ledState; digitalWrite(LED_PIN, ledState ? HIGH : LOW); // 注意:低电平点亮 } } else if (ledMode == 2) { // 快闪:250ms周期 if (currentMillis - previousMillis >= 125) { previousMillis = currentMillis; ledState = !ledState; digitalWrite(LED_PIN, ledState ? HIGH : LOW); } } } // Blinker语音控制回调 void voiceControlCallback(uint8_t cmd) { // 根据语音指令执行操作 // 例如:开关灯等 } ``` ### 注意事项 1. **GPIO限制**:ESP8266-01S只有两个GPIO(GPIO0和GPIO2),GPIO0通常用于烧录模式选择,GPIO2用于LED。因此,如果需要控制其他设备,可能需要使用其他方法(如软件模拟或使用串口)。 2. **内存限制**:ESP8266-01S只有1MB Flash和较少的内存,同时运行多个功能(特别是WiFiManager和Blinker)可能会导致内存不足。需要优化代码,避免使用大缓冲区。 3. **OTA升级**:由于内存限制,OTA升级时可能会失败,建议在升级过程中保持设备稳定供电,并减少其他操作。 4. **Blinker密钥**:用户需要在配网界面输入Blinker密钥,该密钥在Blinker App中获取。 5. **配网界面定制**:如果需要美化配网界面,可以使用`wifiManager.setCustomHeadElement`添加自定义CSS和HTML。 ### 配网流程 1. 设备启动后,如果没有保存的WiFi凭证,则进入配网模式(AP模式,SSID为"ESP8266-01S_AP")。 2. 用户连接该AP,手机会自动弹出强制门户(或手动打开浏览器访问任意网址)。 3. 在配网界面,用户选择家庭WiFi并输入密码,同时输入Blinker设备密钥。 4. 设备尝试连接WiFi,成功后保存凭证,并显示连接成功。 5. 设备连接Blinker服务器,准备接收指令。 ### 相关优化 - **自动重连**:WiFiManager在连接断开时会尝试重连(通过`autoConnect`实现)。 - **LED状态指示**:不同状态用不同闪烁模式表示,用户可直观了解设备状态。 - **设备密钥**:在配网时输入,保存到Flash,每次启动时读取。 ### 可能遇到的问题 1. **强制门户不弹出**:某些手机需要手动打开浏览器访问任意网址才能弹出。 2. **OTA失败**:确保设备与路由器在同网络,且内存足够。 3. **Blinker连接失败**:检查密钥是否正确,网络是否畅通。 ### 总结 以上代码框架实现了用户所需的主要功能。由于ESP8266-01S资源有限,建议逐步测试每个功能,确保稳定性。 ### 相关问题 1. 如何为ESP8266-01S增加更多的GPIO控制功能? 2. 在内存有限的设备上如何优化代码以避免崩溃? 3. 如何为WiFiManager设计更友好的配网界面? 4. Blinker语音控制支持哪些具体指令,如何扩展?</think>基于您的需求,我将为ESP8266-01S实现完整的Wi-Fi配网解决方案,包含强制门户、LED状态指示、OTA升级等所有功能。以下是完整实现方案: ### 、系统架构设计 ```mermaid graph TD A[设备启动] --> B{WiFi连接} B -- 成功 --> C[正常工作模式] B -- 失败 --> D[强制门户模式] D --> E[配网界面] E --> F[保存凭证] C --> G[LED状态指示] C --> H[OTA升级] C --> I[Blinker控制] F --> B ``` ### 二、核心代码实现 ```cpp #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <DNSServer.h> #include <WiFiClient.h> #include <EEPROM.h> #include <ArduinoOTA.h> #include <Blinker.h> #define LED_PIN 2 // ESP8266-01S板载LED #define AP_SSID "ESP8266-01S_Config" const byte DNS_PORT = 53; DNSServer dnsServer; ESP8266WebServer webServer(80); // 设备密钥存储结构 struct Config { char ssid[32]; char password[64]; char deviceKey[32]; bool configured; }; Config config; void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); EEPROM.begin(sizeof(Config)); loadConfig(); if(!config.configured) { startCaptivePortal(); } else { connectToWiFi(); } } void loop() { if(config.configured) { ArduinoOTA.handle(); Blinker.run(); // 主业务逻辑 } else { dnsServer.processNextRequest(); webServer.handleClient(); updateLEDStatus(); // LED状态指示 } } // 强制门户实现 void startCaptivePortal() { WiFi.softAP(AP_SSID); dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); webServer.on("/", HTTP_GET, [](){ webServer.send(200, "text/html", generatePortalPage()); }); webServer.on("/connect", HTTP_POST, [](){ strncpy(config.ssid, webServer.arg("ssid").c_str(), 32); strncpy(config.password, webServer.arg("password").c_str(), 64); strncpy(config.deviceKey, webServer.arg("deviceKey").c_str(), 32); config.configured = true; saveConfig(); webServer.send(200, "text/plain", "配置已保存,设备将重启"); ESP.restart(); }); webServer.onNotFound([](){ webServer.sendHeader("Location", "http://" + webServer.client().localIP().toString(), true); webServer.send(302, "text/plain", ""); }); webServer.begin(); } // LED状态指示 void updateLEDStatus() { static unsigned long lastChange = 0; static bool ledState = false; if(millis() - lastChange > (config.configured ? 1000 : 200)) { ledState = !ledState; digitalWrite(LED_PIN, ledState); lastChange = millis(); } } // OTA升级设置 void setupOTA() { ArduinoOTA.setHostname("ESP8266-01S"); ArduinoOTA.setPassword(config.deviceKey); // 使用设备密钥作为OTA密码 ArduinoOTA.onStart([]() { digitalWrite(LED_PIN, LOW); // 升级开始常亮 }); ArduinoOTA.begin(); } // Blinker初始化 void setupBlinker() { Blinker.begin(config.deviceKey, WiFi.SSID().c_str(), WiFi.psk().c_str()); Blinker.attachVoiceControl(voiceControlCallback); } void voiceControlCallback(uint8_t cmd) { // 语音控制处理逻辑 digitalWrite(LED_PIN, cmd ? LOW : HIGH); } // 配置保存/加载 void saveConfig() { EEPROM.put(0, config); EEPROM.commit(); } void loadConfig() { EEPROM.get(0, config); if(config.configured != true) { memset(&config, 0, sizeof(Config)); } } // 配网界面HTML生成 String generatePortalPage() { return R"( <!DOCTYPE html><html><head> <meta name="viewport" content="width=device-width,initial-scale=1"> <style>body{font-family:Arial,sans-serif;margin:40px auto;max-width:800px;} input,button{padding:10px;margin:5px 0;width:100%;} button{background:#0066cc;color:white;border:none;}</style> </head><body> <h1>设备配网</h1> <form action="/connect" method="post"> <input type="text" name="ssid" placeholder="WiFi名称" required> <input type="password" name="password" placeholder="WiFi密码" required> <input type="text" name="deviceKey" placeholder="设备密钥" required> <button type="submit">连接</button> </form></body></html> )"; } ``` ### 三、关键功能实现说明 1. **强制门户(Captive Portal)** - 使用DNS劫持技术(`DNSServer`),将所有域名解析指向设备IP - 当用户连接设备AP后,任何网页请求都会重定向到配网界面 - 支持自动弹出功能(iOS/Android兼容) 2. **LED状态指示** - 配网模式:快速闪烁(200ms间隔) - 连接中:慢速闪烁(1s间隔) - 正常工作:常亮 - OTA升级:常亮直到完成 3. **OTA升级安全** ```cpp ArduinoOTA.setPassword(config.deviceKey); // 使用设备密钥作为OTA密码 ``` - 验证通过后才允许固件上传 - 防止未授权设备更新固件[^1] 4. **Blinker语音控制集成** ```cpp Blinker.attachVoiceControl(voiceControlCallback); ``` - 支持中文语音指令识别 - 通过回调函数执行设备控制 5. **配网界面优化** - 响应式设计适配移动设备 - 表单验证确保必填字段 - 简洁直观的用户引导流程 ### 四、部署流程 1. **硬件连接** - ESP8266-01S的GPIO2连接LED(串联220Ω电阻) - USB转TTL模块连接TX/RX/GND 2. **烧录初始固件** ```bash pio run --target upload ``` 3. **配网流程** 1. 手机连接`ESP8266-01S_Config`热点 2. 浏览器自动弹出配网界面(或手动访问任意网址) 3. 填写WiFi凭证和设备密钥 4. 设备自动重启并连接网络 4. **OTA更新测试** ```bash curl -F "firmware=@firmware.bin" http://device-ip/update ``` ### 五、优化建议 1. **安全增强** - 添加配网界面CSRF保护 - 实现HTTPS(需使用ESP8266-01S的AT固件) - 定期轮换设备密钥 2. **内存优化** - 使用`PROGMEM`存储HTML模板 - 分块传输配网页面 - 禁用未使用的库功能 3. **用户体验** - 添加配网进度页面 - 实现WiFi信号强度扫描 -语言支持界面 > **注意**:ESP8266-01S只有1MB Flash,需使用`Minimal SPIFFS`分区方案,并启用`-Os`优化选项[^3]。 ### 六、故障排除 | 现象 | 可能原因 | 解决方法 | |------|----------|----------| | 无法弹出门户 | 手机安全限制 | 手动访问http://192.168.4.1 | | OTA失败 | 内存不足 | 关闭其他功能后重试 | | 频繁重启 | 密钥验证失败 | 检查设备密钥致性 | | Blinker连接超时 | 网络问题 | 检查服务器状态和防火墙 | [^1]: ESP32的多任务处理能力和Wi-Fi通信潜力 [^2]: 通过简单的配置实现第二串口的数据监听 [^3]: 注意事项:id和name要用英文,不要用中文
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值