Node

ModuleId 0

Purpose

The node module is the central part of the FruityMesh Algorithm. It is a mandatory module. It is responsible for clustering and other mesh related tasks and functionalities that are part of FruityMesh.

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 will also activate scanning if necessary and monitor the incoming discovery packets of other nodes. It will create 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 will exchange information about their clusters and will 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 on 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 the connected device, giving 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

The 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 though, that the reason really is unknown and as such could have any other reason as well.

1

HARDFAULT

A hardfault occurred on the device.

2

APP_FAULT

Some runtime check of the firmware failed, which rebooted the device.

3

SD_FAULT

The 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 "reset" command was used, without any additional parameters.

8

REMOTE_RESET

The "action NodeId node reset" command was sent to the node.

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.

Terminal Commands

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 does not have 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}

Setting prefered connections

action [nodeId] node set_preferred_connections [ignored / penalty] {up to eight preferred nodeIDs}

Sets the given nodeIDs 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 nodeID that is NOT in the thereafter following list. NOTE: For a connection to happen, both connection partners have to set each other as a preferred connection partner. This means that if you want to set the preferred connections of a mesh, you should best start at the leafs of the mesh.

WARNING!!! 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 to you, you have 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 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}

Sensor values

// Generate a sensor event and send through mesh
// (Only used for debugging)
component_sense [nodeId] [moduleId] [actionType] [component] [registerAddress] [dataHex] {requestHandle=0}

//E.g. broadcast sensor event for module 123 from component 7 and registerAddress 77
component_sense 0 123 0 7 77 AA:BB

Following low-level data structure transports sensor measurement values across the mesh.

enum SensorMessageActionType {
    UNSPECIFIED = 0, // E.g. Generated by sensor itself
    ERROR_RSP = 1, // Error during READ/WRITE/...
    READ_RSP = 2, // Response following a READ
    WRITE_RSP = 3 // Response following a WRITE_ACK
}
Bytes Type Name Description

5

connPacketHeader

header

1

u8

moduleId

The module that generated this value

1

u8

requestHandle

Optional request handle, otherwise 0

1

u8

actionType

One of the above actionTypes

2

u16

component

Some number identifying the source of the measurements such as a lamp head (vendor specific)

2

u16

registerAddress

An address used to differentiate data transported such as a hardware register number or a message profile id (vendor specific)

1-..

u8[]

payload

Actual binary data that represents a sensor reading or multiple.

The packet header consumes 12 bytes, which allows for 8 bytes of payload in a single packet and should be enough for most sensor values. For bigger payloads, it will be split.

The output on a sink will be:

{
	"nodeId": 5,                // sender
	"type": "component_sense",  // discriminator
	"module": 123,              // moduleId
	"requestHandle": 0,
	"actionType" : 0,
	"component" : 7,
	"register" : 77,
	"payload": "abcdeQ=="       // base64 encoded
}

Actor message

// Instruct device to write data into a register address
component_act [nodeId] [moduleId] [actionType] [component] [registerAddress] [dataHex] {requestHandle=0}

The following message is used for transporting write or read requests through the mesh.

enum ActorMessageActionType {
    RESERVED = 0, // Unused
    WRITE = 1, // Write without acknowledgement
    READ = 2, // Read a value
    WRITE_ACK = 3 // Write with acknowledgement
}
Bytes Type Name Description

5

connPacketHeader

header

1

u8

moduleId

The module that should act on the message

1

u8

requestHandle

Optional request handle, otherwise 0

1

u8

actionType

One of the above actionTypes

2

u16

component

Some number identifying the destination for the action (vendor specific)

2

u16

registerAddress

An address, e.g. hardware register number or a message profile id where the data should be written (vendor specific)

1-..

u8[]

payload

For READ, this must be a singly byte that represents the number of bytes to read. For WRITE and WRITE_ACK, the payload is the bytes that should be written.

No json representation is necessary for the moment as the meshgateway will not react on act messages.

Getting basic information (Local Command)

status

It is very conveniant 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 Terminal Mode (Local Command)

When working with UART (Terminal and Uart must be enabled), FruityMesh supports a convenient blocking terminal mode with echo back functionality. For communication with another device such as a MeshGateway, an interrupt based input method and json output is used. To toggle between these two modes, there are two commands.

startterm

Using startterm will use a blocking mode where all functionality is halted and user input is received in a busy loop until a line feed '\r' is received. The command will then be processed and other functionality will be resumed. The input will be echoed back on the terminal and backspace is supported as well for most terminal programs. If the command cannot be recognized, a warning will be echoed.

stopterm

The stopterm command will switch the node into an interrupt based input mode where terminal input does not affect the functionality until a line feed '\r' is received. All output messages will be in json format.

Rebooting (Local Command)

reset

If you just want to reset the node via the terminal, enter this command and it will do a software reboot.

Time Synchronization

If you want to synchronize a time over the mesh, you must first set the time to your local node using the settime command. It is internally stored as an unsigned 32 bit integer.

settime [u32 unixTimestampSec] [i16 offsetMinutes]

Afterwards, you can query the time of the local node using:

gettime

The output should give you the current time and date of the node in a human readable form. Be aware that this is only an approximate calculation. It is just for debugging if the time was set correctly. Internally, the nodes only work with unix timestamps.

Querying Active Modules

get_modules [nodeId]

Often, it is necessary to get a list of modules that are available on a node. The returned list 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
        },
        // ...
    ]

Sending raw data

General overview

raw data flow

Sending raw data can be used to send any arbitrary data, for example (but not limited to) zip files. The data which should be sent is split into various chunks which are then sent through the mesh. The data which should be sent is called the "payload".

Every raw data transaction (except raw_data_light, which will be explained later) starts with a raw_data_start message. This message includes the amount of chunks in the transaction and the protocol of the payload. Once the receiver receives this message, he answers with a raw_data_start_received message, which indicates to the sender that the receiver is ready for the transaction of the chunks.

When the sender receives this message, he starts sending all the raw_data_chunks. Besides part of the payload, every raw_data_chunk includes a chunkID which is a uniquely (regarding the current transaction) ascending number, starting at 1 for the raw_data_chunks. The chunkID 0 is reserved for the raw_data_start which always implicitly has the chunkID 0. Using this chunkID, and the information of the amount of chunks form the raw_data_start message, the receiver is able to determine whether or not a received chunk is the last chunk in the transaction. Once he receives the last chunk he reports back to the sender using a single raw_data_report. This message includes up to three missing chunkIDs. If all chunks were received, the list of missing chunks is empty, which tells the sender that the transaction was successful. If however, the list of missing chunks is not empty, the sender must send the chunks with the corresponding chunkIDs again. The last chunkID of the previous raw_data_report message acts as a last chunk, regarding the sending of additional raw_data_reports. This sending of raw_data_chunks and raw_data_reports is repeated until raw_data_report has an empty list of missing chunkIDs.

All devices involved in the communication (meaning both sender and receiver as well as the mesh) are able to cancel the communication by sending a raw_data_error message to the sender as well as the receiver. If the sender or the receiver is the device that hung up the transmission, it is allowed to not send the error to itself. The raw_data_error message includes an error code, indicating the reason for the cancellation. It is possible to receive a raw_data_error message without an open transmission. This can happen for example, if the sender cancels the transmission using a raw_data_error, but this error is dropped during the transmission. The receiver then might send another raw_data_error indicating a timeout while the sender already canceled the communication. Such messages without an open transmission may be discarded.

Dropped messages

As any other message in the mesh, every message in the raw send protocol could be dropped. This section describes how an implementation must behave in such scenarios. It also tells the obligations of the sender and the receiver.

Dropped message Reaction

raw_data_start or raw_data_start_received

The sender must send the raw_data_start again after a timeout of 10 seconds or stop the transmission. The receiver thus has to be able to handle several successive raw_data_start messages with the same content (in case the raw_data_start_received message is dropped) as well as closing a dropped connection after a timeout of 15 seconds (in case the sender does not send another raw_data_start).

raw_data_chunk

Missing chunks are reported in raw_data_report once the last chunk is received. These missing chunks must be resent.

Last raw_data_chunk or raw_data_report

Using the ChunkID, both sender and receiver are able to identify the last data chunk. If this message or the raw_data_report is dropped, the sender must send the last chunk again. This however means that the receiver is only allowed to save the last chunk id once the first chunk after a raw_data_report is received, not immediately after the raw_data_report is sent.

raw_data_error

If a raw_data_error message is dropped, the sender or receiver has already canceled the transmission, leading to the sending of another raw_data_error upon receiving an invalid out-of-transmission message or a raw_data_error indicating a timeout. In the rare cases where the origin of the raw_data_error is the mesh itself, it could happen that both raw_data_errors are dropped. In such cases the connection is still up but probably will create another raw_data_error once the ill-formed chunk is sent again.

Start of a transmission

raw_data_start [receiverId] [destinationModuleId] [numChunks] [protocolId] {requestHandle = 0}

This command starts a raw data transmission. The payload shall be sent using raw_data_chunk messages.

Parameter Type Description

receiverId

u16

The NodeID that this message should be sent to

destinationModuleId

u8

The ModuleId is used for giving context to this message. If the transmission should only be printed on the receiver and otherwise be ignored by the firmware, it must be set to 0.

numChunks

u24

Number of Chunks for the total message. Must not be 0.

protocolId

u8

One of the protocolIds mentioned in the table below

requestHandle

u8

A handle that can be used to distinguish between different raw data transmissions (Default: 0)

Protocol ID Name Description

0

Invalid

Invalid Protocol ID

1

HTTP

A raw HTTP request or response

2

GZIPPED_JSON

A JSON that was gzipped

3 - 199

Reserved

Not yet used

200 - 255

User defined

May be different in each implementation

If received by a JSON capable device, the raw_data_start will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_start",
	"module":4,
	"numChunks":3,
	"protocol":1,
	"fmKeyId":2,
	"requestHandle":0
}

Accepting a transmission

raw_data_start_received [receiverId] [destinationModuleId] {requestHandle = 0}

Once a raw_data_start is received, the receiver shall send the sender a raw_data_start_received message.

Parameter Type Description

receiverId

u16

The NodeID that this message should be sent to

destinationModuleId

u8

The ModuleId is used for giving context to this message. If the transmission should only be printed on the receiver and otherwise be ignored by the firmware, it must be set to 0.

requestHandle

u8

A handle that can be used to distinguish between different raw data transmissions (Default: 0)

If received by a JSON capable device, the raw_data_start will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_start_received",
	"module":4,
	"requestHandle":0
}

Subsequent chunk messages

raw_data_chunk [receiverId] [destinationModuleId] [chunkId] [payloadHex] {requestHandle = 0}

Once a raw transmission was started, the appropriate number of chunks should follow in the correct order. Once the last chunk is received by the receiver it is possible to reassemble and parse the whole message. The moduleId is present in all chunks so that they can be assigned to the correct stream and to avoid clashes between different modules. A module can send intermittent data streams if is uses different request handles.

Parameter Type Description

receiverId

u16

The NodeID that this message should be sent to

destinationModuleId

u8

The ModuleId is used for giving context to this message. If the transmission should only be printed on the receiver and otherwise be ignored by the firmware, it must be set to 0.

chunkId

u24

ID of this data chunk starting from 0.

payloadHex

HexString or Base64String

The binary data to send. E.g. AA:BB:CC. The maximum length is 60 bytes for HexStrings, 120 bytes for Base64Strings.

requestHandle

u8

A handle that can be used to distinguish between different raw data transmissions (Default: 0)

If received by a JSON capable device, the raw_data_start will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_chunk",
	"module":4,
	"chunkId":1,
	"payload":"abcdeQ==",
	"requestHandle":0
}

Sending a report

raw_data_report [receiverId] [destinationModuleId] [MissingChunkIds] {requestHandle = 0}

Once the last chunk is received, the receiver sends this message to the sender, indicating either a successful transmission (empty missing chunk IDs) or informs the sender about missing chunk IDs.

Parameter Type Description

receiverId

u16

The NodeID that this message should be sent to

destinationModuleId

u8

The ModuleId is used for giving context to this message. If the transmission should only be printed on the receiver and otherwise be ignored by the firmware, it must be set to 0.

MissingChunkIds

Comma separated Integers or the literal string "-" (without "")

Up to three chunkIDs of missing chunks. Must not contain spaces! E.g. 2,17,312

requestHandle

u8

A handle that can be used to distinguish between different raw data transmissions (Default: 0)

If received by a JSON capable device, the raw_data_start will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_report",
	"module":4,
	"missing":[2,17,312],
	"requestHandle":0
}

Or in cases where the transmission was successful:

{
	"nodeId":5,
	"type":"raw_data_report",
	"module":4,
	"missing":[],
	"requestHandle":0
}

Sending an error

raw_data_error [receiverId] [destinationModuleId] [errorCode] [destination] {requestHandle = 0}

This command indicates that some error occurred and the transmission must be closed. Will be sent to the receiver as well as the sender.

Error Code Name Meaning

1

Unexpected end of transmission

Three timeouts happened without receiving a message from the transmission partner.

2

Not in a transmission

A raw_data_chunk or raw_data_report was received without an open transmission.

3

Malformed Message

A message was received which was malformed and did not fit any other error code.

4

Unsupported Protocol

The receiver is unable to interpret the given protocol.

5

Malformed GZIP

The receiver got all chunks but could could not unpack the gzip.

6

Malformed Type

Thrown in case of a gzip communication. The unpacking worked, but the provided type inside the gzip json was unknown.

7

Invalid Chunk ID

The given chunk ID was out of range.

0, 4 - 199

Reserved

Not yet used

200 - 255

User defined

May be different in each implementation.

Destination Code Name Meaning

1

Sender

The error is sent to the sender.

2

Receiver

The error is sent to the receiver.

3

Both

The error is sent both to the sender and the receiver.

If received by a JSON capable device, the raw_data_error will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_error",
	"module":4,
	"error":1,
	"destination":1
	"requestHandle":0
}

Sending a single light message of arbitrary data

raw_data_light [receiverId] [destinationModuleId] [protocolId] [payload] {requestHandle = 0}

Sends a single, responseless chunk of arbitrary data to the receiver. There is no guarantee that the message is transmitted. The sender thus should make sure to have some resending logic.

Parameter Type Description

receiverId

u16

The NodeID that this message should be sent to

destinationModuleId

u8

The ModuleId is used for giving context to this message. If the transmission should only be printed on the receiver and otherwise be ignored by the firmware, it must be set to 0.

protocolId

u8

One of the protocolIds mentioned in the table of raw_data_start

payloadHex

HexString or Base64String

The binary data to send. E.g. AA:BB:CC. The maximum length is 60 bytes for HexStrings, 120 bytes for Base64Strings.

requestHandle

u8

A handle that can be used to distinguish between different raw data transmissions (Default: 0)

If received by a JSON capable device, the raw_data_start will be printed out like this:

{
	"nodeId":5,
	"type":"raw_data_light",
	"module":4,
	"protocol":2,
	"payload":"abcdeQ==",
	"requestHandle":0
}

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 after which both nodes know who is bigger.

Bytes Type Name Description

5

connPacketHeader

header

messageType: MESSAGE_TYPE_CLUSTER_WELCOME(20)

4

ClusterId

clusterId

The id of the cluster

4

ClusterSize

clusterSize

Size of the cluster

4

u16

meshWriteHandle

Write handle for the mesh rx characteristic for data transmission. (Allows us 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

The id of the other clusters network

ClusterAck1 (Local Handshake between two nodes)

Acknowledge packet sent by the smaller cluster to acknowledge that it is now taking part in the mesh.

Bytes Type Name Description

5

connPacketHeader

header

messageType: MESSAGE_TYPE_CLUSTER_ACK_1(21)

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

connPacketHeader

header

messageType: MESSAGE_TYPE_CLUSTER_ACK_2(22)

4

ClusterId

clusterId

The 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

connPacketHeader

header

messageType: MESSAGE_TYPE_CLUSTER_INFO_UPDATE(23)

4

u32

reserved

deprecated

4

ClusterSize

clusterSize

Change in clusterSize or absolute size

4

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

-

Raw Data Messages

raw_data_start

Bytes Type Name Description

8

connPacketModule

Conn Packet Module

Message Type = 54, Action Type = 0.

3

u24

Number of Chunks

The total amount of raw_data_chunk messages for this transmission.

1

u8

Protocol ID

See above for a list of valid protocol IDs.

4

u32

fmKeyId

See EncryptionKeys Heading.

raw_data_start_received

Bytes Type Name Description

8

connPacketModule

Conn Packet Module

Message Type = 54, Action Type = 1.

raw_data_error

Bytes Type Name Description

8

connPacketModule

Conn Packet Module

Message Type = 54, Action Type = 4.

1

u8

Error Code

See above for a list of possible error codes.

1

u8

Destination Code

See above for a list of possible Destination Codes.

raw_data_chunk

Bytes Type Name Description

8

connPacketModule

Conn Packet Module

Message Type = 54, Action Type = 2.

3

u24

Chunk ID

The ID of this chunk. The first chunk has ID 1.

1

u8

reserved

A reserved value that must be set to 0.

1-60

u8[1-60]

payload

The payload.

raw_data_report

Bytes Type Name Description

8

connPacketModule

Conn Packet Module

Message Type = 54, Action Type = 3.

12

u32[3]

missings

The IDs of the missing chunks.

raw_data_chunk

Bytes Type Name Description

5

connPacketHeader

Conn Packet Header

Message Type = 55

1

u8

module ID

The module ID which should receive this message. Must be 0 in most cases.

1

u8

request Handle

The request Handle under which the receiver may (or may not) answer.

1

u8

Protocol ID

See above for a list of all protocol IDs.

1-60

u8[1-60]

payload

The payload.