Michel Deslierres

AHSDK - Talking to the CM15A

The ActiveHome Component

Once the package is installed in Delphi, an ActiveHome component is added to its IDE's ActiveX palette. The TActiveHome component is pretty sparse. It has an overloaded function, SendAction, which is used to send messages to the CM15A with different numbers of parameters :

function SendAction(bszAction: OleVariant): OleVariant; overload;
function SendAction(bszAction, bstrParam: OleVariant): OleVariant; overload;
function SendAction(bszAction, bstrParam, vReserved1: OleVariant): OleVariant; overload
function SendAction(bszAction, bstrParam, vReserved1, vReserved2: OleVariant): OleVariant; overload;

While it's not quite obvious which to use, it turns out that the second, two strings, version is the one most used. So to turn on an appliance module at address B9 on, the syntax would be SendAction("sendplc", "B9 ON"). Of course, you will probably want to change the address B9 to the address of a nearby device to easily verify that the command was send over the power line.

To test this, drop the TActiveHome component (make it invisible), a memo, and a button on a form and in the button's OnClick event handler add this:

function ovtoint(value: OleVariant): integer;
begin
  result := value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  res: OleVariant;
  Action: string;
  Param: string;
begin
  Action := 'sendplc';
  Param := 'B9 ON';  // case insensitive
  res := ActiveHome1.SendAction(Action, Param);
  Memo1.Lines.add(Format('Send: "%s" "%s",  result = %d', [Action, Param, ovtoint(res)]));
end;


The helper function ovtoint, that converts an OleVariant to an integer, is needed because Format does not know how to handle an OleVariant directly.

If your device is wireless, then the CM15A will need to send a message on a specific radio frequency. You need to change Action := 'sendplc' to Action := 'sendrf'.

If you are using Delphi 2010 and the application will not compile because ActiveHomeScriptLib_TLB.dcu has not been found, you can move the file from the folder C:\Users\{user}\Documents\RAD Studio\7.0\Imports to the folder C:\Program Files (x86)\Embarcadero\RAD Studio\7.0\Imports which contains many other imported type library units and which is in Delphi 2010's default search path.

Problems with errors

If you run this small application with the USB cable connected to the CM15A and the device is turned on, the result will be a 0. However, if you run the application with the cable not connected to the X10 transceiver, the result will again be 0 even though, obviously, no command was sent over the power line.

You would think that not being connected to the interface would constitute an error worth reporting, unfortunately that it is not the case. So what kind of errors are reported by SendFunction? Strictly speaking none it would seem. Change Param := 'B9 ON' to 'B9 ONF' and run the example again. You will get a rather lengthy generic syntax error message :

syntax error message

But you will also notice that nothing is written to the memo in that case. So SendFunction does not return an error code when it cannot parse 'ONF', instead it raises an exception. Furthermore, SendFunction will not report a syntax error when Param := 'B9ON' while that is clearly an error since the device will not be turned on. Similarly, using the 3 strings version of SendAction('sendplc', 'A9', 'on') will not work and yet will neither raise an exception nor return an error code. On the other hand, the 4 strings version SendAction('sendplc', 'A', '9', 'on') will raise an exception.

It's all very strange and someone might be tempted to further study how the dll handles errors. I have decided it was not worth the effort, the obvious solution is to do all error checking in your application and reporting them to the user.

The more important question of finding out if the CM15A is connected or not will be addresses a little further on.

Small Application

A small application capable of sending most commands including dim/bright commands is easily built. Here's a screen shot of the main and only form of such a program.

cm15_test.exe window

The source consisting of only 4 files

cm15_test.dpr
cm15_test.res
Unit1.pas
Unit1.dfm

can be downloaded here (cm15_test.zip, 3003 bytes, 2013-07-12). To compile this application, the ActiveHome type library must have been imported and the TActiveHome component must be installed on the component palette.

Looking at the imported type library for ahscript.dll, it is easy to spot a likely event to use to listen to the CM15A.  Called OnRecvAction it is of type TActiveHomeRecvAction which has the following declaration :

TActiveHomeRecvAction = procedure(ASender: TObject; bszAction: OleVariant;
              bszParm1, bszParm2, bszParm3, bszParm4, bszParm5: OleVariant;
              bszReserved: OleVariant) of object;

So if you check Try to listen to CM15A in the application, a handler is installed for that event. It simply translates all these OleVariant results to strings and concatenates them all to a single string that is copied to the memo. There's only one problem. It does not work.

The next page looks at the solution.