Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

X10 API For PowerLinc2 in VB.NET

0.00/5 (No votes)
2 Feb 2007 1  
An article describing how to interface with the SmartHome PowerLinc 1132B.

Sample image

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.

  1. Sends 0x06 0x0D to indicate an the device is ready to receive commands (as shown above)
  2. 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

  1. Start Command (and wait for ready acknowledgement)
  2. Send X10 Command (0x63)
  3. Send House Code
  4. Send Unit Code
  5. Send Function Code
  6. 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} 'Start with 0x02

            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
                'Wait a little because the device is pretty slow

                Thread.Sleep(250)
                _serialPort.Write(outBuffer, 0, outBuffer.Length)
                byteRead = _serialPort.ReadByte()
                attempts += 1
            End While
            If byteRead = 6 Then
                'Acknowledged - Receive the carriage return

                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:

  1. Transmission Received (0x58)
  2. House Code
  3. Unit Code
  4. Repeat Count (usually 0x31 � Sent Once)
  5. Carriage Return (0x0D)
  6. Device Data (0x78)
  7. House Code
  8. Function Code
  9. Repeat Count (usually 0x31 � Sent Once)
  10. 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
            'Give the device a little more time

            Thread.Sleep(500)
            If _serialPort.BytesToRead = 0 Then
                If IsNothing(rVal) OrElse _
                   rVal(rVal.GetUpperBound(0)) <> 13 Then
                    'Wait for the result

                    Thread.Sleep(250)
                    waitCount += 1
                Else
                    Exit Do
                End If
            End If
        Else
            'Grab the data

            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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here