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.
What Mongoose can do? Turn a device into a RESTful server - make it controllable via a browser! Or, call an external RESTful server. Or, create a Web UI for a device. Push data in real-time over WebSocket or MQTT. Or, make a device discoverable in the local network (mDNS/dns-SD, Bonjour). Or, many more other possibilities. See a long list of ready-made examples at Github.
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 either nRF52 or nRF51 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, nRF51/52 has only BlueTooth connectivity, so it cannot connect to the Internet directly; however, Nordic Semiconductor provides software to support 6LoWPAN, i.e. in this case it's IPv6 over BlueTooth.
There is a number of possible ways to establish a 6LoWPAN connection: we can implement 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.
nRF51/52 firmware with MongooseLuckily, 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 nRF51 IoT SDK for nRF51, and nRF5 IoT SDK for nRF52.
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 nRF51, the bare minimum is: -DCS_PLATFORM=CS_P_NRF51
. And for nRF52, respectively, -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:
-DMG_DISABLE_HTTP_DIGEST_AUTH -DMG_DISABLE_MD5 -DMG_DISABLE_HTTP_KEEP_ALIVE
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 initialization followed by an endless loop in which events are handled. So now, before entering event loop, we're going to add the Mongoose initialization:
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: open config/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 connectionAs 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):
$ sudo bash -c 'echo "connect 00:AA:BB:CC:DD:EE 1" > /sys/kernel/debug/bluetooth/6lowpan_control'
(Note that we can't just sudo echo "...." > ....
, because in this case the redirection won't be covered by sudo
)
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
Yay!
ConclusionNow we have the whole power of Mongoose in our nRF51/nRF52 device; we can implement whatever functionality we actually need. Of course, we're limited by the amount of memory available on the device; but given the event-based model which Mongoose uses, it really has a very low footprint.
You can learn more about Mongoose by reading the doc.
Comments