HBus protocol
December 31, 2023 · View on GitHub
rev 1.5, matching code:
- WiFi Gateway sketch WiFiGw.ino rev 1.10
- Arduino firmware core rev 1.1
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:
- 0 = binary
- 1 = JSON
- 2 = MessagePack
- other - TBD.
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:
- 0 = binary
- 1 = JSON
- 2 = MessagePack
- other - TBD.
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