earlephilhower / arduino-pico

Raspberry Pi Pico Arduino core, for all RP2040 boards
GNU Lesser General Public License v2.1
1.88k stars 394 forks source link

WiFi.status() wrongly reports WL_CONNECTED even when WiFi is lost and RSSI is always 0 dBm #762

Closed khoih-prog closed 1 year ago

khoih-prog commented 1 year ago

Bug Description

  1. The Terminal output shows that even after WiFi was powered down, WiFi.status() still reports WL_CONNECTED

https://github.com/earlephilhower/arduino-pico/blob/5787b4c02bdd58911f7eca8e46615b48be7abdcd/libraries/WiFi/src/WiFiClass.cpp#L472-L484

  1. The RSSI is always shows 0 dBm, even WiFi is connected

https://github.com/earlephilhower/arduino-pico/blob/5787b4c02bdd58911f7eca8e46615b48be7abdcd/libraries/WiFi/src/WiFiClass.cpp#L320-L323

This bug is very similar to [Portenta_H7] WiFi.status() wrongly reports WL_CONNECTED even when WiFi is lost #381


MRE

Using the following sketch

Arduino IDE v1.8.19, arduino-pico core v2.4.0 RASPBERRY_PI_PICO_W using CYW43439 WiFi


#include <WiFi.h>

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "YOUR_SSID";            // your network SSID (name)
char pass[] = "YOUR_PASSWORD";        // your network password (use for WPA, or use as key for WEP)

int status = WL_IDLE_STATUS;

void printWifiStatus() 
{
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void setup()
{
  //Initialize serial and wait for port to open:
  Serial.begin(115200);
  while (!Serial && millis() < 5000);

  Serial.print(F("\nStarting RP2040W_WiFi_Bug on ")); Serial.println(BOARD_NAME);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_SHIELD)
  {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED)
  {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 3 seconds for connection:
    delay(3000);
  }
}

void loop()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println("Connected to wifi");
    printWifiStatus();
  }
  else
  {
    Serial.println("Not connected to wifi");
  }

  delay(5000);
}

Terminal output


Starting RP2040W_WiFi_Bug on RASPBERRY_PI_PICO_W
Attempting to connect to SSID: HueNet1
Attempting to connect to SSID: HueNet1
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm    <========  RSSI = 0 dBm when connected
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm
...                         <============  Power down WiFi, WiFi.status() is still WL_CONNECTED
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
signal strength (RSSI):0 dBm

earlephilhower commented 1 year ago

Thanks for the report and MCVE.

The RSSI reporting 0dbm while connected or disconnected is actually a limitation of the CYW43 driver. It doesn't report common things like that. It doesn't even report BSSID or channel. Nothing we can do here unless the driver gets fixed (it talks to a undocumented proprietary blob).

The incorrect connected() report, though, we should be able to work on here.

earlephilhower commented 1 year ago

On the WL_CONNECTED side, the code here also seems correct:

https://github.com/earlephilhower/arduino-pico/blob/5787b4c02bdd58911f7eca8e46615b48be7abdcd/libraries/WiFi/src/WiFiClass.cpp#L472-L484

The logic simply polls the CYW43 blob driver and uses that to determine connected or not. If the CYW43 driver reports the connection is still up, there's not much we can do here.

As you probably are guessing, I've found the CYW43 driver something of a work in progress. We've even had to patch it to support simple things like multicast.

--edit--- Digging into the driver, while the link status call seems not to work: https://github.com/earlephilhower/cyw43-driver/blob/02533c10a018c6550e9f66f7699e21356f5e4609/src/cyw43_ctrl.c#L565-L582

We may be able to hook into the event callbacks and track the link state on our own, ignoring what the driver says: https://github.com/earlephilhower/cyw43-driver/blob/02533c10a018c6550e9f66f7699e21356f5e4609/src/cyw43_ctrl.c#L379-L392

khoih-prog commented 1 year ago

As you probably are guessing, I've found the CYW43 driver something of a work in progress. We've even had to patch it to support simple things like multicast.

I totally understand and post the issue here so that we can work on a patch similar to multicast

We may be able to hook into the event callbacks and track the link state on our own, ignoring what the driver says: https://github.com/earlephilhower/cyw43-driver/blob/02533c10a018c6550e9f66f7699e21356f5e4609/src/cyw43_ctrl.c#L379-L392

This seems the best way to fix.

earlephilhower commented 1 year ago

I just did a quick test to patch in some logic based on the callbacks and it seems like the event is not actually being received on associate/disassociate. :disappointed:

I probably need to instrument it and dump all events while adding/removing to see exactly what is happening and where it might be overridden.

diff --git a/cores/rp2040/sdkoverride/cyw43_arch_threadsafe_background.c b/cores/rp2040/sdkoverride/cyw43_arch_threadsafe_background.c
index 07ef28a9f..a95bd110e 100644
--- a/cores/rp2040/sdkoverride/cyw43_arch_threadsafe_background.c
+++ b/cores/rp2040/sdkoverride/cyw43_arch_threadsafe_background.c
@@ -316,6 +316,9 @@ void cyw43_arch_poll() {
     //    }
 }

+
+bool _cyw43_cb_link_up = false;
+
 #ifdef ARDUINO_RASPBERRY_PI_PICO_W
 void __attribute__((weak)) cyw43_cb_tcpip_init(cyw43_t *self, int itf);
 void cyw43_cb_tcpip_init(cyw43_t *self, int itf) {
@@ -331,11 +334,15 @@ void __attribute__((weak)) cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf);
 void cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf) {
     (void) self;
     (void) itf;
+    printf("\nlu\n");
+    _cyw43_cb_link_up = true;
 }
 void __attribute__((weak)) cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf);
 void cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf) {
     (void) self;
     (void) itf;
+    printf("\nld\n");
+    _cyw43_cb_link_up = false;
 }
 void __attribute__((weak)) cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf);
 void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf) {
--- a/libraries/WiFi/src/WiFiClass.cpp
+++ b/libraries/WiFi/src/WiFiClass.cpp
@@ -469,13 +469,14 @@ int32_t WiFiClass::RSSI(uint8_t networkItem) {

     return: one of the value defined in wl_status_t
 */
+extern "C" bool _cyw43_cb_link_up;
 uint8_t WiFiClass::status() {
     if (_apMode && _wifiHWInitted) {
         return WL_CONNECTED;
     }
     switch (cyw43_wifi_link_status(&cyw43_state, _apMode ? 1 : 0)) {
     case CYW43_LINK_DOWN: return WL_IDLE_STATUS;
-    case CYW43_LINK_JOIN: return localIP().isSet() ? WL_CONNECTED : WL_DISCONNECTED;
+    case CYW43_LINK_JOIN: return (_cyw43_cb_link_up && localIP().isSet()) ? WL_CONNECTED : WL_DISCONNECTED;
     case CYW43_LINK_FAIL: return WL_CONNECT_FAILED;
     case CYW43_LINK_NONET: return WL_CONNECT_FAILED;
     case CYW43_LINK_BADAUTH: return WL_CONNECT_FAILED;
khoih-prog commented 1 year ago

This is just a temporary and inefficient kludge using WiFiClient.connect() to test or use (if necessary) while waiting for the real fix

#include <WiFi.h>

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "YOUR_SSID";            // your network SSID (name)
char pass[] = "YOUR_PASSWORD";        // your network password (use for WPA, or use as key for WEP)

int status = WL_IDLE_STATUS;

void printWifiStatus() 
{
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
}

void setup()
{
  //Initialize serial and wait for port to open:
  Serial.begin(115200);
  while (!Serial && millis() < 5000);

  Serial.print(F("\nStarting RP2040W_WiFi_Bug on ")); Serial.println(BOARD_NAME);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_SHIELD)
  {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED)
  {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 3 seconds for connection:
    delay(3000);
  }
}

// Use any public host or your local host, such as ADSL/Cable modem, router, server
//const char* host = "arduino.tips";
const char* host = "192.168.2.1";
const uint16_t port = 80;

bool isWiFiConnected()
{
  // Use WiFiClient class to create TCP connections
  WiFiClient client;

  if (!client.connect(host, port)) 
  {
    Serial.print("connection failed. Local IP = "); Serial.println(client.localIP());
    Serial.print("connection failed. WiFi IP = "); Serial.println(WiFi.localIP());
    return false;
  }

  Serial.print("Client connected, Local IP = "); Serial.println(client.localIP());
  return true;
}

void loop()
{
  //if (WiFi.status() == WL_CONNECTED)
  // Temporary kludge to get around
  if (isWiFiConnected())
  {
    Serial.println("Connected to wifi");
    printWifiStatus();    
  }
  else
  {
    Serial.println("Not connected to wifi");
  }

  delay(5000);
}

Terminal output

Starting RP2040W_WiFi_Bug on RASPBERRY_PI_PICO_W
Attempting to connect to SSID: HueNet1
Client connected, Local IP = 192.168.2.180
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
Client connected, Local IP = 192.168.2.180
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
Client connected, Local IP = 192.168.2.180
...            <========= Power OFF WiFi AP
connection failed. Local IP = (IP unset)
connection failed. WiFi IP = 192.168.2.180
Not connected to wifi
connection failed. Local IP = (IP unset)
connection failed. WiFi IP = 192.168.2.180
Not connected to wifi
...            <========= Power ON WiFi AP
Client connected, Local IP = 192.168.2.180
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
Client connected, Local IP = 192.168.2.180
Connected to wifi
SSID: HueNet1
IP Address: 192.168.2.180
Client connected, Local IP = 192.168.2.180
earlephilhower commented 1 year ago

Why not make it something more universal? If you ping 1.1.1.1 (CloudFlare DNS) or 8.8.8.8 (Google DNS) it should theoretically work everywhere except behind the Great Firewall of China. No need for users to adjust in the common case.

In the meantime, I'll leave this open as something to work on. I'm not sure how much progress we can make, but I'll give it a try since it's such a basic operation.

khoih-prog commented 1 year ago

I first tried ping(), but somehow it didn't work correctly. That's why I had to use the worse and extreme WiFiClient.connect().

I'll try again later to see what's really wrong with ping()

I hope I'll have a look at the cyw43 driver, but not sure if I can spend time there to understand. Hopefully @oddstr13 can help here.

oddstr13 commented 1 year ago

@khoih-prog unfortunately I don't have any inside knowledge of how the firmware works, all I have to go by is the public git repo.

I don't have time right now (and likely not for a good while) to have a deeper dive into this.

The pico doxygen might be helpful for discovering what's available. https://raspberrypi.github.io/pico-sdk-doxygen/group__cyw43__driver.html#ga71e656d02aabca214ae344f29ae1d033

I'd suggest submitting issues upstream, even tho they seem to be slow at responding right now.

earlephilhower commented 1 year ago

YEah, that document suggests that the existing code is correctly calling the right API, but the API is returning "connected" when the AP itself is down.

@khoih-prog how long after the AP is shut off do you wait before checking the connected status? There may be some internal timeout where the CYW43 doesn't report disconnected, just because WiFi sometimes has short dropouts . If it's still reporting connected 30 seconds after the AP is powered off, though, we definitely have an issue. I haven't tried yet since I need to dig up and config a dummy AP.

khoih-prog commented 1 year ago

If it's still reporting connected 30 seconds after the AP is powered off, though, we definitely have an issue.

I've tried to power off AP, after many minutes, still connected.

khoih-prog commented 1 year ago

I've retested using ping() and OK now to use ping to local gateway (auto detected, no user intervention) to detect WiFi lost.

The ttl is configurable, according to network

The code

#include <WiFi.h>

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "YOUR_SSID";            // your network SSID (name)
char pass[] = "YOUR_PASSWORD";        // your network password (use for WPA, or use as key for WEP)

int status = WL_IDLE_STATUS;

void printWifiStatus()
{
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
}

void setup()
{
  //Initialize serial and wait for port to open:
  Serial.begin(115200);
  while (!Serial && millis() < 5000);

  Serial.print(F("\nStarting RP2040W_WiFi_Bug on ")); Serial.println(BOARD_NAME);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_SHIELD)
  {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED)
  {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 3 seconds for connection:
    delay(3000);
  }
}

bool isWiFiConnected()
{
  // You can change longer or shorter depending on your network response
  // Shorter => more responsive, but more ping traffic
  static uint8_t theTTL = 10;

  // Use ping() to test TCP connections
  if (WiFi.ping(WiFi.gatewayIP(), theTTL) == theTTL)
  {
    return true;
  }

  return false;
}

void check_status()
{
  static uint32_t checkwifi_timeout    = 0;

  static uint32_t current_millis;

// You can change longer or shorter depending on your network response
// Shorter => more responsive, but more ping traffic
#define WIFICHECK_INTERVAL    1000L

  current_millis = millis();

  // Check WiFi every WIFICHECK_INTERVAL (1) seconds.
  if ((current_millis > checkwifi_timeout) || (checkwifi_timeout == 0))
  {
    //if (WiFi.status() == WL_CONNECTED)
    // Temporary fix to get around
    if (isWiFiConnected())
    {
      Serial.println("Connected to wifi");
      printWifiStatus();
    }
    else
    {
      Serial.println("Not connected to wifi");
    }

    checkwifi_timeout = current_millis + WIFICHECK_INTERVAL;
  }
}

void loop()
{
  check_status();
}

Debug Terminal

21:51:24.060 -> Starting RP2040W_WiFi_Bug on RASPBERRY_PI_PICO_W
21:51:24.060 -> Attempting to connect to SSID: HueNet2
21:51:41.109 -> Connected to wifi
21:51:41.109 -> SSID: HueNet2
21:51:41.109 -> IP Address: 192.168.2.180
...
21:51:44.128 -> Connected to wifi
21:51:44.128 -> SSID: HueNet2
21:51:44.128 -> IP Address: 192.168.2.180       <==== Power OFF AP
21:51:55.078 -> Not connected to wifi           <===== WiFi lost
21:52:05.066 -> Not connected to wifi
21:52:05.099 -> Connected to wifi               <==== Power ON AP
21:52:05.099 -> SSID: HueNet2
21:52:05.099 -> IP Address: 192.168.2.180
21:52:06.128 -> Connected to wifi
...
earlephilhower commented 1 year ago

@khoih-prog can you give #774 a try and report back? A simple "print connected()" loop now seems to work properly for me.

earlephilhower commented 1 year ago

Oh yeah, it takes ~5 seconds to notice a link drop in my experience with this chip.

khoih-prog commented 1 year ago

@earlephilhower

I've tested and can confirm that WiFi.status() WL_CONNECTED is working OK now, with 5s delay from link drop / power off. Auto-reconnect is working OK after powering on the AP.

Great work, please merge the PR #774