Examples¶
There are many example applications in the Mira SDK, including bare metal use of MiraMesh.
Each example includes a README that describes the example more in depth. The examples can be found in the directory mira-examples/
in the Mira SDK.
The examples with documentation can also be found in our mira-examples repository on GitHub.
MiraMesh examples¶
Getting started with MiraOS¶
This is a step-by-step guide on how to create the network_sender example located in mira-examples
Start by creating a directory that will contain your application, for this example we use the name network_sender
.
Unpack libmira.tar.gz
next to the network_sender
folder.
Create two files Makefile
and network_sender.c
in the network_sender
folder:
Makefile¶
To Makefile
add the following:
PROJECT_NAME = network_sender
# Keep the node's debug port enabled:
APPROTECT_DISABLED?=yes
TARGET ?= nrf52840ble-os
LIBDIR ?= $(CURDIR)/..
SOURCE_FILES = \
network_sender.c
include $(LIBDIR)/Makefile.include
PROJECT_NAME
will decide the name of the output files when building your application.
Setting APPROTECT_DISABLED
to yes will turn of Access Port Protection on nRF MCUs, allowing
for connecting to it with a debugger. If it's set to no means that it is not possible to connect
a debugger to the MCU unless it is recovered first.
TARGET
should be set to one of the targets supported by Mira (nrf52840ble-os, nrf52832ble-os, mkw41z-os).
LIBDIR
should point to the libmira folder.
SOURCE_FILES
should contain all desired source files that should be compiled when building the application.
The include
at the end adds the required make files and dependencies needed to link the application against libmira.
Source file¶
Start by adding Mira's header file to network_receiver.c
and some libc headers.
mira_setup
that we have to create.
Add:
#include <mira.h>
#include <stdio.h>
#include <string.h>
void mira_setup(void)
{
mira_status_t uart_ret;
mira_uart_config_t uart_config = {
.baudrate = 115200,
.tx_pin = MIRA_GPIO_PIN(0, 6),
.rx_pin = MIRA_GPIO_PIN(0, 8)
};
MIRA_MEM_SET_BUFFER(8340);
uart_ret = mira_uart_init(0, &uart_config);
if (uart_ret != MIRA_SUCCESS) {
}
}
mira_setup
when called, will configure and enable the UART. The MIRA_MEM_SET_BUFFER(8340)
defines how much memory MiraOS is
allowed to dynamically allocate. This value will vary depending on various other configurations chosen. A root node typically needs to
allocated almost the double amount compared to a mesh or leaf node.
We also have to add the UART to Mira's IODEFS as a file descriptor:
#include <mira.h>
#include <stdio.h>
#include <string.h>
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
void mira_setup(void)
{
mira_status_t uart_ret;
mira_uart_config_t uart_config = {
.baudrate = 115200,
.tx_pin = MIRA_GPIO_PIN(0, 6),
.rx_pin = MIRA_GPIO_PIN(0, 8)
};
MIRA_MEM_SET_BUFFER(8340);
uart_ret = mira_uart_init(0, &uart_config);
if (uart_ret != MIRA_SUCCESS) {
}
}
Now it is time to define the process that will schedule the sending of messages to a root node.
We start by defining a process, adding PROCESS(main_process, "Main process")
and calling
process_start
with a reference to the process:
#include <mira.h>
#include <stdio.h>
#include <string.h>
PROCESS(main_process, "Main process");
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
void mira_setup(
void)
{
mira_status_t uart_ret;
mira_uart_config_t uart_config = {
.baudrate = 115200,
.tx_pin = MIRA_GPIO_PIN(0, 6),
.rx_pin = MIRA_GPIO_PIN(0, 8)
};
MIRA_MEM_SET_BUFFER(8340);
uart_ret = mira_uart_init(0, &uart_config);
if (uart_ret != MIRA_SUCCESS) {
/* Nowhere to send an error message */
}
process_start(&main_process, NULL);
}
mira_setup
function add:
This is a minimal MiraOS process, PROCESS_THREAD(main_process, ev, data)
defines the entry point of the process.
PROCESS_BEGIN()
defines the beginning of the process, it has to be located inside the PROCESS_THREAD
definition and
there must exist a PROCESS_END() statement and the end of the process.
The PROCESS_PAUSE()
statement will yield this process and let the scheduler run other processes, this process will be scheduled to run
after the other processes has been ran. Yielding from the process that is started from mira_setup
is needed for MiraOS to finish startup.
Let's now define the logic of our process.
In MiraOS, processes share the same stack. For a process to have its own
persistent variables between context switches, they have to be declared before PROCESS_BEGIN()
and be static.
Let's add the variables we need:
PROCESS_THREAD(main_process, ev, data)
{
static struct etimer timer; /* Timer that is used for scheduling sending */
static mira_net_udp_connection_t* udp_connection; /* Pointer for storing the allocated UDP socket */
static mira_net_address_t net_address; /* Root address storage */
static char buffer[MIRA_NET_MAX_ADDRESS_STR_LEN] /* Buffer for formatting address to string for printf */
static mira_status_t res; /* Return codes from the Mira API */
static const char* message = "Hello from example code"; /* Message to send over MiraMesh */
PROCESS_BEGIN();
PROCESS_PAUSE();
PROCESS_END();
}
network_config
as a global variable, along with defines for port and send interval at the top of the source file.
#include <mira.h>
#include <stdio.h>
#include <string.h>
#define UDP_PORT 456 /* UDP port to send to */
#define SEND_INTERVAL 60 /* How often the device sends the message */
static const mira_net_config_t network_config = {
.pan_id = 0x11586f69, /* ID of the network, use mira_gen_panid.py to generate a compatible ID */
.key = { 0x11, /* AES key, change this to anything considered secure, i.e a cryptographically secure random number */
0x12,
0x13,
0x14,
0x21,
0x22,
0x23,
0x24,
0x31,
0x32,
0x33,
0x34,
0x41,
0x42,
0x43,
0x44 },
.mode = MIRA_NET_MODE_MESH, /* Run as a mesh node */
.rate = MIRA_NET_RATE_MID, /* How often should the radio be on? MID runs the radio every 470 ms */
.antenna = 0,
.prefix = NULL, /* Use default */
.max_connections = 1,
};
PROCESS(main_process, "Main process");
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
First we have to initialize the network stack with the config created above, lets add the initialization to the process:
PROCESS_PAUSE();
printf("Starting mesh node!\n");
res = mira_net_init(&network_config); /* Initialize Mira's network stack */
if (res != MIRA_SUCCESS) { /* Always check return codes from Mira API */
printf("FAILURE: mira_net_init returned %d\n", res);
while(1);
}
PROCESS_END();
To be able to send UDP message we need to allocate a UDP socket with mira_net_udp_connect
:
PROCESS_PAUSE();
printf("Starting mesh node!\n");
res = mira_net_init(&network_config); /* Initialize Mira's network stack */
if (res != MIRA_SUCCESS) { /* Always check return codes from Mira API */
printf("FAILURE: mira_net_init returned %d\n", res);
while(1);
}
udp_connection = mira_net_udp_connect(NULL, 0, udp_listen_callback, NULL);
if (udp_connection == NULL) {
printf("FAILURE: not possible to allocated udp socket!");
while(1);
}
PROCESS_END();
NULL
will not bind the socket to any specific address, so when
sending mira_net_udp_send_to()
has to be used.
One can also see that we have provided udp_listen_callback
as a callback, the callback is called every time
the device is receiving a UDP message addressed to it.
Lets create the callback, we place it above the mira_setup
function.
.
.
.
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
static process_event_t joined_event;
static void udp_listen_callback(
mira_net_udp_connection_t* connection,
const void* data,
uint16_t data_len,
const mira_net_udp_callback_metadata_t* metadata,
void* storage)
{
char buffer[MIRA_NET_MAX_ADDRESS_STR_LEN];
uint16_t i;
printf("Received message from [%s]:%u: ",
mira_net_toolkit_format_address(buffer, metadata->source_address),
metadata->source_port);
for (i = 0; i < data_len; i++) {
printf("%c", ((char*)data)[i]);
}
printf("\n");
}
void mira_setup(
void)
{
...
}
Now we can continue with our process. The device needs to join the network before it can send anything, so lets add another callback that will be called when we have joined the network.
First we have to allocate an event joined_event
with process_alloc_event()
which will be used to notify the process that we have joined the network. We add joined_event
as a global variable and allocate the event in mira_setup
.
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
static process_event_t joined_event;
static void udp_listen_callback(
mira_net_udp_connection_t* connection,
const void* data,
uint16_t data_len,
const mira_net_udp_callback_metadata_t* metadata,
void* storage)
{
char buffer[MIRA_NET_MAX_ADDRESS_STR_LEN];
uint16_t i;
printf("Received message from [%s]:%u: ",
mira_net_toolkit_format_address(buffer, metadata->source_address),
metadata->source_port);
for (i = 0; i < data_len; i++) {
printf("%c", ((char*)data)[i]);
}
printf("\n");
}
void mira_setup(
void)
{
mira_status_t uart_ret;
mira_uart_config_t uart_config = {
.baudrate = 115200,
.tx_pin = MIRA_GPIO_PIN(0, 6),
.rx_pin = MIRA_GPIO_PIN(0, 8)
};
MIRA_MEM_SET_BUFFER(8340);
uart_ret = mira_uart_init(0, &uart_config);
if (uart_ret != MIRA_SUCCESS) {
/* Nowhere to send an error message */
}
joined_event = process_alloc_event();
process_start(&main_process, NULL);
}
Then we create the callback, placing it below the udp_listen_callback
.
static void network_state_change_callback(
mira_net_state_t network_state)
{
switch (network_state) {
case MIRA_NET_STATE_NOT_ASSOCIATED:
printf("State: not associated\n");
break;
case MIRA_NET_STATE_ASSOCIATED:
printf("State: associated\n");
break;
case MIRA_NET_STATE_JOINED:
printf("State: joined\n");
process_post(&main_process, joined_event, NULL); /* Post event to main process that we have joined the network */
break;
default:
}
}
mira_net_init
have been called:
PROCESS_PAUSE();
printf("Starting mesh node!\n");
res = mira_net_init(&network_config); /* Initialize Mira's network stack */
if (res != MIRA_SUCCESS) { /* Always check return codes from Mira API */
printf("FAILURE: mira_net_init returned %d\n", res);
while(1);
}
res = mira_net_register_net_state_cb(network_state_change_callback);
if (res != MIRA_SUCCESS) {
printf("FAILURE: mira_net_register_net_state_cb returned %d\n", res);
while(1);
}
udp_connection = mira_net_udp_connect(NULL, 0, udp_listen_callback, NULL);
if (udp_connection == NULL) {
printf("FAILURE: not possible to allocated udp socket!");
while(1);
}
PROCESS_END();
Now we can create the loop that will handle the periodic sending of the message:
PROCESS_PAUSE();
printf("Starting mesh node!\n");
res = mira_net_init(&network_config); /* Initialize Mira's network stack */
if (res != MIRA_SUCCESS) { /* Always check return codes from Mira API */
printf("FAILURE: mira_net_init returned %d\n", res);
while(1);
}
res = mira_net_register_net_state_cb(network_state_change_callback);
if (res != MIRA_SUCCESS) {
printf("FAILURE: mira_net_register_net_state_cb returned %d\n", res);
while(1);
}
udp_connection = mira_net_udp_connect(NULL, 0, udp_listen_callback, NULL);
if (udp_connection == NULL) {
printf("FAILURE: not possible to allocated udp socket!");
while(1);
}
PROCESS_WAIT_EVENT_UNTIL(ev == joined_event); /* Yield until we have joined the network */
while(1) {
/* Retrieve the root address */
res = mira_net_get_root_address(&net_address);
if (res != MIRA_SUCCESS) {
printf("Waiting for root address (res: %d)\n", res);
etimer_set(&timer, (SEND_INTERVAL/SEND_INTERVAL) * CLOCK_SECOND); /* Set timer for 1 second */
} else {
printf("Sending to address: %s\n",
mira_net_toolkit_format_address(buffer, &net_address)); /* Print destination address*/
mira_net_udp_send_to(udp_connection, &net_address, UDP_PORT, message, strlen(message));
etimer_set(&timer, SEND_INTERVAL * CLOCK_SECOND); /* Set timer to SEND_INTERVAL seconds */
}
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); /* Yield until it is time to send again */
}
mira_net_udp_close(udp_connection); /* Don't forget to close the socket when finished */
PROCESS_END();
First we yield until a joined_event
is posted to the process by the callback, then we enter the loop that will handle the
periodic sending of messages to the root.
When all the steps above have been completed, network_sender.c
will look like this:
#include <mira.h>
#include <stdio.h>
#include <string.h>
#define UDP_PORT 456 /* UDP port to send to */
#define SEND_INTERVAL 60 /* How often the device sends the message */
static const mira_net_config_t network_config = {
.pan_id = 0x11586f69, /* ID of the network, use mira_gen_panid.py to generate a compatible ID */
.key = { 0x11, /* AES key, change this to anything considered secure, i.e arbitrarly random number */
0x12,
0x13,
0x14,
0x21,
0x22,
0x23,
0x24,
0x31,
0x32,
0x33,
0x34,
0x41,
0x42,
0x43,
0x44 },
.mode = MIRA_NET_MODE_MESH, /* Run as a mesh node */
.rate = MIRA_NET_RATE_MID, /* How often should the radio be on? MID runs the radio every 470 ms */
.antenna = 0,
.prefix = NULL, /* Use default */
.max_connections = 1,
};
PROCESS(main_process, "Main process");
MIRA_IODEFS(MIRA_IODEF_NONE, /* fd 0: stdin */
MIRA_IODEF_UART(0), /* fd 1: stdout */
MIRA_IODEF_NONE /* fd 2: stderr */
);
static process_event_t joined_event;
static void udp_listen_callback(
mira_net_udp_connection_t* connection,
const void* data,
uint16_t data_len,
const mira_net_udp_callback_metadata_t* metadata,
void* storage)
{
char buffer[MIRA_NET_MAX_ADDRESS_STR_LEN];
uint16_t i;
printf("Received message from [%s]:%u: ",
mira_net_toolkit_format_address(buffer, metadata->source_address),
metadata->source_port);
for (i = 0; i < data_len; i++) {
printf("%c", ((char*)data)[i]);
}
printf("\n");
}
static void network_state_change_callback(
mira_net_state_t network_state)
{
switch (network_state) {
case MIRA_NET_STATE_NOT_ASSOCIATED:
printf("State: not associated\n");
break;
case MIRA_NET_STATE_ASSOCIATED:
printf("State: associated\n");
break;
case MIRA_NET_STATE_JOINED:
printf("State: joined\n");
process_post(&main_process, joined_event, NULL); /* Post event to main process that we have joined the network */
break;
default:
}
}
void mira_setup(
void)
{
mira_status_t uart_ret;
mira_uart_config_t uart_config = {
.baudrate = 115200,
.tx_pin = MIRA_GPIO_PIN(0, 6),
.rx_pin = MIRA_GPIO_PIN(0, 8)
};
MIRA_MEM_SET_BUFFER(8340);
uart_ret = mira_uart_init(0, &uart_config);
if (uart_ret != MIRA_SUCCESS) {
/* Nowhere to send an error message */
}
joined_event = process_alloc_event();
process_start(&main_process, NULL);
}
PROCESS_THREAD(main_process, ev, data)
{
static struct etimer timer; /* Timer that is used for scheduling sending */
static mira_net_udp_connection_t* udp_connection; /* Pointer for storing the allocated UDP socket */
static mira_net_address_t net_address; /* Root address storage */
static char buffer[MIRA_NET_MAX_ADDRESS_STR_LEN] /* Buffer for formatting address to string for printf */
static mira_status_t res; /* Return codes from the Mira API */
static const char* message = "Hello from example code"; /* Message to send over MiraMesh */
PROCESS_PAUSE();
printf("Starting mesh node!\n");
res = mira_net_init(&network_config); /* Initialize Mira's network stack */
if (res != MIRA_SUCCESS) { /* Always check return codes from Mira API */
printf("FAILURE: mira_net_init returned %d\n", res);
while(1);
}
res = mira_net_register_net_state_cb(network_state_change_callback);
if (res != MIRA_SUCCESS) {
printf("FAILURE: mira_net_register_net_state_cb returned %d\n", res);
while(1);
}
udp_connection = mira_net_udp_connect(NULL, 0, udp_listen_callback, NULL);
if (udp_connection == NULL) {
printf("FAILURE: not possible to allocated udp socket!");
while(1);
}
PROCESS_WAIT_EVENT_UNTIL(ev == joined_event); /* Yield until we have joined the network */
while(1) {
/* Retrieve the root address */
res = mira_net_get_root_address(&net_address);
if (res != MIRA_SUCCESS) {
printf("Waiting for root address (res: %d)\n", res);
etimer_set(&timer, (SEND_INTERVAL/SEND_INTERVAL) * CLOCK_SECOND); /* Set timer for 1 second */
} else {
printf("Sending to address: %s\n",
mira_net_toolkit_format_address(buffer, &net_address)); /* Print destination address*/
mira_net_udp_send_to(udp_connection, &net_address, UDP_PORT, message, strlen(message));
etimer_set(&timer, SEND_INTERVAL * CLOCK_SECOND); /* Set timer to SEND_INTERVAL seconds */
}
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); /* Yield until it is time to send again */
}
mira_net_udp_close(udp_connection); /* Don't forget to close the socket when finished */
PROCESS_END();
}
It is now possible to build and flash the example to a device. For flashing it to a nRF52840 one can run:
The command assumes libmira.tar.gz has been extracted next to the network_sender folder, theflashall
target will flash all connected devices with the same firmware. To specify one device, replace flashall
with flash.<serial number>
where serial number is the debugger serial number.
To be able to send messages, it is require to have one device either running the network_receiver
example or running
the gateway application and listening on UDP port 456.