I'll start a series of logs titled Code Overview to go over some areas in Hexabitz code in more detail. Here's the first one.
Messaging Protocol
Messaging in the array is carried out via short unacknowledged packets in the following format (each field is a single byte):
| Length || Destination | Source | Code | Code | Par. 1 | Par. 2 | ------- | CRC ||
- The Length byte is sent first. After that the message is sent. Message length includes all message bytes including the CRC (without the length byte itself).
- Maximum message length is 50, i.e., maximum number of parameters per message is 45 bytes.
- Message length cannot be 0x0D = 13. If message length is 13, i.e., parameters are 8 bytes, then the message is padded with extra zero parameter to make the length 14.
- Source and destination are IDs of source and destination modules.
- If 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 with the following format:
- 16th bit (MSB): a flag for long messages. If 1, then message parameters continue in the next message.
- 15th bit: a flag to enable/disable Message only response.
- 14th bit: a flag to enable/disable CLI only response. (note that to enable this bit, bit 15 must be enabled as well).
- 13th bit: a flag to enable TRACE mode (visually trace message route).
- Bits 1-12: Message codes. Maximum number of message codes is 212 - 1 = 4095.
- Long messages can be broken down into maximum of 20 messages, i.e., 900 bytes of parameters. Any payload larger than 900 bytes will have to setup a DMA stream.
- The CRC byte is not implemented yet. Right now it has a fixed value of 0x75. It will be replaced later with CRC8 of the message.
Messaging Workflow:
- Source module builds a message in the form shown above.
- Source sends one byte representing the message length and waits for 1.5 msec.
- Destination receives the length byte (in the UART_RxCpltCallback) and activates a port-to-memory DMA stream to transfer the same amount of bytes requested. Once finished, receive interrupt is activated on this port again.
- After destination receives the last termination byte (0x75 or CRC), it executes UART_RxCpltCallback again and notifies the appropriate messaging task.
- Messages are parsed to read source, destination and code bytes. Received message length is checked againts the Length byte.
- If the message is a transit message, it'll be forwarded directly. If it's a broadcast message, it will be broadcasted and then processed.
- Messages are processed according to their message codes. After that buffers are cleared and receive interrupt is activated on this port again.
- Message response and TRACE flags are verified to generate the appropriate response.
- If the message is long, the longMessage flag is activated and can be used to concatenate consecutive payloads before processing them.
Routing
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 number of Message parameters. If the Message is originating from this module, you can use src=myID. The next API sends a Message to a 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 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 a unicast or broadcast Message via the BOS.response parameter:
- BOS_RESPONSE_ALL: Enable response on all Messages.
- BOS_RESPONSE_MSG: Disable response on remote CLI Commands and enable on other Messages.
- BOS_RESPONSE_NONE: Disable response on any Message.
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.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.