MONGOOSE EMBEDDED NETWORKING LIBRARY ON nRF52

Mongoose embedded networking library on nRF52

 

Mongoose networking library aims to run pretty much everywhere. Written in ANSI C, it is extremely portable. However, if we target embedded platforms, then other than writing platform-independent code, we also have to support various embedded implementations of the TCP/IP stack and account for some platform-specific quirks. Mongoose has already been ported to a lot of platforms, e.g. CC3200, ESP8266, STM32, PIC32, to name a few.

We decided that it might also be interesting to run Mongoose on BlueTooth-equipped devices. So today, I'm going to show you how to run Mongoose on a nRF52 Development Kit.

 

For the sake of simplicity, we'll use an HTTP example. However, Mongoose supports a lot of protocols, including MQTT which is a bidirectional Pub/Sub protocol that can be used either to send data to the device (e.g. adjust some settings) or report some data from the device.

 

As you know, nRF52 has only BlueTooth connectivity, so it cannot connect to the Internet directly; however, Nordic Semiconductor provides software to support6LoWPAN, i.e. in this case it's IPv6 over BlueTooth.

There is a number of possible ways to establish a 6LoWPAN connection: we canimplement a proper commissioning, or just connect devices manually to a host Linux machine. The exact way to establish an Internet connection is out of scope of this text. I'm going to use the option to connect devices manually to a host Linux machine. For our needs today, the bare minimum is enough: make a particular device accessible through a link-local IPv6 address.

 

Let's begin!

 

In order to reproduce this, you'll need a Linux machine with a 6LoWPAN-enabled kernel (I tested it on 4.4.0), and BlueTooth 4.0 hardware.

 

nRF52 firmware with Mongoose

Luckily, nRF5 IoT SDK uses LwIP: a popular TCP/IP stack for embedded systems. LwIP was already supported by Mongoose, so, no real porting was required. Only adjust LwIP client code a bit (make it handle IPv6 connections correctly), and glue things together by providing the right compiler flags.

 

You can see a ready-made example code, Keil uVision project and arm-gcc Makefile here (make sure to read the readme there); or you can follow the guide below and implement it step-by-step. In any case, you'll need a nRF5 IoT SDK.

 

We're going to modify a TCP server example shipped with the SDK. It's located in examples/iot/tcp/server .

 

Mongoose is distributed as a single C source file, plus C header (you can get both of them from the Cesanta website). Adding it to your project is just a matter of adding a single file mongoose.c and providing a few compile flags. For nRF52, the bare minimum is: -DCS_PLATFORM=CS_P_NRF52 .

 

We would also like to disable some extra functionality which we don't need at the moment. So, we're going to provide a few more flags:

 

 

When we add the aforementioned mongoose.c to the build and add the needed flags to CFLAGS , the project will compile. Not bad! Let's now try to do something with Mongoose.

 

First of all, we'll need a bit more of the heap memory. By default, it's 512 bytes; for our example, we'll need at least 1024. It is defined as a preprocessor macro __HEAP_SIZE .

 

In the main() function, there is some initialisation followed by an endless loop in which events are handled. So now, before entering event loop, we're going to add the Mongoose initialisation:

struct mg_mgr mgr; 
   /* Initialize event manager object */ 
   mg_mgr_init(&mgr, NULL); 
   /* 
    * Note that many connections can be added to a single event manager 
    * Connections can be created at any point, e.g. in event handler function 
    */ 
   const char *err; 
   struct mg_bind_opts opts = {}; 
   opts.error_string = &err; 
   /* Create listening connection and add it to the event manager */ 
   struct mg_connection *nc = mg_bind_opt(&mgr, "80", ev_handler, opts); 
   if (nc == NULL) { 
     printf("Failed to create listener: %s\n", err); 
     return 1; 
   } 
   /* Attach a built-in HTTP event handler to the connection */
   mg_set_protocol_http_websocket(nc); 

 And obviously, we also need to add a few calls to the event loop itself:

     sys_check_timeouts(); 
     mg_mgr_poll(&mgr, 0); 

Now, the only thing missing is the ev_handler : a callback which will be called by Mongoose at certain events. In this example, it's going to be a simple HTTP event handler:

// Define an event handler function 
void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { 
 if (ev == MG_EV_POLL) return; 
 /* printf("ev %d\r\n", ev); */ 
 switch (ev) { 
   case MG_EV_ACCEPT: { 
     char addr[32]; 
     mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), 
                         MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); 
     printf("%p: Connection from %s\r\n", nc, addr); 
     break; 
   } 
   case MG_EV_HTTP_REQUEST: { 
     struct http_message *hm = (struct http_message *) ev_data; 
     char addr[32]; 
     mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), 
                         MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); 
     printf("%p: %.*s %.*s\r\n", nc, (int) hm->method.len, hm->method.p, 
            (int) hm->uri.len, hm->uri.p); 
     mg_send_response_line(nc, 200, 
                           "Content-Type: text/html\r\n" 
                           "Connection: close"); 
     mg_printf(nc, 
               "\r\n<h1>Hello, sir!</h1>\r\n" 
               "You asked for %.*s\r\n", 
               (int) hm->uri.len, hm->uri.p); 
     nc->flags |= MG_F_SEND_AND_CLOSE; 
     LEDS_INVERT(LED_THREE); 
     break; 
   } 
   case MG_EV_CLOSE: { 
     printf("%p: Connection closed\r\n", nc); 
     break; 
   } 
 } 
} 

We might also want to adjust the device name: openconfig/ipv6_medium_ble_cfg.h and set DEVICE_NAME to, say,"Mongoose_example" .

Make sure you have #include "mongoose.h" in your make.c , and now we can build and flash our example project! After that, LED1 should turn on, which means that the device is in the advertising mode.

 

Having that done, we need to finally establish 6LoWPAN connection from our Linux host to the device we've just flashed.

 

Establish IPv6 over BlueTooth connection

As I mentioned in the beginning of the article, there are a few ways to organize connections; here we're going with the simplest one.

 

First of all, our device should already advertise itself. Let's check it:

$ sudo hcitool lescan 
LE Scan ... 
00:31:A0:49:01:27 Mongoose_example 
00:31:A0:49:01:27 (unknown) 
00:31:A0:49:01:27 Mongoose_example 
00:31:A0:49:01:27 (unknown) 

Awesome, here it is. Before we can connect to it, we need to load and enable the bluetooth_6lowpan kernel module:

$ sudo modprobe bluetooth_6lowpan 
$ sudo bash -c 'echo 1 > /sys/kernel/debug/bluetooth/6lowpan_enable' 

Now, we can make a connection by executing the following command (replace 00:AA:BB:CC:DD:EE with the actual address of your device):

$ echo "connect 00:AA:BB:CC:DD:EE 1" > /sys/kernel/debug/bluetooth/6lowpan_control

After that, the LED2 should turn on, which means that the device is connected.

 

And we can ping it by the link-local address of the following form:

$ ping6 fe80::2aa:bbff:fecc:ddee%bt0

(again, replace aa , bb , cc , dd , ee with the address of your device)

 

If that works, then we can finally access our HTTP endpoint on the device!

$ curl http://fe80::2aa:bbff:fecc:ddee%bt0/foo/bar 
<h1>Hello, sir!</h1> 
You asked for /foo/bar 
Contact Us
Have questions about Cesanta, our licensing, support services or anything else? Let us know and we'll get back to you.
GET IN TOUCH