simple example of scanning wifi AP and sending to geolocation resolver on cloud

Dependencies:   mbed-http lr1110 sx12xx_hal

test Wifi geolocation with resolving using HTTP POST.

Use with mbed board with internet access and arduino form factor.
With HTTPS, the RAM requirement is minimum 128Kbytes.

and, use with radio shield for europe
or radio shield for USA
which is programmed with trx firmware from updater tool.

This project presents to user mbed STDIO serial port at 115200bps, which lets you perform wifi access point scan on LR1110 and send the resulting access point list to a geolocation provider on the cloud.

Use the project by itself to run wifi scan on the serial terminal (at 115200bps), or use with lr1110_wifi_geolocation_device to receive wifi list from remote device (over LoRa) to resolve location of that device using gelocation provider on cloud.

On serial terminal, use ? question mark to see list of commands. ws to run wifi scan, or ws p to wifi scan and resolve location with cloud provider via HTTP POST.

project setup

Edit main.h to uncomment which geolocation provider you wish to use, and get API key from them:

notice

This project is not using LoRaWAN, instead just LoRa transceiver directly to geolocation provider

Files at this revision

API Documentation at this revision

Comitter:
Wayne Roberts
Date:
Tue Feb 09 10:49:02 2021 -0800
Parent:
0:fd6707e25c57
Commit message:
add source files

Changed in this revision

combain.cpp Show annotated file Show diff for this revision Revisions of this file
google_geolocation.cpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
main.h Show annotated file Show diff for this revision Revisions of this file
mbed_app.json Show annotated file Show diff for this revision Revisions of this file
mbedtls_entropy_config.h Show annotated file Show diff for this revision Revisions of this file
network-helper.h Show annotated file Show diff for this revision Revisions of this file
source/select-demo.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/combain.cpp	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,127 @@
+#include "main.h"
+#if (GEOLOCATION_PROVIDER == COMBAIN)
+#include "radio.h"
+
+char my_copy[128];
+
+const char COMBAIN_SSL_CA_PEM[] = "-----BEGIN CERTIFICATE-----\n"
+"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n"
+"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n"
+"DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n"
+"PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n"
+"Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n"
+"AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n"
+"rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n"
+"OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n"
+"xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n"
+"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n"
+"aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n"
+"HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n"
+"SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n"
+"ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n"
+"AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n"
+"R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n"
+"JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n"
+"Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n"
+"-----END CERTIFICATE-----\n";
+
+/* parse_json: get result sent from server */
+void parse_json(const char *body, float *lat, float *lng, int *accuracy)
+{
+    bool in_location = false;
+    bool get_lat = false;
+    bool get_lng = false;
+    bool get_accuracy = false;
+    unsigned n;
+    strncpy(my_copy, body, sizeof(my_copy));
+    strtok(my_copy, "\"");
+    for (n = 0; n < 10; n++) {
+        char *t = strtok(NULL, "\":");
+        if (!t)
+            break;
+        if (in_location) {
+            if (get_lat) {
+                sscanf(t, "%f", lat);
+                get_lat = false;
+            } else if (get_lng) {
+                sscanf(t, "%f", lng);
+                get_lng = false;
+            }
+            if (strchr(t, '}'))
+                in_location = false;
+            else if (strcmp(t, "lat") == 0)
+                get_lat = true;
+            else if (strcmp(t, "lng") == 0)
+                get_lng = true;
+        } else {
+            if (get_accuracy) {
+                sscanf(t, "%d", accuracy);
+                get_accuracy = false;
+            }
+            if (strcmp(t, "location") == 0)
+                in_location = true;
+            else if (strcmp(t, "accuracy") == 0)
+                get_accuracy = true;
+        }
+    }
+}
+
+int post_scan_result(const char *body, float *lat, float *lng, int *accuracy)
+{
+    HttpsRequest* post_req = new HttpsRequest(network, COMBAIN_SSL_CA_PEM, HTTP_POST, "https://apiv2.combain.com?key=YOUR_API_KEY");
+
+    post_req->set_header("Content-Type", "application/json");
+
+    HttpResponse* post_res = post_req->send(body, strlen(body));
+    if (!post_res) {
+        printf("HttpRequest failed (error code %d)\n", post_req->get_error());
+        return -1;
+    }
+
+    dump_response(post_res);
+    parse_json(post_res->get_body_as_string().c_str(), lat, lng, accuracy);
+    delete post_req;
+
+    return 0;
+}
+
+/*
+ * https://combain.com/api/#request-body
+ */
+
+void wifi_result_to_json(bool first, const uint8_t *result, unsigned macStart, unsigned rssi_idx)
+{
+    char str[8];
+    unsigned i;
+
+    if (!first)
+        strcat(json, ",");  // end previous wifiAccessPoint
+
+    strcat(json, "{\"macAddress\": \"");
+    for (i = 0; i < 6; i++) {
+        sprintf(str, "%02x", result[i + macStart]);
+        strcat(json, str);
+        if (i < 5)
+            strcat(json, ":");
+    }
+    strcat(json, "\",\"signalStrength\": ");
+    sprintf(str, "%d", (int8_t)result[rssi_idx]);
+    strcat(json, str);
+    /* TODO: combain takes channel and/or frequency of access point */
+
+    strcat(json, "}");
+}
+
+void json_start()
+{
+    strcpy(json, "{\"considerIp\": \"false\",");
+    strcat(json, "\"wifiAccessPoints\": [");
+}
+
+void json_end()
+{
+    strcat(json, "]");
+    strcat(json, "}");
+}
+
+#endif /* GEOLOCATION_PROVIDER == COMBAIN */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/google_geolocation.cpp	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,128 @@
+#include "main.h"
+#if (GEOLOCATION_PROVIDER == GOOGLE)
+#include "radio.h"
+
+const char GOOGLE_SSL_CA_PEM[] = "-----BEGIN CERTIFICATE-----\n"
+"MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G\n"
+"A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp\n"
+"Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1\n"
+"MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG\n"
+"A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\n"
+"hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL\n"
+"v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8\n"
+"eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq\n"
+"tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd\n"
+"C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa\n"
+"zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB\n"
+"mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH\n"
+"V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n\n"
+"bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG\n"
+"3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs\n"
+"J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO\n"
+"291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS\n"
+"ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd\n"
+"AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7\n"
+"TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==\n"
+"-----END CERTIFICATE-----\n";
+
+char my_copy[128];
+
+/* parse_json: get result sent from server */
+void parse_json(const char *body, float *lat, float *lng, int *accuracy)
+{
+    bool in_location = false;
+    bool get_lat = false;
+    bool get_lng = false;
+    bool get_accuracy = false;
+    unsigned n;
+    strncpy(my_copy, body, sizeof(my_copy));
+    strtok(my_copy, "\"");
+    for (n = 0; n < 10; n++) {
+        char *t = strtok(NULL, "\":");
+        if (!t)
+            break;
+        if (in_location) {
+            if (get_lat) {
+                sscanf(t, "%f", lat);
+                get_lat = false;
+            } else if (get_lng) {
+                sscanf(t, "%f", lng);
+                get_lng = false;
+            }
+            if (strchr(t, '}'))
+                in_location = false;
+            else if (strcmp(t, "lat") == 0)
+                get_lat = true;
+            else if (strcmp(t, "lng") == 0)
+                get_lng = true;
+        } else {
+            if (get_accuracy) {
+                sscanf(t, "%d", accuracy);
+                get_accuracy = false;
+            }
+            if (strcmp(t, "location") == 0)
+                in_location = true;
+            else if (strcmp(t, "accuracy") == 0)
+                get_accuracy = true;
+        }
+    }
+}
+
+/*
+ * https://developers.google.com/maps/documentation/geolocation/overview
+ */
+int post_scan_result(const char *body, float *lat, float *lng, int *accuracy)
+{
+    HttpsRequest* post_req = new HttpsRequest(network, GOOGLE_SSL_CA_PEM, HTTP_POST, "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR-API-KEY");
+
+    post_req->set_header("Content-Type", "application/json");
+
+    HttpResponse* post_res = post_req->send(body, strlen(body));
+    if (!post_res) {
+        printf("HttpRequest failed (error code %d)\n", post_req->get_error());
+        return -1;
+    }
+
+    dump_response(post_res);
+    parse_json(post_res->get_body_as_string().c_str(), lat, lng, accuracy);
+    delete post_req;
+
+    return 0;
+}
+
+void json_start()
+{
+    strcpy(json, "{\"considerIp\": \"false\",");
+    strcat(json, "\"wifiAccessPoints\": [");
+}
+
+void json_end()
+{
+    strcat(json, "]");
+    strcat(json, "}");
+}
+
+void wifi_result_to_json(bool first, const uint8_t *result, unsigned macStart, unsigned rssi_idx)
+{
+    char str[8];
+    unsigned i;
+
+    if (!first)
+        strcat(json, ",");  // end previous wifiAccessPoint
+
+    strcat(json, "{\"macAddress\": \"");
+    for (i = 0; i < 6; i++) {
+        sprintf(str, "%02x", result[i + macStart]);
+        strcat(json, str);
+        if (i < 5)
+            strcat(json, ":");
+    }
+    strcat(json, "\",\"signalStrength\": ");
+    sprintf(str, "%d", (int8_t)result[rssi_idx]);
+    strcat(json, str);
+    strcat(json, ",\"signalToNoiseRatio\": 0");
+
+    strcat(json, "}");
+}
+
+#endif /* GEOLOCATION_PROVIDER == GOOGLE */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,555 @@
+#include "main.h"
+#include "network-helper.h"
+#include "radio.h"
+
+#define TX_DBM              20
+#define BW_KHZ              500
+#define SPREADING_FACTOR    11
+#define CF_HZ               919000000
+
+/* geolocation provider wont operate with less than 3 wifi access points */
+#define MINIMUM_REQUIRED_ACCESS_POINTS      2
+
+bool wifiResultFormatBasic;
+
+struct location {
+    float lat, lng;
+    int accuracy;
+};
+
+typedef struct {
+    const char* const cmd;
+    void (*handler)(uint8_t args_at);
+    const char* const arg_descr;
+    const char* const description;
+} menu_item_t;
+
+EventQueue queue(4 * EVENTS_EVENT_SIZE);
+
+RawSerial pc(USBTX, USBRX, MBED_CONF_PLATFORM_STDIO_BAUD_RATE); // speed from mbed_app.json
+char pcbuf[64];
+int pcbuf_len;
+
+NetworkInterface* network;
+
+event_callback_t    serialEventCb;
+
+uint8_t wifiScan_buf[9];
+uint64_t wifi_start_at, wifi_scan_dur;
+bool post_enable;
+bool send_reply;
+
+void cmd_help(uint8_t);
+
+struct location geoloc_result;
+
+uint8_t remote_chip_eui[8];
+
+struct wifidr {
+    const char *txt;
+    float Mbps;
+};
+
+const struct wifidr wifiDatarates[] = {
+    /*   0 */ { NULL, 0},
+    /*   1 */ { "DBPSK", 1},
+    /*   2 */ { "DQPSK", 2},
+    /*   3 */ { "BPSK", 6},
+    /*   4 */ { "BPSK", 9},
+    /*   5 */ { "QPSK", 12},
+    /*   6 */ { "QPSK", 18},
+    /*   7 */ { "16-QAM", 24},
+    /*   8 */ { "16-QAM", 36},
+    /*   9 */ { "(9)", 0},
+    /*  10 */ { "(10)", 0},
+    /*  11 */ { "BPSK", 6.5},
+    /*  12 */ { "QPSK", 13},
+    /*  13 */ { "QPSK", 19.5},
+    /*  14 */ { "16-QAM", 26},
+    /*  15 */ { "16-QAM", 39},
+    /*  16 */ { "(16)", 0},
+    /*  17 */ { "(17)", 0},
+    /*  18 */ { "(18)", 0},
+    /*  19 */ { "BPSK", 7.2},
+    /*  20 */ { "QPSK", 14.4},
+    /*  21 */ { "QPSK", 21.7},
+    /*  22 */ { "16-QAM", 28.9},
+    /*  23 */ { "16-QAM", 43.3},
+};
+
+char json[1536];
+
+void dump_response(HttpResponse* res)
+{
+    printf("Status: %d - %s\n", res->get_status_code(), res->get_status_message().c_str());
+
+    printf("Headers:\n");
+    for (size_t ix = 0; ix < res->get_headers_length(); ix++) {
+        printf("\t%s: %s\n", res->get_headers_fields()[ix]->c_str(), res->get_headers_values()[ix]->c_str());
+    }
+    printf("\nBody (%lu bytes):\n\n%s\n", res->get_body_length(), res->get_body_as_string().c_str());
+}
+
+void cfg_lora()
+{
+    Radio::LoRaModemConfig(BW_KHZ, SPREADING_FACTOR, 1);
+    Radio::SetChannel(CF_HZ);
+    Radio::set_tx_dbm(TX_DBM);
+               // preambleLen, fixLen, crcOn, invIQ
+    Radio::LoRaPacketConfig(8, false, true, false);
+}
+
+void cmd_wifi_scan(uint8_t idx)
+{
+    Radio::radio.xfer(OPCODE_WIFI_SCAN, 9, 0, wifiScan_buf);
+    wifi_start_at = Kernel::get_ms_count();
+    printf("wifiScan...\r\n");
+
+    post_enable = pcbuf[idx] == 'p';
+    send_reply = false;
+}
+
+/* List of trusted root CA certificates
+ * currently two: Amazon, the CA for os.mbed.com and Let's Encrypt, the CA for httpbin.org
+ *
+ * To add more root certificates, just concatenate them.
+ */
+const char HTTBIN_ORG_SSL_CA_PEM[] =  "-----BEGIN CERTIFICATE-----\n"
+    "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n"
+    "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n"
+    "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n"
+    "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n"
+    "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n"
+    "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n"
+    "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n"
+    "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n"
+    "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n"
+    "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n"
+    "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n"
+    "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n"
+    "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n"
+    "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n"
+    "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n"
+    "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n"
+    "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n"
+    "rqXRfboQnoZsG4q5WTP468SQvvG5\n"
+    "-----END CERTIFICATE-----\n"
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n"
+    "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n"
+    "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n"
+    "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n"
+    "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n"
+    "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n"
+    "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n"
+    "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n"
+    "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n"
+    "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n"
+    "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n"
+    "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n"
+    "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n"
+    "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n"
+    "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n"
+    "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n"
+    "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n"
+    "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n"
+    "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n"
+    "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n"
+    "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n"
+    "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n"
+    "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n"
+    "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n"
+    "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n"
+    "-----END CERTIFICATE-----\n";
+
+
+void cmd_httpbin_post(uint8_t idx)
+{
+    const char body[] = "{\"hello\":\"world\"}";
+
+#if DEMO == DEMO_HTTPS
+    printf("\n----- HTTPS POST request -----\n");
+    HttpsRequest* post_req = new HttpsRequest(network, HTTBIN_ORG_SSL_CA_PEM, HTTP_POST, "https://httpbin.org/post");
+#elif DEMO == DEMO_HTTP
+    printf("\n----- HTTP POST request -----\n");
+    HttpRequest* post_req = new HttpRequest(network, HTTP_POST, "http://httpbin.org/post");
+#endif
+
+    post_req->set_header("Content-Type", "application/json");
+
+    HttpResponse* post_res = post_req->send(body, strlen(body));
+    if (!post_res) {
+        printf("HttpRequest failed (error code %d)\n", post_req->get_error());
+        return;
+    }
+
+    dump_response(post_res);
+    delete post_req;
+}
+
+
+void cmd_print_status(uint8_t idx)
+{
+    stat_t stat;
+    uint8_t buf[4];
+    printf("[NWKH] IP address: %s\n", network->get_ip_address());
+
+    stat.word = Radio::radio.xfer(OPCODE_GET_VERSION, 0, 0, NULL);
+    stat.word = Radio::radio.xfer(0x0000, 0, 4, buf);
+    if (stat.bits.cmdStatus == CMD_DAT) {
+        printf("LR1110 chip:%02x use:%02x fw-v%u.%u\r\n",
+            buf[0], /* silicon rev */
+            buf[1], /* use case */
+            buf[2], /* firmware major */
+            buf[3]  /* firmware minor */
+        );
+    }
+
+    stat.word = Radio::radio.xfer(OPCODE_GET_STATUS, 4, 0, buf);
+    printf("chipMode:%d, cmdStatus:%d\r\n", stat.bits.chipMode, stat.bits.cmdStatus);
+}
+
+const menu_item_t menu_items[] = 
+{
+    { "phb", cmd_httpbin_post, "","test post to httpbin.org"},
+    { "ws", cmd_wifi_scan, "","local wifi scan"},
+    { ".", cmd_print_status, "","print status"},
+    { "?", cmd_help, "","this list of commands"},
+    { NULL, NULL, NULL, NULL }
+};
+
+void
+console()
+{
+    int i;
+    uint8_t user_cmd_len;
+
+    if (pcbuf_len < 0) {
+        printf("abort\r\n");
+        pcbuf_len = 0;
+        return;
+    }
+
+    printf("\r\n");
+
+    if (pcbuf_len > 0) {
+        /* get end of user-entered command */
+        user_cmd_len = 1;   // first character can be any character
+        for (i = 1; i <= pcbuf_len; i++) {
+            if (pcbuf[i] < 'A' || (pcbuf[i] > 'Z' && pcbuf[i] < 'a') || pcbuf[i] > 'z') {
+                user_cmd_len = i;
+                break;
+            }
+        }
+
+     
+        for (i = 0; menu_items[i].cmd != NULL ; i++) {
+            int mi_len = strlen(menu_items[i].cmd);
+            if (menu_items[i].handler && user_cmd_len == mi_len && (strncmp(pcbuf, menu_items[i].cmd, mi_len) == 0)) {
+                while (pcbuf[mi_len] == ' ')   // skip past spaces
+                    mi_len++;
+                menu_items[i].handler(mi_len);
+                break;
+            }
+        }
+    }
+   
+    pcbuf_len = 0;
+    printf("> ");
+    fflush(stdout); 
+}
+
+void echo(char c)
+{
+    if (c == 8) {
+        pc.putc(8);
+        pc.putc(' ');
+        pc.putc(8);
+    } else
+        pc.putc(c);
+}
+
+uint8_t serial_rx_buf;
+
+void serialCb(int events)
+{
+    if (events & SERIAL_EVENT_RX_COMPLETE) {
+        char c = serial_rx_buf;
+        static uint8_t pcbuf_idx = 0;
+        static uint8_t prev_len = 0;;
+        if (c == 8) {
+            if (pcbuf_idx > 0) {
+                queue.call(echo, 8);
+                pcbuf_idx--;
+            }
+        } else if (c == 3) {    // ctrl-C
+            pcbuf_len = -1;
+            queue.call(console);
+        } else if (c == '\r') {
+            if (pcbuf_idx == 0) {
+                pcbuf_len = prev_len;
+            } else {
+                pcbuf[pcbuf_idx] = 0;   // null terminate
+                prev_len = pcbuf_idx;
+                pcbuf_idx = 0;
+                pcbuf_len = prev_len;
+            }
+            queue.call(console);
+        } else if (pcbuf_idx < sizeof(pcbuf)) {
+            pcbuf[pcbuf_idx++] = c;
+            queue.call(echo, c);
+        }
+    }
+
+    if (pc.read(&serial_rx_buf, 1, serialCb) != 0)
+        printf("Serial-Read-Fail\r\n");
+}
+
+void cmd_help(uint8_t args_at)
+{
+    int i;
+    
+    for (i = 0; menu_items[i].cmd != NULL ; i++) {
+        printf("%s%s\t%s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description);
+    }
+}
+
+
+void print_wifi_result(const uint8_t *result)
+{
+    char out[96];
+    char str[24];
+    unsigned n, macStart;
+    wifiType_t wt;
+    wifiChanInfo_t ci;
+    wt.octet = result[0];
+    ci.octet = result[1];
+    out[0] = 0;
+    strcat(out, "802.11");
+    switch (wt.bits.signal) {
+        case 1: strcat(out, "b"); break;
+        case 2: strcat(out, "g"); break;
+        case 3: strcat(out, "n"); break;
+    }
+    sprintf(str, " %s %.1fMbps", wifiDatarates[wt.bits.datarate].txt, wifiDatarates[wt.bits.datarate].Mbps);
+    strcat(out, str);
+    strcat(out, " ");
+
+    sprintf(str, "ch%u ", ci.bits.channelID);
+    strcat(out, str);
+    switch (ci.bits.channelID) {
+        // table 10-5
+    }
+    strcat(out, " ");
+    sprintf(str, "mv:%u ", ci.bits.macValidationID);
+    strcat(out, str);
+    switch (ci.bits.macValidationID) {
+        case 1: strcat(out, "gateway"); break;
+        case 2: strcat(out, "phone"); break;
+        case 3: strcat(out, "?"); break;
+        // table 10.8
+    }
+
+    strcat(out, " ");
+
+    if (wifiResultFormatBasic) {
+        macStart = 3;
+    } else {
+        macStart = 4;
+    }
+    for (n = 0; n < 6; n++) {
+        sprintf(str, "%02x", result[n+macStart]);
+        strcat(out, str);
+        if (n < 5)
+            strcat(out, ":");
+    }
+
+    sprintf(str, " rssi:%d ", (int8_t)result[2]);
+    strcat(out, str);
+
+    if (!wifiResultFormatBasic) {
+        sprintf(str, "frameCtrl:%02x ", result[3]);
+        strcat(out, str);
+    }
+    printf("%s\r\n", out);
+}
+
+void take_result()
+{
+    printf("result %f, %f, %d\r\n", 
+        geoloc_result.lat,
+        geoloc_result.lng,
+        geoloc_result.accuracy
+    );
+
+    /* TODO: store result to database and show on map */
+
+    if (send_reply) {
+        unsigned len;
+        memcpy(Radio::radio.tx_buf, remote_chip_eui, 8);
+        Radio::radio.tx_buf[8] = 0; // rfu
+        Radio::radio.tx_buf[9] = 0; // rfu
+        len = sprintf((char*)(Radio::radio.tx_buf + HEADER_LENGTH), "%f, %f, %u",
+            geoloc_result.lat,
+            geoloc_result.lng,
+            geoloc_result.accuracy
+        );
+        Radio::Send(len + HEADER_LENGTH, 0, 0, 0);   /* begin transmission */
+        send_reply = false; // sent
+    }
+}
+
+void service()
+{
+    irq_t irq;
+    irq.dword = Radio::radio.service();
+    if (irq.bits.WifiDone) {
+        stat_t stat;
+        uint8_t nbResults;
+        json_start();
+        stat.word = Radio::radio.xfer(OPCODE_GET_WIFI_NB_RESULTS, 0, 0, NULL);
+        stat.word = Radio::radio.xfer(0x0000, 0, 1, &nbResults);
+        if (stat.bits.cmdStatus == CMD_DAT) {
+            unsigned n;
+            printf("%ums nbResults:%u\r\n", (unsigned)wifi_scan_dur, nbResults);
+            for (n = 0; n < nbResults; n++) {
+                uint8_t buf[3];
+                uint8_t resultBuf[22];
+                buf[0] = n;
+                buf[1] = 1; // number of results in this read
+                buf[2] = wifiResultFormatBasic ? 4 : 1;
+                stat.word = Radio::radio.xfer(OPCODE_WIFI_READ_RESULTS, 3, 0, buf);
+                // basic =  9byte length
+                // full  = 22byte length
+                stat.word = Radio::radio.xfer(0x0000, 0, wifiResultFormatBasic ? 9 : 22, resultBuf);
+                if (stat.bits.cmdStatus == CMD_DAT) {
+                    wifiChanInfo_t ci;
+                    print_wifi_result(resultBuf);
+                    ci.octet = resultBuf[1];
+                    if (ci.bits.macValidationID == 1)   // 1 is AP
+                        wifi_result_to_json(n == 0, resultBuf, wifiResultFormatBasic ? 3 : 4, 2);
+                } else
+                    printf("readResult:%s\r\n", Radio::radio.cmdStatus_toString(stat.bits.cmdStatus));
+            }
+        }
+        json_end();
+        //printf("JSON %s\r\n", json);
+        if (post_enable) {
+            printf("post_enabled\r\n");
+            if (nbResults > MINIMUM_REQUIRED_ACCESS_POINTS) {
+                post_scan_result(json, &geoloc_result.lat, &geoloc_result.lng, &geoloc_result.accuracy);
+                queue.call(take_result);
+            } else
+                printf("only %u access points\r\n", nbResults);
+        }
+
+        cfg_lora();
+        Radio::Rx(0);
+    } // ..if (irq.bits.WifiDone)
+}
+
+void radio_irq_handler()
+{
+    wifi_scan_dur = Kernel::get_ms_count() - wifi_start_at;
+    queue.call(service);
+}
+
+void txDoneCB()
+{
+    Radio::Rx(0);
+}
+
+void parse_remote_wifi_scan(uint8_t pktLen)
+{
+    uint8_t ap_cnt = 0;
+    uint8_t pkt_idx = HEADER_LENGTH;
+    json_start();
+    while (pkt_idx < pktLen) {
+        wifi_result_to_json(pkt_idx == HEADER_LENGTH, Radio::radio.rx_buf + pkt_idx, 0, 6);
+        if (strlen(json) >= sizeof(json)) {
+            printf("json-overrun\r\n");
+            return;
+        }
+        pkt_idx += 7;
+        ap_cnt++;
+    }
+    json_end();
+
+    if (ap_cnt > MINIMUM_REQUIRED_ACCESS_POINTS) {
+        post_scan_result(json, &geoloc_result.lat, &geoloc_result.lng, &geoloc_result.accuracy);
+        queue.call(take_result);
+    } else
+        printf("only %u access points\r\n", ap_cnt);
+
+    send_reply = true;
+}
+
+void rxDoneCB(uint8_t size, float rssi, float snr)
+{
+    unsigned i;
+    printf("%.1fdBm  snr:%.1fdB\t", rssi, snr);
+
+    for (i = 0; i < size; i++) {
+        printf("%02x ", Radio::radio.rx_buf[i]);
+    }
+    printf("\r\n");
+
+    for (i = 0; i < 8; i++)
+        remote_chip_eui[i] = Radio::radio.rx_buf[i];
+
+    parse_remote_wifi_scan(size);
+}
+
+const RadioEvents_t rev = {
+    /* Dio0_top_half */     radio_irq_handler,
+    /* TxDone_topHalf */    NULL,
+    /* TxDone_botHalf */    txDoneCB,
+    /* TxTimeout  */        NULL,
+    /* RxDone  */           rxDoneCB,
+    /* RxTimeout  */        NULL,
+    /* RxError  */          NULL,
+    /* FhssChangeChannel  */NULL,
+    /* CadDone  */          NULL
+};
+
+int main()
+{
+    {   /* wifi scan defaults, see LR1110 user manual section 10.2 */
+        unsigned chanmask = 0x0421; // ch1, ch6, ch11
+        unsigned timeout = 105; // in milliseconds, 100 wifi TUs (beacon interval)
+
+        wifiScan_buf[0] = 0x01; // wifi type
+        wifiScan_buf[2] = chanmask; // chanmask-lo
+        chanmask >>= 8;
+        wifiScan_buf[1] = chanmask; // chanmask-hi
+        wifiScan_buf[3] = 0x02; // acqMode
+        wifiScan_buf[4] = 0x0a; // NbMaxRes
+        wifiScan_buf[5] = 0x10; // NbScanPerChan
+        wifiScan_buf[7] = timeout; // Timeout-lo
+        timeout >>= 8;
+        wifiScan_buf[6] = timeout; // Timeout-hi
+        wifiScan_buf[8] = 0x00; // AbortOnTimeout
+    }
+
+    serialEventCb = serialCb;
+
+    if (pc.read(&serial_rx_buf, 1, serialCb) != 0)
+        printf("serial-read-fail\r\n");
+
+    // Connect to the network with the default networking interface
+    // if you use WiFi: see mbed_app.json for the credentials
+    network = connect_to_default_network_interface();
+    if (!network) {
+        printf("Cannot connect to the network, see serial output\n");
+        return 1;
+    }
+
+    Radio::Init(&rev);
+
+    Radio::Standby();
+    cfg_lora();
+    Radio::Rx(0);
+
+    queue.dispatch();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.h	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,25 @@
+#include "select-demo.h"
+
+#include "mbed.h"
+#if DEMO == DEMO_HTTPS
+    #include "https_request.h"
+#elif DEMO == DEMO_HTTP
+    #include "http_request.h"
+#endif
+
+#define GOOGLE      1
+#define COMBAIN     2
+//#define GEOLOCATION_PROVIDER        GOOGLE
+//#define GEOLOCATION_PROVIDER        COMBAIN
+
+#define HEADER_LENGTH           10  /* for chipEUI and extra reserved */
+
+int post_scan_result(const char*, float*, float*, int*);
+void wifi_result_to_json(bool first, const uint8_t *result, unsigned macStart, unsigned rssi_idx);
+void json_start(void);
+void json_end(void);
+extern char json[];
+extern bool wifiResultFormatBasic;
+extern NetworkInterface* network;
+void dump_response(HttpResponse* res);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed_app.json	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,31 @@
+{
+    "config": {
+        "main-stack-size": {
+            "value": 8192
+        }
+    },
+    "macros": [
+        "MBEDTLS_MPI_MAX_SIZE=1024",
+        "MBEDTLS_MPI_WINDOW_SIZE=1",
+        "MBEDTLS_USER_CONFIG_FILE=\"mbedtls_entropy_config.h\"",
+        "MBEDTLS_TEST_NULL_ENTROPY",
+        "MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES",
+        "MBED_HEAP_STATS_ENABLED=1"
+    ],
+    "target_overrides": {
+        "*": {
+            "platform.stdio-baud-rate": 115200,
+            "platform.stdio-convert-newlines": true,
+            "mbed-mesh-api.6lowpan-nd-channel-page": 0,
+            "mbed-mesh-api.6lowpan-nd-channel": 12,
+            "mbed-trace.enable": null,
+            "mbed-http.http-buffer-size": 2048,
+            "nsapi.default-wifi-security": "WPA_WPA2",
+            "nsapi.default-wifi-ssid": "\"SSID\"",
+            "nsapi.default-wifi-password": "\"Password\""
+        },
+        "DISCO_L475VG_IOT01A": {
+            "target.network-default-interface-type" : "WIFI"
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbedtls_entropy_config.h	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,28 @@
+/*
+ *  Copyright (C) 2006-2016, ARM Limited, All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ */
+
+#include "select-demo.h"
+
+/* Enable entropy for devices with TRNG. This means entropy is disabled for all other targets. */
+/* Do **NOT** deploy this code in production on other targets! */
+/* See https://tls.mbed.org/kb/how-to/add-entropy-sources-to-entropy-pool */
+#if defined(DEVICE_TRNG)
+#undef MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
+#undef MBEDTLS_TEST_NULL_ENTROPY
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/network-helper.h	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,34 @@
+#ifndef _MBED_HTTP_EXAMPLE_H_
+#define _MBED_HTTP_EXAMPLE_H_
+
+#include "mbed.h"
+#include "NetworkInterface.h"
+
+/**
+ * Connect to the network using the default networking interface,
+ * you can also swap this out with a driver for a different networking interface
+ * if you use WiFi: see mbed_app.json for the credentials
+ */
+NetworkInterface *connect_to_default_network_interface() {
+    printf("[NWKH] Connecting to network...\n");
+
+    NetworkInterface* network = NetworkInterface::get_default_instance();
+
+    if (!network) {
+        printf("[NWKH] No network interface found, select an interface in 'network-helper.h'\n");
+        return NULL;
+    }
+
+    nsapi_error_t connect_status = network->connect();
+
+    if (connect_status != NSAPI_ERROR_OK) {
+        printf("[NWKH] Failed to connect to network (%d)\n", connect_status);
+        return NULL;
+    }
+
+    printf("[NWKH] Connected to the network\n");
+    printf("[NWKH] IP address: %s\n", network->get_ip_address());
+    return network;
+}
+
+#endif // _MBED_HTTP_EXAMPLE_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/select-demo.h	Tue Feb 09 10:49:02 2021 -0800
@@ -0,0 +1,14 @@
+#ifndef _SELECT_METHOD_H_
+#define _SELECT_METHOD_H_
+
+#define         DEMO_HTTP                   1
+#define         DEMO_HTTP_SOCKET_REUSE      2
+#define         DEMO_HTTP_IPV6              3
+#define         DEMO_HTTPS                  4
+#define         DEMO_HTTPS_SOCKET_REUSE     5
+#define         DEMO_HTTPS_CHUNKED_REQUEST  6
+#define         DEMO_TESTS                  7
+
+#define         DEMO            DEMO_HTTPS
+
+#endif // _SELECT_METHOD_H_