HBus protocol

December 31, 2023 · View on GitHub

rev 1.5, matching code:

rev 1.6 differs by extended REV and DESCR commands; matching code:

Byte-stuffing

Code 0x1B (eg ESC symbol) marks the beginning of a 2-byte sequence. The following byte pairs are defined:

  • 0x1B-0x02 - start of HBus frame (SOF)
  • 0x1B-0x03 - start of MQTT-SN frame (SOF)
  • 0x1B-0x04 - start of encrypted HBus frame (SOF)
  • 0x1B-0x05 - start of encrypted MQTT-SN frame (SOF)
  • 0x1B-0x07 - end of frame (EOF)
  • 0x1B-0x08 - insert 0x1B into data flow
  • 0x1B-0x09 - insert 0x1B, 0x1B into data flow

Frame structure

Prefix SOF Message CRC EOF
1 byte 2 bytes 12...136 bytes 2 bytes 2 bytes
priority (0x1B-0x02) or (0x1B-0x04) HBus message content CRC 0x1B-0x07
priority (0x1B-0x03) or (0x1B-0x05) MQTT message content CRC 0x1B-0x07

Prefix used to wake-up CAN receivers from standby mode. Also it is used for early collision detection. If echo does not match the sent byte then sender must switch off its transmitter and wait until bus is free.

  • Sender can start transmission if there were no traffic on bus for 2 ms or more.
  • Sender starts transmission with Prefix byte. Its value depends on message priority:
    • 0xFF - Low priority message
    • 0xFC - Medium priority message
    • 0xF0 - High priority message

CRC covers message content, Prefix and SOF exluded. Sender calculates CRC before byte-stuffing added to the message. Receiver calculates CRC after byte-stuffing removed from the received message.

While debugging nodes, it is possible to transfer text messages duirng the pause from EOF to Prefix. In normal operation all debugging traffic should be disabled.

Encryption

Cipher is a combination of XTEA block cipher and LFSR-32 stream cipher.

First 8 bytes of message are encrypted by XTEA cipher. While encrypting the block, an intermediate value is used to initialise 32-bit LFSR. The rest of the message is encrypted by LFSR stream cipher. Cipher encrypts both message content and its CRC.

Decryption is made similarly. First 8 bytes of the message decrypted by XTEA block cipher, an intermediate value is used to initialise 32-bit LFSR. The rest of the message, including CRC, is decrypted by 32-bit LFSR stream cypher.

XTEA uses a 128-bit key. The key is combined from two parts: EEPROM key and flash key. Flash key defined at compile time, it should be unique for the user. EEPROM key defined during node configuration, it should be unique for the particular project.

At power-up node reads EEPROM key and encrypts it using its flash key. The result is used as XTEA key to encrypt HBus and MQTT-SN messages.

Fields

Big endian used, eg MSB byte sent first, LSB byte sent last.

Name Bits Description
NodeA_ID 16 ID of Node A, it must be unique in current network
NodeB_ID 16 ID of Node B, it must be unique in current network
MsgId 8 Message ID; it is incremented with every request; reply repeats MsgId of the request. Valid range 0x01…0xFE
Nonce 8 random number
RdWr 8 Read/write operation: 0 = read, 1 = write
OkErr 8 OK = 0; error codes must be in the range [0x80...0xFE], other codes reserved
TS 32 Time stamp, seconds since 00:00:00 of 01/01/2001 UTC, big endian

HBus mode (config and control messages)

List of HBus commands

Command Name Target Description
1 REV Node Read node description, hardware and software revisions
2 STATUS Node Read node status
3 COLLECT Group A group of nodes must reply within specified time window, each node selects a random time slot
4 PING Node Request node acknowledge
5 SET_ID Node Set permanent node ID
6 BOOT Node Send a chunk of code to selected node
7 BEEP Node Beep and LED flash to identify the node
8 DESCR Node Read/write node description
9 SECURITY Node Set/read security settings
10 C_CMD Node Custom command
11 TOPIC Node Read TopicId and TopicName

[1] REV

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x01 NodeA_ID NodeB_ID MsgId Nonce 0 TS

A sample of request from node 0x0404 to node 0x0010:

01 04 04 00 10 D2 8F 00  23 2C DC 9E 

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22:25]
0x81 NodeA_ID NodeB_ID MsgId Nonce OkErr TS DevType DevModel HwRevMaj HwRevMin BootRevMaj BootRevMin SwRevMaj SwRevMin HbusRevMaj HbusRevMin SwSignature

A sample of reply from node 0x0010 to node 0x0404

81 04 04 00 10 D2 69 00  23 2C DC AE 02 01 01 00  00 01 01 0A 01 00 7E 5C  B2 76

Please note a 16 sec mismatch between TS of node 0x0404 (0x232CDC9E from NodeTest on PC) and TS of node 0x0010 (0x232CDCAE from HBus Gateway). Unfortunatelly PC time is not accurate.

[2] STATUS

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x02 NodeA_ID NodeB_ID MsgId Nonce 0 TS

A sample of request from node 0x0404 to node 0x0010:

02 04 04 00 10 2C E3 00  23 2C EB AE 

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:N]
0x82 NodeA_ID NodeB_ID MsgId Nonce DF TS Data, content depends on DevType and DevModel

DF is data format:

A sample of reply from node 0x0010 to node 0x0404

82 04 04 00 10 2C C9 01  23 2C EB BF {tid:[512,513,514,515,516,517], val:[0,14.01,23.02,1020.15,52.53,845.82]}

[3] COLLECT

Request from node A to a Group

[0] [1:2] [3] [4] [5] [6] [7] [8:11]
0x03 NodeA_ID Group Slots MsgId Nonce 0 TS
  • Group - defines a group of nodes. The following groups defined so far:
    • 1 - all nodes
    • 2 - nodes with temporary ID
    • 3 - nodes with permanent ID
  • Slots - is number of 10ms time slots. Device should output its reply in a randomly selected time slot within specified number of slots. Number of slots must be in the range [4..63]

A sample of request from node 0x0404 to group 1 (all nodes), 128 slots:

03 04 04 01 80 2F 4C 00  23 2C FC 6B 

Reply from a Group member Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x83 NodeA_ID NodeB_ID MsgId Nonce 0 TS

A sample of reply from node 0x0010:

83 04 04 00 10 2F 76 00  23 2C FC 7B 

[4] PING

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x04 NodeA_ID NodeB_ID MsgId Nonce Param TS

Param - time interval, seconds. During that interval target node should not respond to the COLLECT command.

A sample of request from node 0x0404 to node 0x0010:

04 04 04 00 10 30 0B 0A  23 2C FD B9 

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x84 NodeA_ID NodeB_ID MsgId Nonce OkErr TS

A sample of reply from node 0x0010 to node 0x0404

84 04 04 00 10 30 08 00  23 2C FD C9 

[5] SET_ID

Node ID is used for point-to-point node addressing. Node ID is a 16-bit number.

If a node does not have permanent ID, it assigns itself a random temporary ID of 0xFyyy, where y - a hex digit. During configuration stage permanent IDs should be assigned to all nodes. Permanent IDs start from any hex digit other than 0xF.

It is recommended to limit permanent ID by the range [0x0001..0x07FF] (eg by the range [1..2047]).

Permanent ID can be assigned only once. Node will refuse to re-write permanent Node ID.

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:13]
0x05 NodeA_ID NodeB_ID MsgId Nonce 1 TS New_ID

New_ID - a new ID for node B.

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x85 NodeA_ID NodeB_ID MsgId Nonce OkErr TS

If OkErr = 0 then NodeB_ID is the New_ID, otherwise NodeB_ID is the old node ID value.

[6] BOOT

Request from Node A to Node B. Node B should write the received chunk of code into its EEPROM at specified address. Field Code holds binary data in the same format as Intel HEX without colon char.

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:N]
0x06 NodeA_ID NodeB_ID MsgId Nonce 0 TS Code

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x86 NodeA_ID NodeB_ID MsgId Nonce OkErr TS

[7] BEEP

Request beep and LED flash to physically identify the node.

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x07 NodeA_ID NodeB_ID MsgId Nonce Duration TS

Duration specifies duration of the beep, sec.

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x87 NodeA_ID NodeB_ID MsgId Nonce OkErr TS

[8] DESCR

Read/write target node text description, such as name, location, etc.

Read description, request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x08 NodeA_ID NodeB_ID MsgId Nonce StrNoWr TS
  • StrNoWr - string No in bits [3:1] and Read=0 in bit [0]

String numbers:

  • 0 - name
  • 1 - location
  • 2 - description
  • 3 - project (read only)
  • 4 - sketch (read only)
  • 5 - h/w module (read only)

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12] [13:(13+N)]
0x88 NodeA_ID NodeB_ID MsgId Nonce OkErr TS N Text
  • N - length of text, typically up to 63 bytes
  • Text - node description, UTF-8
  • if OK then OkErr fieild repeats StrNoWr field

Write description, request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12] [13:(13+N)]
0x08 NodeA_ID NodeB_ID MsgId Nonce StrNoWr TS N Text
  • StrNoWr - string No in bits [2:1] and Write=1 in bit [0]
  • N - length of text, typically up to 63 bytes
  • Text - node description, UTF-8

String numbers:

  • 0 - name
  • 1 - location
  • 2 - description

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x88 NodeA_ID NodeB_ID MsgId Nonce OkErr TS

If OK then OkErr fieild repeats StrNoWr field

[9] SECURITY

Assign/query node security settings

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:13] [14:21]
0x09 NodeA_ID NodeB_ID MsgId Nonce RdWr TS Security EEPROM Key

Reply from Node B to Node A:

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:13]
0x89 NodeA_ID NodeB_ID MsgId Nonce OkErr TS Security

8-byte long EEPROM Key field is optional. EEPROM Key can be written only once when EEPROM is blank. Once assigned, it cannot be rewritten, field value is ignored.

[10] C_CMD

Optional custom command to HBus node. Content depends on device type and model, software revision, etc. For example, it can be a calibration command, etc.

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:(13+N)]
0x0A NodeA_ID NodeB_ID MsgId Nonce DF TS Cmd
  • DF is data format:
    • 0 = binary
    • 1 = JSON
    • 2 = MessagePack
    • other - TBD.
  • Cmd - custom command itself

Reply from Node B to Node A

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:(12+X)]
0x8A NodeA_ID NodeB_ID MsgId Nonce OkErr TS Rply

Rply is an optional reply.

[11] TOPIC

Read one of TopicId and TopicName stored by node.

Request from Node A to Node B

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x0B NodeA_ID NodeB_ID MsgId Nonce ti TS
  • ti - topic index.

Reply from Node B to Node A if topic exists:

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:13] [14:(14+N)]
0x8B NodeA_ID NodeB_ID MsgId Nonce 0 TS TopicId TopicName
  • TopicName is UTF-8 string less than 64 bytes long

Reply from Node B to Node A if topic index is out of list:

[0] [1:2] [3:4] [5] [6] [7] [8:11]
0x8B NodeA_ID NodeB_ID MsgId Nonce 0xEE TS

MQTT-SN mode (broadcast messages)

Message structure

In that mode messages are made similar to MQTT for Sensor Networks – MQTT-SN. However, HBus does not require MQTT broker. In a network segment all local messages are available for all nodes. It is up to node to select messages of interest from the stream.

Message structure is as follows:

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:N]
MsgType NodeID TopicId MsgId Nonce DF TS Data
  • MsgType - four msb bits are random; four lsb bits of that byte is message type as per MQTT-SN:
    • 0x0A - REGISTER
    • 0x0C - PUBLISH
  • NodeId - ID of the broadcasting node; used for debug and monitoring.
  • TopicId - MQTT-SN topic ID, 16-bit unsigned integer.
  • MsgId - message ID, all MQTT messages should use common MsgId incremented with every broadcasted message.
  • DF is data format:

REGISTER

Binds TopicId and TopicName.

  • If (TopicId == 0) then it is a request. Any node having correct binding of TopicId with TopicName should reply with another REGISTER message where TopicId is not 0.
  • If (TopicId > 0) and (TopicId < 0xFFFF) then all nodes with matching TopicName should bind the specified TopicId with it.
  • If (TopicId >= 0xFFFF) then related TopicId is cleared.
[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:N]
0x0A NodeID TopicId MsgId Nonce 0 TS TopicName
  • TopicName is UTF-8 string less than 64 bytes long

Typically nodes are pre-programmed with TopicNames, but all TopicIds set to 0. After power-up nodes request TopicId for every TopicName they have. If there is no answer, nodes propose a TopicId derived from their NodeId:

  • TopicId = (NodeId << 5) | TopicIndex

Thus, every node can assign up to 32 unique TopicId.

TopicId in the range [0x0000...0x001F] cannot be assigned by nodes because NodeId=0 is illegal. In that range TopicName and TopicId are pre-defined. Only few pairs defined so far, other values reserved for future use:

  • TopicName="time", TopicId = 0x0001, UTC time (seconds since 00:00:00 of 01/01/2001), time of the day (seconds since 00:00:00, hour, minute)
  • TopicName="timezone", TopicId = 0x0002, local time zone, offset from UTC in minutes
  • TopicName="debug", TopicId = 0x000D, debug messages
  • TopicName="error", TopicId = 0x000E, software errors
  • TopicName="failure", TopicId = 0x000F, hardware failures

PUBLISH

Broadcast Payload to specified TopicId.

[0] [1:2] [3:4] [5] [6] [7] [8:11] [12:N]
0x0C NodeID TopicId MsgId Nonce DF TS Payload

A sample of pre-defined topic "time" message (header shown in hex, payload in ASCII):

4C 00 10 00 01 02 FA 01  23 2C A3 69 {atime:590127977, tz:600, daysec:51977, hr:14, min:26}
  • 4C - high nibble 0x4 is a random value; low nibble 0xC is PUBLISH code
  • 00 10 - message issued by NodeID = 0x0010
  • 00 01 - TopicID=1, it is pre-defined topic "time"
  • 02 - MsgId = 0x02
  • FA - Nonce = 0xFA, random value
  • 01 - DF=1, JSON payload
  • 23 2C A3 69 - timestamp, hex number of seconds since 00:00:00 01/01/2001 UTC
  • atime - absolute time, decimal number of seconds since 00:00:00 01/01/2001 UTC
  • tz - time zone, offset 600 min from UTC, it is 10hr 00 min, eg Australia East Coast time zone (Sydney/Melbourne/Brisbane)
  • daysec - number of seconds since midnight, local time zone
  • hr - day hour, local time zone
  • min - minute of the hour, local time zone