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

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();
}