Light control tutorial
The application below demonstrates a simple light control application, where devices can control the LED status of all other devices in the network. You can build the application for the unsecure 6LoWPAN-ND network.
See the 6LoWPAN overview for the definition of star and mesh networks.
Please install Mbed CLI to complete the tutorial.
Requirements
This tutorial requires:
- Hardware that supports entropy.
- A radio.
- An RF driver.
- A separate application,
nanostack-border-router
to test the border router.
Import the application
If using Mbed CLI:
mbed import mbed-os-example-mesh-minimal
cd mbed-os-example-mesh-minimal
If using the Mbed Online Compiler, click Import into Mbed IDE below:
/*
* Copyright (c) 2016 ARM Limited. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mbed.h"
#include "nanostack/socket_api.h"
#include "mesh_led_control_example.h"
#include "common_functions.h"
#include "ip6string.h"
#include "mbed-trace/mbed_trace.h"
static void init_socket();
static void handle_socket();
static void receive();
static void my_button_isr();
static void send_message();
static void blink();
static void update_state(uint8_t state);
static void handle_message(char* msg);
#define multicast_addr_str "ff15::810a:64d1"
#define TRACE_GROUP "example"
#define UDP_PORT 1234
#define MESSAGE_WAIT_TIMEOUT (30.0)
#define MASTER_GROUP 0
#define MY_GROUP 1
DigitalOut led_1(MBED_CONF_APP_LED, 1);
InterruptIn my_button(MBED_CONF_APP_BUTTON);
DigitalOut output(MBED_CONF_APP_RELAY_CONTROL, 1);
NetworkInterface * network_if;
UDPSocket* my_socket;
// queue for sending messages from button press.
EventQueue queue;
// for LED blinking
Ticker ticker;
// Handle for delayed message send
int queue_handle = 0;
uint8_t multi_cast_addr[16] = {0};
uint8_t receive_buffer[20];
// how many hops the multicast message can go
static const int16_t multicast_hops = 10;
bool button_status = 0;
void start_mesh_led_control_example(NetworkInterface * interface){
tr_debug("start_mesh_led_control_example()");
MBED_ASSERT(MBED_CONF_APP_LED != NC);
MBED_ASSERT(MBED_CONF_APP_BUTTON != NC);
network_if = interface;
stoip6(multicast_addr_str, strlen(multicast_addr_str), multi_cast_addr);
init_socket();
}
static void blink() {
led_1 = !led_1;
}
void start_blinking() {
ticker.attach(blink, 1.0);
}
void cancel_blinking() {
ticker.detach();
led_1=1;
}
static void send_message() {
tr_debug("send msg %d", button_status);
char buf[20];
int length;
/**
* Multicast control message is a NUL terminated string of semicolon separated
* <field identifier>:<value> pairs.
*
* Light control message format:
* t:lights;g:<group_id>;s:<1|0>;\0
*/
length = snprintf(buf, sizeof(buf), "t:lights;g:%03d;s:%s;", MY_GROUP, (button_status ? "1" : "0")) + 1;
MBED_ASSERT(length > 0);
tr_debug("Sending lightcontrol message, %d bytes: %s", length, buf);
SocketAddress send_sockAddr(multi_cast_addr, NSAPI_IPv6, UDP_PORT);
my_socket->sendto(send_sockAddr, buf, 20);
//After message is sent, it is received from the network
}
// As this comes from isr, we cannot use printing or network functions directly from here.
static void my_button_isr() {
button_status = !button_status;
queue.call(send_message);
}
static void update_state(uint8_t state) {
if (state == 1) {
tr_debug("Turning led on\n");
led_1 = 0;
button_status=1;
if (MBED_CONF_APP_RELAY_CONTROL != NC) {
output = 0;
} else {
printf("Pins not configured. Skipping the RELAY control.\n");
}
}
else {
tr_debug("Turning led off\n");
led_1 = 1;
button_status=0;
if (MBED_CONF_APP_RELAY_CONTROL != NC) {
output = 1;
} else {
printf("Pins not configured. Skipping the RELAY control.\n");
}
}
}
static void handle_message(char* msg) {
// Check if this is lights message
uint8_t state=button_status;
uint16_t group=0xffff;
if (strstr(msg, "t:lights;") == NULL) {
return;
}
if (strstr(msg, "s:1;") != NULL) {
state = 1;
}
else if (strstr(msg, "s:0;") != NULL) {
state = 0;
}
// 0==master, 1==default group
char *msg_ptr = strstr(msg, "g:");
if (msg_ptr) {
char *ptr;
group = strtol(msg_ptr, &ptr, 10);
}
// in this example we only use one group
if (group==MASTER_GROUP || group==MY_GROUP) {
update_state(state);
}
}
static void receive() {
// Read data from the socket
SocketAddress source_addr;
memset(receive_buffer, 0, sizeof(receive_buffer));
bool something_in_socket=true;
// read all messages
while (something_in_socket) {
int length = my_socket->recvfrom(&source_addr, receive_buffer, sizeof(receive_buffer) - 1);
if (length > 0) {
int timeout_value = MESSAGE_WAIT_TIMEOUT;
tr_debug("Packet from %s\n", source_addr.get_ip_address());
timeout_value += rand() % 30;
tr_debug("Advertisiment after %d seconds", timeout_value);
queue.cancel(queue_handle);
queue_handle = queue.call_in((timeout_value * 1000), send_message);
// Handle command - "on", "off"
handle_message((char*)receive_buffer);
}
else if (length!=NSAPI_ERROR_WOULD_BLOCK) {
tr_error("Error happened when receiving %d\n", length);
something_in_socket=false;
}
else {
// there was nothing to read.
something_in_socket=false;
}
}
}
static void handle_socket() {
// call-back might come from ISR
queue.call(receive);
}
static void init_socket()
{
my_socket = new UDPSocket();
my_socket->open(network_if);
my_socket->set_blocking(false);
my_socket->bind(UDP_PORT);
my_socket->setsockopt(SOCKET_IPPROTO_IPV6, SOCKET_IPV6_MULTICAST_HOPS, &multicast_hops, sizeof(multicast_hops));
ns_ipv6_mreq_t mreq;
memcpy(mreq.ipv6mr_multiaddr, multi_cast_addr, 16);
mreq.ipv6mr_interface = 0;
my_socket->setsockopt(SOCKET_IPPROTO_IPV6, SOCKET_IPV6_JOIN_GROUP, &mreq, sizeof mreq);
if (MBED_CONF_APP_BUTTON != NC) {
my_button.fall(&my_button_isr);
my_button.mode(MBED_CONF_APP_BUTTON_MODE);
}
//let's register the call-back function.
//If something happens in socket (packets in or out), the call-back is called.
my_socket->sigio(callback(handle_socket));
// dispatch forever
queue.dispatch();
}
Change the channel settings (optional)
See the file mbed_app.json
in the imported folder for an example of defining an IEEE 802.15.4 channel.
Select the optimal Nanostack configuration
To optimize the flash usage, set the Nanostack and Mbed Mesh API configuration to use. The configuration depends mostly on the preferred use case.
The network is based on 6LoWPAN-ND. Select the device role:
- Mesh network: a router. (default)
- Star network: nonrouting device. Also known as a host or sleepy host.
6LoWPAN-ND
nsapi.default-mesh-type: LOWPAN
Device role | nanostack.configuration value |
mbed-mesh-api.6lowpan-nd-device-type value |
---|---|---|
Mesh router (default) | lowpan_router | NET_6LOWPAN_ROUTER |
Nonrouting device | lowpan_host | NET_6LOWPAN_HOST |
Configuration example
An example configuration file is provided under configs/
directory. You may override the mbed_app.json
. The configuration file is configs/mesh_6lowpan.json
and is used for 6LoWPAN-ND based mesh network. For example, the mbed_app.json
file can be set to:
"target_overrides": {
"*": {
"nanostack.configuration": "lowpan_router",
"nsapi.default-mesh-type": "LOWPAN",
"mbed-mesh-api.6lowpan-nd-panid-filter": "0xffff",
"mbed-mesh-api.6lowpan-nd-channel-page": 0,
"mbed-mesh-api.6lowpan-nd-channel": 12,
"mbed-mesh-api.6lowpan-nd-channel-mask": "(1<<12)",
"mbed-mesh-api.heap-size": 14000,
"mbed-trace.enable": false,
"platform.stdio-convert-newlines": true,
"platform.stdio-baud-rate": 115200,
"atmel-rf.provide-default": true,
"mcr20a.provide-default": false,
"target.device_has_add": ["802_15_4_PHY"],
"target.network-default-interface-type": "MESH"
}
}
Security-based hardware requirements
The networking stack in this example requires TLS functionality. On devices where hardware entropy is not present, TLS is disabled by default. This results in compile time failures or linking failures.
To learn why entropy is required, read the TLS porting guide.
See Notes on different hardware for combinations of development boards and RF shields that we have tested.
Input and output configuration
Check how LEDs and buttons are configured for your hardware, and update the .json
file accordingly.
Radio and radio driver
To run a 6LoWPAN-ND network, you need a working RF driver for Nanostack. This example uses the Atmel AT86RF233 by default. Place the shield on top of your board.
To change the RF driver, set the preferred RF driver provide_default
value to true
in mbed_app.json
. For example, to use the MCR20a RF driver:
"atmel-rf.provide-default": false,
"mcr20a.provide-default": true
Compiling and flashing the application
-
To compile the application:
mbed compile -m K64F -t GCC_ARM
-
A binary is generated at the end of the build process.
-
Connect the board to your computer, and power on the board.
-
To program the application, drag and drop the binary to the board (shown as storage device on the computer).
Update the firmware of the border router
This example supports the Nanostack-border-router.
The border router supports static and dynamic backhaul configuration. Use the static configuration if your network infrastructure does not supply an IPv6 address. If your network infrastructure provides IPv6 connectivity, then the border router learns an IPv6 prefix from the router advertisements, and you can use the dynamic configuration.
Remember to connect the Ethernet cable between the board and your router. Then power cycle on the board.
Testing with border router
By default, the application is built for the LED control demo, in which the device sends a multicast message to all devices in the network when the button is pressed. All devices that receive the multicast message will change the LED status (red LED on/off) to the state defined in the message.
As soon as both the border router and the target are running, you can verify the correct behavior. Open a serial console, and see the IP address obtained by the device.
Note: This application uses the baud rate of 115200.
connected. IP = 2001:db8:a0b:12f0::1
You can use this IP address to ping
from your PC and verify that the connection is working correctly.
Memory optimizations
On some limited boards, for example NCS36510 or KW24D, running this application might cause the device to run out of RAM or ROM. In those cases, you can try optimizing memory use.
Use a customized Mbed TLS configuration
The example Mbed TLS configuration minimizes the RAM and ROM use of the application; it saves you 8.7 kB of RAM but uses 6.8 kB of additional flash. This is not guaranteed to work on every Mbed Enabled platform.
You can customize the Mbed TLS configuration by adding "macros": ["MBEDTLS_USER_CONFIG_FILE=\"mbedtls_config.h\""]
to the .json
file.
Disable the LED control example
You can disable the LED control example by specifying enable-led-control-example": false
in mbed_app.json
.
This saves you about 2.5 kB of flash.
Change the network stack's event loop stack size
Nanostack's internal event loop is shared with Device Managmenet Client, and therefore requires lots of stack to complete the security handshakes using TLS protocols. If you're not using client functionality, you can define the following to use 2 kB of stack:
"nanostack-hal.event_loop_thread_stack_size": 2048
This saves you 4 kB of RAM.
Change the Nanostack's heap size
Nanostack uses internal heap, which you can configure in the mbed_app.json
:
In mbed_app.json
, you find the following line:
"mbed-mesh-api.heap-size": 15000
For 6LoWPAN, you can try 12 kB. For the smallest memory use, configure the node to be in nonrouting mode. See module-configuration for more detail.
Move the Nanostack's heap to the system heap
You can move the Nanostack's internal heap to the system heap. This helps on devices with split RAM, and on devices in which the compiler fails to fit statically allocated symbols into one section, for example the NXP KW24D device.
The Mesh API has the use-malloc-for-heap option to help this.
Add the following line to mbed_app.json
to test:
"mbed-mesh-api.use-malloc-for-heap": true
Use release profile
For devices with a small memory, we recommend using release profiles for building and exporting. Please see the documentation about build profiles.
Examples:
$ mbed export -m KW24D -i make_iar --profile release
OR
$ mbed compile -m KW24D -t IAR --profile release
Troubleshooting
If you have problems, you can review the debugging documentation for suggestions on what could be wrong and how to fix it.