X10 CM15A USB ActiveHome Controller
This little piece of hardware is fun. It allows for two-way communications between a PC and an X10 network. It supports both powerline and RF-based X10 communications. Bundled with it is a proprietary program known as ActiveHome Pro. I bought the box, hoping that the SDK that was advertised would be worth its salt.
That SDK consists of an OCX that lets you send UNIT ON and UNIT OFF commands to the network. It does not let you take advantage of the fact that the CM15A talks to the PC at all. I wanted to set up software actions to react to X10 commands. This just wouldn't do.
Goal
My goal was to develop enough knowledge to understand all of the signals coming over the powerline and all of the signals coming over the radio, and to be able to write a program that would react to these signals and take some sort of software action (turning music on or off, changing the playlist, changing the volume, etc).
Note that my goal involved developing read capabilities only. I never actually used the ActiveHome Pro software while developing this all, and personally don't have any documentation on how to send to the network yet.
Resources
- Snoopy Pro: lets you see the contents of USB packets
- LibUSB (win32): lets you write USB drivers as userland code
- CM15A research on Eclipse Home Automation: has Linux drivers, and an incomplete CM15A protocol description
- Linux Home Automation's research: more prior research on the CM15A
Procedure
I followed the following procedure to get this protocol description:
- Fired up Snoopy Pro
- Attached it to the CM15A
- Hit a button on my X10 remote and verified that it incremented the captured packet count
- Began writing down the hex dumps of the packets I saw while iterating through each unit code from 1 to 16, making sure to capture both an ON and an OFF packet
- Froze the unit code and rotated between house codes from A to P, capturing data as I went
- Looked at my data and compared packets to see which bits changed in reaction to the house code changing and which reacted to the unit code changing
- Realized that the bits designating the unit and house code were shuffled throughout the packet, and found a way to put them back together
- Implemented a test driver using libusb to test that my findings worked
Protocol Description
The protocol differentiates between X10 commands received via powerline, X10 commands received via RF, X10 commands sent, and several controller-specific items (macros, memory dumps, clock signals, and the like). This is an incomplete description of the protocol and only features the reception and transmission of X10 commands. I haven't played with controller-specific items, and don't particularily care to--if you figure those out, feel free to contact me if you'd like me to post your information.
General Commands (opcode 0x5a)
The general commands were described in detail by Eclipse Home Automation's PDF on the CM15a prior to the writing of this article. As such, credit should be given to them for this section, since they figured it out first. You may see some similarities, as I have kept their convention for designating which nibble belongs to a house code, a unit code, or a function code.
Format: 5a sz pt [data] ...
where sz is the number of bytes following sz, including tt
and onward;
where pt is the type of the packet, to be described below
Encoded House, Unit, and Function Codes
Either the CM15a or the X10 network does some encoding on the unit, house, and function codes before sending them. They're bit-scrambled and I wound up brute-forcing them, so here's a lookup table that describes them.
Encoding Table
| Src. Dec. | Src. Hex | Function | House | Unit | Dst. Dec. | Dst. Hex |
|---|---|---|---|---|---|---|
| 0 | 0x00 | ALL_UNITS_OFF | A | 1 | 6 | 0x06 |
| 1 | 0x01 | ALL_LIGHTS_ON | B | 2 | 14 | 0x0e |
| 2 | 0x02 | ON | C | 3 | 2 | 0x02 |
| 3 | 0x03 | OFF | D | 4 | 10 | 0x0a |
| 4 | 0x04 | DIM | E | 5 | 1 | 0x01 |
| 5 | 0x05 | BRIGHT | F | 6 | 9 | 0x09 |
| 6 | 0x06 | ALL_LIGHTS_OFF | G | 7 | 5 | 0x05 |
| 7 | 0x07 | EXTENDED_CODE | H | 8 | 13 | 0x0d |
| 8 | 0x08 | HAIL_REQ | I | 9 | 7 | 0x07 |
| 9 | 0x09 | HAIL_ACK | J | 10 | 15 | 0x0f |
| 10 | 0x0a | PRESET_DIM_LOW | K | 11 | 3 | 0x03 |
| 11 | 0x0b | PRESET_DIM_HIGH | L | 12 | 11 | 0x0b |
| 12 | 0x0c | EXTENDED_DATA | M | 13 | 0 | 0x00 |
| 13 | 0x0d | STATUS_ON | N | 14 | 8 | 0x08 |
| 14 | 0x0e | STATUS_OFF | O | 15 | 4 | 0x04 |
| 15 | 0x0f | STATUS_REQ | P | 16 | 12 | 0x0c |
Decoding Table
| Dst. Dec. | Dst. Hex | Function | House | Unit | Src. Dec. | Src. Hex |
|---|---|---|---|---|---|---|
| 12 | 0x0c | EXTENDED_DATA | M | 13 | 0 | 0x00 |
| 4 | 0x04 | DIM | E | 5 | 1 | 0x01 |
| 2 | 0x02 | ON | C | 3 | 2 | 0x02 |
| 10 | 0x0a | PRESET_DIM_LOW | K | 11 | 3 | 0x03 |
| 14 | 0x0e | STATUS_OFF | O | 15 | 4 | 0x04 |
| 6 | 0x06 | ALL_LIGHTS_OFF | G | 7 | 5 | 0x05 |
| 0 | 0x00 | ALL_UNITS_OFF | A | 1 | 6 | 0x06 |
| 8 | 0x08 | HAIL_REQ | I | 9 | 7 | 0x07 |
| 13 | 0x0d | STATUS_ON | N | 14 | 8 | 0x08 |
| 5 | 0x05 | BRIGHT | F | 6 | 9 | 0x09 |
| 3 | 0x03 | OFF | D | 4 | 10 | 0x0a |
| 11 | 0x0b | PRESET_DIM_HIGH | L | 12 | 11 | 0x0b |
| 15 | 0x0f | STATUS_REQ | P | 16 | 12 | 0x0c |
| 7 | 0x07 | EXTENDED_CODE | H | 8 | 13 | 0x0d |
| 1 | 0x01 | ALL_LIGHTS_ON | B | 2 | 14 | 0x0e |
| 9 | 0x09 | HAIL_ACK | J | 10 | 15 | 0x0f |
X10 Commands Received from Powerline
The way in which the RF transceiver I own operates is a two-step process, involving packet types 0x00, 0x01, and 0x02, as follows.
Step 1 Format: 0x5a 02 00 hd
where h is an encoded house code nibble;
where d is an encoded unit code nibble
This step selects the device about to be commanded.
Step 2 Format: 0x5a 02 01 hf
where h is an encoded house code nibble;
where f is an encoded function code nibble
This step actually executes a function upon the previously selected unit. Two other variants of this command supposedly exist (one with sz = 0x03, and one with sz = 0x04), but were not seen on my network. The Eclipse Home Automation document describes these.
These two commands are all you need to be aware of to receive commands from a MiniPad remote and the basic X10 receiver.
X10 Commands Received over Radio
Format: 0x5d 20 hb ~hb ua ub
where hb is a house code designated by a scrambling format for which a map follows;
where ~hb is the result of a binary NOT on hh;
where ua ub is a two-byte function specification (containing both unit code and function code), to be described
hb Format
b is a nibble describing whether we're looking at a high bank or a low bank. It is equal to 0x04 if we're on a high bank (units 9-16), and is equal to 0x00 if we're using a low bank (unit codes 1-8). It may be a bit-field containing other flags that I haven't discovered.
h is a nibble describing the house code. It is not encoded in the same way as powerline signals. This time, it's governed by either an algorithm I failed to reverse-engineer, or just arbitrary shuffling. Either way, an encoding table follows.
h Encoding Table
| House | Number | Coded Nibble |
|---|---|---|
| A | 0 | 0x6 |
| B | 1 | 0x7 |
| C | 2 | 0x4 |
| D | 3 | 0x5 |
| E | 4 | 0x8 |
| F | 5 | 0x9 |
| G | 6 | 0xa |
| H | 7 | 0xb |
| I | 8 | 0xe |
| J | 9 | 0xf |
| K | 10 | 0xc |
| L | 11 | 0xd |
| M | 12 | 0x0 |
| N | 13 | 0x1 |
| O | 14 | 0x2 |
| P | 15 | 0x3 |
h Decoding Table
| House | Number | Coded Nibble |
|---|---|---|
| M | 12 | 0x0 |
| N | 13 | 0x1 |
| O | 14 | 0x2 |
| P | 15 | 0x3 |
| C | 2 | 0x4 |
| D | 3 | 0x5 |
| A | 0 | 0x6 |
| B | 1 | 0x7 |
| E | 4 | 0x8 |
| F | 5 | 0x9 |
| G | 6 | 0xa |
| H | 7 | 0xb |
| K | 10 | 0xc |
| L | 11 | 0xd |
| I | 8 | 0xe |
| J | 9 | 0xf |
ua ub Format
ua ub appears to always equal 0x88 77 for "BRIGHT" and 0x98 67 for "DIM".
For all other commands, take apart the unit code you wish to address, down to the binary level. For instance, unit code 16 is a decimal 15, so in binary it is 1111. Similarly, unit code 8 would equal 0111. We'll consider the rightmost bit to be B1, and the leftmost to be B4.
The following is a description of each bit in a ON or OFF command. ON will equal 1 if we're turning a device on, and will equal 0 if we're turning one off. ~B1 will equal the NOT of B1.
ua Format: [constant 0] [B3] [~ON] [B1] [B2] [constant 0] [constant 0] [constant 0]
ub Format: [constant 1] [~B3] [ON] [~B1] [~B2] [~B4] [constant 1] [constant 1]
You'll notice that the second byte is the NOT of the first one, with one exception: B4 does not appear in the first byte. I don't know why this occurs. Either way, I've also attached an encoding table for this one.
ua ub Encoding Table
| Unit | ua ub for ON | ua ub for OFF |
|---|---|---|
| 1 | 0x00 ff | 0x20 df |
| 2 | 0x10 ef | 0x30 cf |
| 3 | 0x08 f7 | 0x28 d7 |
| 4 | 0x18 e7 | 0x38 c7 |
| 5 | 0x40 bf | 0x60 9f |
| 6 | 0x50 af | 0x70 8f |
| 7 | 0x48 b7 | 0x68 97 |
| 8 | 0x58 a7 | 0x78 87 |
| 9 | 0x00 fb | 0x20 db |
| 10 | 0x10 eb | 0x30 cb |
| 11 | 0x08 f3 | 0x28 d3 |
| 12 | 0x18 e3 | 0x38 c3 |
| 13 | 0x40 bb | 0x60 9b |
| 14 | 0x50 ab | 0x70 8b |
| 15 | 0x48 b3 | 0x68 93 |
| 16 | 0x58 a3 | 0x78 83 |