Skip to content

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:

touch Makefile
touch network_sender.c

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.

#include <mira.h>
#include <stdio.h>
#include <string.h>
Now that we have access to Mira's API, we can start creating the entry point for the program. After MiraOS has initialized it will call in to the application, this is done to a function called 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);
}
This just allocates a process, now we have to write the body of the process, below the mira_setup function add:
PROCESS_THREAD(main_process, ev, data)
{
    PROCESS_BEGIN();

    PROCESS_PAUSE();

    PROCESS_END();
}
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();
}
Now we can continue to write the logic of our process, which will have the device send a message every minute to a root node. Start by creating the network config 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();
Calling connect with the address set to 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)
{
...
}
This callback will print the received message along with source address of the message.

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:
    }
}
The callback has to be registered to the Mira API after 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:

make TARGET=nrf52840ble-os LIBDIR=../libmira flashall
The command assumes libmira.tar.gz has been extracted next to the network_sender folder, the flashall 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.

Back to top