Introduction
X10 is a protocol that uses the electrical wiring in a house as a network to communicate with devices that can turn power on or off to devices. In addition, devices are available to control just about anything in your house. The focus of this article will be on how to implement an API for an X10 thermostat. The resources for building a computer interface to X10 devices are far and few between. It seems that most retailers in this space have decided that it's better to sell easy to use interfaces than allow power users to build their own applications. I have been looking into X10 for several years now, and have been unable to build an effective API until now. In this article, I'll discuss the different devices that are available to interface with X10, and the specifics of how to write your own applications to take advantage of X10
Devices
There are a few different vendors of X10 devices. Most notably, SmartHome is a reputable vendor which resells equipment from many manufacturers. They sell two different devices which connect your PC to the wiring in your house and allow X10 signals to be sent.
- PowerLinc Controller (USB)
- PowerLinc Serial/Tw523 (Serial)
Both of these devices plug into a standard wall outlet and then run a data cable to a computer. They differ mainly in the computer data communication. One uses USB and the other is serial. You would think that it is a no brainer decision; go with the USB device. Many people have tried this route only to fail. There are several solutions (e.g. here) but the protocol has to be reverse engineered which means there's no guarantee that something hasn't been missed. SmartHome will not release the spec, so I count it as a loss. Too bad, really, I'll bet they'd sell a bunch more of these things. The only device left is the serial version which can be purchased at SmartHome. The interface is serial so the System.IO.Ports namespace, which is new in .Net 2.0, is extremely useful. SmartHome provides VB 6 source code for their Synapse software that interacts with the PowerLinc. This code was critical in my research on how to control the device. They also provide a 7 page protocol manual, which is somewhat useful. In addition, SmartHome provides a technical overview of the PowerLinc/Tw523 device, but it's not useful unless you're a electrical/computer engineer.
In addition to the devices from SmartHome it's important to note that X10 Industries also offers a device called Firecracker which can be used to control X10 devices. The main advantage that the PowerLinc has over Firecracker is it can send and receive commands, but the Firecracker can only send commands. John Gonzalez has written an excellent article on how to use this device.
The Protocol
Now that we have all of the research material to go from, we can start to understand the protocol. First we need to determine how to interact with the PowerLinc device. The protocol manual says that all X10 commands are started by sending a 0x02 (&H2 in VB.Net). The PowerLinc responds by either sending an ACK (acknowledgement) if it's ready to accept commands or a NAK (non acknowledgement) if it's not ready. If an ACK was sent, then it is immediately followed by a carriage return (0x0D) and is ready to accept your x10 command.
Let's get into the meat of the protocol. Here's a chart that gives an example and description of an exchange between the PC and the PowerLinc:
Direction From PC
|
Hex Value
|
Meaning
|
Send
|
0x02
|
start command
|
Receive
|
0x06
|
acknowledgement
|
Receive
|
0x0D
|
carriage return
|
Send
|
0x63
|
start X10 command
|
Send
|
0x4F
|
house code 'J'
|
Send
|
0x4C
|
unit code 1
|
Send
|
0x00
|
function code (none)
|
Send
|
0x41
|
repeat command once
|
Receive
|
0x58
|
transmission received
|
Receive
|
0x4F
|
house code 'J'
|
Receive
|
0x4C
|
unit code 1
|
Receive
|
0x31
|
sent once
|
Receive
|
0x0D
|
carriage return
|
Receive
|
0x58
|
transmission received
|
Receive
|
0x4F
|
house code 'J'
|
Receive
|
0x00
|
function code (none)
|
Receive
|
0x31
|
sent once
|
Receive
|
0x0D
|
carriage return
|
Let's discuss this exchange.
As you can see, the exchange starts by the PC sending 0x02 (&H2 in VB.Net). The PowerLinc responds in one of two ways.
- Sends 0x06 0x0D to indicate an the device is ready to receive commands (as shown above)
- Sends 0x15 to indicate that the device is not ready.
Next, the PC sends 0x63 to indicate that it is going to send an X10 command. There are several options here, including 0x80 to send any extended data, but the scope of this article is limited to sending X10 commands. Following the "start X10 command" is the house code. The housecode is a letter that gets mapped to a hex value (see the charts below for details). In the example above, the house code is "J", which is 0x4F. Generally speaking all devices within your house all use the same house code. This is a guideline rather than a rule, and some appliances use an entire house code for themselves (e.g. RCS Thermostats). The unit code follows the house code. This is generally used to specify a device within a house code. There can be up to 16 devices (units) within a house code. After the unit code is the function code. The function code is used to specify the type of action that the device should take, for instance, turn the light on or off. We finish the sequence by specifying how many times the powerlinc should repeat the command set. Honestly, I'm not sure what the purpose of this is, but specifying a value of 0x41 (sent once) seems to work fine.
After the command is sent, the PowerLinc responds by echoing the command in two five byte segments. The first segment includes the unit code, and the second segment has the function code. It's important to note that each segment starts with 0x58. This is used when the X10 device is responding to a direct command. Some devices can report changes in their status (e.g. the light has turned off or the thermostat's mode has changed). When these changes occur, the device will send a segment that starts with 0x78.
X10 Code Chart
|
X10 Code
|
Hex Value
|
X10 Code
|
Hex Value
|
A
|
46
|
8
|
5A
|
B
|
4E
|
9
|
4E
|
C
|
42
|
10
|
5E
|
D
|
4A
|
11
|
46
|
E
|
41
|
12
|
56
|
F
|
49
|
13
|
40
|
G
|
45
|
14
|
50
|
H
|
4D
|
15
|
48
|
I
|
47
|
16
|
58
|
J
|
4F
|
All Units Off
|
41
|
K
|
43
|
All Lights On
|
43
|
L
|
4B
|
On
|
45
|
M
|
40
|
Off
|
47
|
N
|
48
|
Dim
|
49
|
O
|
44
|
Bright
|
4B
|
P
|
4C
|
All Lights Off
|
4D
|
1
|
4C
|
Extended Code
|
4F
|
2
|
5C
|
Hail Request
|
51
|
3
|
44
|
Pre-Set Dim High
|
57
|
4
|
54
|
Pre-Set Dim Low
|
55
|
5
|
42
|
Extended Data (analog)
|
59
|
6
|
52
|
Status = on
|
5B
|
7
|
4A
|
Status = off
|
5D
|
|
|
Status Request
|
5F
|
The Code
Because the PowerLinc 1132b communicates with the PC via an RS232 serial connection, the System.IO.Ports.SerialPort class, which is new in .Net 2.0, is the perfect method to accomplish the task. The first thing our application needs to do is connect to the PowerLinc via serial.
Public Sub New(ByVal portName As String)
_portName = portName
End Sub
Public Sub Open()
SyncLock (_syncLock)
_serialPort = New SerialPort()
_serialPort.PortName = PortName
_serialPort.BaudRate = 9600
_serialPort.Parity = Parity.None
_serialPort.DataBits = 8
_serialPort.StopBits = 1
_serialPort.Handshake = Handshake.None
AddHandler _serialPort.DataReceived, AddressOf DataReceived
AddHandler _serialPort.ErrorReceived, AddressOf ErrorReceived
_serialPort.Open()
End SyncLock
End Sub
The _portName field is used to store the name of the port that the PowerLinc is working on (e.g. "Com1", "Com2", "Com4", etc.) As you can see, the Powerlinc communicates at a zippy 9600 baud, no parity bits, 8 data bits, 1 stop bit, and no handshaking. We also add two handlers to grab any data that the PowerLinc sends without us asking for it. This happens when a device needs to report a change in its status (e.g. thermostat set point changed).
Once we've connected, we send a command. As was stated before a command consists of 6 parts
- Start Command (and wait for ready acknowledgement)
- Send X10 Command (0x63)
- Send House Code
- Send Unit Code
- Send Function Code
- Send Repeat Count (usually one)
The start command is handled in the following code:
Private Sub SendStartCommand()
SyncLock (_syncLock)
If _serialPort.IsOpen Then
Dim outBuffer As Byte() = {&H2}
Dim readBuffer(0) As Byte
Dim byteRead As Byte
Dim attempts As Integer = 0
While _serialPort.BytesToRead > 0
byteRead = _serialPort.ReadByte
End While
_serialPort.Write(outBuffer, 0, outBuffer.Length)
byteRead = _serialPort.ReadByte()
While byteRead <> 6 And attempts < 100
Thread.Sleep(250)
_serialPort.Write(outBuffer, 0, outBuffer.Length)
byteRead = _serialPort.ReadByte()
attempts += 1
End While
If byteRead = 6 Then
byteRead = _serialPort.ReadByte()
Else
Throw New PowerLincException("The device is not ready")
End If
End If
End SyncLock
End Sub
If the device isn't ready after 100 attempts (maybe a little excessive) it throws an exception, otherwise it returns without error indicating that the device is ready to receive the X10 command.
After the start command has been sent and received, the actual X10 command is sent. This is accomplished via the SendBytes method, which accepts the house code, unit code, function code as byte values. It also accepts an integer that gives a guideline for how many bytes we're expecting. If the value is set to 0, the routine attempts to retrieve as many bytes as possible.
Private Function SendBytes(ByVal houseCode As Byte, & _
ByVal unitCode As Byte, & _
ByVal functionCode As Byte, & _
ByVal expectedResponseBytes As Integer) & _
As Byte()
SyncLock (_syncLock)
Dim rVal As Byte() = Nothing
SendStartCommand()
_serialPort.Write(New Byte() {&H63, houseCode, unitCode, & _
functionCode, &H41}, 0, 5)
Thread.Sleep(1000)
rVal = GetBytes(expectedResponseBytes)
Return rVal
End SyncLock
End Function
There are several overloaded methods in the PowerLinc2Api class, but the all end up calling the above code. The last aspect that we need to inspect is how we retrieve data that is received from the PowerLinc. A message that is received as a response contains the following bytes:
- Transmission Received (0x58)
- House Code
- Unit Code
- Repeat Count (usually 0x31 � Sent Once)
- Carriage Return (0x0D)
- Device Data (0x78)
- House Code
- Function Code
- Repeat Count (usually 0x31 � Sent Once)
- Carriage Return (0x0D)
Items 1 � 5 are basically an echo of the command that was sent. Notice that the first byte is 0x58. The remaining 5 bytes are data that the device has sent. Notice that the first byte for this sequence is 0x78. Receiving the data turned out to be a little tricky because the device is not as quick as I would like it to be. I had to throw in a few Thread.Sleep commands to make sure that I wasn't leaving any bytes behind. Ultimately I checked to see if there were any bytes left to receive, if there weren't then I waited for another 500 ms and checked again. If there were still no more bytes and the last byte received was 0x0D (carriage return, indicating the end of a segment) I returned the bytes. Also, I added a parameter which contained the number of bytes that I was expecting. This came in handy when I knew that a command was going to return a certain number of bytes. Check out the code for further details:
Private Function GetBytes(ByVal expectedBytes As Integer) As Byte()
Dim waitCount As Integer = 0
Dim rVal As Byte() = Nothing
Do
If waitCount > 16 Or (expectedBytes > 0 AndAlso _
Not IsNothing(rVal) _
AndAlso rVal.Length >= expectedBytes) Then
Exit Do
ElseIf _serialPort.BytesToRead = 0 Then
Thread.Sleep(500)
If _serialPort.BytesToRead = 0 Then
If IsNothing(rVal) OrElse _
rVal(rVal.GetUpperBound(0)) <> 13 Then
Thread.Sleep(250)
waitCount += 1
Else
Exit Do
End If
End If
Else
If IsNothing(rVal) OrElse rVal.GetUpperBound(0) = -1 Then
ReDim rVal(0)
Else
ReDim Preserve rVal(rVal.Length)
End If
rVal(rVal.GetUpperBound(0)) = _serialPort.ReadByte
End If
Loop
Return rVal
End Function
That's pretty much it for the PowerLinc portion of the code. There are a bunch more routines but these are used mostly for translating byte values into house codes, unit codes, and function codes and vice versa.
The second and final section of code was to deal with the thermostat. The code here isn't very interesting as it is mostly an implementation of the decode table found on page 6 of the TX15-B Thermostat X-10 Bi-directional Protocol Manual. Check out the RcsTx15bApi.vb file in the source code for details. Once the PowerLinc API was done, this code was extremely easy to write. One of the more difficult things about using the thermostat was discovering how the preset dim levels were mapped. It turns out that these mappings are only available in the source code for SmartHome's Synapse application (which is their direct control app for the PowerLinc).
Dim Level to House Code
|
|
|
|
|
|
|
|
|
|
Preset Dim Low
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
12
|
13
|
14
|
15
|
16
|
Preset Dim High
|
17
|
18
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
26
|
27
|
28
|
29
|
30
|
31
|
32
|
House Code
|
M
|
N
|
O
|
P
|
C
|
D
|
A
|
B
|
E
|
F
|
G
|
H
|
K
|
L
|
I
|
J
|
Hex Value
|
40
|
48
|
44
|
4C
|
42
|
4A
|
46
|
4E
|
41
|
49
|
45
|
4D
|
43
|
4B
|
47
|
4F
|
The dim levels are sent as house codes which map to the chart above (this is really important if you're going to be using a device that accepts dim levels). It takes two separate commands to issue a preset dim command. You start by sending the house code and unit code with the second command consisting of the house code that maps to the correct dim level. Because there are only 16 house codes and there are 32 preset dim levels you need to indicate whether to use the preset dim low or preset dim high. This is indicated in the second command set's unit code (either 0x55 for preset dim low or 0x57 for preset dim high). Let's take an example of turning the HVAC system off. The exchange looks like this:
Direction From PC
|
Hex Value
|
Meaning
|
Send
|
0x02
|
Start Command
|
Receive
|
0x06
|
Acknowledgement
|
Receive
|
0x0D
|
Acknowledgement
|
Send
|
0x63
|
Send X10 Command
|
Send
|
0x4F
|
House Code J
|
Send
|
0x54
|
Unit Code 4 (Send Command)
|
Send
|
0x00
|
Function Code (None)
|
Send
|
0x41
|
Repeat Count = 1
|
Receive
|
0x58
|
Transmission Received
|
Receive
|
0x4F
|
Echo House Code
|
Receive
|
0x54
|
Echo Unit Code
|
Receive
|
0x31
|
Repeat Count = 1
|
Receive
|
0x0D
|
Carriage Return
|
Send
|
0x02
|
Start Command
|
Receive
|
0x06
|
Acknowledgement
|
Receive
|
0x0D
|
Acknowledgement
|
Send
|
0x63
|
Send X10 Command
|
Send
|
0x40
|
House Code M (Dim Level 1)
|
Send
|
0x55
|
Function Code (Preset Dim Low)
|
Send
|
0x00
|
No Unit Code
|
Send
|
0x41
|
Repeat Count = 1
|
Receive
|
0x58
|
Transmission Received
|
Receive
|
0x40
|
Echo House Code
|
Receive
|
0x55
|
Echo Unit Code
|
Receive
|
0x31
|
Repeat Count = 1
|
Receive
|
0x0D
|
Carriage Return
|
Once you see this on paper it seems easy enough, but coming up with this was quite challenging.
Conclusions
Writing the code for this project was somewhat challenging due to the limited number of resources available. The fact that mapping house codes to dim levels can only be found in the source code for SmartHome's PowerLinc software is a good indicator of this. I did find a few implementation for Linux and Java, but was unable to find any .Net code. It's fair to note, that although the code works well with the thermostat, I have not tested it with any other X10 device (because I don't have any). If someone finds that it doesn't work with a particular device, let me know so I can update the project accordingly. Also, if you find yourself using the project, send me an e-mail and let me know what you're using it for.
History
29-JAN-2007 - Initial Release