BHS-4000/IntelliBus Reverse Engineering

BHS-4000 is a burglar/fire alarm system manufactured by Honeywell for the old Brinks Home Security. It was, to my knowledge, the last model they sold, and one of the most common, before they rebranded to Broadview and got bought by ADT. (As a side note, I appreciate how ADT has been a lot less hostile to DIYers, not to mention the new Brinks actively encourages it.)

I used to have this system installed in my home, and I guess I still do, but I just have it installed in my bedroom now, to mess around with. One thing I've been experimenting with lately is the system's data bus, that all of the keypads and other peripherals use to communicate with the alarm control panel. It uses a proprietary protocol called IntelliBus, for which I haven't been able to find any documentation online, but despite that I've figured out a lot about it.

Physical Layer

The first thing to know about IntelliBus is that it runs on an RS-485 connection at 38,400 baud 8N1. The more observant among you may find this somewhat questionable, as the terminals for the data bus are labeled "DATA" and "CLK", and RS-485 is asynchronous, meaning it doesn't use a clock line. As it turns out, the labeling on the terminals is inaccurate; I'm not sure why it's labeled that way. But the truth is, the DATA and CLK terminals are actually the two complementary data lines as specified by RS-485.

Data is sent in distinct packets, and each packet is both introduced and followed by a RS (0x1E) control character. Interestingly, they chose 0x7D ('}') as the escape character, followed by the byte (in practice, either 0x1E or 0x7D) XORed with 0x20. In other words, a literal 1E is encoded in a packet as "7D 3E", and a literal 7D is encoded as "7D 5D".

At first I thought 0x7D, being a printable character, was an odd choice, and wondered why they didn't use one of the control characters designed for that purpose, like 0x10 (DLE) or 0x1B (ESC). But in a protocol that rarely transmits ASCII text, using a printable character isn't really an issue, and those lower bytes are likely more commonly used anyway.

Packet Structure

Now on to the actual content of the packets. There are three distinct formats of packets I've seen. The most common appear to be synchronization packets, one type that's sent from the panel to another device, and another type sent in the other direction. Here is what those packets look like:

Structure of a synchronization packet sent by the panel (henceforth "sync ping")
1E 03 80 21 10 6A 1E
BytesHexBinDecExplanation
1E1E0001111030Start of packet
03 80800311Master sync flag
0000000000000113Device address
21210010000133Lower byte of active broadcast address
10 6A6A10011010100001000027152CRC-16/MCRF4XX checksum of preceding bytes, not including the 1E
1E1E0001111030End of packet
Structure of a synchronization packet sent to the panel (henceforth "sync reply")
1E 03 00 D0 DA 1E
BytesHexBinDecExplanation
1E1E0001111030Start of packet
03 00000300Slave sync flag
0000000000000113Device address
D0 DADAD0110100001101101056016CRC-16/MCRF4XX checksum of preceding bytes, not including the 1E
1E1E0001111030End of packet

Sync ping packets (left) are transmitted from the panel to every device it knows about, several times per second. Sync reply packets (right) are transmitted the other way, generally (if not always) in response to the former.

Structure of a "message" packet
1E 01 00 00 00 04 0D 00 DA 07 00 00 00 00 3F 00 30 00 00 28 25 1E
BytesHexBinDecExplanation
1E1E0001111030Start of packet
01 00000100Slave sync flag
0000000000000011Destination device address
00 00000000Master sync flag
0000000000000000Source device address
0404000001004Always 04 as far as I can tell
0D 00000D000000000000110113Packet length (starting at next byte, ending after CRC)
DA 0707DA00000111110110102010Message/command ID
...Parameter data (varies in meaning)
1E1E0001111030End of packet

While these packets are by far the most commonly transmitted, they don't actually do anything but synchronize flow control between the devices, and let the panel know a device is connected. It's the third, more complex type of packet that's actually responsible for communication between the devices. The example to the right is a packet sent from the panel to a keypad. The message/command ID, as you probably guessed, is what determines what the packet will do. In this case, it's set to 0x7DA, which means "set display on Premium Keypad". The bytes that follow mean different things for each command, and some commands (such as 0xBBC, "learn devices") don't even have any following bytes besides the CRC and the terminating 1E.

For message 0x7DA, the first byte in this section specifies the "account" ID associated with the packet, which is comparable to the "partition" function on panels like the Ademco Vista 20p. The following bytes are a bitfield, where each bit maps to an individual element on the Premium Keypad's LCD. This one was sent after activating the "Alarm Memory" function, so while I haven't tested it, I'm assuming this one will turn on the "Alarm Memory" indicator, and display a digit in the 7-segment display. (The 7-segment display is actually rendered on the panel, meaning this packet describes the specific segments to light up rather than just specifying a digit.)

One thing that's important to note: when a packet has a destination address in the range 0x7000-0x707F, it is treated as a broadcast packet. That is, it's intended for all devices that can use it, not just a specific one. As an example, sending message 0x7E0 to a keypad will cause it to make sound, but sending it as a broadcast will cause all keypads to make sound, as long as they are on the account specified in the message. Message 0x7DA, shown above, is usually sent as a broadcast, to update the display on all the keypads with a single packet.

Learning Devices

Message ID's related to device discovery
IDDescriptionArgumentsDirection
# BytesMeaning
0xBBC"Learning new devices; anyone need an address?"(None)Panel → Dev
0xBB9"Okay, I'm ____ and I need an address!"2Seems to always be 01 00Dev → Panel
2Model number of the device (e.g. 3121)
2Device class identifier (what kind of device)
2Hardware configuration bytes
2Firmware version
0xBBA"Alright ____, you take address __."6Target device serial numberPanel → Dev
2New device address
0xBBB"Got it, I'm ____, address __ now."6Serial numberDev → Panel
2New device address

IntelliBus supports "plug-and-play" functionality, in that devices are automatically assigned an address. But it isn't quite like USB, where you can plug in a new device and have it (hopefully) start functioning right away. Until it is instructed to "Learn Devices" via the programmer, the panel will only communicate with devices it already knows about. Devices of the same type aren't interchangeable either; it needs to be the specific one the panel knows about, based on its serial number. (The programmer is the exception, having a fixed serial number of FFFFFFFF or something.)