[How-To] Control an Elk M1 via External Perl Scripting

123

Senior Member
Overview
This document describes how to use ElkM1::Control, and custom scripts, to control and monitor an ElkM1 without the use of a Home Automation (HA) program.

Introduction
Many commercial HA applications (i.e. CQC, HomeSeer, HouseBot, PowerHome, etc) provide a means to monitor and control an Elk M1 via a driver. The driver can be an integral, optional, or a user-contributed component of the HA application. An effective M1 driver must create an Application Programming Interface (API) between the HA application and the M1. The API is considered complete if it encompasses all capabilities described in Elk's "ASCII Protocol RS-232 Interface Specification" document.

Normally, an M1 driver is used exclusively with its native HA application. In most cases, the driver is an extension of the HA program and cannot function without it. However, thanks to Neil Cherry's posting in the MisterHouse mailing list, I learned of an M1 API that is not tied to a specific HA program. Early in 2006, James A. Russo created a new project on Sourceforge, called ElkM1::Control, providing a standalone API to the M1.

Developed in Perl, an interpreted language, ElkM1::Control provides most of the capabilities described in Elk's "ASCII Protocol" document. Its source code is freely available to everyone and, by virtue of Perl, can be used on Windows or Linux PCs. Unfortunately, only one version (0.02) was ever posted but it was amply documented, well-designed, and included commented code.

What can ElkM1::Control do for me?
ElkM1::Control is a Perl-based API to the ELK M1 and is a natural complement to MisterHouse (also written in Perl). However, it can be used to create useful applications without MisterHouse or any other HA program. For example, you can write standalone Perl scripts to:
  • Arm/Disarm the M1.
  • Enable/Disable an Output.
  • Read the temperature from a keypad, thermostat, or probe.
  • Perform an action when a zone is violated.
  • Monitor the M1's messages.
Here's the script for arming an M1 in Away mode:
Code:
	use ElkM1::Control;
		my $elk = ElkM1::Control->new(host => '192.168.0.251', port => 2101);
		$elk->armAway(code => 1234);
		$elk->disconnect();
It is easy to see what this command does:
$elk->disarm(code => 1234);

Here are the commands to turn off output 9, pause for ten seconds, and then turn on output 10 for ten minutes (600 seconds):
$elk->controlOutputOff(output => 9);
sleep 10;
$elk->controlOutputOn(output => 10, timeout => 600);


Here's how to get, and print, the temperature recorded by the second keypad:
$elk->requestTemperature(group => 1, device => 2);
my $msg = $elk->readMessage;
print "Temperature is ".$msg->getTemperature" if (ref($msg) eq 'ElkM1::Control::Message::TemperatureReply');


Here are the commands to activate a task and turn on device C5:
$elk->activateTask(task => 1);
$elk->turnOnPLCDevice(house => 'C', unit => 5);


What do I need to use it?
You will need the following hardware:
  • Elk M1
  • Elk M1XEP Ethernet Module
  • PC connected to the M1XEP via a LAN.
Ensure the M1XEP is properly configured, as per Elk's recommendations, and port 2101 is activated.

You will need the following software:
  1. ActiveState's Perl interpreter (free).
  2. The attached zip archive.
The zip file contains the following files:
  • James Russo's ElkM1::Control files in the \ElkM1 folder.
  • IO:Socket::SSL file in the \IO folder.
  • A reference document, in MS Word 2003 format, for ElkM1::Control.
Installation requires two steps:
  1. Download and install ActiveState's Perl interpreter in "c:\perl" (accept all defaults).
  2. Unzip Perl_Modules.zip and copy the \Elk and \IO folders to "c:\perl\site\lib". Put the reference document in a convenient place on your hard-drive.
A confession ...
In order to simplify your life, my installation instructions have cut a few corners. James Russo designed ElkM1::Control to support encrypted communications, using Secure Socket Layers (SSL), via port 2601. However, this capability is lost if you follow my installation instructions.

Some Perl modules, such as ElkM1::Control, can be installed by simply copying them to the \perl\site\lib folder. However, other modules must first be "built" using tools that are not commonly found on a typical Windows PC (i.e. C compiler, C libraries, nmake utility, etc).

Proper installation of the IO::Socket::SSL module is a complicated affair because it depends on other modules that must be compiled. To avoid this headache, I cheated by creating a version of IO::Socket::SSL that is simply a copy of an existing module: IO::Socket::INET. This trick serves to satisfy the Perl interpreter, lets us talk to the M1 on an unencrypted port (2101), but eliminates the ability to successfully communicate via an encrypted port.

If you absolutely require encrypted communications, see the following article: "How to Add Support for Encrypted Communications" (posted below).

How do I use it?
Copy example #1 into your favourite text editor.

Code:
# Example 1: Say the current time
use ElkM1::Control;
	my $elk = ElkM1::Control->new(host => '192.168.0.251', port => 2101);
	$elk->speakPhrase(phrase => 238);
	$elk->disconnect();
  1. Replace the host address (192.168.0.251) with the IP address of your M1XEP.
  2. Save the file on your Desktop as saytime.pl.
  3. Open the Command Prompt (Start | Run, type cmd and press the Enter key).
  4. At the command prompt, type:
cd desktop
saytime.pl
The M1 should say the current time. If you see "unable to connect", the M1's port 2101 may be closed or perhaps there's a network connectivity problem. If Windows does not know how to execute the file it means the ".pl" extension is not associated with ActiveState's Perl interpreter. A quick solution to this problem is to execute the file like so:
perl saytime.pl

Copy example #2 into a text editor, revise the IP address, and save it as readmsg.pl.

Code:
# Example 2: Display all M1 messages
use ElkM1::Control;
	my $elk = ElkM1::Control->new(host => '192.168.0.251', port => 2101);
	while (1) {  # Loop forever
		while (my $msg = $elk->readMessage) { # Read the M1's messages
				print $msg->toString;  # Print the messages
		}
	}

Run readmsg.pl at the command prompt. Nothing may be displayed for awhile unless you create some activity (i.e. violate a zone) or simply wait for the next Ethernet Module Test message (sent every 30 seconds).

Have fun!
 

Attachments

  • Perl_Modules2.zip
    96.3 KB · Views: 425
How to add support for encrypted communications
ElkM1::Control is a useful addition to anyone's Home Automation bag-of-tricks. In order to expose ElkM1::Control to a wider audience, my installation instructions maintained simplicity by sacrificing functionality. I eliminated support for encrypted communciations because it takes some effort to install the proper Perl modules and OpenSSL on a Windows PC. However, I recognize that some people won't use the "dumbed-down" version of ElkM1::Control because they must have encrypted communications.

Background
ElkM1::Control relies on a set of Perl modules, and OpenSSL, in order to communicate via an encrypted port. The following instructions explain how to install the required modules, and OpenSSL, on a Windows PC.

ActiveState's Perl interpreter (V5.8.8) includes a utility called "ppm-shell" and it will be used to install two Perl modules and OpenSSL. The University of Winnipeg maintains a repository of Perl modules and a Windows-specific version of OpenSSL. Use of their repository simplifies the installation process.

Prerequisites
  1. Ensure ActiveState's Perl interpreter has been installed.
  2. Ensure the PC has an active connection to the Internet.
  3. If you followed my original installation instructions for ElkM1::Control (using my zip file), you must delete the following folder and all of its contents "\perl\site\lib\IO". It contains a fake version of IO::Socket::SSL and it must be deleted.
Installation
  1. Start the Command Prompt (Start | Run, type "cmd" and press the Enter key).
  2. At the prompt, enter:
    ppm-shell
  3. At the "ppm>" prompt , enter the following command:
    repo add http://theoryx5.uwinnipeg.ca/ppms/package.xml
  4. Wait for the "ppm>" prompt and then enter:
    install net_ssleay.pm
  5. You will be prompted several times and you should accept the default replies with the following exception:
    If the installation program finds existing copies of "libeay32.dll" and "ssleay32.dll" on your PC, it will suggest to use them and not to download fresh versions. Ignore this suggestion, and force it to fetch new versions by replying "yes" to the following prompts:
    Fetch ssleay32.dll? [no] yes
    Fetch libeay32.dll? [no] yes

  6. Wait for the "ppm>" prompt and then type:
    install io-socket-ssl
  7. Wait for the "ppm>" prompt and then type:
    exit
The attached MS Word file contains transcripts of two installation sessions. One session represents a PC that does not have the DLL files and the other session depicts a PC that has them. All text highlighted in yellow represents the commands you must enter. The yellow arrow indicates where you must respond by pressing the Enter key.

NOTE:
In informal testing, encrypted communications take more time to execute than when they are sent "in the clear" (i.e. unencrypted). If the operating environment is reasonably secure, you may want to use unencrypted communications for better performance. However, if you plan to send commands to the M1 via the Internet, and you're not using a Virtual Private Network (VPN) or stunnel, then you ought to encrypt the communications.
 

Attachments

  • Installation_Transcripts.doc
    39.5 KB · Views: 272
This would be great to read as I just got my m1 up and running with an ethernet connection, I'm using a slug to serve up pages to control my m1g.

Unfortunately I am unable to download your attachments and have sent a pm to the admin but am yet to receive a reply.

I am in the process of writing a perl script to detect when a bluetooth device (phone) comes into range to automatically disable the alarm and will post if once it is working and I am able to download your attachments.
 
...
Unfortunately I am unable to download your attachments and have sent a pm to the admin but am yet to receive a reply.
...

Hmm, I was wondering why my article received over 250 views but only 1 attachment-download!

BSR, I get a "permission-denied" when attempting to download the attachments. Maybe something broke when the article was transferred from the Home Security forum to the How-To forum.

Let me know if you need me to re-post the attachments.
 
BTW, I've managed to get the serial port and Linux working with this module (using open and stty to setup the port). I wrote a class to override some of the Ethernet module's functions. I'm still having trouble getting the Device::SerialPort module working (so it's portable between most OSs that run perl). For some reason I can't read from the tied FH (using the examples from Device::SerialPort).
 
BTW, I've managed to get the serial port and Linux working with this module (using open and stty to setup the port). I wrote a class to override some of the Ethernet module's functions. I'm still having trouble getting the Device::SerialPort module working (so it's portable between most OSs that run perl). For some reason I can't read from the tied FH (using the examples from Device::SerialPort).

Interesting... when you get this working, can you post a how-to and some code? :)
 
BTW, I've managed to get the serial port and Linux working with this module (using open and stty to setup the port). I wrote a class to override some of the Ethernet module's functions. I'm still having trouble getting the Device::SerialPort module working (so it's portable between most OSs that run perl). For some reason I can't read from the tied FH (using the examples from Device::SerialPort).

Interesting... when you get this working, can you post a how-to and some code? :)
Sure no problem, I'm going to build another module for use with Misterhouse (MH). MH doesn't need to open a serial port via the new method (MH opens it another way) so I'll need to inherit the already opened Device::SerialPort IO handle from MH(Device::SerialPort works with MH just fine). Being able to override and inherit has made the coding much easier. I hope to have the web page integrated into Misterhouse by the end of the month (working module, user code and web page). I'll then post the information to Misterhouse Elk M1 Wiki page and my Elk M1 page along with how to use the module without MH. Nothing is there yet but I hope to have something posted there very soon. I'll announce on this thread first.

I'd like to thank Spanky who made the eval kit available to me and .
 
FYI
For whatever reason, the documents I posted in my original message cannot be downloaded. In addition, I cannot re-post the documents in a reply. I've informed the forum's administrator and hopefully this will be resolved shortly.
 
Try now guys, looks like a new security feature was added, but not activated.

ps: very nice work!
 
Here is my XMAS present to you guys, feel free to enhance, correct my coding practices, hopefully someone finds it usefull.

My code was tested on a Linksys NSLU2 running debian slug OS and using an ASUS WL-BTD201M dongle

One important thing to note is this script doesn't connect(pair) with your device, so the required information could be sniffed and smart people could impersonate your device. I do have a shell script that does pairing, has a threshold (rough distance) checking but I haven't yet been able to successfully port that part into perl, I'm hoping next year will bring success.

As I cant quite work out how to add an attachment I'll post my code.

To find your device/phone's mac address and name follow this link . Note you must have your phone set to discoverable during these steps otherwise you will get errors and no information. Once you gathered all the required info change your device to hidden, my script still works fine with hidden devices.

The script is designed to be fired off as an init script and will then run continuously, though as I don't have a housekeeping script checking if it is running I have enabled chiming on the elk so I will get a reminder if the script has failed and I need to punch in the code. If you have multiple devices a separate script will need to be written for each device. I have tested 2 running at once and they didnt encounter any problems, the only one I did have was if the status window was up on the RP software.

Strangely I was unable to get my elk to speak so I didn't include a greeting

[codebox]#!/usr/bin/perl -w
$stateDir = "/tmp" ;
$devicename = "phone_name" ;
$deviceMAC = "xx:xx:xx:xx:xx:xx" ;
$phoneLeftHome = "$stateDir/$devicename.home" ;
$hcitool = "/usr/bin/hcitool" ;
$elkHost = "xxx.xxx.xxx.xxx" ; # elk
$elkPort = "2101" ;
$useSSL = "0" ;
$date = `date` ;
$sleep = "10" ;
$exitSleep = "600" ; # set to 30 for testing
$disarmCode = "xxxxxx" ;
$DEBUG = 0 ;

use ElkM1::Control;

while ( 1 ) {
print "checking phone $devicename\n" if $DEBUG ;

$phone_in_range = &inRange ;

if ($phone_in_range) {
print "phone in range\n" if $DEBUG ;
print "now time to check if alarmed\n" if $DEBUG ;

my @status = &checkELK ;
my $alarm_armed = $status[0] ;
my $ExitDelay = $status[1] ;

if ($ExitDelay ) {
print "waiting for phone to get out of range\n" if $DEBUG ;
# wait for the phone to get out of range
sleep($exitSleep) ;

$phone_in_range = &inRange ;

if ($phone_in_range) {
print "2nd waiting for phone to get out of range\n" if $DEBUG ;
sleep($exitSleep) ; # set to 600
$phone_in_range = &inRange ;
if ($phone_in_range) {
print "phone left home\n" ;
# must be left home better set a flag so we wont disarm alarm
open phoneHOME, ">$phoneLeftHome" ;
print phoneHOME "$date" ;
close phoneHOME ;
} else {
print "phone now out of range\n" if $DEBUG ;
}
sleep($sleep) ;
# next ;
}
} else {
if ($alarm_armed) {
print "alarm ARMED\n" if $DEBUG ;
if ( -e $phoneLeftHome ) {
print "phone is left home so ignoring it\n" if $DEBUG ;
sleep($sleep) ;
next ;
}
# panel is armed and phone hasn't been left home so lets disable it
&disarm ;
} else {
print "alarm not armed\n" if $DEBUG ;
if (-e $phoneLeftHome ) {
# people are home so lets delete the flag
unlink "$phoneLeftHome" ;
}
}
}
# sleep here as a successfull connect doesnt take that long
# but an unsucessfull attempt has already taken long enough
sleep($sleep) ;
}
}

sub disarm {

print "disable alarm, using code $disarmCode\n";
my $elk = ElkM1::Control->new(
'host' => $elkHost,
'port' => $elkPort,
'use_ssl' => $useSSL,
'debug' => 0
);
$elk->disarm( code => $disarmCode ) ;
$elk->disconnect() ;

# check alarm has been disabled as we dont want to set it off

while ( &checkELK(1) ) {
print " message still trying to disable alarm\n";
my $elk = ElkM1::Control->new(
'host' => $elkHost,
'port' => $elkPort,
'use_ssl' => $useSSL,
'debug' => 0
);
$elk->disarm( code => $disarmCode ) ;
$elk->disconnect() ;
sleep (2) ;
}

}


sub checkELK {
my $alarmArmed = "0" ;
my $ExitDelay = "0" ;
my $alarmCheckOnly = 0 ;

if ( $_[0] ) {
$alarmCheckOnly = 1 ;
print "alarmCheckOnly\n" if $DEBUG ;
}

my $elk = ElkM1::Control->new(
'host' => $elkHost,
'port' => $elkPort,
'use_ssl' => $useSSL,
'debug' => 0
) or die ;

my $msg = $elk->requestArmingStatus() ;
my @keys = keys %$msg ;

my ($AreaArmingStatus,$AreaReady2Arm,$AreaAlarm) = $$msg{'message'} =~ /^1EAS(\w{8})(\w{8})(\w{8})\d{2}\w

{2}$/ ;
print "AreaArmingStatus $AreaArmingStatus\n" if $DEBUG ;
print "AreaReady2Arm $AreaReady2Arm\n" if $DEBUG ;
print "AreaAlarm $AreaAlarm\n" if $DEBUG ;

if ( $AreaArmingStatus =~ /1/) {
$alarmArmed = "1" ;
}

if ( $AreaReady2Arm =~ /3/) {
$ExitDelay = "1" ;
print "ExitDelay Active\n" if $DEBUG ;
}

$elk->disconnect() ;
if ( $alarmCheckOnly ) {
print "returning alarmArmed $alarmArmed\n" if $DEBUG ;
return($alarmArmed) ;
} else {
return($alarmArmed , $ExitDelay) ;
print "returning alarmArmed:$alarmArmed ExitDelay:$ExitDelay \n" if $DEBUG ;
}
}

sub inRange {
# on the nslu2 this takes about 20 sec to fail
# and about 3-5 sec to pass if the device is in range
print "checking if phone in range\n" if $DEBUG ;

`$hcitool name $deviceMAC > /tmp/$devicename` ;
if ( -e "/tmp/$devicename" ) {
open NAME, "/tmp/$devicename" ;
for (<NAME>) {
if ($_ = $devicename) {
# a simple check that the mac address
# returns the correct name, this test may be
# not actually achieve any increase in security
# but what the heck

print "$devicename IN RANGE\n" if $DEBUG ;
unlink "/tmp/$devicename" ;
return(1) ;
}
}
unlink "/tmp/$devicename" ;
return(0) ;
} else {
print "$devicename OUT OF RANGE\n" if $DEBUG ;
return(0) ;
}
}
[/codebox]
 
A very interesting application of the M1 with a bluetooth-equipped phone!

I don't have the requisite phone to test this but I noticed something in the code you may want to alter. You are instantiating an ElkM1::Control object within "while" loops (i.e. $elk = ElkM1::Control->new) . This means you are opening a new connection to the M1 each time the loop executes and that's unnecessary.

Open a single connection at the beginning of the program (i.e. before all of the loops) and close it at the end of the program. While the connection is open, you can send commands and listen for messages.
 
Back
Top