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:
- google geolocation
- combain geolocation
TODO
others?
(choose one) Put your API key for geolocation provider into the URL in geolocation source file (such asgoogle_geolocation.cpp
orcombain.cpp
)
notice
This project is not using LoRaWAN, instead just LoRa transceiver directly to geolocation provider
main.cpp
- Committer:
- Wayne Roberts
- Date:
- 2021-02-09
- Revision:
- 1:4a05f91c9c38
File content as of revision 1:4a05f91c9c38:
#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(); }