Thursday, January 28, 2010

All Quiet on the western front

Well not really, with a new baby , new job and christmas all happening in the last few months, time has been precious.

I managed to get my heatmiser PRT-HW/N and PRT-N working on my openwrt box with a usb serial and rs232 to 485 adapter. Now using a spare sony ericson z610i phone via usb , I can control my heatmiser controls via sms. This is done using smstools , a bash file that parses the sms body and then runs a perl script based on the commands in the message.

The perl script does some serial communication to program the 2 heatmiser stats. I can set the temp, turn on/off frost control etc.

I hope to tidy it up a little bit, but so far it's quite robust, so a small tidy before I put the code somewhere for others to copy/use.

Other things , I have been fixing some bugs in m0n0wall and adding some features and adding to ipv6 support. I just ordered a usb GPS unit, so I can use it on m0n0wall or openwrt with gpsd and ntpd to create a stratum 1 time source. It was only around $30 so will be fun, even though it's my 4th gps unit now ! A commercial ntp unit of this nature is 6000 euro, though does have a better local timesource if gps fails, but not 5950 euros worth !

19 comments:

Jean-Pierre said...

Hi there, am running openwrt and have just done some extensive works to my house and installed two Heatmiser Networks Thermostats. Am looking to do a similar remote heating setup as you, though I was thinking of running a simple webserver for remote control rather than via a usb phone. Was understandably delighted to find your blog!

Any chance you can share some (or all) of the code? My aim was to use an exisiting cheapo USB to RS232 cable attached to a RS232 to RS485 adapter (ebay probably). Also which packages would need to be installed on the router? Thanks in advance!

Desra said...

Hi,

I used bash to do the sms control piece and that script then called perl scripts that controlled the heatmiser unit.


http://www.heatmiser.co.uk/support/admin/attachments/protocolv3system.pdf

and I followed what these guys did http://www.comfortforums.com/view_topic.php?forum_id=47&highlight=hometronic&id=918

I will do two posts, one with getheatmiserstatus.pl which you run with the unit number you want to query (I have 2 units on my chain)

perl getheatmiserstatus.pl 1

The other post with have toggleheatmiser.pl which you also pass a unit number to, and a command

perl toggleheatmiser.pl unit 1 on
perl toggleheatmiser.pl unit 1 off
perl toggleheatmiser.pl frost 1 on
perl toggleheatmiser.pl frost 1 off
perl toggleheatmiser.pl settemp 1 24
perl toggleheatmiser.pl setfrost 1 7

settemp passes the temp you want to set the unit too
setfrost sets the frost temp

Desra said...

oops, on my embedded system, I couldn't get perl to behave with the serial port, so I used ser2net which allowed me to access my serial port via telnet, then used Telnet.pm with my perl script

Desra said...

here is getheatmiserstatus.pl


use Net::Telnet ();
$t = new Net::Telnet( Timeout => 15 );
my $address = 0x01;
if ($ARGV[0] =~ /\d{1,2}/ ) {
$address = $ARGV[0];
}else {
print "Missing unit number\n";
exit;
}
my $function = 0x26;
my $data = 0x00;
my ($sum) = ( $address + $function + $data );
$t->open( Host => "127.0.0.1", Port => 2009 );
@lines = $t->print( chr($address), chr($function), chr($data), chr($sum) );
$line = $t->getline;
$line2 = $t->getline;
$t->close;
$line = $line . $line2;
@bytes = split( //, $line );
foreach (@bytes) {
$lastone = ord($_);
$newcalcsum = $newcalcsum + $lastone;
}
$newcalcsum = $newcalcsum - $lastone;
$newcalcsum = ( $newcalcsum | 0xff00 ) - 0xff00;

if ( $newcalcsum == $lastone ) {

my $floor_status = $bytes[3] >> 7;
my $preheat = ( $bytes[3] >> 4 ) & 0x07;
my $week = $bytes[3] & 0x0F;
my $hour = ord( $bytes[4] );
my $minute = ord( $bytes[5] );
my $temp = ord( $bytes[6] );
my $switching_diff = ord( $bytes[7] ) >> 4;
my $part_no = ord( $bytes[7] ) % 0x0F;
my $tempformat = ord( $bytes[8] ) & 0x01;
my $frost_mode = ord( $bytes[8] ) & 0x02;
my $sensor = ord( $bytes[8] ) & 0x04;
my $floor_limit = ord( $bytes[8] ) & 0x08;
my $output = ord( $bytes[8] ) & 0x10;
my $frost_protect = ord( $bytes[8] ) & 0x20;
my $keys_locked = ord( $bytes[8] ) & 0x40;
my $state = ord( $bytes[8] ) & 0x80;
my $set_temp = ord( $bytes[9] );
my $frost_temp = ord( $bytes[10] );
my $output_delay = ord( $bytes[11] );
my $floor_temp = ord( $bytes[12] );

#print $line . "\n";
# print "Floor Status " . ( ($floor_status) ? "ON\n" : "OFF\n" );
# print "Pre-heat " . ( ($preheat) ? "ON\n" : "OFF\n" );
print "Switching Diff $switching_diff\n";
# print "Time " . $hour . ":" . $minute;
# print "Part No $part_no\n";
print "Temp Format " . ( ($tempformat) ? "F\n" : "C\n" );
print "Frost Mode " . ( ($frost_mode) ? "ENABLED\n" : "DISABLED\n" );
print "Keys " . ( ($keys_locked) ? "LOCKED\n" : "UNLOCKED\n" );
print "Temp = $temp\n";
print "Set Temp = $set_temp Frost Temp = $frost_temp\n";
print "Output delay = $output_delay\n";
print "Frost Protect " . ( ($frost_protect) ? "ON\n" : "OFF\n" );
print "State " . ( ($state) ? "ON\n" : "OFF\n" );
print "Output " . ( ($output) ? "ON\n" : "OFF\n" );
}
else {
print "Checksum Error\n";
foreach (@bytes) {
print ord($_) . " ";
}
}

Desra said...

and here is toggleheatmiser.pl



use Net::Telnet ();
$t = new Net::Telnet( Timeout => 15 );
my $address = 0x01;
my $function = 0x00;
my $data = 0x00;

if ($ARGV[0] =~ /unit/i ) {
$function = 0x82;
} elsif ($ARGV[0] =~ /Frost$/i ) {
$function = 0xe4;
} elsif ($ARGV[0] =~ /settemp/i ) {
$function = 0x84;
} elsif ($ARGV[0] =~ /frosttemp/i ) {
$function = 0x87;
}else {
print "Missing function\n";
exit;
}

if ($ARGV[1] =~ /off/i ) {
$data = 0x00;
} elsif ($ARGV[1] =~ /on/i ) {
$data = 0xFF;
} elsif ($ARGV[1] =~ /\d{1,2}/ ) {
$data = $ARGV[1];
}else {
print "Missing data\n";
exit;
}

if ($ARGV[2] =~ /\d{1,2}/ ) {
$address = $ARGV[2];
}else {
print "Missing unit number\n";
exit;
}

my ($sum) = ( ($address + $function + $data ) | 0xff00 ) - 0xff00;
$t->open( Host => "127.0.0.1", Port => 2009 );
#print chr($address) .chr($function) . chr(0xff) . chr($sum);
@lines = $t->print( chr($address), chr($function), chr($data), chr($sum) );
$line = $t->getline;
$t->close;

@bytes = split( //, $line );

$lastone = 'fail';

foreach (@bytes) {
$lastone = ord($_);
$newcalcsum = $newcalcsum + $lastone;
}
$newcalcsum = $newcalcsum - $lastone;
$newcalcsum = ( $newcalcsum | 0xff00 ) - 0xff00;

if ( $newcalcsum == $lastone ) {
print "Success\n";
}
else {
print "Checksum Error\n";
}

japers said...

Thanks so much for the quick reply and all the files! Am getting excited about trying to get this all to work, just trying to order a RS485 adapter and trying to get my head round your physical wiring setup, is it:

WRT->USB->RS232->RS485 or
WRT->USB->RS485 or
WRT->RS232->RS485

And then do you have a second USB for the phone or is it running on a separate PC?

My concern is that a USB to RSxxx adapter would need drivers (and my router doesn't have RS232 unless I open and solder!)?

Thanks for bearing with me, am quite new to Linux so going through a steep learning curve!

Desra said...

the wrt I have is a netgear wgt634u which has a usb, I installed openwrt on it, and from the packages system installed the usb serial drivers.

something like this

ipkg install kmod-usb-serial-pl2303

I had the usb to serial (rs232) lying around, so just got an rs232 to rs485 adapter

http://www.dealextreme.com/details.dx/sku.6040

so i'm WRT->USB->RS232->RS485

Desra said...

oops, and I used a usb hub, so I could also plug in my sony phone, and installed it's driver

ipkg install kmod-usb-acm

i think (can't access box from here)

and

ipkg install smstools

Desra said...

If you only have 1 thermostat , you can use their software for free

http://www.heatmiser.co.uk/pclink.htm on a windows pc

japers said...

Thanks again for all the info & tips! RS485 on order and spare weekend coming up so I will keep you updated on whether I succeed...

I have 2 thermostats and as it is a weekend home I don't really want to leave a PC running 24/7 but happy to leave the router plugged in hence why WRT is my favoured option!

japers said...

Hi am still working on this...slowly! I decided to flash the recently released Buffalo DD-WRT on my router which works great but seem to have a problem with the pl2303 kernels - they will not "insmod". I believe it is a Kernel mismatch issue (2.6.24.111 on DD-WRT), unless I can find a solution I will revert to OpenWRT. Before I do this, I was wondering which OpenWRT release you are running with the ipkg pl2303 kernel - I assume it must be 10.03.1-RC3?

japers said...

Hello again! Apologies about the earlier post - utterly pointless in hindsight but I was lost! Back on OpenWRT now, packages installed, dmesg showing all USB ok. Trying to get my head round the script using telnet.pm:

From what I gather you are looping the signal back (127.0.0.1) to a current telnet session on port 2009 (you have selected 2009 rather than the usual 23 for security I assume)- this would also explain why there is no login/password? I connect the typical way using ssh and struggling to enable telnet:

/etc/init.d/telnet start (or enable and reboot - nothing visible after typing "ps")

I have also simply tried telnetd which is loaded but it drops any connection attempt.

I found the following forum post but a little nervous to use it in case I have completely misunderstood the above!

https://forum.openwrt.org/viewtopic.php?id=11910

Is this effectively what you have done? Thanks again!

Desra said...

No, I had serial port problems ,so used ser2net to access my serial ports via TCP. so 2009 is a config line in ser2net.conf like this

2009:raw:5:/dev/ttyUSB0:4800


nothing to do with telnetd

japers said...

Thanks so much for correcting me, I was utterly confused and off on a tangent there! All seems to work - will be connecting it to the Heatmisers over the weekend and will report back :-)

japers said...

I have been quiet a long time as struggling to make it work! I have done a test using my Laptop using PC-Link and it works fine so all seems ok from a hardware perspective. Using 'dmesg' on router shows that my pl2303 USB is ok (TTYUSB0) and drivers loaded. Since installing ser2net and adding the 2009 line to ser2net.conf, the script appears to run (daemon is running). However, if I use the "get" script, it returns blank information (0 all temps). If I use the "toggle" script (say perl toggleheatmiser.pl settemp x 24), I always get a "Success" but the thermostats do not change their settings. I have tried this script on non-existent thermostats (say No5) or with the RS-485 disconnected and I still get "Success". I can't help think the problem is somewhere in ser2net but I just can't figure where. sorry to be a pain and ask if you might have any ideas? On another note, I have made a detailed list of all the code needed for this project from a fresh OpenWRT install- I'll be happy to send it if it helps anyone else.

japers said...

Hi, back again and I have been long at work on the Heatmiser issue! With quite a bit of help, I have figured out firstly why these scripts don't work for me - they seem to be written for Heatmiser V2 thermostats and mine are V3! The key is that the CRC is calculated differently as well as some other differences...

From the comfort forums, I have been sent a perl logging script which works with V3 thermostats. The problem is that it uses Device::SerialPort to communicate rather than telnet.pm. I could not get this to work - was this the same problem when you mentioned you struggled get the serial port to behave or which other 'perl to serial' method(s) did you try?

I bought a FTDI USB Serial adapter which has Tx and Rx LEDs so that I know when data is sent which has proven extermely useful. I was also given the tip to use a USB sniffer to get the correct code which was a brilliant idea.

This has brought me back full circle to your script and using Telnet.pm - I know something is being sent thanks to the LED but I get no reply and I'm trying to trace the problem. What I have done is completely cut down your getheatmiser.pl script to the bare essential 'send' element of the code (no listening). The idea being that if the Rx LED flashes back on the cable then I know the thermostat has responded and I can then build from that. I am pretty convinced my script adaptation is correct but I still get no answer. Would you mind if I ask whether I have missed anything out? If you think the script is OK, then the problem must be elsewhere and it will help me narrow down!

use Net::Telnet ();
$t = new Net::Telnet( Timeout => 15 );
# This code Works using Device::SerialPort
my $destination = 0x01;
my $length = 0x0A;
my $source = 0x81;
my $function = 0x00;
my $startl = 0x00;
my $starth = 0x00;
my $rwlengthl = 0xFF;
my $rwlengthh = 0xFF;
my $CRCl = 0x2C;
my $CRCh = 0x09;
# Send Code
$t->open( Host => "127.0.0.1", Port => 2009 );
@lines = $t->print( chr($destination), chr($length), chr($source), chr($function), chr($startl), chr($starth), chr($rwlengthl), chr($rwlengthh), chr($CRCl), chr($CRCh) );
$line = $t->getline;
$t->close;

Desra said...

Hi, playing catchup. Do you still have this problem ?

japers said...

Hi, thanks for your reply, I have found a "temporary" solution which is to adapt a script written by Steve on the comfort forums.

Rather than use Telnet.pm, his script uses SerialPort - the problem being, as I think you experienced, that I cannot run/compile SerialPort on/from OpenWRT. If there is anyway of getting your script (well, my butchered adaptation of it!) as per my previous post to work that would be amazing!

I have also bought a null modem cable to try and analyse the serial messages being sent in order to debug - just ran out of time recently.

neil said...

For the record I have some python code that works with Heatmiser V3 API
Precious little code available on the web, so it may help someone.