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]