Internals of the embeNET demo for NRF52-DK board

Table of contents Table of contents

This document describes the internals of the embeNET demo application running on NRF52-DK boards and provides information on how to modify and extend the demo.

How does the demo application work

The demo application uses the embeNET Node C API library to join the network and run two network services:

  • ENMS: embeNET Network Management service that allows to gather information about the network operation from the nodes
  • custom exemplary service, that sends out a simple message every 5 seconds and also reacts to some simple messages

Below is a short explanation of all important steps. Basically the whole application fits into the main function.

Initialization

The initialization part configures interrupts, system clocks and RTT:

init();

Next we configure the logging facility of the embeNET:

LOGGER_SetOutput(logOutputWrapper, NULL);
LOGGER_Enable();

Next the embeNET stack is initialized:

// Initialize structure to provide Stack with user-defined event handlers
.onJoined = onJoined,
.onLeft = onLeft,
.onJoinAttempt = onJoinAttempt,
.onDataOnUnregisteredPort = dataOnUregisteredPort,
.onQuickJoinCredentialsObsolete = onQuickJoinCredentialsObsolete
};
// Initialize network stack
EMBENET_NODE_Init(&handlers);
EMBENET_Result EMBENET_NODE_Init(const EMBENET_NODE_EventHandlers *eventHandlers)
EMBENET_NODE_OnJoined onJoined

After that the ENMS service is initialized. This service accepts a 128-bit hardware identifier. We set it up using 64-bit DEVICEID available in the nrf52 chips.

// Set up hardware ID for ENMS using 64-bit DEVICEID field
uint8_t hardwareId[16] = {0x00};
uint64_t uid = EMBENET_NODE_GetUID();
memcpy(hardwareId, (void const*)&uid, sizeof(uid));
EMBENET_EUI64 EMBENET_NODE_GetUID(void)

And with that we initialize the ENMS service.

// Initialize ENMS service on its default port. You may specify custom Hardware Identifier and firmware version
if (ENMS_NODE_RESULT_OK == ENMS_NODE_Init(&enmsNode, ENMS_DEFAULT_PORT, hardwareId, 1, 0, 0)) {
printf("ENMS service initialized\n");
} else {
printf("Failed to initialize ENMS service!\n");
}
EnmsNodeResult ENMS_NODE_Init(EnmsNode *enmsNode, uint16_t port, const uint8_t hwId[16], const EnmsIndicationPolicy *indicationPolicy)

Now the code splits - we either start the root node operation...

printf("Acting as root\n");
// When the application is built for Root node, start as root instead of joining the network
EMBENET_Result EMBENET_NODE_RootStart(void const *panData, size_t panDataSize)

... or we start acting as regular node. In this case we initialize our custom demo service:

printf("Acting as node\n");
// Initialize exemplary, user-defined custom service
custom_service_init();
// Additionally tell the ENMS service that user-defined custom service is running
(void)ENMS_NODE_RegisterService(&enmsNode, "custom", 1);
EnmsNodeResult ENMS_NODE_RegisterService(EnmsNode *enmsNode, const char *serviceName, uint8_t serviceState)

Joining the network

In order to join the network the node has to setup the required network configuration:

// embeNET network configuration:
// K1 key, used to authenticate the network node should join and
// PSK - Node's secret key.
// Note that the psk value should be preferably stored in secure memory, or be preloaded using custom bootloader.
.k1.val = { 0xc0, 0x8b, 0x76, 0x62, 0x77, 0x09, 0x9e, 0x7d, 0x7e, 0x9c, 0x02, 0x22, 0xf1, 0x68, 0xcc, 0x9e },
.psk.val = { 0x46, 0xd7, 0xdc, 0x94, 0xe8, 0xee, 0x74, 0x96, 0xce, 0xaf, 0x54, 0xa3, 0xab, 0x64, 0xcb, 0xeb },
};
// Make the node join the network
EMBENET_Result EMBENET_NODE_Join(const EMBENET_NODE_Config *config)
uint8_t val[16]

The main loop

The application's main loop consists only of a call to a embeNET Node processing function:

/* Infinite loop */
while (1) {
// Periodically call embeNET Node process function.
}
void EMBENET_NODE_Proc(void)

Network join event

Once the node joins the network an event callback will be called. In our demo application this callback simply starts the network services:

static void onJoined(EMBENET_PANID panId, const EMBENET_NODE_QuickJoinCredentials *quickJoinCredentials) {
printf("Joined network with PANID: x%04" PRIx16 "\n", panId);
// Start ENMS Service that provides network-wide telemetry information
EnmsNodeResult enmsStartStatus = ENMS_NODE_Start(&enmsNode);
if (ENMS_NODE_RESULT_OK == enmsStartStatus) {
printf("ENMS service started\n");
} else {
printf("ENMS service failed to start with status %d\n", (int)enmsStartStatus);
}
// Start exemplary, user-defined custom service
custom_service_start();
}
uint16_t EMBENET_PANID
EnmsNodeResult ENMS_NODE_Start(EnmsNode *enmsNode)
EnmsNodeResult

Network leave event

In case when node gets disconnected from the network, another callback is called. In our demo application this callback stops the network services:

static void onLeft(void) {
printf("Node has left the network\n");
// Stop ENMS service
EnmsNodeResult enmsStopStatus = ENMS_NODE_Stop(&enmsNode);
if (ENMS_NODE_RESULT_OK == enmsStopStatus) {
printf("ENMS service stopped\n");
} else {
printf("ENMS service failed to stop with status %d\n", (int)enmsStopStatus);
}
}
EnmsNodeResult ENMS_NODE_Stop(EnmsNode *enmsNode)

{.c}

Handling critical errors

In cases when the embeNET stack detects a critical condition, the following handler is called by the stack, giving a chance to react:

__attribute__((noreturn)) void EXPECT_OnAbortHandler(char const* why, char const* file, int line) {
printf("Program aborted: %s %s:%i\n", why, file, line);
while(1) {
;
}
__builtin_unreachable();
}
EXPECT_INTERNAL_NORETURN void EXPECT_OnAbortHandler(char const *why, char const *file, int line)

Customizing and extending the demo application

The structure of the firmware project embenet_demo_src is simple:

  • the main.c file includes the main function, embeNET Node callbacks and initialization code
  • the custom_service.c includes implementation of a custom demo service
  • the embenet_node_lib includes the embeNET Node library
  • the embenet_node_port includes the embeNET Node port for nRF52-DK board
  • the embenet_node_port_interface includes the interface definition for the embeNET Node port

Writing or extending a custom network service.

The best starting point is to look at implementation of the existing custom_service.

Disabling "zero rule"

In order to disable the "zero rule", delete the rule with "uid": 0, from your config.json file, and add appropriate rules for your nodes, for example:

"join_rules": [
{
"uid": 2,
"psk": "0x1ccd738f70a35be9574738ad190f86f2"
},
{
"uid": 3,
"psk": "0xdc2eafe4016303ebb1aa1b4b2ed4fda0"
}
]

This will allow only nodes with uid = 2 and 3, to join the network, provided that their PSKs match those from config.json file.

Using custom name for network interface

You may specify the name of desired network interface in .json file, by adding on root level:

"interface_name": "your-name"

Limitations of embeNET demo application

The demo version of the embeNET stack allows to connect up to 10 nodes only, with network depth of max. 3 hops.

Contact us

Any question or remarks? Just write us a message!

Contact Information

Feel free to get in touch