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
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. By compiling wlp_api.c with the
-Os
flag (if gcc is used), it will become roughly 4 kb on an AVR8 platform and about 2 kb on an x86 platform.
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:
#ifdef WL_MODE_AP /* introduced in 1.2.0 */
#define WLP_VERSION_1_2_0_OR_LATER
#endif
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 */
For wlp_api versions prior to 1.2.0, the baudrate specified in the call to
wlp_init()
is the baudrate that will be configured on the device, the application on the host must ensure that the UART which the device is connected is configured to 57600 before the call to wlp_init() and then reconfigured to use the same baudrate as the device when
wlp_init()
returns. See the documentation for
wlp_init()
in
wlp_api/wlp_api.h
for details and for a list of supported baudrates.For wlp_api versions 1.2.0 or later, the baudrate can be later configured with the new
wlp_set_baudrate()
function. Note that even though the
wlp_set_baudrate()
call is provided in the example above, it is optional. Also note the use of the
WLP_VERSION_1_2_0_OR_LATER
define in the code above.
Since the oWL Pico API needs to read and write data from the SPB800 device, methods for reading and writing to the UART 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
owl/ports/linux/owl_uart.c
and
owl/ports/avr8/owl_uart.c
files.
The parameters
uart_read_f
and
uart_write_f
provided to
wlp_init()
above are callback functions that should read and write from the UART. Below is a pseduo-implemenation example for the
uart_read_f
and uart_write_f parameters:
int uart_read_f(void *ctx, void *buf, int len)
{
int i;
char *in = buf;
for (i = 0; i < len; i++) { /* loop until all bytes are read */
while (!(uart->STATUS & UART_RXREADY_BIT)); /* wait for a char */
in[i] = uart->DATA; /* read the char into the provided buffer */
}
return len;
}
int uart_write_f(void *ctx, const void *buf, int len)
{
int i;
const char *out = buf;
for (i = 0; i < len; i++) { /* loop until all bytes are written */
while (!(uart->STATUS & UART_TXEMPTY_BIT)); /* wait until we are ready to transmit next char */
uart->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