Table of Contents
- Networking basics
- Stack architecture
- Stack handling
- Network handling
- Joining the network
- Quick join
- Leaving the network
- Event handlers
- Group handling
- Task handling
- UDP and sockets
- Random numbers
This is the embeNET Node User Guide. You can read through the following topics:
- Networking basics
- Stack architecture
- Stack handling
- Network handling
- Group handling
- Task handling
- UDP and sockets
- Random numbers
The following sections give brief information about the embeNET networking. This is just an overview. Most topics are covered in depth in the following chapters.
The embeNET network is a wireless mesh network capable of providing communication between hundreds of nodes. It uses a special TSCH (Time Slotted Channel Hopping) scheme for accessing the radio channels, which greatly reduces idle listening and packet collisions even in dense networks. The network works with IEEE 802.15.4 compatible radio transceivers. The network is synchronized meaning that all nodes that joined the same network have a common sense of time, which is very precise and allows the nodes to schedule radio transmission and reception in exact moments.
The embeNET network is started by a special device called border router. Other nodes need to join the network started by the border router in order to be able to communicate. The border router may accept or reject a node based on the specifically configured joining rules.
In order to join a network the node must know the pre-configured network key (K1). This key is shared across all devices within a network and typically can be the same for all devices used in an organization. It is a utility measure and network security is not based on this key. The second key is a pre-shared key (PSK) which is associated with the particular node. It is advised but not mandatory for each node to have a unique pre-shared key.
The joining of node to the network is a multi-step process. Firstly the node synchronizes to the network, meaning that it can follow the scheduled slots in which the communication with other nodes takes place. It selects a node (called pledge) through which it wishes to join. Next it sends credentials including own UID and PSK to the border router, which accepts the node or denies access. Once the node is accepted it can communicate with any other node in the network.
The embeNET network uses the IPv6 protocol for addressing the nodes. It supports multicast addressing mode so that devices can be grouped and the data can be sent to all devices within a group simultaneously. Each device in the network is identified by a 64-bit unique identifier (UID) which is an EUI64 address. In most cases this number comes from the hardware platform the stack works on and should not be altered by the application code. In addition each network started by the border router is identified by a 64-bit network prefix. These two values - the network prefix and the UID are used to form a unicast IPv6 address of the node. A multicast address is being formed by the network prefix and group identifier.
The embeNET network uses UDP protocol for transporting data between the nodes. The UDP datagrams are handled by sockets which are registered in the stack.
The network is synchronized meaning that all nodes that joined the same network have a common sense of time, which is very precise. Thanks to this, the embeNET Node library allows to schedule application-defined events that can be triggered nearly simultaneously in multiple networked nodes. The embeNET Node library allows also to schedule events in local node time, which is not synchronized, but in turn work even if the node is not joined to any network.
The embeNET Node library implements a stack of wireless communication protocols that allow the device that runs the stack to communicate in a wireless mesh network. From the user perspective it is a library that provides several interfaces, that the user application (and other middleware - such as network services) can use. However, due to the fact that the library is also portable across multiple hardware platforms, it also relies on some required interfaces. Those need to be implemented in either the 'port' or 'bsp'. Here, we will only focus on the provided interfaces - see embeNET Node Porting Guide for the description of the required interfaces and how to implement them on a new or custom hardware.
The embeNET Node library utilizes event-driven style of programming. Most actions taken by the node last for a relatively long time before results are observed. Thus many function calls take callbacks as arguments.
The embeNET Node library provides the following interfaces:
The embeNET Node API provides a set of functions that allow the stack to initialize and run in the node. It also allows the node to join a specific network that is typically started by a special device called border router. The node can also join several groups which are then addressable through multicast addressing. This interface also allows to schedule user defined tasks that are run in an event-driven fashion in the node.
The embeNET UDP C API provides a set of functions that allow to register network sockets, through which all user communication is carried out. The protocol of choice here is UDP. See embeNET UDP C API for the description of functions and Using UDP sockets for the information on how to use them.
The embeNET Node follows a simple init-proc-deinit rule. Before using the stack you need to initialize the library through a call to EMBENET_NODE_Init. Once the stack is initialized it is expected that the EMBENET_NODE_Proc is called periodically - usually within the main program loop. When (and if) the application is done with the stack it may call EMBENET_NODE_Deinit to deinitialize it.
The following example illustrates the idea:
Once the stack is initialized the following elements of the API are available:
- network management (see Network handling)
- group management (see Group handling)
- task management (see Task handling)
- utility functions, however some of them may not return valid results until the node joins the network (consult their description)
When networking begins, the stack generates important events that can and should be handled by the application code. These events are handled through callback handlers that are passed the the EMBENET_NODE_Init function. This function accepts a structure that gathers all the handlers which are later called by the stack on specific events. See Network handling for the description of the events and the callbacks.
There is just one more thing to consider. The embeNET Node stack uses EXPECT error handling utility for handling input validation and unrecoverable errors. Whenever the stack detects an really faulty condition it calls the EXPECT_OnAbortHandler which must be defined in the user code. The program must not continue operation after calling this function. A typical behavior of such function is to:
- go to a safe state
- log the error
- halt or reset the application
Below is the empty implementation of such function that you can copy, paste and extend.
Once the embeNET Node is initialized through a call to EMBENET_NODE_Init it is possible to start networking. The following sections will guide you through the topics concerning networking.
In order to join a network the node should provide three things:
- UID - This is the unique node identifier, also called EUI64. This should be unique across the whole device inventory as it identifies the actual piece of hardware the stack runs on. Typically this address should be provided by the hardware and the stack user should not alter it. The application code may get this UID through a call to EMBENET_NODE_GetUID. In rare cases the application may want to change the UID. This can be done through a call to EMBENET_NODE_SetUID, however it is strongly advised not to do so.
- network key (K1) - This is a common 128-bit key shared across all the nodes within a single network. This key is not used for security and must be the same in all the nodes wishing to communicate, including the border router.
- pre-shared-key (PSK) - This is a device-specific 128-bit key. Depending on the join rule strategy applied in the border router this key can be used to authorize the node in the border router. It is recommended that each node in the network has a different PSK key, as it allows the border router to apply more selective joining process of nodes. The PSK key can be treated as a device signature and should be kept secret.
At this point it is worth to mention how network joining rules work in the border router. The border router may apply one of these rules:
- every node may join the network - this is useful for development but not for real deployments, due to security concerns
- only nodes with matching UIDs may join - this rule only considers UIDs of the nodes and provides very limited security, as the UIDs are visible to the outside world
- only nodes with matching PSK may join - this rule only considers pre-shared-keys so that only nodes with matching PSKs can join, which gives reasonable security as PSK should be kept secret
- only nodes with matching pairs of UID+PSK can join - this is the most secure node authorization strategy, but requires most preparation in the border router.
As a result, employing different PSKs in nodes gives most options in deploying various authorization strategies in the border router.
In order to join the network the application should call EMBENET_NODE_Join after the stack is initialized. The following example illustrates the idea:
In the above example, there is just one callback function hooked up as an event handler - EMBENET_NODE_EventHandlers.onJoin. This function will be called when (and only if) the node joins the network with the given configuration options.
Once the EMBENET_NODE_Join is called the node enables radio reception listening to the neighboring networks. It then tries to connect to the networks that match the K1 network key. During the join process the node sends authentication data to the border router, which accepts or rejects the node. This process may take a significant amount of time, depending on the network structure and load.
If there is more than one network visible to the node, the node will sequentially try to join each one of them. For each attempt to join a network the stack will call the EMBENET_NODE_EventHandlers::onJoinAttempt event handler to indicate that a particular network is considered.
The joining process stops only when the node successfully joins to one of the available networks. The join process has no timeout. If the application needs to have such timeout then it should call EMBENET_NODE_Leave after the timeout was reached (see Task handling on how such a task can be scheduled).
When the node joins the network, the onJoinCallback function will be called from the context of the EMBENET_NODE_Proc function. This callback will receive the PAN ID identifier that identifies the radio network that the node has joined. In addition the application will receive a set of credentials (EMBENET_NODE_QuickJoinCredentials) that can be used to quickly re-join the same network. See Quick join for more details.
Once the node has joined the network it is possible to:
- obtain the border router IPv6 address through a call to EMBENET_NODE_GetBorderRouterAddress
- obtain the network time using EMBENET_NODE_GetNetworkTime
- schedule tasks in network time (see Task handling)
In some cases joining the network can take a significant amount of time. To speed up this process a quick join technique is introduced. It is particularly useful in cases when the node goes through to reset (either intentional or unintentional) and it would be beneficial to quickly rejoin the network the node was joined to when that happened.
The quick join mechanism relies on the fact that during regular network join the node obtains a set of credentials that are negotiated with the border router. Knowing these credentials allows the node to participate in the network. These credentials (EMBENET_NODE_QuickJoinCredentials) are provided in the EMBENET_NODE_EventHandlers::onJoined event callback. The application that wishes to re-use them for quick join should store them in secure, non-volatile memory. Once this is done the next time the node joins the network it is possible to call EMBENET_NODE_QuickJoin (instead of EMBENET_NODE_Join), providing the recalled credentials.
Once the EMBENET_NODE_QuickJoin is called the node tries to join the network matching the credentials. In such a network is found, the process takes up to 1 minute. And if the join succeeded, the EMBENET_NODE_EventHandlers::onJoined event handler is called, just like during regular join.
However if the quick join fails to join, it is assumed that the provided credentials became already obsolete. This might happen for example if the whole network is restarted. In such case the EMBENET_NODE_EventHandlers::onQuickJoinCredentialsObsolete event handler is called, indicating that the stored credentials should probably be forgotten (deleted from the application memory). This however does not stop the join process. The node falls back to the regular join process without any additional user intervention and may receive EMBENET_NODE_EventHandlers::onJoined callback if the matching network becomes available.
The following code is an exemplary application of the quick join mechanism.
In the above code, after stack initialization (EMBENET_NODE_Init) the 'join' function is called. This function checks if there are any quick join credentials stored. If so it starts the quick join mechanism and calling the EMBENET_NODE_QuickJoin function. If not a regular join (EMBENET_NODE_Join) is performed. In any case if the join is successful the EMBENET_NODE_EventHandlers::onJoined event handler is called and the new credentials are stored. However if the quick join is performed and it fails, the EMBENET_NODE_EventHandlers::onQuickJoinCredentialsObsolete is called where the stored credentials should be cleared.
Note, that in the above code the 'saveQuickJoinCredentials', 'loadQuickJoinCredentials' and 'clearQuickJoinCredentials' need to be implemented for the given hardware platform.
If the application wishes to leave the network it should call EMBENET_NODE_Leave. As a result all network activity of the node will be stopped. In addition, if the node was joined to the network then all the tasks that were scheduled in network time get canceled. This call can also be used to stop the on-going joining process.
Leaving the network can also be caused by external conditions. If the node permanently looses wireless connection to other nodes it will in turn leave the network automatically.
In any case, if the node was joined and then left the network (either by a call to EMBENET_NODE_Leave or due to external circumstances causes it to leave the network then the EMBENET_NODE_EventHandlers::onLeft event handler will be called.
The EMBENET_NODE_EventHandlers structure gathers all the event handlers. During stack initialization this structure is passed to the EMBENET_NODE_Init function. If a particular event handler is not used it should be set to NULL.
The following table lists all the event handlers with their description:
|EMBENET_NODE_EventHandlers::onJoined||Called when the node joins a network (see Joining the network)|
|EMBENET_NODE_EventHandlers::onLeft||Called when the node leaves a network (see Leaving the network)|
|EMBENET_NODE_EventHandlers::onJoinAttempt||Called when the node attempts to join a network (see Joining the network)|
|EMBENET_NODE_EventHandlers::onQuickJoinCredentialsObsolete||Called when the credentials used for quick join become obsolete (see Quick join)|
|EMBENET_NODE_EventHandlers::onDataOnUnregisteredPort||Called when an UDP datagram is received but no socket can handle it|
Nodes can be organized in groups. The idea is to enable multicast addressing of such a group so that UDP packets can be sent to all the members of the group simultaneously. It is the node that decides to which groups it belongs to. The border router only observes the grouping behavior of nodes and can list all the group members, but it cannot directly make a given node join or leave particular group.
Each group is identified by a EMBENET_GroupId group identifier, which is freely chosen by the nodes.
In order to join a given group the nodes application should use EMBENET_NODE_JoinGroup after the stack was initialized. Node can join multiple groups by subsequently calling the EMBENET_NODE_JoinGroup function.
The node may also leave particular group using EMBENET_NODE_LeaveGroup function.
At any time after stack initialization the nodes application can get the number of groups it belongs to using EMBENET_NODE_GetGroupCount. Next, it is possible to get group identifiers the node belongs to by calling EMBENET_NODE_GetGroupByIndex with index starting from 0 to the value returned by EMBENET_NODE_GetGroupCount - 1.
The example below illustrates this API usage:
The embeNET Node library provides additional scheduling mechanism that allows to run application code callbacks from the context of the EMBENET_NODE_Proc at a given time. This is useful for implementing periodic behavior such as gathering data from sensors or sending it to the network.
The embeNET Node library uses two notions of time - local time and network time.
Local time is the time taken from a clock that starts to run once the embeNET Node stack is initialized by the EMBENET_NODE_Init function. This clock ticks in milliseconds from 0 until the EMBENET_NODE_Deinit is called. This time can be accessed through a call to EMBENET_NODE_GetLocalTime and it has 64-bit resolution.
In embeNET, every node connected to the network is synchronized to the common network time. The origin of this network clock is the border router. Network time is accessible to the node'a application after the node synchronizes to a network. It can be read through a call to EMBENET_NODE_GetNetworkTime. The network time accessible to the application in measured in milliseconds with 64-bit resolution. Due to security concerns, the network time does not start from zero, but from a random value established at the border router. It is important to node that this time 'flows' only for nodes that are synchronized to the network. If a node desynchronizes - for example leaves the network - then the* network time in that node stops. If the node synchronizes again to the same network then the network time may already have a larger value. Moreover, if the node synchronizes to different network or the network is restarted, the network time may have a completely different (even earlier) value.
The embeNET Node library uses an abstraction of task. Once the stack is initialized by the EMBENET_NODE_Init function, a task can be created using EMBENET_NODE_TaskCreate. The function accepts two arguments:
- the actual function to call as task implementation,
- a user-defined context that will be passed to the task implementation function once it gets called.
The created task is identified by the EMBENET_TaskId value - a task identifier. The stack can only handle a limited number of tasks, so if EMBENET_NODE_TaskCreate returns EMBENET_TASKID_INVALID then no more task can be created.
Once the task is created it can be scheduled to run at a given time - local time or network time - using EMBENET_NODE_TaskSchedule function. When the actual time comes, the task will be run from the EMBENET_NODE_Proc function.
The following example illustrates the task API:
It is worth to mention, that it is relatively easy to reschedule the task that was run inside the task callback function, as shown in the above example.
It is possible to cancel the scheduled task using EMBENET_NODE_TaskCancel. Such tasks can be scheduled to run later.
When the task is not needed, it can be destroyed by a call to EMBENET_NODE_TaskDestroy, however the taskId may be reused by the stack when another task is created.
The tasks that are scheduled in the same network time are invoked synchronously across all the nodes that scheduled them. This opens the opportunity to implement precise synchronous behavior in many networked nodes. However the drawback is that if the node desynchronizes from the network, the events scheduled in network time get canceled automatically and may need to be rescheduled once the node synchronizes again.
For the description of the UDP and sockets refer to Using UDP sockets.
In many cases, internally the embeNET Node library uses the underlying hardware to generate random numbers. For convenience, the random number generator is also exposed to the API in to form of the EMBENET_NODE_GetRandomValue.
Generated on Wed Feb 8 2023 19:52:39 for embeNET NODE by 1.9.3