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

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.

It doesn't have to be a phone, could be mp3 player, car radio ...

The opening and closing was written that way on purpose, as it the program never end's. I don't know enough about the elk's ethernet server's ability to would handle a connection held open for months or longer, weather it would suffer from some sort of memory leak or it's impact on other client connections.

It's a first cut so people can see another possible use for this interface, so with a bit more experience and testing a more elegant solution could be crafted.
 
For what it's worth, you can keep the connection open without any issues. I wrote a php script for my linksys router to be in constant communications with the M1, and I believe the Elk TS07 touchscreens keep the connection alive as well.
 
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.

I found that bluetooth proximity sensor script before as well, but I was never able to get it working quite well. I ported it over to perl and made some improvements that made things more reliable for me.

The original script was meant to automatically lock your computer screen when the bluetooth device signal strength is below a certain threshold. It would then detect when your device came back into range and automatically unlock the screen. Pretty neat, especially seeing how my local electronics store sells some proprietary system to do the exact same thing for $50 and it only works with windows.

I was having a lot of issues with the script locking my screen when it got a false read on the signal strength. I added a 2nd pass of signal strength checking so that it would not lock until 2 reads were below the threshold.

I have included my code here as a sample for you to use. I had to pull some stuff out that wasn't relevant to what you need and I haven't tested it to see if it runs or not. As you can see, I have a daemon option for the script to detach from the command line and run in the background. It has the endless loop already, so all you need to do is check the alarm armed status near the top of the script and do "$elk->disarm( code => $disarmCode ) ;" in place of the "#disarm;" line if the alarm is armed.


[codebox]
#!/usr/bin/perl

### README #############
## Make sure /usr/bin/hcitool has suid bit set:
## sudo chmod 4755 /usr/bin/hcitool

use Getopt::Long;
$| = 1;
$Getopt::Long::autoabbrev = 1 ;
$Getopt::Long::ignorecase = 1 ;

# How often to check the distance between phone and computer in seconds
$near_check_interval = 60;
$far_check_interval = 3;

# The RSSI threshold at which a phone is considered far or near
$threshold = -2;

$hcitool = "/usr/bin/hcitool";

my @optl = ("help", "mac_addr=s", "verbose", "daemon");
GetOptions @optl;

if ($opt_help) {
print "$0 -daemon -help -mac_addr <addr> \n\n";
print "-daemon Detach and run as daemin\n";
print "-mac_addr MAC address of bluetooth device to monitor\n";
print "-verbose print debug info to screen\n";
print "-help\n";
exit(0);
}

# Phone
if ($opt_mac_addr) {
$device = $opt_mac_addr;
} else {
$device = "xx:xx:xx:xx:xx:xx";
}

$connected = 0;

sub msg {
print "@_" if ($opt_verbose);
}

sub check_connection {
$connected=0;
my $found = 0;
foreach $s (`$hcitool con 2>&1`) {
if ($device =~ /$s/) {
$found = 1;
}
}
if ($found) {
$connected = 1;
} else {
msg "$time | Attempting connection... ";
if (`$hcitool cc $device 2>&1` eq "") {
msg 'Connected. ';
$connected = 1;
} else {
if (`l2ping -c 2 $device 2>&1` eq "") {
if (`$hcitool cc $device 2>&1` eq "") {
msg 'Connected. \n';
$connected = 1;
} else {
msg 'Not connected. \n';
$connected = 0;
}
}
}
msg "Could not connect to device $device. \n" unless ($connected);
}
if ($connected) {
$rssi =`$hcitool rssi $device`;
$rssi =~ s/RSSI return value: //g; chomp $rssi;
msg "RSSI: $rssi \n";
} else {
$rssi = "-99";
}
return $rssi;
}

## Become a daemon
sub daemonize {
exit(0) if fork();
chdir("/");
setpgrp(0,0);
close(STDIN);
close(STDOUT);
close(STDERR);
}


$name=`$hcitool name $device`;
chomp $name;
$time = `date "+%D %H:%M"`; chomp $time;
msg "$time | Monitoring proximity of \"$name\" [$device]\n";

$state="near";

daemonize() if ($opt_daemon);

while (1) {

$time = `date "+%D %H:%M"`; chomp $time;
my $rssi = check_connection;
if ($state eq "near" && $rssi <= $threshold) {
msg "Phone might have left, pause and recheck \n";
# probe again after 1 second
sleep 1;
$rssi = check_connection;
if ( $rssi <= $threshold) {
msg "$time | *** Device \"$name\" [$device] has left proximity:\n";
$state="far";
#####################
#arm;
#####################
}
} else {
if ($state eq "far" && $rssi >= $threshold+2 ) {
msg "$time | *** Device \"$name\" [$device] is within proximity:\n";
$state="near";
#####################
#disarm;
#####################
}
}
if ( $state eq "far" ) {
sleep $far_check_interval;
} else {
sleep $near_check_interval;
}
}

[/codebox]

On my ubuntu box, you have to be root to run hcitool, but I get around this by setting the uid bit of the hcitool binary.

I'm curious about how you envision a disarm cycle would occur. Do you open a door and expect this script to detect the bluetooth device before the entry timer expires and the alarm goes off?
 
If my "How-To use Perl and the Elk M1" has piqued your interest, please feel free to PM me with your ideas for examples. I'll write the code for the most common requests and post them here.
 
On my ubuntu box, you have to be root to run hcitool, but I get around this by setting the uid bit of the hcitool binary.
May I recommend instead that you setup sudo to require no password for the user using hcitool then you can simply add sudo to the front of the command and not worry about possibly loosening the security of the entire system. I find that I prefer not to give root access to any of the other users on my systems but rather sudo access to specific commands that they need to access.
 
I'm interested in playing around with this in Linux. I've only used CPAN to install perl modules before. How/where do I install the libraries? or can I just keep them in a directory and and 'include' them?
 
... How/where do I install the libraries? or can I just keep them in a directory and and 'include' them?

I haven't used perl in a Linux environment so I'mnot 100% sure where it keeps its librairies (depends on how it gets installed. Try looking for an environment variable (something like PERL5LIB maybe) to give you a hint where it stores its modules.
 
I'm interested in playing around with this in Linux. I've only used CPAN to install perl modules before. How/where do I install the libraries? or can I just keep them in a directory and and 'include' them?

Dont worry it's not to complicated.

Download it
cd to where you downloaded the file
uncompress it using tar -zxvf ElkM1-Control-0.02.tar.gz
cd ElkM1-Control-0.02

Then from the README file, that most perl modules should have.

Perl Makefile.PL
make
make test

HOW TO INSTALL (note: must be root or the equiv using sudo)

make install

From then you should be good to go
 
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).

I curios how the the serial port integration is coming along? I am new to perl hacking so I have a steep learning curve ahead of me but what a fun way to get started with perl.
 
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).

I curios how the the serial port integration is coming along? I am new to perl hacking so I have a steep learning curve ahead of me but what a fun way to get started with perl.
My apologies, I've been a bit buried with school, work, home and scheduled HA presentations. Here's what I have so far:

Sample code:
Code:
use lib '/home/njc/dev/v2-104/lib';	# IMPORTANT

#use strict;
use warnings;
use Carp;

use ElkM1::Control::Serial;	# IMPORTANT

# IMPORTANT
my $elk = ElkM1::Control::Serial->new( 'port'   => '/dev/ttyM1',
									   'speed'  => 115200);

# Main loop, read any message.
my $msg;
$| = 1;

while (1) {
	$msg = $elk->readMessage();

	if(!defined($msg)) {
		sleep 1;
	} else {
		#while (defined($msg = $elk->readMessage)) {
		if (ref($msg) eq 'ElkM1::Control::ZoneChangeUpdate') {
			print "zone ".$msg->getZone." is now ".getState;

			# If zone 1 is violated, activate task 1
			# and do something else.
			if ($msg->getZone == 1 and $msg->isViolated) {
				$elk->activateTask(task => 1);
				# ... something else ...
			}
		} else {
			print $msg->toString . "\n";
		}
	}
}

This code is very similar to the code that James include with his package. You'll need to grab the Serial.pm module and drop it into the ElkM1 directory that you untar'd from James' package. James' package untars into ElkM1-Control-0.02/ . I renamed it ElkM1 (that's important). Note that in the example above I included the line use lib '/home/njc/dev/v2-104/lib';, in that directory is my ElkM1 subdirectory and in there is where I put my Serial.pm file . The use lib line allows Perl to know where to look for the modules. This adds to the existing Perl library path. The reason for all this mess is that I'm trying to experiment with the code and I don't want it installed in the normal Perl paths (where I wouldn't need to include the use lib '...'; line).

Note that in the example above there are a few sections that differ from James example. I had a lot of trouble with NULL $msg being returned (maybe undefined?) so I need to alter the code in the manner above. Also, I've created an ELKM1 support web page to support my additions to the code and perhaps extend the original code. At this time it's not a very useful page but I'll add to it as I get things properly working. Much of my code will be added to Misterhouse but not James original module, so the page will also reflect that. At the moment I'm trying to clean up my HA server so I can run two copies of Misterhouse. One that runs the home the other that run my development environment. It's difficult to do since I have so many things running there.

I hope the above is useful, I can help out where needed. If I disappear for a while send me an email and I'll come back and answer any questions. And now back my presentation and home work. <_<
 
I read the doc, but am wondering what the method would be to set a light brightness level on a house/unit code? i see getStatus can read it, but nothing clearly saying how to set it. my best guess would be a variation of controlPLCDevice?
 
I read the doc, but am wondering what the method would be to set a light brightness level on a house/unit code?
... my best guess would be a variation of controlPLCDevice?

controlPLCDevice is the correct method.

The following example sets device A2 to a brightness level of 54% for 180 seconds.

elk->controlPLCDevice( unit => 2, house => 'A', function => 9, extended => 54, ontime => 180 );
or
elk->controlPLCDevice( index => 2 , function => 9, extended => 54, ontime => 180 );

You can refer to the device using the traditional X10 addressing scheme, House Code (A-P) and Unit Code (1-16), or using Elk's device index, 1-256 (e.g. A2 is index 2 and C5 is index 37).

Elk's ASCII RS-232 Protocol document defines the Function code as follows:
FF = Function Code as follows:
01 = X10_ALL_UNITS_OFF in a House code
02 = X10_ALL_LIGHTS_ON in a House code
03 = X10_UNIT_ON
04 = X10_UNIT_OFF
05 = X10_DIM, EE extended value holds number of dims
06 = X10_BRIGHT, EE extended value holds number of brights
07 = X10_ALL_LIGHTS_OFF in a House code
08 = X10_EXTENDED_CODE
09 = X10_PRESET_DIM, EE extended value hold level 0 to 99%
10 = X10_EXTENDED_DATA
11 = X10_STATUS_REQ
12 = X10_HAIL_REQUEST
13 = X10_HAIL_ACK, not used
14 = X10_STATUS_ON, not used
15 = X10_STATUS_OFF, not used
TTTT = ON Time in seconds, range - 0 to 9999 decimal

Based on this table, function 09 instructs the M1 that the "Extended" parameter specifies the brightness level.
 
dude thanks for all this 123, i was able to grant my girlfriends wish that the lights in our room gradually ramp up over the course of a half hour to mimick the sunrise as an alarm clock.

i ran into some issues though (that i fixed). namely line 1084 of Control.pm should have $onTime instead of $extended. There were a few other quirks in the code, maybe it was just my version of perl (using CentOS 5)
 
elk->controlPLCDevice( unit => 2, house => 'A', function => 9, extended => 54, ontime => 180 );
or
elk->controlPLCDevice( index => 2 , function => 9, extended => 54, ontime => 180 );


I have been trying this all day. I can arm, disarm, check the status, get it to say the time but I can't get the PLC control to work.

It doesn't seam to want to take the house code 'A' and also doesn't take just the index.

When I try: $elk->controlPLCDevice( index => 3 , function => 9, extended => 99, ontime => 1 );
I get: ElkM1::Control::controlPLCDevice - required argument 'house' not found. at C:Documents and SettingsPabloDesktoptesting.pl line 5

When I try: $elk->controlPLCDevice( unit => 3, house=> 'A', function => 9, extended => 99, ontime => 1 );
I get: Argument "A" isn't numeric in numeric ge (>=) at C:/Perl/site/lib/ElkM1/Control.pm line 1053.
Argument "A" isn't numeric in numeric ge (>=) at C:/Perl/site/lib/ElkM1/Control.pm line 1053.
Argument "P" isn't numeric in numeric le (<=) at C:/Perl/site/lib/ElkM1/Control.pm line 1053.
Use of uninitialized value in sprintf at C:/Perl/site/lib/ElkM1/Control.pm line 1097.

When I try: $elk->togglePLCDevice( unit => 3, house => 'A');
I get: Argument "A" isn't numeric in sprintf at C:/Perl/site/lib/ElkM1/Control.pm line 1020.

When I try: $elk->togglePLCDevice( index => 3);
I get: Argument "A" isn't numeric in sprintf at C:/Perl/site/lib/ElkM1/Control.pm line 1020.

I am new to perl but everything else works and I am stumped on this please help...
 
I checked the lines of codes you indicated and I don't know why the module is stumbling over them. It is a fundamental test to determine if the supplied house code, a simple ASCII letter, is within a permissible range. The only thing that comes to mind is it may have something to do with Perl's operating environment. Tell me more about your PC's:

Operating system
Perl version
Default language
 
Back
Top