-->
Page 1 of 6

[CODE SNIPPET] How I connect to an AP

PostPosted: Thu Mar 21, 2019 1:09 am
by davydnorris
OK everyone,

I thought I would get the ball rolling in this advanced group by posting up a code snippet that shows something useful that maybe not everyone knows. This is running and working code, but it may not be complete - for the advanced user it should be enough to get them going.

This is something I have recently been looking at and this code is now working a treat, although I do have a question for the group as well. This is the guts of my wifi AP connection code, which does the following:
- I have a list of known APs that I store in persistent storage (flash) (variable wifi_p)
- I have the last AP I was connected to, it's bssid and its channel and I store those in volatile storage (RTC) (variable wifi_v)
- when I wake up I look to see if I have anything in volatile store, and if so I set the wifi config so that it tries going directly to the bssid and channel, if the AP is still exactly the same it connects extremely quickly
- if that fails, I drop the channel and try again
- if that fails, I drop the bssid and try again
- if that fails I try the AP one last time just with SSID
- if that doesn't work I then start cycling through my list of APs looking for a connection
- when I manage to connect, I save the channel and bssid for next time

There are other failure modes I also deal with:
- if the AP connects but I can't get an IP address, I disconnect and reconnect the same way
- if the AP rejects me because the password has changed, I skip it and move on
- if the AP is connected and then drops out, I first try to reconnect the same way but then go through my fallback sequence

Here is the code:

Code: Select all/*
 * File       : wifi.c
 * Description:
 * Author     : Davyd Norris
 *
 * Copyright(c) 2019 Olive Grove IT Pty Ltd
 */
.
.
.

static void ICACHE_FLASH_ATTR configure_ap_info(void) {
   APInfo_t *ap_info = &(wifi_p->ap_list[wifi_v->ap]);

   INFO("WIFI", "configuring AP %d...", wifi_v->ap);
   INFO("WIFI", "setting ssid = %s, password = %s", ap_info->ssid, ap_info->password);

   struct station_config station_conf;
   os_memset(&station_conf, 0, sizeof(struct station_config));

   os_sprintf(station_conf.ssid, "%s", ap_info->ssid);
   os_sprintf(station_conf.password, "%s", ap_info->password);
   if (os_memcmp(wifi_v->bssid, "\0\0\0\0\0\0\0", 6) != 0) {   /* if bssid is known then try it again to speed up connect */
      INFO("WIFI", "setting bssid = "MACSTR, MAC2STR(wifi_v->bssid));
      os_memcpy(&station_conf.bssid, wifi_v->bssid, 6);
      station_conf.bssid_set = 1;
   }

   wifi_station_set_config_current(&station_conf);

   if (wifi_v->channel != 0) {                           /* if channel is known then try it again too */
      INFO("WIFI", "setting channel = %d", wifi_v->channel);
      wifi_set_channel(wifi_v->channel);                  /* has to be set after wifi_station_set_config_*() */
   }
}

static void ICACHE_FLASH_ATTR wifi_event_cb(System_Event_t *evt) {
   INFO("WIFI", "event: %s", wifi_events[evt->event]);

   switch (evt->event) {
      case EVENT_STAMODE_CONNECTED: {
         wifi_status = WIFI_CONNECTED;

         Event_StaMode_Connected_t *e = &(evt->event_info.connected);
         INFO("WIFI", "connected ssid %s, channel %d, bssid = "MACSTR, e->ssid, e->channel, MAC2STR(e->bssid));

         /* connected - cache bssid and channel info for faster reconnect next time (AP index is already cached) */

         wifi_v->channel = e->channel;
         os_memcpy(wifi_v->bssid, e->bssid, 6);
         sysinfo_set_dirty(SS_WIFI|STORAGE_VOLATILE);
      }
      break;

      case EVENT_STAMODE_DISCONNECTED: {
         wifi_status = WIFI_DISCONNECTED;

         Event_StaMode_Disconnected_t *e = &(evt->event_info.disconnected);
         uint8_t reason = e->reason;
         const char *reason_str = (reason >= 200) ? wifi_reason_ap[reason - 200] : wifi_reason_phy[reason];
         INFO("WIFI", "disconnect ssid %s, reason %d, %s", e->ssid, reason, reason_str);

         /* disconnected - work out why and adjust wifi AP connection params */

         if (reason == REASON_AUTH_FAIL) {            /* bad password - try another AP */
            wifi_v->channel = 0;
            os_memset(wifi_v->bssid, '\0', 6);
            wifi_v->ap = (wifi_v->ap + 1) % wifi_p->num_aps;
         } else if (reason == REASON_NO_AP_FOUND) {      /* can't find AP - relax filters */
            if (wifi_v->channel != 0) {               /* ignore last channel */
               wifi_v->channel = 0;
            } else if (os_memcmp(wifi_v->bssid, "\0\0\0\0\0\0\0", 6) != 0) { /* ignore bssid */
               os_memset(wifi_v->bssid, '\0', 6);
            } else {                           /* really not there - try another AP */
               wifi_v->ap = (wifi_v->ap + 1) % wifi_p->num_aps;
            }
         } else if (reason == REASON_BEACON_TIMEOUT) {   /* AP has stopped responding - try to reconnect */
            wifi_station_disconnect();
         } else if (reason == REASON_ASSOC_LEAVE) {       /* device has disconnected itself */
            break;
         }

         configure_ap_info();
         wifi_status = WIFI_CONNECTING;
         wifi_station_connect();
      }
      break;

      case EVENT_STAMODE_AUTHMODE_CHANGE: {
         Event_StaMode_AuthMode_Change_t *e = &(evt->event_info.auth_change);
         INFO("WIFI", "authmode change %s -> %s", wifi_auth_mode[e->old_mode], wifi_auth_mode[e->new_mode]);
      }
      break;

      case EVENT_STAMODE_GOT_IP: {
         wifi_status = WIFI_IP_ACQUIRED;

         Event_StaMode_Got_IP_t *e = &(evt->event_info.got_ip);
         INFO("WIFI", "got IP addr= " IPSTR ", mask= " IPSTR ", gw= " IPSTR,
               IP2STR(&e->ip),   IP2STR(&e->mask), IP2STR(&e->gw));

         /* we have an IP and are ready to go - connect the time */

         configure_sntp();
      }
      break;

      case EVENT_STAMODE_DHCP_TIMEOUT: {
         INFO("WIFI", "couldn't get an IP address. Retry...");

         /* don't have an IP address - try to disconnect and reconnect */

         wifi_status = WIFI_DISCONNECTING;
         wifi_station_disconnect();
         wifi_status = WIFI_CONNECTING;
         wifi_station_connect();
      }
      break;

      default:
         break;
   }
}

void ICACHE_FLASH_ATTR wifi_connect(WifiCallback cb) {

.
.
.

   INFO("WIFI", "Initialising connection");
   wifi_status = WIFI_CONNECTING;
   wifi_set_opmode_current(STATION_MODE);
   wifi_station_set_reconnect_policy(false);
   wifi_set_event_handler_cb(wifi_event_cb);

   configure_ap_info();

   wifi_station_connect();
}

Re: [CODE SNIPPET] How I connect to an AP

PostPosted: Thu Mar 21, 2019 1:16 am
by davydnorris
Now my questions:
- can anybody think of a more elegant way to check the bssid value? is memcmp with "\0\0\0\0\0\0" the most elegant way? I initially used a union with a uint64_t but this took up valuable space in the RTC memory

- when the fast connection fails for the first time, I see a message in the debug info that says 'scandone'. This is what is printed at the end of a channel scan. So I was wondering if anybody knew if the SDK automatically does a channel scan when a connection fails? Perhaps I don't need all my fallbacks?

All suggestions and improvements very welcome!!

Re: [CODE SNIPPET] How I connect to an AP

PostPosted: Thu Mar 21, 2019 1:10 pm
by eriksl
My $0.02, from my (very limited) experience on this matter.

I read somewhere (in official SDK documentation or maybe on the Espressif forum) that the SDK code first tries to do a fast reconnect and if that fails do a "normal" one.

Where fast is defined as: store the current channel in RTC memory after succesful association; after reset, check RTC memory for channel and flash for ssid+password. Then immediately try to associate with these parameters. Normal is after full scan.

I think it works well, a fast reconnect is considerably quicker.

My approach is like this. I only ever use one SSID+password, so it may be simpler than your case.

- check operational mode from (own) config, if it's AP mode, jump to other code
- then check
* current opmode, if non STATION_MODE (maybe AP+STA by default from reboot or crash, who knows)
* auto (SDK) connect (if auto connect is off, it won't matter anyway)
* ssid, passwd from current (SDK) config, if they're unequal to what I've in my own config, assume re-association is required
- if all of above are OK, we don't need to do anything, SDK already associated using "auto connect" or is in the process of doing that, and it incorporates a "fast connect" attempt
- if not all of above are OK
* disconnect
* set operational mode to STATION
* explicitly set SSID+passwd using wifi_station_set_config
* connect using wifi_station_connect()
* and then do wifi_station_set_autoconnect(1)
* this procedure will store SSID+passwd in flash for SDK autoconnect next time

Re: [CODE SNIPPET] How I connect to an AP

PostPosted: Thu Mar 21, 2019 1:19 pm
by eriksl
BTW is there a specific reason you try to maintain some ap data in flash (like BSSID)?

I have two access points in my house so the ESP's often have a choice to which they associate with. I found it works quite well. Even if my reboot one of the access points, within a few minutes all of them move to the other access point (on another channel).

I guess this means that after a fast reconnect this may have some of the ESP's associate with the AP that's further away. I don't think that is a problem, as long as it works. Is it that why you want to maintain a list of BSSID's?

And also, BTW, I have a fail-safe construction, where, if the ESP doesn't have an association + ip address, within a minute (which sounds reasonable to me), it goes into access point mode and allows me to connect to it, set SSID+passwd and then try again. I've never needed to take the ESP out, put it into the "programmer" and write to the flash config directly for this purpose, so apparently it works ;-)

Having said that, exactly what all of the wifi_* function do and don't is described very poorly by Espressif, I think all of us agree there.