rubin Posted January 9, 2011 Posted January 9, 2011 I spent a couple hours tonight hacking up a perl script to communicate with the ISY-26 web interface, since there is no API. The result is you can turn devices or scenes on and off from the command line. Thought I'd post it here since I couldn't find anything like it for the -26. I'll try to inline it here, but for a clean copy try http://simplanet.org/~rubin/isy.pl Tested on ubuntu linux. Here are some examples: Provide a list of devices and their node names (needed for other commands) isy.pl -host 192.168.1.10 -query -password mypass Turn on a light: isy.pl -host 192.168.1.10 -on -node "5 48 76 1" -password mypass View node names for your scenes isy.pl -host 192.168.1.10 -query -password mypass -type scenes execute a "fast off" for a scene isy.pl -host 192.168.1.10 -fastoff -password mypass -type scenes -node 28328 [see reply below for improved version]
Michel Kohanim Posted January 9, 2011 Posted January 9, 2011 Hello rubin, Thanks so very much!!! This is excellent. May I humbly request that you also post this article in the Developer Forum under WSDK: http://forum.universal-devices.com/viewforum.php?f=62 If you are planning to upgrade to 99i, please note that you get a developer discount. Thanks again and with kind regards, Michel
RatRanch Posted January 9, 2011 Posted January 9, 2011 Rubin, This is very cool! Thanks for contributing the very useful script. It also runs fine on Mac OS X Snow Leopard (however, the pQuery module, which is not not part of the OS X Perl distribution, also needs to be installed). One minor bug: the script won't work out of the box if the ISY username has been changed from the default of "admin" because there is no option to specify username on the command line. This is easily fixed by editing the #Defaults section of the script. Cheers, -Jim
rubin Posted January 10, 2011 Author Posted January 10, 2011 Rubin, however, the pQuery module, which is not not part of the OS X Perl distribution, also needs to be installed). I actually didn't end up using pQuery, so you can just delete the "use pQuery" line. I left it in by mistake. Here is a new version with that fixed, and the ability to query just a specific node for status (on/off) useful for building other scripts to toggle status. #!/usr/bin/perl -w # # Created by Rubin =head1 NAME isy.pl - Control an isy-26 via its web interface, from commandline =head1 SYNOPSIS isy.pl [options] Commands: (short) -query (-q) -on -off -faston -fastoff Options: (short) -help -verbose (-v) -host -cfile -username -password -protocol (-p) -type (-t) -node (-n) =head1 OPTIONS =over 8 =item B Fetches the list of devices or scenes from the ISY and prints them. If you specify a -node, only that node details will be printed. It will print the status by itself by default, but if called with -verbose, name, node, and status will be printed. =item B Action: Turn something on =item B Action: Turn something off =item B Action: Turn something fast-on =item B Action: Turn something fast-off =item B Action: Dim something by one level =item B<-brighten Action: Brighten something by one level =item B Show this message =item B Prints more details while running. =item B Specifies if you are interacting with Devices or Scenes. Default is Devices =item B Which node to act on for actions =item B A credencial file to read the password from. The password must be on the 1st line of the file. =item B Username to use. Defaults to admin =item B Password to use. Passing passwords in arguments is extremely insecure in *NIX type operating systems, as they show up in the process list. Use -cfile instead of this. =back =head1 DESCRIPTION A tool to talk to an isy-26 via its html/form interface from commandline =cut use strict; use Data::Dumper; use Carp qw(confess cluck); use File::Slurp; use Getopt::Long; use Pod::Usage; #use FindBin qw($RealBin); #use lib "$RealBin"; use LWP::UserAgent; use HTTP::Request::Common qw(POST GET); use HTML::TreeBuilder; #Defaults my $default_type = "devices"; my $username = "admin"; my $protocol = "http"; my $host = "isy"; my $password = ""; my %options; GetOptions( \%options, 'verbose', 'help', 'type:s', 'query', 'on', 'off', 'faston', 'fastoff', 'dim', 'brighten', 'node:s', 'repeat:i', 'repeatdelay:i', 'host:s', 'password:s', 'username:s', 'cfile:s' ) or confess("Error"); if ($options{'help'}) { pod2usage(0); exit 0; } if($options{'verbose'}) { print "Being verbose\n"; } if($options{'host'}) { $host = $options{'host'}; } else { print "No host specified. You must use -host.\n"; pod2usage(1); exit 1; } if($options{'cfile'}) { # open cfile and read password from it if( -f $options{'cfile'}) { my @content = read_file($options{'cfile'}); $password = $content[0]; $password =~ s/\r|\n//g; } } else { if($options{'password'}) { $host = $options{'password'}; } else { print "No password specified. You must use -password or -cfile\n"; exit 1; } } if($options{'username'}) { $username = $options{'username'}; } if($options{'protocol'}) { if($options{'protocol'} =~ /^http[s]?/) { $protocol = $options{'protocol'}; } else { print "Unsupported protocol. http or https only\n"; exit 1; } } # Setup the url now that we know the host and user/pass info my $url = "$protocol://$username:$password\@$host"; # Called by query() to display the results of the query sub parse_result { my $content = shift; my $page = HTML::TreeBuilder->new(); $page->parse($content); $page->eof; #Find any forms that post to "/change" and iterate over them #TODO: Should adjust this to itterate over first so we can snipe the name and status too my %items; #items that can be adjusted on the page, ie, devices or scenes foreach my $row ($page->look_down( '_tag', 'tr')) { # For each form, iterate over its inputs building a hash my %item; my $colcounter = 0; foreach my $td ($row->look_down( '_tag', 'td')) { $colcounter++; if($colcounter == 1) { #print "DEBUG: Got col 1, setting Description\n"; $item{'name'} = $td->as_text; } elsif($colcounter == 2) { #This will only be status on DEVICES page my $td_content = $td->as_text; if($td_content =~ /^on|off$/i) { #print "DEBUG: Got col 2, setting status\n"; $item{'status'} = $td_content; } } } foreach my $form ($row->look_down( '_tag', 'form', sub { $_[0]->{'action'} eq "/change" } )) { #print "DEBUG: Got a form ". $form->{'method'}." to ". $form->{'action'}. "\n"; foreach my $input ($form->look_down( '_tag', 'input', sub { $_[0]->{'type'} ne "submit"} )) { #print "DEBUG: Got an input: ". $input->{'value'}. "\n"; $item{$input->{'name'}} = $input->{'value'}; } #print "DEBUG: Built an item:\n"; #print Dumper(\%item); #print "\n"; } if($item{'node'}) { $items{$item{'node'}} = \%item; } #else { # print "DEBUG: not adding this row because no node was found\n"; #} } #print "DEBUG: Built an item tree\n"; #print Dumper(\%items); #print "\n\n"; return \%items; } sub print_result { my $items = shift; my $type = shift; print "Available $type:\n"; printf("---------------------------------------------------------\n"); printf("%30s: %13s %6s\n", "Name", "Node", "Status"); printf("---------------------------------------------------------\n"); foreach my $node (keys %$items) { my %item = %{$items->{$node}}; #print "DEBUG: Got an item: $node\n"; #print Dumper(\%item); printf("%30s: ", $item{'name'}); printf(" %13s", $item{'node'}); if($item{'status'}) { printf(" %6s", $item{'status'}); } print "\n"; } #print "DEBUG: printing results\n"; #print Dumper($items); } sub parse_single_result { my $content = shift; my $node = shift; my $page = HTML::TreeBuilder->new(); my $name = undef; $page->parse($content); $page->eof; #Find 2nd and then 1st is status on/off my $trcount = 0; my $status = undef; foreach my $row ($page->look_down( '_tag', 'tr')) { $trcount++; if($trcount == 2) { my $tdcount = 0; foreach my $tdrow ($row->look_down( '_tag', "td")) { $tdcount++; if($tdcount == 1) { $status = $tdrow->as_text; } } } } #Name is first h2 block contents foreach my $h2 ($page->look_down( '_tag', 'h2')) { $name = $h2->as_text; last; } if(defined $status) { return({name=>$name, node=>$node, status=>$status}); } return undef; } sub print_single_result { my $result = shift; if($options{'verbose'}) { print $result->{'name'}.":\n"; print " node: ". $result->{'node'}. "\n"; print " status: ". $result->{'status'}. "\n"; } else { print $result->{'status'} . "\n"; } } sub query { my $url = shift; my $type = shift; my $node = shift; #Create a LWP instance and request my $ua = LWP::UserAgent->new; my $req = HTTP::Request->new(GET => $url); my $res = $ua->request($req); if($res->is_success) { if($options{'verbose'}) { print "Got Result: "; #print $res->content; } if($node) { print_single_result(parse_single_result($res->content, $node)); } else { # list all nodes print_result(parse_result($res->content), $type); } } else { print "Query to $host returned an error code:"; print $res->status_line, "\n"; exit 1; } } # Send a POST to the isy to turn something on/off/etc sub action { my $url = shift; my $action = shift; my $type = shift; my $node = shift; $node =~ s/ /+/g; my $repeat = 1; my $repeatdelay = 0; if($options{'repeat'} && $options{'repeat'} > 1 && $options{'repeat'} <10> 1 && $options{'repeatdelay'} < 100) { $repeatdelay = 0 + $options{'repeatdelay'}; } if($node) { for(my $i = 1; $i new; my $req = HTTP::Request->new(POST => $url."/change"); $req->content_type('application/x-www-form-urlencoded'); my $content = "node=$node&raddress=$type&submit=$action"; $req->content($content); #print "DEBUG: content is: $content\n"; my $res = $ua->request($req); if($res->is_success) { if($options{'verbose'}) { print "Action posted successfully\n"; #print $res->content; } #$res->content; } else { print "Action returned an error code:"; print $res->status_line, "\n"; print "Content was:\n$content\n"; exit 1; } if($i < $repeat) { sleep($repeatdelay); } } } else { print "You must specify a node with -node for actions\n"; pod2usage(1); exit 1; } } # If they pass a type, validate it. my $type = $default_type; if($options{'type'}) { if($options{'type'} =~ /^devices|scenes$/) { $type = $options{'type'}; } else { print "Unknown type: ". $options{'type'}. ". "; print "Try one of: devices | scenes\n"; exit 1; } } # Main command list if($options{'query'}) { if($options{'verbose'}) { print "Querying type $type\n"; } if($options{'node'}) { if($type ne "devices") { print "Query of a specific node only available with type devices\n"; exit 1; } query("$url/settings?node=".$options{'node'}, $type, $options{'node'}); } else { query("$url/$type/", $type); } exit 0; } elsif($options{'on'}) { action("$url", "On", $type, $options{'node'}); exit 0; } elsif($options{'off'}) { action("$url", "Off", $type, $options{'node'}); exit 0; } elsif($options{'faston'}) { action("$url", "Fast On", $type, $options{'node'}); exit 0; } elsif($options{'fastoff'}) { action("$url", "Fast Off", $type, $options{'node'}); exit 0; } elsif($options{'dim'}) { action("$url", "Dim", $type, $options{'node'}); exit 0; } elsif($options{'brighten'}) { action("$url", "Brighten", $type, $options{'node'}); exit 0; } # If we didn't do anything, print usage print "No command specified\n"; pod2usage(1); exit 1; [/code]
rubin Posted January 10, 2011 Author Posted January 10, 2011 And for completeness, here is a bash script that queries a device (cant query scenes with this) for its status, and sends a scene control based on that. Result is a scene toggle. If its on, turn it off. If its off turn it on. The reason I didn't include that above is that there are likely to be lots of crazy cases. For example i have some here where any of 2 devices could be on which I'd want to indicate the scene is "on" and turn them all off. Easiest to just do that in bash as need be. #!/bin/bash # ISYPL="/usr/local/bin/isy.pl" ISYCFILE="/secret/isy_password" HOST="isyhostname" STATUS=`$ISYPL -host "$HOST" -cfile "$ISYCFILE" -node "5 94 76 1" -query` if [ "$STATUS" == "Off" ]; then echo "Light is Off. Turning scene on" $ISYPL -host $HOST -cfile $ISYCFILE -type scenes -node "21668" -on else echo "Light is not off. Turning scene off." $ISYPL -host $HOST -cfile $ISYCFILE -type scenes -node "21668" -off fi
rubin Posted January 10, 2011 Author Posted January 10, 2011 Hello rubin,May I humbly request that you also post this article in the Developer Forum under WSDK: http://forum.universal-devices.com/viewforum.php?f=62 Would this be appropriate there?, It doesn't USE the WSDK, it's just scraping the isy website. (Forgive my ignorance, is WSDK the same as the REST interface on the 99s?) Is there a better interface for doing this on the -26es? I was under the impression there wasn't. What did I miss?
RatRanch Posted January 10, 2011 Posted January 10, 2011 Rubin, Would you mind posting a link to the updated script? The inline code is somehow getting mangled when I copy/paste from your post. Thanks, -Jim
rubin Posted January 11, 2011 Author Posted January 11, 2011 Rubin, Would you mind posting a link to the updated script? The inline code is somehow getting mangled when I copy/paste from your post. Thanks, -Jim Sure, I updated the script in the link. -Alex
Recommended Posts