Node Module (ID 0)
The NodeModule has ModuleId 0.
Purpose
The NodeModule is the central part of the BlueRange Mesh algorithm. It is a mandatory module. It is responsible for clustering and other mesh related tasks and functionalities that are part of BlueRange Mesh. It also has additional core functionality implemented.
Functionality
The node keeps track of all MeshConnections and monitors connects and disconnects. It works in conjunction with the MeshConnection and ConnectionManager classes for this. The node is also responsible for broadcasting mesh discovery (JOIN_ME) packets. It also activates scanning if necessary and monitors the incoming discovery packets of other nodes. It creates new MeshConnections to other nodes if necessary. A state machine is used to toggle between different discovery states.
The node only permits one mesh handshake at a certain time. During the handshake, both mesh nodes exchange information about their clusters and decide which cluster is the biggest one. The packets used are specified under Clustering Messages. During the handshake, a backup of the current cluster state is made and used throughout the handshake. After the handshake, cluster changes that happened in the meantime are sent to these connections. Updates of the cluster size or structure are also sent to all MeshAccessConnections.
There are a number of undocumented commands. These are mostly for debugging and there is no guarantee that they will be available in future builds. |
Reboot Message
On reboot, the firmware sends a JSON to a device that is connected to the terminal output, providing more information about the reboot. The JSON has the following structure:
{
"type":"reboot",
"reason":3, //Integer, see below
"code1":17, //Additional information, depending on the reason given. The valid codes are purposefully undocumented, as they are highly subject to change and are mainly intended to help firmware developers.
"stack":128, //Stack address of the reboot, if available
"version":80021 //Version of the firmware
}
The reason can have the following values:
Code | Name | Description |
---|---|---|
0 |
UNKNOWN |
Reboot reason is unknown. A typical case where this can happen is when the node lost power and thus was unable to save any valid reason. Note that the reason really is unknown and as such could be any other reason as well. |
1 |
HARDFAULT |
A hard fault occurred on the device. |
2 |
APP_FAULT |
Some runtime check of the firmware failed and the device rebooted. |
3 |
SD_FAULT |
Softdevice crashed. |
4 |
PIN_RESET |
The power reset button was pressed. |
5 |
WATCHDOG |
The watchdogs were not fed. |
6 |
FROM_OFF_STATE |
The node previously was turned off and is now turned on. |
7 |
LOCAL_RESET |
The |
8 |
REMOTE_RESET |
The |
9 |
ENROLLMENT |
The reboot happened because of an enrollment. |
10 |
PREFERRED_CONNECTIONS |
The preferred connections were set. |
200-255 |
USER_DEFINED |
Some user defined reboot reasons. |
The reboot reason is also stored in the error log
Capability Retrieval
The capability retrieval command helps a user (e.g. the Bluerange Gateway) to find out what some node in the mesh is capable of. Such a capability can be for example if it is a Regent lamp and if so, how many heads and sensors are connected to it. A node can return an arbitrary amount of capabilities, each in its own message. A single capability consists of the following data:
-
CapabilityEntryType - Whether if the capability describes some kind of hardware (e.g. a lamp), some software (e.g. the firmware version of the lamp), supports a certain device catalog metadata or provides custom properties.
-
Manufacturer - Human readable ASCII String of up to 32 chars, describing who built this piece of hardware/software.
-
ModelName - Human readable ASCII String of up to 53 chars, containing the name of the capability such as the hardware model or software component name.
-
Revision - Human readable ASCII String of up to 32 chars, the "value" of the capability. Often it is used as the version, but it can also be the amount of something (e.g. the amount of lamps connected to this node). As such, the interpretation of this field is highly dependent on the capability.
None of the Manufacturer, ModelName, or Revision fields are zero terminated strings. They must always be copied in their entirety. |
Each module in the firmware has a function with the following header: CapabilityEntry GetCapability(u32 index, bool firstCall)
. The function is called with consecutive indices, starting with 0 for every module. If the module has a capability for this index available, it should be returned accordingly. If none is available with the given index, the CapabilityEntryType::INVALID
must be returned. No other valid capabilities may follow after the first invalid index. For some capabilities, some connected vendor controller has to be queried first. In such a case, the capability is not yet ready, but may become ready in the future. CapabilityEntryType::NOT_READY
is returned in these cases. The function will be called again with the same index shortly. For some modules it is important to know if this was the first call of the current capability retrieval. If the first capability of the module would return CapabilityEntryType::NOT_READY
, the module cannot distinguish between the first call solely based on the index. For such cases, the bool firstCall
is passed as well.
Every module has its own distinct local capability index range which always starts with 0. |
The Capability Retrieval has room for some future improvements. We could loosen up the strict restrictions of 32/53/32 chars per field and instead add the restriction of 115 (32 + 53 + 32 - 2) chars in total, shared among the three fields, adding either a header that describes the length of the first two entries or zero terminate them. This could also speed up capability retrieval as smaller messages need to be transferred. Capability retrieval can become quite complex in firmware, such as when having to wait for the response of some controllers. In addition, capabilities are not intended to be queried frequently, but e.g. for auto-detection during initial setup of the system or after update installation. |
Hard- and software capabilities should list the individual components the device is made of. This can be reported as e.g.:
CapabilityEntry entries[] = {
{ CapabilityEntryType::HARDWARE, "M-Way Solutions GmbH", "BlueRange Node", "52-rev B" },
{ CapabilityEntryType::SOFTWARE, "M-Way Solutions GmbH", "BlueRange BootLoader", "1.0.100" },
{ CapabilityEntryType::SOFTWARE, "M-Way Solutions GmbH", "BlueRange Firmware", "1.0.100" },
};
The metadata capabilities refer to additional documents build into the BlueRange system device catalog. The catalog data itself is maintained in and consumed by higher-level software components outside of firmware code. The document referenced states details regarding the actuators and sensors of the device incl. their types, units and possible ranges. Also characteristics curves of the power consumed for evaluation in smart metering may be part of the data provided.
As an example, the entry { CapabilityEntryType::METADATA, "Smart Lights Ltd.", "NightLight Moon", "1.0" }
references version 1.0 of the catalog metadata for the product NightLight Moon
of Smart Lights Ltd.
.
In addition properties may be made available. This may be used to report device-specific values that make no sense looking up in the device catalog. An example use is the number of heads in a multi-part device, e.g. { CapabilityEntryType::PROPERTY, "Smart Lights Ltd.", "Lamp head count", "2" }
. The model name field serves as a key, while the revision serves as a value. Property data is vendor-specific and may be used in catalog data to check its applicability beyond just the hard- and software capabilities.
See this section on how to retrieve the capabilities through the terminal.
Terminal Commands
Getting Basic Information (Local Command)
status
It is very convenient to get easily readable information about a node. The status command displays the currently active connections and their state. It also display device information and the clustering state.
The following will be printed on the local terminal after the command was entered:
Node BBBBB (nodeId: 1) vers: 80000, NodeKey: 01:00:....:00:00 Mesh clusterSize:10, clusterId:4201185286 Enrolled 1: networkId:10, deviceType:0, NetKey 04:00:....:00:00, UserBaseKey 00:00:....:00:00 Addr:00:00:00:01:00:00, ConnLossCounter:3, AckField:0, State: 1 CONNECTIONS 2 (freeIn:0, freeOut:2, pendingPackets:0 IN (0) FM 7, state:4, cluster:fa690006(8), sink:-1, Queue:0-0(0), Buf:1/7, mb:0, hnd:16 OUT(1) FM 10, state:4, cluster:fa690006(1), sink:-1, Queue:0-0(0), Buf:1/7, mb:1, hnd:17
Setting The Discovery State
action [nodeId] node discovery [on / off]
It might be necessary to switch the node’s state machine into a different discovery state. This can be done through the mesh and can be used by a MeshGateway to turn off discovery once all enrolled nodes are connected. This allows the node to use a very low power consumption if scanning doesn’t need to be active for other tasks.
Once the node loses a connection to one of its partners, it will automatically switch discovery on again.
Examples
//E.g. switch discovery off for all nodes
action 0 node discovery off
The response acknowledges the receipt of the command.
{"type":"set_discovery_result","nodeId":123,"module":0}
Resetting Nodes
Sometimes it is necessary to reset one or all nodes connected to a mesh at once. The reset command can be used for this purpose. After receiving the command, each node waits a predefined time before performing a reset. This time can be defined and is set to 10 seconds by default. This ensures that the packet is sent to all nodes before the reset process starts.
//Receiving nodes will reset within a few seconds
action [nodeId] node reset {waitSeconds}
Ping a node
action [nodeId] node ping {requestHandle}
Pings the given nodeId. Once received, a ping response is sent back.
Examples
action 123 node ping
action 123 node ping 100 //Ping with request handle 100
The response acknowledges the receipt of these commands.
{"type":"ping","nodeId":123,"module":0,"requestHandle":0}
{"type":"ping","nodeId":123,"module":0,"requestHandle":100}
Dynamic Group Ids
A node can be added to and removed from one or more dynamic groups. The assignment is stored persistently until a factory reset is performed. These IDs - a subset of the NodeId range - can be used to broadcast messages to a selected group of nodes simultaniously. All nodes that are part of the group will respond to a message that is adressed to it. See the specification for the range of NodeIds belonging to the dynamic group range. Trying to set groupIDs outside of this range will result in an error. A node can belong to up to 20 groups, although this limitation is potentially subject to change in the future.
//Add the node to a group
action [nodeId] node add_group [groupId] {requestHandle}
//Remove the node from a group
action [nodeId] node remove_group [groupId] {requestHandle}
//Remove the node from all groups
action [nodeId] node clear_groups {requestHandle}
//Get all groups
action [nodeId] node get_groups {requestHandle}
The response acknowledges the receipt of these commands.
{"type":"add_group_result","nodeId":1,"module":0,"group":21001,"code":0}
{"type":"remove_group_result","nodeId":1,"module":0,"group":21001,"code":0}
{"type":"clear_groups_result","nodeId":1,"module":0,"group":0,"code":0}
{"type":"get_groups_result","nodeId":1,"module":0,"groups":[21001, 21007]}
The codes are defined as:
Code | Name | Description |
---|---|---|
0 |
SUCCESS |
The operation was successful. This is also returned if adding a node to a group that it already belongs to or removing it from a group that it didn’t belong to. |
1 |
OUT_OF_RANGE |
The given group ID was not inside the valid range. |
2 |
NO_GROUPS_LEFT |
The node already belongs to too many groups. |
100… |
RECORD_STORAGE_ERROR_OFFSET |
The Record Storage reported some error. These error codes are embedded here, with an offset of 100. |
Generating Load
action [nodeId] node generate_load [target] [size] [amountOfMessages] [timeBetweenMessagesDs] {requestHandle}
Can be used to put message load on the mesh, mainly for measuring and debug purposes of installations (thus not part of the DebugModule). After the node with nodeId receives this message it will send messages with a payload of size every timeBetweenMessagesDs to the target until it sent a total of amountOfMessages.
Example
action 2 node generate_load 3 10 2 13 18
The response acknowledges the receipt of this command
{"type":"start_generate_load_result","nodeId":2,"requestHandle":18}
and starts sending generate_load_chunk messages to node 3. Node 3 then logs:
{"type":"generate_load_chunk","nodeId":2,"size":10,"payloadCorrect":1,"requestHandle":18}
{"type":"generate_load_chunk","nodeId":2,"size":10,"payloadCorrect":1,"requestHandle":18}
Querying Device Capabilities through the Terminal
request_capability [nodeId]
Requests the capabilities of the node with nodeId (NodeId 0 cannot be used with this command). The receiver then answers with several messages, each representing a single capability. After all capabilities are sent, the receiver sends a last message indicating the end of the transaction.
A single capability message is a rather big message with 128 bytes in size. As such only a single node in the mesh should be queried for it’s capabilities at a time, else the mesh would be put under heavy load. Broadcasting this command is not supported by the firmware for this reason. |
Examples
//Requesting capabilities of node 4
request_capability 4
The receiver sends all its capabilities:
{
"nodeId":4,
"type":"capability_entry",
"index":0, // Ascending unique number for each capability
"capabilityType":2, // 1: Hardware, 2: Software, 3: Metadata, 4: Property
"manufacturer":"M-Way Solutions GmbH", // Up to 31 chars
"model":"BlueRange Node", // Up to 52 chars
"revision":"0.8.451" // Up to 31 chars
}
{
"nodeId":4,
"type":"capability_entry",
"index":1,
"capabilityType":1,
"manufacturer":"Vendor GmbH",
"model":"Super Fast Chip",
"revision":"Full ASCII support <(^.^)> 4.1"
}
And ends the transaction:
{
"nodeId":4,
"type":"capability_end",
"amount":2 // The amount of capabilities just sent. Can be used to check if all capabilities were received.
}
Setting Preferred Connections
action [nodeId] node set_preferred_connections [ignored / penalty] {up to eight preferred nodeIDs}
Sets the given node IDs as preferred connection partners while meshing. Other partners will be either completely ignored or their cluster score gets a heavy penalty. Executing this command without any nodeID disables this feature. After saving the preferred connections, the node reboots after a delay of 10 seconds. The "ignored / penalty" parameter determines the behaviour regarding the unpreferred connection partners, meaning any node ID that is NOT in the associated list.
For a connection to happen, both connection partners have to set each other as a preferred connection partner. This means to set the preferred connections of a mesh, it is best to start at the leaves of the mesh. |
Using this command with the "ignored" parameter must be used with caution as using invalid or unreachable nodeIDs results in a state where the mesh can not be created. If this happened, there are two options: 1. Flash the beacon. This erases the set preferred connections. 2. Connect to the beacon via a mesh access connection and execute the command again with the correct parameters. |
Examples
//E.g. Sets the preferred connections of 123 to 17, 32 and 12. Other connections partners are ignored for meshing.
action 123 node set_preferred_connections ignored 17 32 12
The response acknowledges the receipt of the command.
{"type":"set_preferred_connections_result","nodeId":123,"module":0}
Setting number of enrolled nodes
action [nodeId] node set_enrolled_nodes {number of nodes in network}
Sets a number of nodes in current network. When network reaches size defined by this command it will enter idle discovery mode. It means it will consume much less energy while still being able to connect new nodes if needed. To reset this feature simply set enrolled nodes to 0.
If size of the network will increase over the value of enrolled nodes it will be assumed that configuration is no longer valid and enrolled nodes will be set to 0. Care is taken to allow bigger network during clustering. |
Examples
//E.g. Sets the number of enrolled nodes to 8. This value is send directly to node 1, but will be auto-distributed to other nodes.
action 1 node set_enrolled_nodes 8
The response acknowledges the receipt of the command.
{"type":"set_enrolled_nodes","nodeId":8,"module":0,"enrolled_nodes":8}
Sensor and Actuator Messages
The node includes functionality to send sensor messages and actuator messages in a vendor specific manner using a generic packet. This is documented under Sensors and Actuators.
Time Synchronization
To synchronize a time over the mesh, the time needs first to be set on the local node using the settime command. The time is stored internally as an unsigned 32-bit integer together with an additional variable that stores the extra number of crystal ticks for better accuracy. The number of offset minutes is used to pass additional time zone information, e.g. pass 120 to get a time zone offset of 2 hours.
settime [u32 unixTimestampSec] [i16 offsetMinutes]
Afterwards, the local time of the local node can be queried on the terminal using:
gettime
The output gives the local time and date of the node in a human-readable format. This is only an approximate calculation. It is just to verify if the time was set correctly. Internally, the nodes work with Unix time stamps.
To query the local time of a node in the network with e.g nodeId 1234, the following command can be used:
action 1234 node gettime
Sample response:
{"type":"get_time_result","nodeId":1234,"module":0,"syncState":2,"time":1654247506,"offset":120,"master":0}
You can see if the requested node is currently regarded as the time master, which means that it was used to sync the time to the network. If logging is enabled, the timestamp will also be logged out in a human readable format.
Querying Active Modules
get_modules [nodeId]
Often it is necessary to get a list of modules that are available on a node. The list provided by the get_modules command includes all modules that are available (compiled into the firmware): their moduleId, their version and whether they are currently active.
{
"nodeId": 1,
"type": "module_list",
"modules": [
{
"id": 1,
"version": 2,
"active": 1
},
{
"id": 2,
"version": 1,
"active": 0
},
// ...
]
}
Rawsend
rawsend [dataHex]
Mostly used for debugging purpose, the rawsend command can be used to send any binary message through the mesh (as long as it is valid). The data can be given either base64 encoded or as a hex string with colons.
//Can be entered on nodeId 1 to send a get_status request to all nodes
//(messageType 0x33, senderId 0x0001, receiverId 0x0000, moduleId x03, requestHandle 0x00, actionType 0x01)
rawsend 33:01:00:00:00:03:00:01
Raw Data
The node offers functionality for sending custom data through the mesh using a lightweight wrapper for either big or small messages. This is documented at the RawData page.
Messages
Clustering Messages
ClusterWelcome (Local Handshake Between Two Nodes)
The ClusterWelcome Packet is sent be the node that thinks it has the bigger cluster. If not, the other node will also send a ClusterWelcome packet so both nodes know who is bigger.
Bytes | Type | Name | Description |
---|---|---|---|
5 |
header |
messageType: MESSAGE_TYPE_CLUSTER_WELCOME(20) |
|
4 |
ClusterId |
clusterId |
ID of the cluster |
4 |
ClusterSize |
clusterSize |
Size of the cluster |
4 |
u16 |
meshWriteHandle |
Write handle for RX characteristics of the mesh for data transmission. (Allows to skip service discovery) |
4 |
ClusterSize |
hopsToSink |
The number of hops to sink if there is one, otherwise -1. |
1 |
u8 |
preferredConnectionInterval |
Preferred interval for the meshConnection |
2 |
NetworkId |
networkId |
Network ID of the other clusters |
ClusterAck1 (Local Handshake Between Two Nodes)
Acknowledge packet sent by the smaller cluster to acknowledge that it is now participating in the mesh.
Bytes | Type | Name | Description |
---|---|---|---|
5 |
header |
messageType: |
|
4 |
ClusterSize |
hopsToSink |
Hops to the shortest sink |
1 |
u8 |
preferredConnectionInterval |
Preferred interval for the meshConnection |
ClusterAck2 (Local Handshake Between Two Nodes)
Acknowledge packet sent by the bigger cluster after receiving ack1 from the smaller cluster
Bytes | Type | Name | Description |
---|---|---|---|
5 |
header |
messageType: |
|
4 |
ClusterId |
clusterId |
ID of the cluster |
4 |
ClusterSize |
clusterSize |
Size of the cluster |
4 |
ClusterSize |
hopsToSink |
The number of hops to sink if there is one, otherwise -1. |
ClusterInfoUpdate
This packet informs a node about a change in the cluster size or structure. It can be sent throughout the mesh but is modified on each node before resending. It will only give the change in clusterSize and not the absolute value, the node must keep count itself. It will however give the absolute size if it is sent over a MeshAccessConnection.
Bytes | Type | Name | Description |
---|---|---|---|
5 |
header |
messageType: |
|
4 |
u32 |
reserved |
deprecated |
2 |
ClusterSize |
clusterSize |
Change in clusterSize or absolute size |
2 |
ClusterSize |
hopsToSink |
The number of hops to sink if there is one, otherwise -1. |
1 bit |
u8 : 1 |
connectionMasterBitHandover |
Hands over the masterBit to the bigger cluster. If sent over the MeshAccessConnection, this is 1 if the node has the masterBit. |
1 bit |
u8 : 1 |
counter |
Next expected sequence number for clusterUpdate |
6 bit |
u8 : 6 |
reserved |
- |
ping
Bytes | Type | Name | Description |
---|---|---|---|
8 |
Conn Packet Module |
ModuleId = 0, Message Type = 51, Action Type = 3. |
ping response
Bytes | Type | Name | Description |
---|---|---|---|
8 |
Conn Packet Module |
ModuleId = 0, Message Type = 52, Action Type = 3. |
start generate load
Bytes | Type | Name | Description |
---|---|---|---|
8 |
Conn Packet Module |
ModuleId = 0, Message Type = 51, Action Type = 4. |
|
2 |
NodeId |
target |
NodeId of the the target that should receive the chunks. |
1 |
u8 |
size |
Size of the payload of each chunk. |
1 |
u8 |
amount |
Amount of chunks to send. |
1 |
u8 |
timeBetweenMessagesDs |
The time between each chunk in deciseconds. |
start generate load response
Bytes | Type | Name | Description |
---|---|---|---|
8 |
Conn Packet Module |
ModuleId = 0, Message Type = 52, Action Type = 4. |