ESP8266 Geo-location and calling the underlying C libraries

My interest was piqued by this document - it proposes the following means of getting an ESP8266 to work out where it is on the planet:

  • Scan for nearby APs, getting 
    • SSID
    • MAC address
    • RSSI (signal strength)
  • Make an API call to Google's Maps API, passing this info and getting back lat/lon with a likelihood radius
The only drawback is that this is done using the AT command set that the ESP8266 supports, rather than the Arduino libraries. Hurray. Let's look at the Arduino library support, and we find WiFi.scanNetworks(). This is demonstrated in the ESP8266WiFiScan example, but however doesn't return all the required information, only
  • SSID
  • RSSI
  • Encryption type (an enumerated type)
Bummer. It works ok, quite cool, but... So how do I get to the same function as the AT command set? 

There is a lower level C-type interface to the chip's functions, in the include file <user_interface.h>. This is demonstrated in CallSDKFunctions, where the function wifi_station_get_hostname is called. A quick check on the Expressif document web page, and there's a doc "ESP8266 Non-OS SDK API Reference" - downloaded!!

Here we find


3.5.16. wifi_station_scan 



Function
Scan all available APs.
Prototype
bool wifi_station_scan (struct scan_config *config, scan_done_cb_t
cb);
Structure
struct scan_config { uint8 *ssid;
// AP’s ssid
// AP’s bssid
//scan a specific channel
};
uint8 *bssid;
uint8 channel;
uint8 show_hidden; //scan APs of which ssid is hidden.
Parameter
  • struct scan_config *config: AP config for scan.
    • -  If config==null, scan all APs.
    • -  If config.ssid==null && config.bssid==null && config.channel!=null,
      ESP8266 Station interface will scan the APs in a specific channel.
    • -  If config.ssid!=null && config.bssid==null &&
      config.channel==null, ESP8266 Station will scan the APs with a specific SSID in all channels.
  • scan_done_cb_t cb: callback function after scanning.
Return
true: Success false: Failure
Note
Do not call this API in user_init. This API need to be called after system initializes and the ESP8266 Station mode is enabled.
along with...

7.2.3. Scan Parameters
struct scan_config {
    uint8 *ssid;
    uint8 *bssid;
    uint8 channel;
    uint8 show_hidden; // Scan APs which are hiding their SSID or not.
};
struct bss_info {
    STAILQ_ENTRY(bss_info)
next;
uint8 bssid[6];
uint8 ssid[32];
uint8 ssid_len;
uint8 channel;
sint8 rssi;
AUTH_MODE authmode;
uint8 is_hidden;
sint16 freq_offset;
sint16 freqcal_val;
uint8 *esp_mesh_ie;
uint8 simple_pair;
// SSID of current AP is hidden or not. // AP’s frequency offset
};
typedef void (* scan_done_cb_t)(void *arg, STATUS status);

SO... how did it go? 

Firstly, my C is incredibly rusty, having been spoilt by Java for a long time. My C++ is even worse. Anyway, I eventually worked out how to use the struct definition, and how to point to things i.e. myStruct -> things. But it still didn't work... I found someone using it, and in defiance of the explicit Note "Do not call this API in user_init" suggested that it had to be called before user_init! Tried that, and finally, it works! I won't go into all the idiot error in the sprintf code etc etc... Here's a few snippets:

#include "user_interface.h"

void scan_done (void *bss_struct, STATUS retstat) { // Couldn't use status, name clash
  if (retstat == OK) {
    Serial.printf("Scan OK [%u]\n", retstat);
    struct bss_info *bss = (struct bss_info *) bss_struct;
    int i = 1;    // Nicer display from 1 :-)
    char mac[40]; // Buffer for formatted MAC ID
    while (bss != 0){
      sprintf(mac, "Mac=%x:%x:%x:%x:%x:%x\0",bss->bssid[0],bss->bssid[1],bss->bssid[2],bss->bssid[3],bss->bssid[4],bss->bssid[5]);
      Serial.printf("%u SSID=%s, Channel=%i, RSSI=%i, Hidden=%i, %s\n", i++, bss->ssid, bss->channel, bss->rssi, bss->is_hidden, mac);
      bss = STAILQ_NEXT(bss, next);
    }
  } else {
    Serial.printf("Scan failed [%u]\n", retstat);
  }
}

void scanAPs() {
  Serial.println("Invoking scan...");
  wifi_station_scan (0, scan_done); // I originally tried this with null, then a nulled scan_config, now 0!
}

void setup() {  // NOTE: Had to put scanAPs() in setup(), doesn't work in loop()!
  Serial.begin(115200);
  if (wifi_set_opmode_current (STATION_MODE))
    Serial.println("STATION mode set");
  else
    Serial.println("STATION mode set failed");
  scanAPs();
}

All I have to do now is get the Google API going... and then combine this geo-location tool with the OpenWeatherMap's current weather API, and I've no need to go outside to see if it's raining ever again.



Comments

Popular posts from this blog

Replacing Coffee Machine Pump (Dualit Espressivo)

Multiple SHT30 sensors on a single I2C bus with Sonoff-Tasmota

NodeMCU + Tasmota code + SHT30