WiFi toggled on/off

本文深入剖析了从设置打开Wi-Fi后,设备系统中框架的所有流程及状态转换,包括如何通过`WiFiManager.setWifiEnabled(boolean enabled)`函数控制Wi-Fi开关。详细分析了流程图中的关键步骤,如设置Wi-Fi启用、恢复开机前Wi-Fi状态、处理Wi-Fi开启与关闭的逻辑、与wpa_supplicant的通信等。同时,文章也介绍了Wi-Fi关闭流程,包括状态机的转换和与wpa_supplicant的断开连接过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一章来分析一下从Settings打开wifi后,framework所有的流程以及状态转换,调用的主要函数就是WiFiMananger的setWifiEnabled(boolean enabled),这个函数在前面介绍的WifiService启动流程中也有提到,当SystemServer 创建了个WifiService后,就会调用它的checkAndStartWifi,在这个函数里面,也同样会调用到setWifiEnabled(boolean enabled),这是为了恢复用户在开机之前的wifi状态。下面来详细分析WifiMananger的setWifiEnabled函数。先来看看总体的流程图


对照上面的流程图,我们从WifiMananger的setWifiEnabled来分析一下代码:

  1. public boolean setWifiEnabled(boolean enabled) {  
  2.     try {  
  3.         return mService.setWifiEnabled(enabled);  
  4.     } catch (RemoteException e) {  
  5.         return false;  
  6.     }  
  7. }  
  1. WifiService.java  
  2.     public synchronized boolean setWifiEnabled(boolean enable) {  
  3.         enforceChangePermission();  
  4.         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()  
  5.                     + ", uid=" + Binder.getCallingUid());  
  6.         if (DBG) {  
  7.             Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");  
  8.         }  
  9.   
  10.         /* 
  11.         * Caller might not have WRITE_SECURE_SETTINGS, 
  12.         * only CHANGE_WIFI_STATE is enforced 
  13.         */  
  14.   
  15.         long ident = Binder.clearCallingIdentity();  
  16.         try {  
  17.             if (! mSettingsStore.handleWifiToggled(enable)) {  
  18.                 // Nothing to do if wifi cannot be toggled  
  19.                 return true;  
  20.             }  
  21.         } finally {  
  22.             Binder.restoreCallingIdentity(ident);  
  23.         }  
  24.   
  25.         mWifiController.sendMessage(CMD_WIFI_TOGGLED);  
  26.         return true;  
  27.     }  

在WifiService里面,先把传进来的参数写到WifiSettingsStore里面,WifiSettingsStore调用Settings提供的ContentProviders写到Sqlite的DB里面,所以我们看到mWifiController.sendMessage(CMD_WIFI_TOGGLED)是没有把这个参数传递过去的。接着去看WifiController里面如何处理CMD_WIFI_TOGGLED,由前面的WifiService启动流程分析,我们知道CMD_WIFI_TOGGLED将由WifiController的ApStaDisabledState来处理:
  1. class ApStaDisabledState extends State {  
  2.     private int mDeferredEnableSerialNumber = 0;  
  3.     private boolean mHaveDeferredEnable = false;  
  4.     private long mDisabledTimestamp;  
  5.   
  6.     @Override  
  7.     public void enter() {  
  8.         mWifiStateMachine.setSupplicantRunning(false);  
  9.         // Supplicant can't restart right away, so not the time we switched off  
  10.         mDisabledTimestamp = SystemClock.elapsedRealtime();  
  11.         mDeferredEnableSerialNumber++;  
  12.         mHaveDeferredEnable = false;  
  13.     }  
  14.     @Override  
  15.     public boolean processMessage(Message msg) {  
  16.         switch (msg.what) {  
  17.             case CMD_WIFI_TOGGLED:  
  18.             case CMD_AIRPLANE_TOGGLED:  
  19.                 if (mSettingsStore.isWifiToggleEnabled()) {  
  20.                     if (doDeferEnable(msg)) {  
  21.                         if (mHaveDeferredEnable) {  
  22.                             //  have 2 toggles now, inc serial number an ignore both  
  23.                             mDeferredEnableSerialNumber++;  
  24.                         }  
  25.                         mHaveDeferredEnable = !mHaveDeferredEnable;  
  26.                         break;  
  27.                     }  
  28.                     if (mDeviceIdle == false) {  
  29.                         transitionTo(mDeviceActiveState);  
  30.                     } else {  
  31.                         checkLocksAndTransitionWhenDeviceIdle();  
  32.                     }  
  33.                 }  
  34.                 break;  

WifiController的ApStaDisabledState的处理很简单,只是简单的transition到DeviceActiveState,因为DeviceActiveState的父State是StaEnabledState,由StateMachine的知识,我们先到StaEnabledState和DeviceActiveState的enter()函数来看看:

  1. class StaEnabledState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.         mWifiStateMachine.setSupplicantRunning(true);  
  5.     }  
  6.   
  7. class DeviceActiveState extends State {  
  8.     @Override  
  9.     public void enter() {  
  10.         mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);  
  11.         mWifiStateMachine.setDriverStart(true);  
  12.         mWifiStateMachine.setHighPerfModeEnabled(false);  
  13.     }  

上面分别是调用WifiStateMachine的四个函数,这四个函数都是给WifiStateMachine发送四个消息,分别是CMD_START_SUPPLICANT、SET_OPERATIONAL_MODE、CMD_START_DRIVER和SET_HTGH_PERF_MODE,如上图中的Figure 1所示。接着我们到WifiStateMachine的InitialState中去看看如何处理CMD_START_SUPPLICANT:

  1. public boolean processMessage(Message message) {  
  2.     switch (message.what) {  
  3.         case CMD_START_SUPPLICANT:  
  4.             if (mWifiNative.loadDriver()) {  
  5.                 try {  
  6.                     mNwService.wifiFirmwareReload(mInterfaceName, "STA");  
  7.                 } catch (Exception e) {  
  8.                     loge("Failed to reload STA firmware " + e);  
  9.                     // continue  
  10.                 }  
  11.   
  12.                 try {  
  13.                     mNwService.disableIpv6(mInterfaceName);  
  14.                 } catch (RemoteException re) {  
  15.                     loge("Unable to change interface settings: " + re);  
  16.                 } catch (IllegalStateException ie) {  
  17.                     loge("Unable to change interface settings: " + ie);  
  18.                 }  
  19.   
  20.                 mWifiMonitor.killSupplicant(mP2pSupported);  
  21.                 if(mWifiNative.startSupplicant(mP2pSupported)) {  
  22.                     setWifiState(WIFI_STATE_ENABLING);  
  23.                     if (DBG) log("Supplicant start successful");  
  24.                     mWifiMonitor.startMonitoring();  
  25.                     transitionTo(mSupplicantStartingState);  
  26.                 } else {  
  27.                     loge("Failed to start supplicant!");  
  28.                 }  
  29.             } else {  
  30.                 loge("Failed to load driver");  
  31.             }  
  32.             break;  

这里主要调用WifiNative的loadDriver和startSupplicant两个函数去加载wifi driver和启动wpa_supplicant,当启动成功wpa_supplicant后,就会调用WifiMonitor的startMonitoring去和wpa_supplicant建立socket连接,并不断的从wpa_supplicant收event。wpa_supplicant是一个独立的运行程序,它和应用程序之间通过socket来通信,主要存在两个socket连接,一个用来向wpa_supplicant发送命令,另一个是wpa_supplicant用来向应用程序通知event,应用程序在收到event后可以知道当前的连接状态来进行下一步动作。我们进入到WifiMonitor.startMonitoring这个函数看看:
  1. public synchronized void startMonitoring(String iface) {  
  2.     WifiMonitor m = mIfaceMap.get(iface);  
  3.     if (m == null) {  
  4.         Log.e(TAG, "startMonitor called with unknown iface=" + iface);  
  5.         return;  
  6.     }  
  7.   
  8.     Log.d(TAG, "startMonitoring(" + iface + ") with mConnected = " + mConnected);  
  9.   
  10.     if (mConnected) {  
  11.         m.mMonitoring = true;  
  12.         m.mWifiStateMachine.sendMessage(SUP_CONNECTION_EVENT);  
  13.     } else {  
  14.         if (DBG) Log.d(TAG, "connecting to supplicant");  
  15.         int connectTries = 0;  
  16.         while (true) {  
  17.             if (mWifiNative.connectToSupplicant()) {  
  18.                 m.mMonitoring = true;  
  19.                 m.mWifiStateMachine.sendMessage(SUP_CONNECTION_EVENT);  
  20.                 new MonitorThread(mWifiNative, this).start();  
  21.                 mConnected = true;  
  22.                 break;  
  23.             }  
  24.             if (connectTries++ < 5) {  
  25.                 try {  
  26.                     Thread.sleep(1000);  
  27.                 } catch (InterruptedException ignore) {  
  28.                 }  
  29.             } else {  
  30.                 mIfaceMap.remove(iface);  
  31.                 m.mWifiStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);  
  32.                 Log.e(TAG, "startMonitoring(" + iface + ") failed!");  
  33.                 break;  
  34.             }  
  35.         }  
  36.     }  
  37. }  

这个方法里面主要调用WifiNative的connenctToSupplicant去和wpa_supplicant建立socket连接,然后给WifiStateMachine发送一个SUP_CONNECTION_EVENT消息,最后新建一个MonitorThread运行,MonitorThread就是一个循环,不断的从wpa_supplicant收event,然后进行解析,并dispatch到不同的函数去处理,后面我们再来分析MonitorThread的流程。回到WifiStateMachine的InitialState中去看看如何处理CMD_START_SUPPLICANT的流程中来,当startMonitoring结束后,WifiStateMachine就跳转到SupplicantStartingState,这时的WifiStateMachine和MessageQueue里面的消息队列上图中的Figure 2。

接着来看当WifiStateMachine处理完SUP_CONNECTION_EVENT消息后,马上会收到SET_OPERATIONAL_MODE和CMD_START_DRIVER消息,这两个消息都会被SupplicantStartingState延迟处理,SET_HTGH_PERF_MODE会被DefaultState处理。接着SupplicantStartingState会收到SUP_CONNECTION_EVENT,处理代码如下:

  1. public boolean processMessage(Message message) {  
  2.     switch(message.what) {  
  3.         case WifiMonitor.SUP_CONNECTION_EVENT:  
  4.             if (DBG) log("Supplicant connection established");  
  5.             setWifiState(WIFI_STATE_ENABLED);  
  6.             mSupplicantRestartCount = 0;  
  7.             /* Reset the supplicant state to indicate the supplicant 
  8.              * state is not known at this time */  
  9.             mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);  
  10.             /* Initialize data structures */  
  11.             mLastBssid = null;  
  12.             mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;  
  13.             mLastSignalLevel = -1;  
  14.   
  15.             mWifiInfo.setMacAddress(mWifiNative.getMacAddress());  
  16.             mWifiConfigStore.loadAndEnableAllNetworks();  
  17.             initializeWpsDetails();  
  18.   
  19.             sendSupplicantConnectionChangedBroadcast(true);  
  20.             transitionTo(mDriverStartedState);  
  21.             break;  

在SUP_CONNECTION_EVENT的处理流程中,主要是调用WifiConfigStore的loadAndEnableAllNetworks函数来加载并enable用户之前连接过并保存的AP,然后会初始化一些Wps相关的信息,最后transition到DriverStartedState上,如上图的Figure 3。再来看DriverStartedState的enter函数,这里面有一些重要的流程:


  1. class DriverStartedState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.   
  5.         /* set country code */  
  6.         setCountryCode();  
  7.         /* set frequency band of operation */  
  8.         setFrequencyBand();  
  9.         /* initialize network state */  
  10.         setNetworkDetailedState(DetailedState.DISCONNECTED);  
  11.   
  12.         mDhcpActive = false;  
  13.   
  14.         startBatchedScan();  
  15.   
  16.         if (mOperationalMode != CONNECT_MODE) {  
  17.             mWifiNative.disconnect();  
  18.             mWifiConfigStore.disableAllNetworks();  
  19.             if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {  
  20.                 setWifiState(WIFI_STATE_DISABLED);  
  21.             }  
  22.             transitionTo(mScanModeState);  
  23.         } else {  
  24.             /* Driver stop may have disabled networks, enable right after start */  
  25.             mWifiConfigStore.enableAllNetworks();  
  26.   
  27.             if (DBG) log("Attempting to reconnect to wifi network ..");  
  28.             mWifiNative.reconnect();  
  29.   
  30.             // Status pulls in the current supplicant state and network connection state  
  31.             // events over the monitor connection. This helps framework sync up with  
  32.             // current supplicant state  
  33.             mWifiNative.status();  
  34.             transitionTo(mDisconnectedState);  
  35.         }  
  1.   
  1.             if (mP2pSupported) {  
  2.                 if (mOperationalMode == CONNECT_MODE) {  
  3.                     mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);  
  4.                 } else {  
  5.                     // P2P statemachine starts in disabled state, and is not enabled until  
  6.                     // CMD_ENABLE_P2P is sent from here; so, nothing needs to be done to  
  7.                     // keep it disabled.  
  8.                 }  
  9.             }  
  10.   
  11.         }  

enter函数的代码比较多,上面是精简后的代码,上面主要分为两条分支,一是OperationalMode != CONNECT_MODE,一种是OperationalMode = CONNECT_MODE,根据官方的解释,OperationalMode一共有三种,分别如下:

1.CONNECT_MODE,这种模式下,STA可以scan并连接热点

2.SCAN_ONLY_MODE,这种模式下,STA只能扫描热点

3.SCAN_ONLY_WIFI_OFF_MODE,在这种模式下,当wifi是toggle off的情况下,也可以进行scan

这三种模式默认的是CONNECT_MODE,后面两种模式现在用到的不多,但按照Google的设计,后面可能会有很多的app会用到,比如利用热点来进行点位,这个应用其实这需要能够scan,并不需要链接热点。那我们接看这OperationalMode = CONNECT_MODE的流程,它直接transition 到DisconnectedState,如上图中的Figure 4,在transition到DisconnectedState之前,还会向WifiNative下reconnect的命令,用于重新连接上次连接但没有forget的AP,即开机后自动连上AP。如平台支持P2P,这里还将会给WifiP2pService发送CMD_ENABLE_P2P的消息,以后在学习P2P模块的时候再来分析。

到这里,Wifi toggle on的流程就分析完了。



WiFi toggled off流程分析

和Wifi toggle on的流程一样,当用户从Settings关闭wifi时,会调用WiFiMananger的 setWifiEnabled (false ),接着WifiService会给WifiController发送一个CMD_WIFI_TOGGLED消息,由前面启动Wifi的流程来看,StaEabledState会处理这个消息,先看看整体的流程图:


进入到StaEabledState的processMessage来看看代码流程:
  1. public boolean processMessage(Message msg) {  
  2.     switch (msg.what) {  
  3.         case CMD_WIFI_TOGGLED:  
  4.             if (! mSettingsStore.isWifiToggleEnabled()) {  
  5.                 if (mSettingsStore.isScanAlwaysAvailable()) {  
  6.                     transitionTo(mStaDisabledWithScanState);  
  7.                 } else {  
  8.                     transitionTo(mApStaDisabledState);  
  9.                 }  
  10.             }  
  11.             break;  

WifiController会transition 到ApStaDisabledState中,这样WifiController这个状态机就回到的最开始的初始状态了,进到ApStaDisabledState的enter函数分析如何对WifiStateMachine这个状态机做处理:

  1. class ApStaDisabledState extends State {  
  2.     private int mDeferredEnableSerialNumber = 0;  
  3.     private boolean mHaveDeferredEnable = false;  
  4.     private long mDisabledTimestamp;  
  5.   
  6.     @Override  
  7.     public void enter() {  
  8.         mWifiStateMachine.setSupplicantRunning(false);  
  9.         // Supplicant can't restart right away, so not the time we switched off  
  10.         mDisabledTimestamp = SystemClock.elapsedRealtime();  
  11.         mDeferredEnableSerialNumber++;  
  12.         mHaveDeferredEnable = false;  
  13.     }  

这里主要调用WifiStateMachine的setSupplicantRunning(false),这个函数直接给WifiStateMachine发送一个CMD_STOP_SUPPLICAN消息,由前面的知识,我们知道SupplicantStartedState会处理这个消息,进到具体代码中分析:

  1. public boolean processMessage(Message message) {  
  2.     switch(message.what) {  
  3.         case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */  
  4.             if (mP2pSupported) {  
  5.                 transitionTo(mWaitForP2pDisableState);  
  6.             } else {  
  7.                 transitionTo(mSupplicantStoppingState);  
  8.             }  
  9.             break;  

如果平台支持P2P,这里会跳转到WaitForP2pDisableState中,如果不支持则会跳转到SupplicantStoppingState中。这里我们看mP2pSupported为true的情况,因为现在大多数平台都应该支持P2P了,另外,在WaitForP2pDisableState中处理完P2P相关的内容后,也会跳转到SupplicantStoppingState中来。我们来分析WaitForP2pDisableState的enter函数:
  1. class WaitForP2pDisableState extends State {  
  2.     private State mTransitionToState;  
  3.     @Override  
  4.     public void enter() {  
  5.         switch (getCurrentMessage().what) {  
  6.             case WifiMonitor.SUP_DISCONNECTION_EVENT:  
  7.                 mTransitionToState = mInitialState;  
  8.                 break;  
  9.             case CMD_DELAYED_STOP_DRIVER:  
  10.                 mTransitionToState = mDriverStoppingState;  
  11.                 break;  
  12.             case CMD_STOP_SUPPLICANT:  
  13.                 mTransitionToState = mSupplicantStoppingState;  
  14.                 break;  
  15.             default:  
  16.                 mTransitionToState = mDriverStoppingState;  
  17.                 break;  
  18.         }  
  19.         mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);  
  20.     }  
  21.     @Override  
  22.     public boolean processMessage(Message message) {  
  23.         switch(message.what) {  
  24.             case WifiStateMachine.CMD_DISABLE_P2P_RSP:  
  25.                 transitionTo(mTransitionToState);  
  26.                 break;  

mTransitionToState根据当前处理消息的不同种类,记录下次将要跳转到的State信息,然后给WifiP2pService发送一个CMD_DISABLE_P2P_REQ消息,P2P的内容后面再来分析,当P2P处理完CMD_DISABLE_P2P_REQ后,就会给WifiStateMachine发送一个CMD_DISABLE_P2P_RSP消息,在WaitForP2pDisableState收到这个消息后,就会跳转到SupplicantStoppingState中,再到这个函数的enter去看看:
  1. class SupplicantStoppingState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.         /* Send any reset commands to supplicant before shutting it down */  
  5.         handleNetworkDisconnect();  
  6.         if (mDhcpStateMachine != null) {  
  7.             mDhcpStateMachine.doQuit();  
  8.         }  
  9.   
  10.         if (DBG) log("stopping supplicant");  
  11.         mWifiMonitor.stopSupplicant();  
  12.   
  13.         /* Send ourselves a delayed message to indicate failure after a wait time */  
  14.         sendMessageDelayed(obtainMessage(CMD_STOP_SUPPLICANT_FAILED,  
  15.                 ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS);  
  16.         setWifiState(WIFI_STATE_DISABLING);  
  17.         mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);  
  18.     }  
  19.     @Override  
  20.     public boolean processMessage(Message message) {  
  21.         switch(message.what) {  
  22.             case WifiMonitor.SUP_CONNECTION_EVENT:  
  23.                 loge("Supplicant connection received while stopping");  
  24.                 break;  
  25.             case WifiMonitor.SUP_DISCONNECTION_EVENT:  
  26.                 if (DBG) log("Supplicant connection lost");  
  27.                 handleSupplicantConnectionLoss();  
  28.                 transitionTo(mInitialState);  
  29.                 break;  
  30.             case CMD_STOP_SUPPLICANT_FAILED:  
  31.                 if (message.arg1 == mSupplicantStopFailureToken) {  
  32.                     loge("Timed out on a supplicant stop, kill and proceed");  
  33.                     handleSupplicantConnectionLoss();  
  34.                     transitionTo(mInitialState);  
  35.                 }  
  36.                 break;  

在SupplicantStoppingState主要调用handleNetworkDisconnect和stopSupplicant函数,handleNetworkDisconnect主要工作是停掉stopDhcp和clear一些状态信息;WifiMonitor的stopSupplicant用于停掉wpa_supplicant,就是向wpa_supplicant发送一个TERMINATE命令,当wpa_supplicant收到TERMINATE命令会,就会给调用者发送CTRL-EVENT-TERMINATING这个event,当WifiMonitor收到这个event后,又会给WifiStateMachine发送SUP_DISCONNECTION_EVENT消息。回到SupplicantStoppingState收到这个消息后,就可以去结束掉wpa_supplicant进程并断开与它的socket连接,并且transition到InitialState,这样WifiStateMachine也恢复到最初的状态了。另外,从上面enter函数可以看到,还会发送一个CMD_STOP_SUPPLICANT_FAILED消息给自己,如果调用stopSupplicant不成功,间隔SUPPLICANT_RESTART_INTERVAL_MSECS毫秒后,也会走到和SUP_DISCONNECTION_EVENT消息处理一样的流程中来。

转载:http://blog.youkuaiyun.com/lilian0118/article/details/22376059 点击打开链接
./BTSnifferBREDR.py --port=/dev/ttyUSB0 --target=E0:D4:E8:19:C7:69 --live-terminal Using options: Serial Port: /dev/ttyUSB0 Serial Baud: 921600 BT Host Program: ./host_stack/sdp_rfcomm_query Host BDAddress: e0:d4:e8:19:c7:68 Target BDAddress: e0:d4:e8:19:c7:69 [!] Reset Done! EN pin toggled HIGH->LOW->HIGH [!] Waiting 0.8s... Traceback (most recent call last): File "./BTSnifferBREDR.py", line 262, in <module> sniffer() File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/runtime/install/lib/python3.8/site-packages/click/core.py", line 1157, in __call__ return self.main(*args, **kwargs) File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/runtime/install/lib/python3.8/site-packages/click/core.py", line 1078, in main rv = self.invoke(ctx) File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/runtime/install/lib/python3.8/site-packages/click/core.py", line 1434, in invoke return ctx.invoke(self.callback, **ctx.params) File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/runtime/install/lib/python3.8/site-packages/click/core.py", line 783, in invoke return __callback(*args, **kwargs) File "./BTSnifferBREDR.py", line 245, in sniffer Sniffer.start() File "./BTSnifferBREDR.py", line 118, in start self.driver = ESP32BTDriver(self.serial_port, self.serial_baud) File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/src/ESP32BTDriver.py", line 87, in __init__ self.get_version() File "/home/anritsen/Work/wifi-bt/git-prj/esp32_bluetooth_classic_sniffer-master/src/ESP32BTDriver.py", line 142, in get_version self.version = version_string.decode('utf-8').split('\n')[0] UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0: invalid start byte
最新发布
07-18
<think>我们正在处理一个UnicodeDecodeError异常,该异常发生在运行BTSnifferBREDR.py脚本时,错误信息指向位置0的字节0xb1。这个错误通常发生在尝试用错误的编码解码字节序列时。由于0xb1在ASCII范围之外,因此使用默认的ASCII解码会导致错误。 根据引用[1],我们知道这个项目是ESP32 Bluetooth Classic Sniffer,其目录结构中有主程序源代码,包括bt_sniffer.c。虽然错误发生在Python脚本中,但我们需要考虑整个数据流:ESP32设备捕获蓝牙数据并通过串口发送给PC,然后Python脚本读取并解析串口数据。 可能的原因和解决方案: 1. **串口通信的编码问题**:Python脚本在读取串口数据时,可能使用了错误的编码(如默认的ASCII)来解码字节数据。蓝牙嗅探数据可能包含非ASCII字符(比如二进制数据),因此不能以文本方式解码。 2. **二进制模式处理**:我们应该以二进制模式读取串口数据,然后根据协议进行解析,而不是将其作为文本字符串处理。 让我们查看错误信息:`UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0: invalid start byte`。这表明脚本尝试用UTF-8解码一个字节序列,但0xb1不是有效的UTF-8起始字节。 因此,解决方案可能是修改Python脚本,使其以二进制模式读取串口数据,并在需要时进行适当的二进制解析,而不是直接解码为字符串。 具体步骤: 1. **检查串口初始化代码**:在Python脚本中,查找打开串口的代码。通常使用`serial.Serial`。确保它以二进制模式打开(默认就是二进制流),并且不要使用`text`模式或指定编码。 例如,正确的打开方式应该是: ```python ser = serial.Serial(port, baudrate) ``` 而不是: ```python ser = serial.Serial(port, baudrate, encoding='utf-8') # 错误:指定了编码 ``` 2. **修改数据读取方式**:如果代码中使用了`ser.readline()`或类似方法,并且随后尝试将读取的字节串解码为字符串,那么这就是问题所在。对于可能包含二进制数据的情况,我们应该避免使用字符串操作,而是直接处理字节。 例如,将: ```python line = ser.readline().decode('utf-8') # 可能导致UnicodeDecodeError ``` 改为: ```python line = ser.readline() # 得到bytes对象 # 然后根据协议解析这个bytes对象 ``` 3. **协议解析**:蓝牙嗅探数据有特定的格式,通常是二进制格式。因此,需要按照协议规范解析每一帧数据。如果数据包结构是二进制的,那么可能需要使用`struct`模块来解包。 4. **检查脚本的特定部分**:由于错误发生在位置0的字节0xb1,这意味着脚本在读取数据后立即尝试将其解码为字符串,而第一个字节就是0xb1。因此,我们需要找到读取并解码的地方进行修改。 5. **参考类似项目**:引用[3]和[4]提到了ESP32的Sniffer模式,虽然一个是WiFi,另一个是蓝牙,但处理二进制数据的方式是类似的。它们都是直接处理二进制数据包。 因此,修复的关键是避免将二进制数据当作文本解码,而是直接处理字节。 假设我们无法直接修改脚本(比如脚本是第三方提供的),我们可以尝试以下方法: - 如果脚本确实需要以文本形式处理某些部分(例如,某些部分是可打印的ASCII字符),那么我们可以尝试使用错误处理策略,例如: ```python line = ser.readline().decode('utf-8', errors='ignore') # 忽略无法解码的字节 ``` 或者 ```python line = ser.readline().decode('latin1') # latin1编码可以解码任何字节,但得到的是字符串,可能不适合二进制协议解析 ``` 但这通常不是最佳方案,因为二进制协议需要精确的字节级解析。 因此,最佳方案是修改脚本,使其以二进制方式读取并解析,跳过字符串解码步骤。 由于我们没有具体的脚本代码,这里给出一个通用的修改思路: 假设原脚本中有类似这样的代码: ```python import serial ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1) while True: line = ser.readline().decode('utf-8') # 这里会出错 # 处理line ``` 修改为: ```python import serial ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1) while True: raw_data = ser.readline() # 返回bytes对象 # 现在直接处理raw_data,它是一个字节串 # 例如,根据协议解析:可能需要使用struct.unpack或者手动解析 # 假设协议中第一个字节是包类型,那么我们可以: # packet_type = raw_data[0] # 这样就不会有解码错误了 ``` 如果脚本中使用了`print`语句来显示接收到的数据,那么对于二进制数据,直接打印字节串可能显示为转义序列。我们可以选择以十六进制形式打印,例如: ```python print(raw_data.hex()) # 以十六进制字符串形式打印 ``` 或者,如果某些部分是可打印的,我们可以尝试混合处理,但需要小心。 总结:修复的关键是停止将接收到的字节数据解码为字符串,而是直接作为字节数组处理。 如果错误发生在脚本的其他部分(例如,处理文件时),同样需要检查该处是否错误地使用了字符串解码,并确保二进制数据以二进制方式处理。 由于问题描述中提到了运行`BTSnifferBREDR.py`脚本,我们假设这个脚本是项目的一部分。根据项目结构(引用[1]),这个脚本可能位于项目目录中,我们可以直接修改它。 请检查脚本中读取串口数据的地方,并按照上述方法修改。 如果问题仍然存在,请检查串口配置(波特率等)是否正确,因为错误的数据流也可能导致解析问题。另外,确保ESP32固件正确烧写,并且硬件连接正常。 最后,如果以上方法都不能解决问题,可能需要查看具体的错误堆栈,确定错误发生的精确位置,然后针对性修复。</think>### 解决 ESP32 Bluetooth Sniffer UnicodeDecodeError 0xb1 错误 #### 问题原因分析 该错误通常发生在 Python 脚本尝试解码串口数据时使用了错误的字符编码。具体表现为: ```python UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0 ``` 字节 `0xb1` 属于扩展 ASCII 字符(± 符号),在 UTF-8 编码中无效。根据引用[1][3][4],ESP32 蓝牙嗅探器输出的数据可能包含: 1. 二进制协议数据(非 UTF-8 文本) 2. 特殊控制字符 3. 混合编码的调试信息 #### 解决方案 ##### 方法 1:强制二进制模式读取(推荐) 修改 `BTSnifferBREDR.py` 的串口读取代码,跳过解码步骤: ```python # 原始代码(可能引发错误) ser = serial.Serial(port, baudrate=115200) line = ser.readline().decode('utf-8') # 问题根源 # 修改为二进制模式 ser = serial.Serial(port, baudrate=115200) raw_data = ser.readline() # 直接获取字节数据 # 示例:按字节解析(需根据协议调整) if len(raw_data) > 0: first_byte = raw_data[0] # 直接访问字节 0xb1 if first_byte == 0xb1: process_packet(raw_data[1:]) # 自定义协议处理 ``` ##### 方法 2:错误忽略策略 在解码时添加错误处理参数: ```python line = ser.readline().decode('utf-8', errors='ignore') # 忽略非法字节 # 或 line = ser.readline().decode('latin-1') # 兼容所有单字节编码 ``` ##### 方法 3:协议级修复 1. **检查固件配置** 确认 ESP32 固件(引用[1]的 `bt_sniffer.c`)是否强制文本输出: ```c // 避免使用 printf 输出二进制数据 // 错误示例 printf("%c", binary_data); // 正确做法:十六进制输出 for(int i=0; i<len; i++){ printf("%02X ", packet[i]); } ``` 2. **统一编码格式** 在固件中明确指定编码(如引用[2]的配网协议): ```c // 在初始化代码中添加 uart_set_encoding(UART_NUM_0, UART_DATA_8_BITS, UART_STOP_BITS_1, UART_PARITY_DISABLE, UART_HW_FLOWCTRL_DISABLE, 115200); ``` #### 验证步骤 1. 使用串口调试工具直接观察原始输出 2. 运行修改后的脚本: ```bash python BTSnifferBREDR.py --debug # 添加调试输出 ``` 3. 捕获数据包示例检查: ```python with open("sniffer_dump.bin", "wb") as f: while True: f.write(ser.read(ser.in_waiting)) ``` #### 相关问题 1. ESP32 蓝牙嗅探器如何解析 BR/EDR 协议数据包? 2. 如何将 ESP32 嗅探的数据导入 Wireshark 进行分析? 3. 蓝牙经典嗅探和 BLE 嗅探在数据捕获上有哪些技术差异? > 参考实践:ESP32 蓝牙嗅探器通常使用 HCI 层监控模式捕获数据,原始输出为二进制 HCI 事件流(引用[1][3]),需通过 `hcidump` 或自定义解析器转换格式[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值