Skip to main content

Transport Layer Description

Message Structure

The transport layer protocol is encapsulated by Start of Transmission (STX = 0x02) and End of Transmission (ETX = 0x03) characters.

All other characters at the transport layer will be printable ASCII characters in the range ‘0’-‘9’ and ‘A’-F’. This implies that the reception of STX or ETX has an unambiguous meaning – they always indicate the start or end of a message.

The protocol data and CRC data are encoded as hex strings. This means a single byte is sent as 2 characters in ASCII, in the order high nibble then low nibble. For example, the value 0x2A will be sent as ASCII chars ‘2’, ‘A’, which is equivalent to 0x32, 0x41 in hex.

Uppercase letters are used for the hex representations of A-F. If lowercase letters are received (a-f), they will be converted to uppercase A-F.

A CRC is computed over the protocol data. The CRC is computed over the data before encoding into the hex string form (i.e. compute the CRC before encoding into ASCII). The STX and ETX are not included in the CRC calculation. See the CRC section for more detail on the CRC.

The table below shows the message structure:

Start (STX)Protocol DataCRC1 High NibbleCRC1 Low NibbleCRC2 High NibbleCRC2 Low NibbleStop (ETX)
0x02Protocol Data, as hex string, where each protocol byte is encoded to 2 characters for transport1 byte, '0'-'9', 'A'-'F'1 byte, '0'-'9', 'A'-'F'1 byte, '0'-'9', 'A'-'F'1 byte, '0'-'9', 'A'-'F'0x03
Only Protocol Data (before ASCII encoding) is included in CRC calculation

To illustrate this, consider the Configuration Write Request:

  • Application layer: the message is simply the operation code 0x05 followed by 3 bytes of parameters 0x05 0x00 0x01. The result is 0x05 0x05 0x00 0x01
  • Transport layer:
    • Compute the CRC as 0x54C3 and append it (low to high byte order): 0x05 0x05 0x00 0x01 0xC3 0x54
    • Encode as hex string: 0x30 0x35 0x30 0x35 0x30 0x30 0x30 0x31 0x43 0x33 0x35 0x34
    • Add STX and ETX, which gives the result to be sent over the UART: 0x02 0x30 0x35 0x30 0x35 0x30 0x30 0x30 0x31 0x43 0x33 0x35 0x34 0x03

Hex String Examples

All data in the protocol is converted to hex strings for transport. This can be illustrated with some examples.

First, consider that the same data can have a variety of representations. We will work in the hex notation.

For example, the ASCII string Hello World! can be represented as a hex array 0x48 0x65 0x6C 0x6C 0x6F 0x20 0x57 0x6F 0x72 0x6C 0x64 0x21. We will work with our data as bytes or collections of bytes with a value 0x00 to 0xFF.

This hex data is encoded as high and low nibbles. Take the first character H above, which is 0x48. This is encoded to 0x34 (‘4’) and 0x38 (‘8’), So our example array becomes:

Hello‘ ‘world!
48656C6C6F20576F726C6421
48656C6C6F20576F726C6421
0x340x380x360x350x360x430x360x430x360x460x320x300x350x370x360x460x370x320x360x430x360x340x320x31

The last row of the table shows the bytes sent over the serial link.

Request / Answer

The application protocol uses a request/answer mechanism, where all requests are initiated by the asset.

info

The asset should wait for an answer to each request before sending another request.

Transport Layer Timing and Timeouts

Answers to requests can be expected to be received within 100 ms after reception of the last byte of the request. Past that delay, the asset can consider that a transmission error has occurred, and the asset should retry.

caution

There are exceptions: configuration save request (CFG_SR), configuration factor reset (CFG_FR), performance counters clear (PER_CR) and context save (CTX_S) may take up to 1.5 sec to reply. Enqueue payload request (PLD_ER) may take up to 1.2 sec to reply.

A 100ms inter-byte timeout is implemented on the module to recover from incomplete requests. Within a single message, bytes should not be spaced by more than 100ms. With more than 100 ms between bytes, the module will discard the in-progress receive operation and wait for the next start byte.

CRC

The CRC bytes allow the integrity of the message to be checked.

The CRC is calculated over all bytes from the application layer before the hex string encoding. The Start (STX) and End (ETX) bytes are excluded from the CRC calculation.

The result is 2 bytes, which are appended to the application layer data as the low then the high byte. The application layer data with CRC should then be encoded to the hex string form.

A CRC-16-CCITT checksum is used, defined by the polynomial x16+x12+x5+1, or 0x1021. The syndrome is initialised to 0xFFFF at the start. This is used in protocols such as Bluetooth.

Examples of Implementation

C - CRC-16 (CCITT) implementation
// Computes the CRC-CCITT
// data The buffer containing the data bytes.
// dataLength The number of bytes to read from the buffer.

uint16_t crc_compute(const uint8_t* data, uint16_t data_length)
{
uint16_t x = 0;
uint16_t crc = 0xFFFF;

while (data_length--)
{
x = crc >> 8 ^ *data++;
x ^= x >> 4;
crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ (x);
}
return crc;
}
MicroPython - CRC-16 (CCITT) implementation with a precomputed lookup table
def crc16(data):
table = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
]

crc = 0xFFFF
for byte in data:
crc = (crc << 8) ^ table[(crc >> 8) ^ byte]
crc &= 0xFFFF # important, crc must stay 16bits all the way through
return crc

def generate_crc(data):
crc_tmp = '{:04X}'.format(crc16(data))
crc = crc_tmp[0]
crc += crc_tmp[1]
crc += crc_tmp[2]
crc += crc_tmp[3]
return crc
Python - CRC-16 (CCITT) implementation with crcmod module
import crcmod

crc16 = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xffff, xorOut=0x0000)

def generate_crc(data):
crc_tmp = '{:04X}'.format(crc16(data))
crc = crc_tmp[0]
crc += crc_tmp[1]
crc += crc_tmp[2]
crc += crc_tmp[3]
return crc
caution

Remember to invert the byte order when appending the CRC code to the application layer data.

Verification Examples

DataCRC-16 CCITTInverted CRCEncoded CRC (application layer)
00 001D 0F0F 1D0x30 0x46 0x31 0x44
00 00 00CC 9C9C CC0x39 0x43 0x43 0x43
AB CD EF 0104 A2A2 040x41 0x32 0x30 0x34
14 56 F8 9A 00 017F D5D5 7F0x44 0x35 0x37 0x46