Messaging Protocol
Messaging in the array is carried out via short unacknowledged packets in the following format (each field is a single byte):
|| H | Z | Length | Destination | Source | Options | Code | Par. 1 | Par. 2 | ------- | CRC8 ||
- ‘H’=72 and ‘Z’=90 are message start delimiters. They mark the beginning of a valid Hexabitz message.
- The Length byte is message length not including H & Z delimiters, the length byte itself and the CRC byte.
- Maximum message length is 56, i.e., the maximum number of parameters per message is 47 or 48 bytes.
- Source and destination are IDs of source and destination modules.
- If the destination is 255 = 0xFF, the message is a broadcast.
- If destination is 254 = 0xFE, the message is a multi-cast (going to a group).
- If destination is 0, the message is sent to an adjacent neighbor only.
- CODE is a 16-bit variable representing Message Codes. If message code is ≤ 255, then only one byte is used. If message code is > 255, then two bytes are used and the Extended Code flag in the Options Byte is set.
- Options: is a byte that contains some option bits and flags. It can be extended using the Extended Options Flag:
- 8th bit (MSB): Long messages flag. If set, then message parameters continue in the next message.
- 6-7th bits: Message response options:
- 00: Send back no response
- 01: Send responses only to Messages.
- 10: Send responses only to CLI Commands.
- 11: Send responses to everything.
- 5th bit: reserved.
- 3rd-4th bits: Message Trace options:
- 00: Show no trace.
- 01: Show Message trace.
- 10: Show Message Response trace.
- 11: Show trace for both messages and their responses.
- 2nd bit: Extended Message Code flag. If set, then message codes are 16 bits.
- 1st bit (LSB): Extended Options flag. If set, then the next byte is an Options byte as well.
- Long messages can be broken down into maximum of 20 messages, i.e., ~940-960 bytes of parameters. Any payload larger than 960 bytes will have to set up a DMA stream.
- The CRC byte is a CRC8 of the entire message starting from the delimiters (except the CRC byte itself).
Calculating and Comparing CRC Codes
If you want to connect Hexabitz modules to any external software or hardware, you need to generate CRC8 codes and append them to your messages. Otherwise, the BOS won’t accept the external message. You can also use CRC codes in the messages generated by Hexabitz modules to verify message integrity over bad communication links (e.g., wireless connection). Due to the way that CRC peripheral in STM32F0 works, you need to generate CRC checksum with the following configuration and do manual byte swap to reverse input endianness if working on other processors. Use this online calculator as a reference with the following configuration:
- CRC32_MPEG2 mode.
- Polynomial: 0x4C11DB7
- Initial value: 0xFFFFFFFF
- Final Xor value: 0x0
- No input/output reflections
- CRC input data type: bytes
The CRC8 result of this message is the LSB of displayed result CRC value (i.e., 0xBD in this example). Note that before inserting message bytes into your CRC algorithm (or the online calculator), you must swap every 4 bytes to reverse word endianness in order to match STM32 MCUs. Also, if the message is not word-aligned (i.e., multiples of 4), you must pad it with zeros. For example, the following message (where 0x01 is the first byte):
0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A
Must be entered as:
0x04 0x03 0x02 0x01 0x08 0x07 0x06 0x05 0x00 0x00 0x0A 0x09
Routing Messages
You can send Messages through the array using the following APIs. The first API sends a Message out a given port to the adjacent module. It is useful when you do not know your neighbor’s ID or you simply want to send something across all or some ports:
BOS_Status SendMessageFromPort(uint8_t port, uint8_t src, uint8_t dst, uint16_t code, uint16_t numberOfParams)
Where port is the output port, src and dst are source and destination module IDs, code is the Message code and numberOfParams is a number of Message parameters. If the Message is originating from this module, you can use src=myID. The next API sends a Message to another module (whether adjacent or not) :
BOS_Status SendMessageToModule(uint8_t dst, uint16_t code, uint16_t numberOfParams)
Where dst is the destination module (source is always the current module), code is the Message code and numberOfParams is a number of Message parameters. SendMessageToModule() API basically calculates the optimal route to destination and which port leads to that route and then calls SendMessageFromPort() to send the Message from that port. Using 0xFF or BOS_BROADCAST for destination, broadcasts the Message to the entire array. You can control response settings of an unicast or broadcast Message via the BOS.response parameter:
- BOS_RESPONSE_ALL: Enable response on all Hexabitz Messages.
- BOS_RESPONSE_MSG: Disable response on remote CLI Commands and enable on other Messages.
- BOS_RESPONSE_CLI: Enable response on remote CLI Commands and disable on other Messages.
- BOS_RESPONSE_NONE: Disable response on all Messages.
Before you call one of the APIs above to send a Message with parameters, you need to fill out the parameters array first:
messageParams[0] = (uint8_t)(600000>>8); messageParams[1] = 2; messageParams[2] = FORWARD; etc…
Array topology (routing table) is saved in the two-dimensional array variable array, which will be either provided in the topology header file or generated by Explore() API. Learn more about topology header files here. The FindRoute() API utilizes Dijkstra‘s shortest path algorithm to calculate the optimal route between two modules in the array based on the pre-populated routing table:
uint8_t FindRoute(uint8_t sourceID, uint8_t desID)
To avoid sending redundant routing information with each Message, this API returns the module port (P1 .. Px) closest to destination module. The Message is sent through this port to the next module in the chain which runs the FindRoute() API again and forwards the Message until it reaches its destination. As mentioned above, you can call the SendMessageToModule() API, and it will take care of the entire process.
Broadcasting Messages
Messages can be broadcasted to the entire array by using 0xFF = 255 as destination ID in the SendMessageToModule() API. Messages are not simply transmitted through all ports, but specific ports called “broadcast routes”. This ensures messages are not transmitted through empty ports, and they are broadcasted throughout the array in the most efficient manner. Every broadcast message has a unique sequential broadcast ID (bcastID) that gets inserted after the last parameters and before the CRC. This unique ID guarantees the message does not get broadcasted over and over again as a reflection.
|| H | Z | Length | 0xFF | Source | Options | Code | Par. 1 | Par. 2 | ------- | Par. N | bcastID | CRC8 ||
Multi-casting Messages
Modules can be grouped into logical groups and given an alias name. Group modules don’t have to be of the same type/part number and don’t have to be adjacent either. You can add a module to a new/existing group using this API, or its equivalent CLI Command:
BOS_Status AddModuleToGroup(uint8_t module, char* group)
Once modules are part of a group, you can direct any message to this particular group by using 0xFE = 254 as destination ID in the SendMessageToModule() API. Multi-cast messages are broadcasted to the entire array, but only members of that particular group will parse the message. Note that group names and members are stored inside the originating module. Thus, multiple bytes must be added to the payload, after parameters and before CRC, in order to route the message correctly. These bytes are (starting from the CRC byte and going backwards):
- Broadcast ID (bcastID) (1-byte): A unique sequential ID for the message to avoid reflections (since it is a broadcast).
- Number of modules in the group (1-byte).
- IDs of group members (1-byte each). Note that order is not important.
|| H | Z | Length | 0xFE | Source | Options | Code | Par. 1 | Par. 2 | --- | Par. N | Mem. 1 | Mem. 2 | --- | NumOfModules | bcastID | CRC8 ||