oWL Pico API Tutorial

This section will give an introdution to network software development with the oWL Pico API.

The oWL Pico API has a footprint of a few kilobytes and provides functions to support full Wi-Fi link management, network address management and TCP, UDP and RAW sockets - resembling a standard BSD socket interface.

In the available reference design, see DownloadSources, the oWL Pico API sources is located in the wlp_api/ folder. Basically, only the file wlp_api/wlp_api.c and wlp_[transport].c (where [transport] can be uart, sdio or spi depending on the host transport method) needs to be compiled in order to develop a custom application with the oWL Pico API. However, wlp_api/wlp_inet.c contains utility function to convert IP addresses to strings etc, and can be used if desired.

See the file wlp_api/wlp_api.h for the oWL Pico API reference documentation. This page is only supplemental information to the documentation in wlp_api/wlp_api.h.

There are three important properties of the oWL Pico API to keep in mind when programming with it.

The first is that oWL Pico API is asynchronous. For instance, when the wlp_linkup() function is called to attempt connection with an access point it will trigger a sequence of packets being exchanged with the access point after which, if everything is okay, a connection has been established. The wlp_linkup() call is asynchronous (or non-blocking) which means that you don't know if the connection attempt has succeeded after the call returns. You only know if the sequence was successfully started or not. To find out if, and when, the connection attempt was successful you must register an event handler using the function wlp_set_link_cb(). This is true of a number of API calls (which is indicated in their documentation).

The second important property is that the oWL Pico API is polled. oWL Pico API never executes "by itself", since it would then have to support interrupts, timers, locks and other operating system dependent features. Instead all asynchronous processes proceed when oWL Pico API is polled by calling the wlp_poll() function. When wlp_poll() is called the oWL Pico API reacts to any received packets, expires any internal timers and performs any other tasks necessary for forward progress. After wlp_poll() returns nothing will happen unless it or some other oWL Pico API function is called again.

The third important property is that oWL Pico API is not thread safe. All oWL Pico API calls must execute in the same context since the library has no knowledge of the locking mechanisms available (if any).

Note that event though the oWL Pico API is asynchrounous, a synchrounous-like, blocking, interface can be easily used. This is described in the last section of this page.

This section will show examples of how to perform the following operations using the oWL Pico API:


Initializing the oWL Pico API

Before any network communication can be implemented, the oWL Pico API must be initialized. To perform the initialization, wlp_init() should be called:

mdelay(1300);           /* wait until device is booted */

uart_init(115200);      /* configure host UART */
sdio_init();               /* or configure host SDIO */
spi_init();                /* or configure host SPI */
 
wlp_init(
     board_exchange, /* function that will read and write data to the transport layer */
     select_cb  /*  /* callback for toggling Chip Select when using SPI */
     sdio_cmd,  /* function that will write SDIO cmds when using SDIO */
     board_poll, /* function that will poll UART incoming data if UART is used */
     NULL,          /* context passed to board_exchange */);
     flags)          /* WLP_HPI_POLL, WLP_SDIO_4BIT */ 

The UART baudrate can be later configured with the new wlp_set_baudrate() function.
Since the oWL Pico API needs to read and write data from the SPB820 device, methods for reading and writing to the UART/SDIO/SPI port must be provided. The implementation of these function depends on the particular platform used. In the reference design software, example implementations are provided in the platform/linux/owl/owl_uart.c and /platform/STM32/owl/{owl_uart.c, owl_spi.c, owl_sdio.c} files.

The parameters board_exchange, select_cb, sdio_cmd and board_poll provided to wlp_init() above are callback functions that should implement writing and reading to the transport layer, sending sdio_cmds, poll the uart port and toggling the SPI
Chip select pin. Depending on the transport layer, some callbacks are not used. These callbacks are used if different transport layers are used:

UART: board_exchange, board_poll
SDIO: board_exchange, sdio_cmd
SPI: board_exchange, select_cb

E.g. if SPI is used, the board_poll parameter can be NULL when calling wlp_init().

Below is a pseduo-implemenation example for the board_exchange parameter when using UART:

int board_exchange(void *ctx, const void *tx, void *rx, int len)
{
        int err = 0;
        
        if (tx) {
                err = uart_write(ctx, tx, len);
        } 
        if (rx) {
                err = uart_read(ctx, rx, len);
        }

        return err;
}

Where uart_write and uart read can look like:

int uart_read(void *ctx, void *rx, int len)
{
    int i;
    char *in = rx;
    uart1 = ctx;

    for (i = 0; i < len; i++) { /* loop until all bytes are read */
        while (!(uart1->STATUS & UART_RXREADY_BIT)); /* wait for a char */
        in[i] = uart1->DATA; /* read the char into the provided buffer */
    }

    return len;
}

int uart_write(void *ctx, const void *tx, int len)
{
    int i;
    const char *out = tx;
    uart1 = ctx;

    for (i = 0; i < len; i++) { /* loop until all bytes are written */
        while (!(uart1->STATUS & UART_TXEMPTY_BIT)); /* wait until we are ready to transmit next char */
        uart1->DATA = out[i]; /* transmit the char */
    } 
    
    return len;
}

In most cases we want to be notified when somethings happens, e.g. the connection status to the access point changes, IP address is acquired, etc. Therefore, we'll register callback functions for these events:

wlp_set_link_cb(link_f,   /* function that will be called when the link status changes */
                NULL      /* context passed to link_f */);
wlp_set_ipaddr_cb(addr_f, /* function that will be called when the IP address changes */
                  NULL    /* context passed to addr_f */);

Again, we pass functions as parameters. The link_f function will be called every time the link status, e.g. the connection to the access point, changes. link_f can be implemented to simply print the current connection status:

static void link_f(void *ctx, int link)
{        
    printf("link_cb link:%d\n\r", link);
} 

The addr_f function will be called every time our IP address changes. addr_f can be implemented to simply print our current IP address:

static void addr_f(void *ctx, const struct ip_addr *ip)
{        
    if (ip == NULL)
        printf("addr_cb ip:none\n\r");
    else
        printf("addr_cb ip:%s\n\r", inet_ntoa(ip));
}

Note the usage of inet_ntoa() above. This function is declared in wlp_api/wlp_inet.h. wlp_aip/wlp_inet.h provides a set of utility functions to handle IP addresses.

In order to ensure that the registered callbacks are eventually called, the current status must be read from the device periodically. This is performed by the function wlp_poll(). Therefore, ensure that wlp_poll() is called peridically. In this example we will invoke wlp_poll() at the end of our application main function like this:

for (;;)
    wlp_poll();

A lso note, if the blocking-style of programming rather than the non-blocking (callback style) programming is used, nor callbacks or the calls to wlp_poll() are not necessary. See the example describing the blocking usage of oWL Pico API at the end of this page for details.

Finally, see main() in main.c for a complete example on how to perform a proper initialization of the oWL Pico API.

Connect to an unprotected network

Once the oWL Pico API is initialized we will try to connect to an unprotected network.

Networks are identified through SSID, which is the name of the network, and/or BSSID, which is the hardware address of the networks. Note that the SSID is not unique, so chances are there exists several networks in the range which have the same SSID. However, BSSID is unique for every network.

Since an SSID can contain NULL-chars, the type wl_ssid_t is used for SSIDs. The code to connect to the access point "hdwireless" is shown below:

struct wl_ssid_t ssid;           
strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

if (wlp_linkup(&ssid, NULL, 0) < 0) /* start linkup process */
    err("linkup failed");

By adding initialization code the more complete example becomes:

#ifdef WL_MODE_AP /* introduced in 1.2.0 */
#define WLP_VERSION_1_2_0_OR_LATER
#endif

int main(void) 
{
    struct wl_ssid_t ssid;  

    mdelay(200);           /* wait until device is booted */
    uart_init(57600);      /* configure host UART */

    wlp_init(
#ifndef WLP_VERSION_1_2_0_OR_LATER
         115200,       /* baudrate */
         0,            /* no hardware flow control */
#endif
         uart_read_f,  /* function that will read buffer from uart */
         uart_write_f, /* function that will write buffer to uart */
         NULL          /* context passed to uart_read_f and uart_write_f */);


#ifdef WLP_VERSION_1_2_0_OR_LATER
    wlp_set_baudrate(115200, 0); /* configure uart on device */
#endif

    mdelay(100);           /* wait until device has reconfigured its UART */
    uart_init(115200);     /* reconfigure host UART */

    wlp_set_link_cb(link_f,   /* function that will be called when the link status changes */
                    NULL      /* context passed to link_f */);
    wlp_set_ipaddr_cb(addr_f, /* function that will be called when the IP address changes */
                      NULL    /* context passed to addr_f */);

    strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
    ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

    if (wlp_linkup(&ssid, NULL, 0) < 0) /* start linkup process */
        err("linkup failed");

    for (;;)
        wlp_poll(); /* make forward progress */

}

Upon successful return from wlp_linkup(), the wifi device will try to establish and maintain a connection to the network specified by the parameters. When the link status (connected, disconnected) changes, the caller will be notified of the new status through the link_f callback registered in wlp_set_link_cb(). See the documentation for wlp_linkup() in wlp_api/wlp_api.h for details.

Now, we can modify our link_f callback to print the actual SSID and BSSID of the access point once the link is up. To get information about the current access point we use the wlp_get_network() function:

static void link_f(void *ctx, int link)
{    
    if (link) {
        int res;
        struct wlp_network_t net;
        char ssid_str[WL_SSID_MAX_LENGTH + 1];
        if (wlp_get_network(&net) < 0) { /* get network info */
            err("get_network failed");
            return;
        }
                
        memcpy(ssid_str, net.ssid.ssid, net.ssid.len);
        ssid_str[net.ssid.len] = 0;
                
        printf("link up: %s %02x:%02x:%02x:%02x:%02x:%02x\n\r",
               net.ssid.len ? ssid_str : "N/A",
               net.bssid.octet[0], net.bssid.octet[1],
               net.bssid.octet[2], net.bssid.octet[3],
               net.bssid.octet[4], net.bssid.octet[5]);
    } else {

        printf("link down\n\r");
    }
}

See the documentation for wlp_get_network() in wlp_api/wlp_api.h for details.

It is important to keep in mind is that despite the fact that the SSID is usually presented as a ASCII string, it is in fact just a byte string and can legally contain all kinds of non-printable characters, including a 0-byte. This means that it is easy to end up with buffer overrun bugs if the SSID is ever treated as a normal string without precautions.

Assign an IP address using static configuration or DHCP

If a static network address configuration should be used, set it using the wlp_set_ipaddr() function. The network configuration consists of the ip address, the network mask, the default gateway and dns server. These are all set through wlp_set_ipaddr() function:

struct ip_addr ip, netmask, gateway, dnsserver;
IP4_ADDR(&ip, 192, 168, 2, 22);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gateway 192, 168, 2, 1);
IP4_ADDR(&dnsserver, 192, 168, 2, 1);

wlp_set_ipaddr(&ip, &netmask, &gateway, &dnsserver);

Note the use of the macro IP4_ADDR to create IP addresses. The macro is defined in wlp_api/wlp_inet.h. When a static IP address is used, the addr_f callback function will be invoked when the link status changes.

To set a dynamic ip address with DHCP, the set_dhcp command should be used:

wlp_set_dhcp(1);

When the link is up and an IP address is obtained from the DHCP server the addr_f callback function will be invoked.

See the documentation for wlp_set_ipaddr_cb(), wlp_set_ipaddr(), wlp_set_dhcp(), wlp_get_ipaddr() and wlp_get_dhcp() in wlp_api/wlp_api.h for details.

By adding DHCP to our more complete example, the code becomes:

#ifdef WL_MODE_AP /* introduced in 1.2.0 */
#define WLP_VERSION_1_2_0_OR_LATER
#endif

int main(void) 
{
    struct wl_ssid_t ssid;  

    mdelay(200);           /* wait until device is booted */
    uart_init(57600);      /* configure host UART */

    wlp_init(
#ifndef WLP_VERSION_1_2_0_OR_LATER
         115200,       /* baudrate */
         0,            /* no hardware flow control */
#endif
         uart_read_f,  /* function that will read buffer from uart */
         uart_write_f, /* function that will write buffer to uart */
         NULL          /* context passed to uart_read_f and uart_write_f */);


#ifdef WLP_VERSION_1_2_0_OR_LATER
    wlp_set_baudrate(115200, 0); /* configure uart on device */
#endif

    mdelay(100);           /* wait until device has reconfigured its UART */
    uart_init(115200);     /* reconfigure host UART */

    wlp_set_link_cb(link_f,   /* function that will be called when the link status changes */
                    NULL      /* context passed to link_f */);
    wlp_set_ipaddr_cb(addr_f, /* function that will be called when the IP address changes */
                      NULL    /* context passed to addr_f */);

    strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
    ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

    if (wlp_linkup(&ssid, NULL, 0) < 0) /* start linkup process */
        err("linkup failed");

    if (wlp_set_dhcp(1) < 0)
        err("set_dhcp failed");
 
   for (;;)
        wlp_poll(); /* make forward progress */

}

Disconnect from the network

Disconnect from the network by using the function wlp_linkdown():

wlp_linkdown();

When disconnect process is complete, the link_f callback function will be invoked, indicating that the link is down.

We will not add the call to wlp_linkdown() to the complete example here since it does not really make sense in such a simple example. Typically, some event will occur that triggers the call to wlp_linkdown(), e.g. a user button press or menu selection.

Connect to a network using WEP security

To connect to a network with WEP security we need to specify the WEP key in the call to wlp_linkup():

struct wl_ssid_t ssid; 
const char *wep_key = "a1b2c3d4e5";
strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

if (wlp_linkup(&ssid, wep_key, 1) < 0) /* start linkup process */
    err("linkup failed");

As usual, the link_f callback function will be invoked when the connection status changes. Since we're trying to connect to a protected network, the link will not establish unless the key is correct.

Connect to a network using WPA security

To connect to a network with WPA security we need to specify the WPA key in the call to wlp_linkup():

struct wl_ssid_t ssid; 
const char *wpa_key = "mysecretkey";
strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

if (wlp_linkup(&ssid, wpa_key, 0) < 0) /* start linkup process */
    err("linkup failed");

As usual, the link_f callback function will be invoked when the connection status changes. Since we're trying to connect to a protected network, the link will not establish unless the key is correct.

Obtain the mac address from the Wi-Fi device

As soon as the oWL Pico API is initialized, it is possible to read the MAC address from the SPB800 device:

int res;
struct wl_mac_addr_t hwaddr;
res = wlp_get_hwaddr(&hwaddr);

if (res < 0) {
    err("get_hwaddr failed");
} else {
    printf("%02x:%02x:%02x:%02x:%02x:%02x\n\r",
           hwaddr.octet[0], hwaddr.octet[1], hwaddr.octet[2],
           hwaddr.octet[3], hwaddr.octet[4], hwaddr.octet[5]);
}

See wlp_get_hwaddr() in wlp_api/wlp_api.h for details.

Connect to a remote TCP socket

So far, functions to configure and get information on the link and network layer has been used.

To perform actual communication with other devices or servers sockets can be used. There exists several functions to work with sockets. The following sections will give a brief introduction to the most basic socket operations.

In this example, we’ll connect to a TCP socket on a linux PC. We’ll create a listening socket on the PC using the netcat utility:

angr@blorg:~/work $ nc -l -p 2000

The linux PC has the IP address 192.168.2.100 and is connected to the hdwireless AP (through wire or wireless).

As sockets operate above the link and network layer we need to establish the communication on those layers to have a working socket connection. As already mentioned, the addr_f callback function will be invoked when we have a link and an IP address. Therefore we will modify our addr_f callback function to perform a socket connect.

To connect to a remote TCP socket we must first create a socket endpoint using the wlp_socket() function. This socket will then be passed to the wlp_connect() call. It is possible to create and use multiple sockets, therefore a socket identifier must be passed to each call that operates on a specific socket. As wlp_connect() is an asynchrounous operation, we can register a callback functions that will be invoked once the connection attempt is completed or when an already established connection is lost. The function wlp_set_conn_cb() is used to register this callback.

int sockid;
struct ip_addr remote_ip;
IP4_ADDR(&remote_ip, 192, 168, 2, 100);
sockid = wlp_socket(WLP_SOCK_STREAM, /* TCP socket */
                    0                /* protocol, not used for TCP sockets */);
if (sockid <= 0) {
    err("socket failed");
    return;
}
if (wlp_connect(sockid,     /* socket identifier */
                &remote_ip, /* ip address to connect to */
                2000        /* port to connect to */) < 0) {
    err("connect failed");
    return;
}

wlp_set_conn_cb(sockid,     /* socket identifier */
                conn_f,     /* function that will be invoked when connection attempt is complete */
                NULL        /* context passed to conn_f and recv_f */);

If wlp_connect() returns successfully, the callback function conn_f will be invoked when the connection attempt is completed. The socket identifier and the connection result will be passed to the conn_f function. We will just print the connection info in our conn_f function:

static void conn_f(void *ctx, int sockid, int connected)
{
    printf("sockid:%d connected:%d\n\r", sockid, connected);
}

We will add the socket connect to the complete example by performing the actual connect in the addr_f callback. Note that nothing prevents us from performing the socket connect before addr_f is invoked, however this would result in either an error code being returned from wlp_connect() or conn_f eventually being called indicating that the connection failed. Note that there is also a call to wlp_set_recv_cb() below; this will be handled in the next topic where we actually send and receive data on our socket.

int sockid = WLP_INVALID_SOCKET_ID; /* global socket identifier */

static void conn_f(void *ctx, int sockid, int connected)
{
    printf("sockid:%d connected:%d\n\r", sockid, connected);
}

static void addr_f(void *ctx, const struct ip_addr *ip)
{        
   if (ip == NULL) {
        printf("ip:none\n\r");
   } else {
        struct ip_addr remote_ip;
        IP4_ADDR(&remote_ip, 192, 168, 2, 100);
        printf("ip:%s\n\r", inet_ntoa(ip));
        
        /* create a socket */
        sockid = wlp_socket(WLP_SOCK_STREAM, /* TCP socket */
                            0                /* protocol, not used for TCP sockets */);
        if (sockid <= 0) {
            err("socket failed");
            return;
        }

        /* start connect process */
        if (wlp_connect(sockid,     /* identifier for the socket created in main() */
                        &remote_ip, /* ip address of our PC */
                        2000,       /* port the PC is listening on */) < 0) {
            err("connect failed");
            return;
        }
        
        wlp_set_conn_cb(sockid,     /* socket identifier */
                        conn_f,     /* invoked when connection attempt is complete */
                        NULL        /* context passed to conn_f */);    
        
        wlp_set_recv_cb(sockid,     /* socket identifier */
                        recv_f,     /* invoked when there is pending data to read */
                        NULL        /* context passed to recv_f */);
    }
}

#ifdef WL_MODE_AP /* introduced in 1.2.0 */
#define WLP_VERSION_1_2_0_OR_LATER
#endif

int main(void) 
{
    struct wl_ssid_t ssid;

    mdelay(200);           /* wait until device is booted */
    uart_init(57600);      /* configure host UART */

    wlp_init(
#ifndef WLP_VERSION_1_2_0_OR_LATER
         115200,       /* baudrate */
         0,            /* no hardware flow control */
#endif
         uart_read_f,  /* function that will read buffer from uart */
         uart_write_f, /* function that will write buffer to uart */
         NULL          /* context passed to uart_read_f and uart_write_f */);


#ifdef WLP_VERSION_1_2_0_OR_LATER
    wlp_set_baudrate(115200, 0); /* configure uart on device */
#endif

    mdelay(100);           /* wait until device has reconfigured its UART */
    uart_init(115200);     /* reconfigure host UART */

    wlp_set_link_cb(link_f,   /* function that will be called when the link status changes */
                    NULL      /* context passed to link_f */);
    wlp_set_ipaddr_cb(addr_f, /* function that will be called when the IP address changes */
                      NULL    /* context passed to addr_f */);

    strcpy(ssid.ssid, "hdwireless"); /* specifiy the ssid */
    ssid.len = strlen("hdwireless"); /* the number of valid bytes in the ssid */

    if (wlp_linkup(&ssid, NULL, 0) < 0) /* start linkup process */
        err("linkup failed");

    if (wlp_set_dhcp(1) < 0)
        err("set_dhcp failed"); 

    for (;;)
        wlp_poll(); /* make forward progress */

    return 0; /* unreachable */
}

Note that there's at least one problem here; what happens if the connection status changes many times? The addr_f callback function is called every time the IP address is no longer valid and when it becomes valid again. Every time addr_f is called and indicating that we have an IP address, a new socket is created. However, the sockets are never free'd, so we'll leak sockets here if the connection is lost and then re-established. This can be tried by e.g. power-cycling the AP which forces the SPB800 device to reconnect, using the settings from the last call to wlp_linkup() and wlp_set_dhcp() (or wlp_set_ipaddr()).

We'll add a call to wlp_close(), which closes and free's a socket, to addr_f when the IP address is no longer valid:

static void addr_f(void *ctx, const struct ip_addr *ip)
{        
   if (ip == NULL) {
        printf("ip:none\n\r");
        if (sockid != WLP_INVALID_SOCKET_ID) {
            wlp_close(sockid); /* close and free the socket */
            sockid = WLP_INVALID_SOCKET_ID;
        }

   } else {
        struct ip_addr remote_ip;
        IP4_ADDR(&remote_ip, 192, 168, 2, 100);
        printf("ip:%s\n\r", inet_ntoa(ip));
        
        /* create a socket */
        sockid = wlp_socket(WLP_SOCK_STREAM, /* TCP socket */
                            0                /* protocol, not used for TCP sockets */);
        if (sockid <= 0) {
            err("socket failed");
            return;
        }

        /* start connect process */
        if (wlp_connect(sockid,     /* identifier for the socket created in main() */
                        &remote_ip, /* ip address of our PC */
                        2000,       /* port the PC is listening on */) < 0) {
            err("connect failed");
            return;
        }

        wlp_set_conn_cb(sockid,     /* socket identifier */
                        conn_f,     /* invoked when connection attempt is complete */
                        NULL        /* context passed to conn_f */);

        wlp_set_recv_cb(sockid,     /* socket identifier */
                        recv_f,     /* invoked when there is pending data to read */
                        NULL        /* context passed to recv_f */);
    }
}

Note that wlp_close() will close the socket independet of its state; i.e it does not matter whether the socket is actually connected or not.

See wlp_socket(), wlp_connect() and wlp_close() in wlp_api/wlp_api.h for details.

Finally, in a real application one might want to separate the task of connecting sockets from the link and network address management. One might trigger the socket connect when a certain event occur, e.g. a user button press.

Send and receive data on a socket

To send and receive data on a socket, the functions wlp_send() and wlp_recv() can be used. For TCP sockets, a connection must be established before any data transfer can occur. There exists other types of sockets (UDP and RAW) that are connectionless, i.e. no connection need to be established before data transfer can happen. This allows sending data to named correspondents using the wlp_sendto() call.

To keep our example simple, we'll transfer a short string of data as soon as a socket is successfully connected. Again, in a real application, one might want to send data upon a certain event rather than when the TCP socket enters the connected state.

We'll add the call to wlp_send() in the conn_f callback function:

static void conn_f(void *ctx, int sockid, int connected)
{
    char *data = "Hello World\n\r";
    int pos = 0;
    int len = strlen(data);

    printf("sockid:%d connected:%d\n\r", sockid, connected);

    if (!connected)
        return; /* don't send anything unless we are connected */

    while (pos < len) { /* loop until all data is sent */
        int res;
        
        /* send a chunk of data */
        if ((res = wlp_send(sockid, data + pos, len - pos)) < 0) {
            err("send failed");
            return;
                                
        } else {
            printf("wrote:%d remain:%d\n\r", res, len - pos - res);

        }
        
        pos += res;
    }
   
}

Note that wlp_send() will return the number of bytes that was actually sent. Therefore, we will loop until all the data is sent.

We could possible receive data from a connected socket at any time. The function wlp_set_recv_cb() can be used to register a function that will be called whenever there is pending data to read from the socket:

wlp_set_recv_cb(sockid,     /* socket identifier */
                recv_f,     /* invoked when there is pending data to read */
                NULL        /* context passed to recv_f */);

The socket id and the number of pending bytes will be passed as parameters to recv_f. We will implement a recv_f that just prints all the data received on the socket:

static void recv_f(void *ctx, int sockid, int len)
{
    int res;
    char buf[64];
        
    while (len > 0) {
        int recvlen;

        /* find out how many bytes we should read into our local buffer */
        if (len > sizeof(buf))
            recvlen = sizeof(buf);
        else
            recvlen = len;
                
        res = wlp_recv(sockid, buf, recvlen);
        if (res < 0) /* fail to read */
            err("fail to recv %d bytes err:%d\n\r", recvlen, res);
        else if (res != len) /* partial read */
            printf("got %d bytes, %d remains\n\r", res, len - res);

        if (res > 0) {
            int i;
            len -= res;

            /* print received data */
            for (i = 0; i < res; i++)
                printf("%02x ", buf[i]);
        }
    }
}

Similar to wlp_send(), wlp_recv() will return the number of bytes actually read. Also, we are reading data into a fixed-size local buffer. Therefore, we will loop until all data is received.

Note that recv_f can be invoked with the len parameter set to 0. This indicates that the connection was closed by the remote end.

See wlp_send(), wlp_recv(), wlp_sendto() in wlp_api/wlp_api.h for details.

Listen for incoming TCP connections

In this example we will reverse the roles of the application and the PC; the PC will connect to a TCP socket which we listen on. To listen for incoming connections, we must first create a socket, using the function wlp_socket() in the same way as when we used a socket to connect to a remote TCP server. To associate our socket to a specific port, the function wlp_bind() should be used. Finally, we can call wlp_listen() to start the listening operation. After listening is started, a callback function registered in the call to wlp_listen() will be invoked as soon as there is a pening connection. A pending connection can be accepted by calling wlp_accept() and will then be in the connected state and allows data to be transferred.

One important property when accepting incoming connections is that a new socket will be created for every incoming connection. This allows e.g. a server to listen on a single port while still handling many clients; all through their own socket.

The following code will create a TCP socket which listens for incoming connections on port 2000:

int sockid;

/* create a TCP socket */
sockid = wlp_socket(WLP_SOCK_STREAM, /* TCP socket */
                    0                /* protocol, not used for TCP sockets */);
if (sockid <= 0) {
    err("socket failed");
    return;
}

/* bind the socket to port 2000 */
if (wlp_bind(sockid,      /* socket identifier */
             IP_ADDR_ANY, /* listen on any IP address (we only have one anyway) */
             2000         /* listen on port 2000 */) < 0) {
    err("bind failed");
    return;
}

/* start listening */
if (wlp_listen(sockid,     /* socket identifier */
               1,          /* number of non-accepted pending connections to allow */
               listen_f,   /* function that will be invoked when there is a pending connection */
               NULL        /* context passed to listen_f */) < 0) { 
    err("listen failed");
    return;
}

The listen_f callback function will be invoked when there is a new incoming connection pending. In the implementation of listen_f function, we will simply accept the new connection. Note that the socket identifier passed to listen_f is the id of the listening socket and that wlp_accept() will return the id of the new connection.

static void listen_f(void *ctx, int sockid)
{
    int acceptid;
    printf("accepting:%d\n\r", sockid);

    if ((acceptid = wlp_accept(sockid)) < 0) {
        err("accept failed");
        return;
    } 

    wlp_set_conn_cb(acceptid,   /* socket identifier */
                    conn_f,     /* function that will be invoked when connection attempt is complete */
                    NULL        /* context passed to conn_f */);

    wlp_set_recv_cb(acceptid,   /* socket identifier */
                    recv_f,     /* invoked when there is pending data to read */
                    NULL        /* context passed to recv_f */);
}

Similar to the case with the wlp_connect() function, we will call wlp_set_conn_cb() and wlp_set_recv_cb() on our new socket to register the conn_f and recv_f callback functions. Once connected, there is no difference between a connection established through wlp_connect() or wlp_accept().

We will add the socket listen to the complete example by starting the listening in the addr_f callback. Note that nothing prevents us from performing the socket connect before addr_f is invoked, however this would result in either an error code being returned from wlp_bind() indicating that we don't have an IP address to listen on.

int sockid = WLP_INVALID_SOCKET_ID; /* global identifier for our listening socket */

static void addr_f(void *ctx, const struct ip_addr *ip)
{        
   if (ip == NULL) {
        printf("ip:none\n\r");
        if (sockid != WLP_INVALID_SOCKET_ID) {
            wlp_close(sockid); /* stop listening and free the socket */
            sockid = WLP_INVALID_SOCKET_ID;
        }

   } else {
        printf("ip:%s\n\r", inet_ntoa(ip));
        
        /* create a socket */
        sockid = wlp_socket(WLP_SOCK_STREAM, /* TCP socket */
                            0                /* protocol, not used for TCP sockets */);
        if (sockid <= 0) {
            err("socket failed");
            return;
        }

        /* bind the socket to port 2000 */
        if (wlp_bind(sockid,      /* socket identifier */
                     IP_ADDR_ANY, /* listen on any IP address (we only have one anyway) */
                     2000         /* listen on port 2000 */) < 0) {

            err("bind failed");
            return;
        }

        /* start listening */
        if (wlp_listen(sockid,     /* socket identifier */
                       1,          /* number of non-accepted pending connections to allow */
                       listen_f,   /* function that will be invoked when there is a pending connection */
                       NULL        /* context passed to listen_f */) < 0) { 
            err("listen failed");
            return;
        }
    }
}

When wlp_accept() returns successfully, the new socket is connected and data transmission and reception can be performed e.g. in the same way as we did in the previous example:

static void listen_f(void *ctx, int sockid)
{
    char *data = "Hello World\n\r";
    int pos = 0;
    int len = strlen(data);
    int acceptid;

    printf("accepting:%d\n\r", sockid);
    if ((acceptid = wlp_accept(sockid)) < 0) {
        err("accept failed");
        return;
    }

    wlp_set_conn_cb(acceptid,   /* socket identifier */
                    conn_f,     /* function that will be invoked when connection attempt is complete */
                    NULL        /* context passed to conn_f */);

    wlp_set_recv_cb(acceptid,   /* socket identifier */
                    recv_f,     /* invoked when there is pending data to read */
                    NULL        /* context passed to recv_f */);

    while (pos < len) { /* loop until all data is sent */
        int res;
        
        /* send a chunk of data */
        if ((res = wlp_send(acceptid, data + pos, len - pos)) < 0) {
            err("send failed");
            return;
                                
        } else {
            printf("wrote:%d remain:%d\n\r", res, len - pos - res);

        }
        
        pos += res;
    }
   
}

The recv_f callback function will be identical to the one in the previous example, so we will not repeat it here. Again, in a real application, one might want to send data upon a certain event rather than when the TCP socket enters the connected state.

See wlp_socket(), wlp_listen() and wlp_accept() in wlp_api/wlp_api.h for details.

To connect to our "server" running on port 2000 the netcat application can be used from a linux PC, make sure to replace the IP address below with the actual address assigned to the device:

angr@blorg:~$ nc 192.168.2.22 2000
Hello World

Upon successful connect, we should receive the "Hello World" string that was sent by our oWL Pico application.

Lookup a DNS name

The previous example where a TCP socket was used to communicate with a remote server used a fixed IP addresses to specifiy which server to connect to. oWL Pico API also has support for DNS, which means that we can lookup which IP address belongs to a given DNS name, e.g. www.hd-wireless.se.

To lookup a DNS name use the function wlp_get_hostbyname() :

if (wlp_get_hostbyname("www.hd-wireless.se",  /* hostname to lookup */
                       lookup_f,              /* function to call when lookup is complete or considered failed */
                       NULL                   /* context passed to lookup_f */) < 0) 
    err("get_hostbyname failed");

The lookup_f callback function is passed to wlp_get_hostbyname(). This function will be invoked when the lookup is complete. The IP address corresponding to the hostname will be passed as a parameter to lookup_f upon successful lookup:

static void lookup_f(void *ctx, const struct ip_addr *ip)
{
    if (ip == NULL)
        printf("lookup_cb: host not found\n\r");
    else
        printf("ip:%s\n\r", inet_ntoa(ip));
}

In order to use wlp_get_hostbyname(), we need to have a proper DNS server configured. This could either be done statically with the wlp_set_ipaddr() function or dynamically with the wlp_set_dhcp() function.

See wlp_get_hostbyname() in wlp_api/wlp_api.h for details.

Use the oWL Pico API to configure the device as an access point

The SPB800 device supports access point mode in fw versions 1.2.1 or later. The access point mode allows the device to act as an access point which clients (e.g. PC's, phones, other SPB800 devices, etc) can connect to. All functions included in the oWL Pico API works in access point mode as well. E.g socket creation, address assignment and dns functions have equal function in access point mode as in station mode. The access point mode only changes how the Wi-Fi link is established.

To create an access point, first make sure that the SPB800 device operates in access point mode by using the wlp_set_mode() function:

wlp_set_mode(WL_MODE_AP);
wlp_reset();

The call to wlp_set_mode() only takes effect after wlp_reset() has been called. The current mode can be obtainted through wlp_get_mode():

uint8_t mode;

wlp_get_mode(&mode);

if (mode == WL_MODE_STA)
    printf("operation mode: sta\n\r");
else if (mode == WL_MODE_AP)
    printf("operation mode: ap\n\r");

Note that the operation mode will persists after a power cycle, so it is generally a good idea to check the operation mode at program startup.

To start transmitting beacons (so clients can see our access point), use wlp_linkup():

struct wl_ssid_t ssid; 
strcpy(ssid.ssid, "spb800 access point"); /* specifiy the ssid */
ssid.len = strlen("spb800 access point"); /* the number of valid bytes in the ssid */
wlp_linkup(&ssid, NULL, 1); /* start access point with SSID "spb800 access point", no encryption, channel 1 */

Similar to station mode, a function can be registered that will be invoked when the link status changes. In access point mode, "link up" means that at least one station is connected.

static void link_f(void *ctx, int link)
{        
    printf("link_cb link:%d\n\r", link);
}
wlp_set_link_cb(
    link_f,   /* function that will be called when first client connects to our access point */
    NULL      /* context passed to link_f */);

In access point mode, it is very common that clients will configure their ip addresses using DHCP. Therefore, the SPB800 device supports an embedded DHCP server. It can be enabled using the wlp_set_dhcpd() function:

struct ip_addr ip, netmask, gateway, dnsserver;
IP4_ADDR(&ip, 192, 168, 2, 22);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gateway 192, 168, 2, 1);
IP4_ADDR(&dnsserver, 192, 168, 2, 1);

wlp_set_ipaddr(&ip, &netmask, &gateway, &dnsserver); /* configure SPB800 ip address */
wlp_set_dhcpd(1);                                    /* start dhcp server */

Note that the IP address of the SPB800 device should be configured as well to make sure that the DHCP server offers ip addresses in the correct network range.

Using oWL Pico API in blocking-style

TBD

Topic revision: r2 - 2015-08-27 - 12:23:25 - AndersGrahn
 
This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback