From 3c1718d8a937d366a15a89ced10cf1e14042343d Mon Sep 17 00:00:00 2001 From: waynieack Date: Sun, 5 Jul 2020 00:35:21 -0500 Subject: [PATCH 1/3] Add BondHome.pm --- lib/BondHome.pm | 534 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 lib/BondHome.pm diff --git a/lib/BondHome.pm b/lib/BondHome.pm new file mode 100644 index 000000000..671bcaf97 --- /dev/null +++ b/lib/BondHome.pm @@ -0,0 +1,534 @@ + +=head1 B + + +=head2 DESCRIPTION + +Module for interfacing with the BondHome Hub to control devices configured in it. + +=head2 CONFIGURATION + +At minimum, you must define the Interface and one BondHome_Device object. +This allows for the display and control of these objects as separate items +in the MH interface and allows users to interact directly with these objects +using the basic Generic_Item functions such as tie_event. + + +The BondHome_Device object is for tracking the state of and controlling +devices configured in the bondhome hub. + +Misterhouse loads all devices and device commands from BondHome when it is started. +You must reload the BondHome device and trigger and retrieve an auth token by setting the +parent object to "GETTOKEN" with in 1 min after the reboot. + +=head2 Interface Configuration + +mh.private.ini configuration: + +In order to allow for multiple BondHome Hubs, instance names are used. +the following are prefixed with the instance name (BondHome). + + + +The IP of the BondHome Hub: + BondHome_ip=192.168.1.50 + + +Max command retry: + BondHome_maxretry=4 + + +=head2 Defining the Interface Object + +In addition to the above configuration, you must also define the interface +object. The object can be defined in the user code. + +In user code: + + $BondHome = new BondHome('BondHome'); + +Wherein the format for the definition is: + + $BondHome = new BondHome(INSTANCE); + +=head2 Device Object + + $BondHome_Device = new BondHome_Device('BondHome'); + +Wherein the format for the definition is: + $BondHome_Device = new BondHome_Device(INSTANCE); + +States: +Dynamic from the bond home + + + +=head2 NOTES + +An example mh.private.ini: + + BondHome_maxretry=4 + BondHome_ip=192.168.1.50 + + +An example user code: + + #noloop=start + use BondHome; + $BondHome = new BondHome('BondHome'); + $MasterFan = new BondHome_Device('BondHome','MasterFan'); + #noloop=stop + + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + +package BondHome; +@BondHome::ISA = ('Generic_Item'); + +use Data::Dumper; +use JSON qw(decode_json); + +sub new { + my ( $class, $instance ) = @_; + $instance = "BondHome" if ( !defined($instance) ); + ::print_log("Starting $instance instance of BondHome interface module"); + + my $self = new Generic_Item(); + + # Initialize Variables + $$self{instance} = $instance; + $$self{maxretry} = $::config_parms{ $instance . '_maxretry' }; + $$self{ip} = $::config_parms{ $instance . '_ip' }; + my $year_mon = &::time_date_stamp( 10, time ); + $$self{log_file} = $::config_parms{'data_dir'} . "/logs/BondHome.$year_mon.log"; + + bless $self, $class; + + #Store Object with Instance Name + $self->_set_object_instance($instance); + $self->restore_data( 'token' ); + $$self{token} = '4b8d109022195f1b'; + @{$$self{states}} = ('gettoken','reboot','logdevs','reloadcache'); + return $self; +} + +sub get_object_by_instance { + my ($instance) = @_; + return $Interfaces{$instance}; +} + +sub _set_object_instance { + my ( $self, $instance ) = @_; + $Interfaces{$instance} = $self; +} + +sub init { + +} + +=item C + +Used to associate child objects with the interface. + +=cut + +sub register { + my ( $self, $object, $class ) = @_; + if ( $object->isa('BondHome_Device') ) { + ::print_log("Registering Child Object for BondHome Device"); + push @{ $self->{device_object} }, $object; + } +} + + + +sub set { + my ( $self, $p_state, $p_setby, $p_response ) = @_; + ::print_log( "[BOND] Unknown request " . $p_state . " for " . $self->get_object_name ); + if ( $p_state eq 'GETTOKEN' ) { + ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + $self->gettoken( $object, $class ); + } + elsif ( $p_state eq 'REBOOT' ) { + ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + $self->reboot( $object, $class ); + } + elsif ( $p_state eq 'LOGDEVS' ) { + ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + $self->logdevs( $object, $class ); + } + elsif ( $p_state eq 'RELOADCACHE' ) { + ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + $self->reloadcache( $object, $class ); + } + else { + ::print_log( "[BondHome] Unknown request " . $p_state . " for " . $self->get_object_name ); + } +} + + + +sub devexists { + my ( $self, $object, $devicename, $class ) = @_; + my $ip = $$self{ip}; + my $token = $$self{token}; + + unless ( $token ) { + ::print_log ("[BOND] (Sub devexists) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"); + return; + } + + unless ( $self->{devicehash}->{devicename} ) { + ::print_log ("[BOND] There are no devices in cache, running reload cache ". $self->get_object_name ); + $self->reloadcache( $object, $class ); + + #::print_log Dumper $self; + + unless ( $self->{devicehash}->{devicename} ) { + ::print_log ("[BOND] (Sub devexists) There are no devices in cache after reloading the cache, something went wrong"); + return; + } + } + + return 1 if ( exists $self->{devicehash}->{devicename}->{$devicename} ); + + ::print_log ( "[BOND] there is no device with the name \"$devicename\" configured on the BondHome Hub " . $self->get_object_name ); + return; +} + + +sub getdevstates { + my ( $self, $object, $class ) = @_; + my $token = $$self{token}; + my $devicename = $$object{devicename}; + + unless ( $token ) { + ::print_log "[BOND] (Sub getdevstates) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + + unless ( $self->{devicehash}->{devicename} ) { + ::print_log "[BOND] There are no devices in cache, running reload cache"; + $self->reloadcache( $object, $class ); + + unless ( $self->{devicehash}->{devicename} ) { + ::print_log "[BOND] (Sub getdevstates) There are no devices in cache after reloading the cache, something went wrong"; + return; + } + } + + + my @states; + foreach my $command (keys %{$self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}} ) { + push @states, cleanstring($command); + } + return @states; +} + + +sub sendcmd { + my ( $self, $object, $devicename, $cmd, $class ) = @_; + + unless ( $$self{token} ) { + ::print_log "[BOND] (Sub sendcmd) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + + my $maxretry = $$self{maxretry}; + $devicename = cleanstring($devicename); + $cmd = cleanstring($cmd); + + if ( $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmd}->{id} ) { + my $deviceid = $self->{devicehash}->{devicename}->{$devicename}->{id}; + my $cmdid = $self->{devicehash}->{devicename}->{device}->{commands}->{name}->{$cmd}->{id}; + for (0..$maxretry) { + my $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands/$cmdid/tx" ); + last if $response; + } + } else { + ::print_log "[BOND] Invalid command: $cmd for " . $self->get_object_name; + } + +} + +sub cleanstring { + my ( $string ) = @_; + $string = uc $string; + $string =~ s/ //g; + return $string; +} + + +sub reboot { + my ( $self, $object, $class ) = @_; + my $token = $$self{token}; + unless ( $token ) { + ::print_log "[BOND] (Sub reboot) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + $self->bondcmd( $object, $class, '/v2/sys/reboot' ); +} + + +sub logdevs { + my ( $self, $object, $class ) = @_; + my $token = $$self{token}; + + unless ( $token ) { + ::print_log "[BOND] (Sub logdevs) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + + unless ( $self->{devicehash}->{devicename} ) { + ::print_log "[BOND] There are no devices in cache, running reload cache"; + $self->reloadcache( $self, $object, $class ); + } + foreach my $devicename (keys %{$self->{devicehash}->{devicename}}) { + ::print_log "[BOND] Device: $device"; + ::print_log "[BOND] ---- Commands:"; + foreach my $command (keys %{$self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}} ) { + ::print_log "[BOND] ---- $command"; + } + } +} + + +sub gettoken { + my ( $self, $object, $class ) = @_; + ::print_log "[BOND] Getting Token"; + my $response = $self->bondcmd( $object, $class, '/v2/token' ); + my $message = $response->decoded_content; + eval { $message = decode_json($message) }; + if ( $message->{locked} ) { + ::print_log "[BOND] You must reboot the Bond Home before running gettoken"; + return; + } + $$self{token} = $message->{token}; + delete $self->{devicehash} if $self->{devicehash}; + $self->{devicehash} = $self->getbonddevs( $object, $class); +} + + +sub reloadcache { + my ( $self, $object, $class ) = @_; + + ::print_log "[BOND] Reloading local device cache from Bond"; + delete $self->{devicehash}->{devicename} if $self->{devicehash}->{devicename}; + $self->getbonddevs( $object, $class ); +} + + +sub getbonddevs { + my ( $self, $object, $class ) = @_; + my $maxretry = $$self{maxretry}; + my $token = $$self{token}; + my $response; + unless ( $token ) { + ::print_log "[BOND] (Sub getbonddevs) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + for (0..$maxretry) { + $response = $self->bondcmd( $object, $class, '/v2/devices' ); + } + return unless $response; + my $message = $response->decoded_content; + eval { $message = decode_json($message) }; + + ::print_log("[BOND] reloading MH device cache from bond"); + + foreach my $deviceid (keys %{$message}) { + next if $deviceid =~ /_/; + for (0..$maxretry) { + $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid" ); + last if $response; + } + next unless $response; + my $message = $response->decoded_content; + eval { $message = decode_json($message) }; + my $devicename = $message->{name}; + $self->{devicehash}->{devicename}->{$devicename}->{id}=$deviceid; + #::print_log Dumper $message; + my $response2; + for (0..$maxretry) { + $response2 = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands" ); + last if $response2; + } + next unless $response2; + my $message2 = $response2->decoded_content; + eval { $message2 = decode_json($message2) }; + foreach my $cmdid (keys %{$message2}) { + next if $cmdid =~ /_/; + for (0..$maxretry) { + $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands/$cmdid" ); + last if $response; + } + next unless $response; + my $message = $response->decoded_content; + eval { $message = decode_json($message) }; + $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$message->{name}}->{id}=$cmdid; + #::print_log Dumper $$self{devicehash}; + } + + ::print_log "[BOND] \n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n" if $debug; + } +} + + +sub bondcmd { + my ( $self, $object, $class, $url ) = @_; + use LWP::UserAgent; + use HTTP::Request; + my $ip = $$self{ip}; + my $token = $$self{token}; + my $userAgent = LWP::UserAgent->new(); + $userAgent->timeout(1); + my $request; + if ( ($url =~ /\/tx$/) or ($url =~ /\/reboot$/) ) { + $request = HTTP::Request->new(PUT => 'http://'.$ip.$url); + $request->content('{}'); + } else { + $request = HTTP::Request->new(GET => 'http://'.$ip.$url); + } + + unless ( $url =~ /\/token$/ ) { + $request->header('Host' => "$ip"); + $request->header('BOND-Token' => "$token"); + } + + my $response = $userAgent->request($request); + if ($response->is_error) { + ::print_log("[BOND] http request: http://$ip$url failed - ". $response->status_line); + if ($response->status_line =~ /read timeout/) { + ::print_log("[BOND] retrying request: http://$ip$url"); + } + return 0; + } + return $response; +} + +=back + +=head1 B + +=head2 SYNOPSIS + +User code: + + $BondHome_Device = new BondHome_Device('BondHome'); + + Wherein the format for the definition is: + $BondHome_Device = new BondHome_Device(INSTANCE); + +See C for a more detailed description of the arguments. + + +=head2 DESCRIPTION + + Configures a device from the BondHome to be controlled by MH. + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + +package BondHome_Device; +@BondHome_Device::ISA = ('Generic_Item'); + +=item C + +Instantiates a new object. + +$instance = The instance of the parent BondHome hub object that this device is found on + +$devicename = The name of the device used on the bondhome hub + +=cut + + +sub new { + my ( $class, $instance, $devicename ) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = BondHome::get_object_by_instance($instance); + $$self{parent}->register( $self, $class ); + $$self{devicename} = $devicename; + + $$self{parent}->devexists( $self, $devicename, $class ); + + #@{ $$self{states} } = ('ON','OFF'); + @{ $$self{states} } = $$self{parent}->getdevstates( $self, $class ); + + return $self; +} + + +sub set { + my ( $self, $p_state, $p_setby, $p_response ) = @_; + ::print_log( "[BOND] Unknown request " . $p_state . " for " . $self->get_object_name ); + + if ( $self->validstate( $p_state ) ) { + ::print_log( "[BondHome::Device] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + $$self{parent}->sendcmd( $self, $$self{devicename}, $cmd, $class ) + } + else { + ::print_log( "[BondHome::Device] Received INVALID request " . $p_state . " for " . $self->get_object_name ); + } +} + +sub validstate { + my ( $self, $p_state ) = @_; + + foreach my $state ( @{ $$self{states} } ) { + if ( $state eq $p_state ) { + return 1; + } + } + return 0; +} + + +sub updatestates { + my ( $self, $class ) = @_; + @{ $$self{states} } = $$self{parent}->getdevstates( $self, $class ); +} + +=back + +=head2 INI PARAMETERS + +=head2 NOTES + +=head2 AUTHOR + +Wayne Gatlin + +=head2 SEE ALSO + +=head2 LICENSE + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +=cut + From 604628e5c8449cedbe8d7fefc2609529077bcd08 Mon Sep 17 00:00:00 2001 From: waynieack Date: Sun, 19 Jul 2020 13:01:56 -0500 Subject: [PATCH 2/3] BondHome version 1 --- data/web/collections.json | 1238 +++++++++++++++++++------------------ lib/BondHome.pm | 574 ++++++++++++----- lib/read_table_A.pl | 19 +- 3 files changed, 1072 insertions(+), 759 deletions(-) diff --git a/data/web/collections.json b/data/web/collections.json index e8a21760a..6b0bef1f2 100644 --- a/data/web/collections.json +++ b/data/web/collections.json @@ -1,215 +1,179 @@ { - "113" : { - "link" : "/clock/index.html", - "icon" : "fa-clock-o", - "name" : "LED Clock" - }, - "69" : { - "mode" : "advanced", - "link" : "/ia5/security/webcam.shtml", - "name" : "Windowed Overview", - "icon" : "fa-th-large" + "59" : { + "name" : "Weather.com - Local", + "icon" : "fa-cloud", + "iframe" : "http://www.weather.com/weather/local/91403" }, - "11" : { - "name" : "Events, Calendar, & Clock", + "9" : { + "icon" : "fa-microphone", + "name" : "Speech", "children" : [ - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 113, - 115, - 112, - 114 - ], - "icon" : "fa-calendar" - }, - "29" : { - "icon" : "fa-globe", - "name" : "List Global Variables", - "link" : "/ia7/#path=/vars_global" + 97, + 98, + 100, + 99 + ] }, - "73" : { - "link" : "/ia5/security/backyardcam.shtml", - "icon" : "fa-pagelines", - "name" : "Backyard Camera" + "56" : { + "name" : "Browse Appliances", + "icon" : "fa-sitemap", + "link" : "/ia7/#path=/objects&parents=Appliances" }, - "116" : { - "link" : "/bin/weather_graph_zoom.pl", - "icon" : "fa-search", - "name" : "Weather Zoom" + "114" : { + "icon" : "fa-exclamation-circle", + "mode" : "advanced", + "name" : "Browse Timers", + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Timers" }, - "94" : { - "link" : "/ia5/entertain/internetradio.shtml", - "name" : "Internet Radio", - "icon" : "fa-sitemap" + "91" : { + "link" : "/ia5/entertain/shortcuts.shtml", + "icon" : "fa-bookmark-o", + "name" : "TV Shortcuts" }, - "7" : { - "children" : [ - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85 - ], - "name" : "Phone Calls & VoiceMail Msgs", - "icon" : "fa-phone" + "46" : { + "icon" : "fa-inbox", + "name" : "Postal Mailbox", + "link" : "/ia5/news/postalmail.shtml" }, - "19" : { - "icon" : "fa-gears", - "mode" : "advanced", - "name" : "Browse Widgets", + "8" : { + "icon" : "fa-music", + "name" : "TV/Radio Guide & MP3 Music", "children" : [ - 31, - 32, - 33, - 34, - 35 + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96 ] }, - "40" : { - "link" : "/ia5/statistics/webstats.shtml", - "icon" : "fa-link", - "mode" : "advanced", - "name" : "WebServer Statistics" - }, - "79" : { - "link" : "/bin/phone_search.pl", - "icon" : "fa-search", - "name" : "Search Calls" + "121" : { + "link" : "/ia7/#path=/rrd?now-6hour?wind_speed", + "icon" : "wi-strong-wind", + "name" : "Wind Speed" }, - "92" : { - "external" : "http://www.google.com/search?&q=movie%3A+91403", - "icon" : "fa-film", - "name" : "Local Movies" + "104" : { + "name" : "Browse Entertainment", + "icon" : "fa-gamepad", + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Entertainment" }, - "42" : { - "name" : "Browse This Category", - "icon" : "fa-ellipsis-v", - "mode" : "advanced", - "link" : "/ia7/widgets_checkbox" + "74" : { + "icon" : "fa-desktop", + "name" : "Desktop Camera", + "link" : "/ia5/security/desktopcam.shtml" }, - "41" : { - "name" : "HouseServer Statistics", - "icon" : "fa-home", - "mode" : "advanced", - "link" : "/ia5/statistics/housestats.shtml" + "75" : { + "link" : "/ia7/#path=/floorplan?Sample_Floorplan", + "icon" : "fa-building-o", + "name" : "Floorplan View" }, - "600" : { - "name" : "Status", - "link" : "/ia7/#path=/objects&parents=ia7_status_items" + "80" : { + "icon" : "fa-list", + "name" : "Phone List", + "link" : "/bin/phone_list.pl" }, "89" : { "name" : "TV Guide", - "icon" : "fa-book", - "external" : "http://tvguide.com/Listings/index.asp?I=70620&Zip=91403" + "external" : "http://tvguide.com/Listings/index.asp?I=70620&Zip=91403", + "icon" : "fa-book" }, - "34" : { - "link" : "/ia7/widgets_radiobutton", - "mode" : "advanced", - "icon" : "fa-dot-circle-o", - "name" : "Radiobutton Widgets" + "120" : { + "link" : "/ia7/#path=/rrd?now-6hour?indoor_humid", + "name" : "Indoor Humidity", + "icon" : "wi-humidity" }, - "63" : { - "icon" : "fa-moon-o", - "name" : "Sun & Moon Data", - "link" : "/ia5/outside/sunmoon.shtml" + "62" : { + "link" : "/ia7/#path=/objects&parents=HVAC", + "name" : "HVAC", + "icon" : "fa-dashboard" }, - "109" : { - "icon" : "fa-shopping-cart", - "name" : "Shopping List", - "link" : "/bin/shopping_list.pl" + "77" : { + "name" : "Recent Incoming Calls", + "icon" : "fa-arrow-down", + "link" : "/bin/phone_in.pl" }, - "124" : { - "link" : "/ia7/#path=/rrd?now-6hour?rain_rate", + "119" : { "icon" : "wi-sprinkle", - "name" : "Rain Rate" + "name" : "Outdoor Humidity", + "link" : "/ia7/#path=/rrd?now-6hour?outdoor_humid" }, - "meta" : { - "version" : "1.4" + "700" : { + "user" : "$Authorized" }, - "4" : { - "icon" : "fa-lightbulb-o", - "name" : "Lights & Appliances", - "children" : [ - 51, - 52, - 53, - 54, - 55, - 56, - 57 - ] + "67" : { + "link" : "/ia5/outside/browse.shtml", + "name" : "Browse Category", + "icon" : "fa-archive" }, - "44" : { - "name" : "Read CNN", - "icon" : "fa-book", - "external" : "http://www.cnn.com" + "60" : { + "name" : "Weather.com - National", + "external" : "http://www.weather.com/maps/maptype/currentweatherusnational/index_large.html", + "icon" : "fa-globe" }, - "30" : { - "link" : "/ia7/#path=/vars_save", - "icon" : "fa-save", - "name" : "List Save Variables" + "49" : { + "link" : "/bin/menu.pl", + "mode" : "advanced", + "icon" : "fa-list-alt", + "name" : "Menu Control" }, - "81" : { - "link" : "/ia5/phone/voicemail.shtml", - "icon" : "fa-envelope-o", - "name" : "VoiceMail Messages" + "36" : { + "name" : "View Print Log", + "icon" : "fa-list", + "link" : "/ia7/#path=/print_log" }, - "121" : { - "link" : "/ia7/#path=/rrd?now-6hour?wind_speed", - "name" : "Wind Speed", - "icon" : "wi-strong-wind" + "99" : { + "link" : "/ia5/speak/speechsettings.shtml", + "mode" : "advanced", + "icon" : "fa-cog", + "name" : "Speech Settings" }, - "78" : { - "name" : "Recent Outgoing Calls", - "icon" : "fa-arrow-up", - "link" : "/bin/phone_out.pl" + "45" : { + "name" : "Newsgroups", + "external" : "http://groups.google.com/grphp", + "icon" : "fa-group" }, - "53" : { - "link" : "/ia7/#path=objects&type=X10_Appliance", - "icon" : "fa-sitemap", - "name" : "Control X10 Appliances" + "90" : { + "external" : "http://tvguide.com/tv", + "name" : "What's On Now", + "icon" : "fa-desktop" }, - "28" : { - "icon" : "fa-desktop", - "mode" : "advanced", - "name" : "Setup TV Provider", - "link" : "/bin/set_parm_tv_provider.pl" + "86" : { + "name" : "MP3 Jukebox", + "icon" : "fa-music", + "link" : "/misc/mp3.html" }, - "119" : { - "name" : "Outdoor Humidity", - "icon" : "wi-sprinkle", - "link" : "/ia7/#path=/rrd?now-6hour?outdoor_humid" + "73" : { + "link" : "/ia5/security/backyardcam.shtml", + "name" : "Backyard Camera", + "icon" : "fa-pagelines" }, - "50" : { - "name" : "Browse Modes", - "icon" : "fa-th", - "link" : "/ia5/modes/browse.shtml" + "88" : { + "link" : "/tv", + "icon" : "fa-calendar-o", + "name" : "TV Today" }, - "60" : { - "icon" : "fa-globe", - "name" : "Weather.com - National", - "external" : "http://www.weather.com/maps/maptype/currentweatherusnational/index_large.html" + "83" : { + "name" : "White Pages", + "external" : "http://www.whitepages.com/", + "icon" : "fa-home" }, - "118" : { - "icon" : "wi-thermometer", - "name" : "Indoor Temp", - "link" : "/ia7/#path=/rrd?now-6hour?indoor_temp" + "98" : { + "icon" : "fa-volume-up", + "name" : "Speak Text", + "link" : "/ia7/house/speaktext.shtml" }, - "77" : { - "icon" : "fa-arrow-down", - "name" : "Recent Incoming Calls", - "link" : "/bin/phone_in.pl" + "96" : { + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Music", + "name" : "Browse Music", + "icon" : "fa-headphones" }, "20" : { + "icon" : "fa-wrench", "name" : "Setup MrHouse", "children" : [ 21, @@ -219,197 +183,214 @@ 25, 26, 27, - 28 - ], - "icon" : "fa-wrench" - }, - "114" : { - "icon" : "fa-exclamation-circle", - "name" : "Browse Timers", - "mode" : "advanced", - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Timers" + 28, + "126" + ] }, - "64" : { - "link" : "/ia5/outside/earthquakes.shtml", - "name" : "Earthquakes", - "icon" : "fa-bullseye" + "115" : { + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Timed_Events", + "name" : "Browse Timed_Events", + "icon" : "fa-archive" }, - "104" : { - "name" : "Browse Entertainment", - "icon" : "fa-gamepad", - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Entertainment" - }, - "38" : { - "icon" : "fa-warning", - "name" : "View Error Log", - "link" : "/ia5/statistics/errorlog.shtml" - }, - "46" : { - "link" : "/ia5/news/postalmail.shtml", - "name" : "Postal Mailbox", - "icon" : "fa-inbox" - }, - "112" : { - "name" : "Timers", - "icon" : "fa-exclamation", - "mode" : "advanced", - "link" : "/misc/timers.shtml" - }, - "12" : { - "icon" : "fa-bar-chart-o", + "6" : { "children" : [ - 36, - 37, - 38, - 39, - 40, - 41, - 42 + 68, + 70, + 72, + 73, + 74, + 75, + 76, + 69, + 71 ], - "name" : "Statistics & Logged Data" + "icon" : "fa-video-camera", + "name" : "Security Cameras" }, - "93" : { - "external" : "http://realguide.real.com/", - "name" : "Radio Guide", - "icon" : "fa-signal" + "42" : { + "icon" : "fa-ellipsis-v", + "mode" : "advanced", + "name" : "Browse This Category", + "link" : "/ia7/widgets_checkbox" }, - "17" : { - "link" : "/ia7/#path=objects&type=Group", - "icon" : "fa-group", - "name" : "Browse Groups" + "102" : { + "icon" : "fa-desktop", + "external" : "$config_parms{html_dir}/misc/photos.shtml", + "name" : "Picture Frame" }, "22" : { "name" : "User Code Activation", "icon" : "fa-code", "link" : "/bin/code_unselect.pl" }, - "99" : { - "name" : "Speech Settings", - "mode" : "advanced", - "icon" : "fa-cog", - "link" : "/ia5/speak/speechsettings.shtml" + "47" : { + "link" : "/ia5/news/browse.shtml", + "name" : "Browse News", + "icon" : "fa-list-alt" }, - "3" : { - "name" : "Modes", - "children" : [ - 48, - 50, - 49 - ], - "icon" : "fa-tasks" + "25" : { + "name" : "INI Editor", + "icon" : "fa-table", + "link" : "/bin/iniedit.pl" }, - "85" : { - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Phone", - "name" : "Browse Phone", - "icon" : "fa-archive" + "50" : { + "link" : "/ia5/modes/browse.shtml", + "icon" : "fa-th", + "name" : "Browse Modes" }, - "700" : { - "user" : "$Authorized" + "10" : { + "children" : [ + 101, + 102, + 103, + 104 + ], + "name" : "Comics & Pictures", + "icon" : "fa-picture-o" }, - "82" : { - "external" : "http://www.yellowpages.com/", - "name" : "Yellow Pages", - "icon" : "fa-building-o" + "92" : { + "icon" : "fa-film", + "external" : "http://www.google.com/search?&q=movie%3A+91403", + "name" : "Local Movies" }, - "75" : { - "link" : "/ia7/#path=/floorplan?Sample_Floorplan", - "icon" : "fa-building-o", - "name" : "Floorplan View" + "30" : { + "link" : "/ia7/#path=/vars_save", + "icon" : "fa-save", + "name" : "List Save Variables" }, - "115" : { - "icon" : "fa-archive", - "name" : "Browse Timed_Events", - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Timed_Events" + "meta" : { + "version" : "1.6" }, - "27" : { - "link" : "/bin/headercontrol.pl", - "icon" : "fa-wrench", + "65" : { + "icon" : "fa-fire", "mode" : "advanced", - "name" : "Header Control" + "name" : "Iridium Flares", + "link" : "/ia5/outside/sattelite.shtml" }, - "59" : { - "iframe" : "http://www.weather.com/weather/local/91403", - "icon" : "fa-cloud", - "name" : "Weather.com - Local" + "17" : { + "link" : "/ia7/#path=objects&type=Group", + "icon" : "fa-group", + "name" : "Browse Groups" }, - "102" : { - "icon" : "fa-desktop", - "name" : "Picture Frame", - "external" : "$config_parms{html_dir}/misc/photos.shtml" + "26" : { + "name" : "Program IRMAN", + "mode" : "advanced", + "icon" : "fa-rss", + "link" : "/ia5/house/irman.shtml" }, - "100" : { - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Speech", - "icon" : "fa-microphone", - "name" : "Browse Speech" + "81" : { + "icon" : "fa-envelope-o", + "name" : "VoiceMail Messages", + "link" : "/ia5/phone/voicemail.shtml" }, - "55" : { - "link" : "/ia7/#path=/objects&parents=All_Lights", - "name" : "Browse All Lights", - "icon" : "fa-lightbulb-o" + "71" : { + "link" : "/cameras/", + "icon" : "fa-film", + "mode" : "advanced", + "name" : "Camera Files" }, - "70" : { - "icon" : "fa-clock-o", - "name" : "Time Lapse Viewer", - "link" : "/ia5/security/wc_sshow.shtml" + "35" : { + "link" : "/ia7/widgets_checkbox", + "icon" : "fa-check-square-o", + "name" : "Checkbox Widgets" }, - "500" : { + "3" : { "children" : [ - 700 + 48, + 50, + 49 ], - "name" : "Gear Settings", - "icon" : "fa-home" - }, - "37" : { - "icon" : "fa-bullhorn", - "name" : "View Speech Log", - "link" : "/ia7/#path=/print_speaklog" + "name" : "Modes", + "icon" : "fa-tasks" }, "68" : { + "link" : "/ia5/security/main.shtml", "name" : "Basic Overview", - "icon" : "fa-video-camera", - "link" : "/ia5/security/main.shtml" + "icon" : "fa-video-camera" + }, + "64" : { + "link" : "/ia5/outside/earthquakes.shtml", + "name" : "Earthquakes", + "icon" : "fa-bullseye" }, "72" : { - "icon" : "fa-home", + "link" : "/ia5/security/frontdoor.shtml", "name" : "Frontdoor Camera", - "link" : "/ia5/security/frontdoor.shtml" + "icon" : "fa-home" }, - "26" : { - "mode" : "advanced", - "icon" : "fa-rss", - "name" : "Program IRMAN", - "link" : "/ia5/house/irman.shtml" + "1" : { + "children" : [ + 13, + 15, + 16, + 17, + 18, + 20, + 29, + 30, + 19 + ], + "icon" : "fa-home", + "name" : "Mr. House Home" }, - "16" : { - "icon" : "fa-archive", - "name" : "Browse Categories", - "link" : "/ia7/#path=/objects&type=Category" + "79" : { + "name" : "Search Calls", + "icon" : "fa-search", + "link" : "/bin/phone_search.pl" }, - "83" : { - "name" : "White Pages", - "icon" : "fa-home", - "external" : "http://www.whitepages.com/" + "61" : { + "children" : [ + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125 + ], + "name" : "Weather Station", + "icon" : "fa-bolt" }, - "107" : { - "link" : "/organizer/contacts.pl", - "name" : "Address Book", - "keys" : "true", - "icon" : "fa-book" + "106" : { + "icon" : "fa-calendar", + "name" : "Calendar", + "link" : "/organizer/calendar.pl", + "keys" : "true" }, - "110" : { - "name" : "List Manager", - "icon" : "fa-list-alt", - "link" : "/bin/ListManager.pl" + "101" : { + "name" : "Daily Comics", + "icon" : "fa-picture-o", + "link" : "/comics/index.html" }, - "125" : { - "name" : "Baro Pressure", - "icon" : "wi-cloud-refresh", - "link" : "/ia7/#path=/rrd?now-6hour?outside_press" + "78" : { + "link" : "/bin/phone_out.pl", + "name" : "Recent Outgoing Calls", + "icon" : "fa-arrow-up" }, - "80" : { - "link" : "/bin/phone_list.pl", - "name" : "Phone List", - "icon" : "fa-list" + "116" : { + "name" : "Weather Zoom", + "icon" : "fa-search", + "link" : "/bin/weather_graph_zoom.pl" + }, + "48" : { + "icon" : "fa-tasks", + "name" : "Control Modes & Events", + "link" : "/ia7/house/modes.shtml" + }, + "41" : { + "link" : "/ia5/statistics/housestats.shtml", + "name" : "HouseServer Statistics", + "mode" : "advanced", + "icon" : "fa-home" + }, + "69" : { + "name" : "Windowed Overview", + "mode" : "advanced", + "icon" : "fa-th-large", + "link" : "/ia5/security/webcam.shtml" }, "5" : { "children" : [ @@ -424,381 +405,406 @@ 65, 66 ], - "name" : "HVAC & Weather", - "icon" : "fa-umbrella" + "icon" : "fa-umbrella", + "name" : "HVAC & Weather" }, - "15" : { - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=MisterHouse", - "icon" : "fa-home", - "name" : "Browse MrHouse" - }, - "120" : { - "name" : "Indoor Humidity", - "icon" : "wi-humidity", - "link" : "/ia7/#path=/rrd?now-6hour?indoor_humid" + "28" : { + "link" : "/bin/set_parm_tv_provider.pl", + "name" : "Setup TV Provider", + "icon" : "fa-desktop", + "mode" : "advanced" }, - "91" : { - "name" : "TV Shortcuts", - "icon" : "fa-bookmark-o", - "link" : "/ia5/entertain/shortcuts.shtml" + "29" : { + "name" : "List Global Variables", + "icon" : "fa-globe", + "link" : "/ia7/#path=/vars_global" }, - "13" : { - "link" : "/ia7/house/main.shtml", - "name" : "About MrHouse", - "icon" : "fa-home" + "97" : { + "link" : "/ia7/#path=/print_speaklog", + "name" : "View Speech Log", + "icon" : "fa-bullhorn" }, - "57" : { - "link" : "/ia7/#path=/floorplan?Sample_Floorplan", - "name" : "Floorplan View", - "icon" : "fa-home" + "44" : { + "external" : "http://www.cnn.com", + "name" : "Read CNN", + "icon" : "fa-book" }, - "108" : { - "link" : "/organizer/tasks.pl", - "name" : "TODO List", - "keys" : "true", - "icon" : "fa-list" + "15" : { + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=MisterHouse", + "name" : "Browse MrHouse", + "icon" : "fa-home" }, - "105" : { - "icon" : "fa-check", - "name" : "Calendar Facts", - "link" : "/ia5/calendar/main.shtml" + "113" : { + "icon" : "fa-clock-o", + "name" : "LED Clock", + "link" : "/clock/index.html" }, - "117" : { - "icon" : "fa-leaf", - "name" : "Outdoor Temp", - "link" : "/ia7/#path=/rrd?now-6hour?outdoor_temp" + "63" : { + "link" : "/ia5/outside/sunmoon.shtml", + "name" : "Sun & Moon Data", + "icon" : "fa-moon-o" }, - "98" : { - "icon" : "fa-volume-up", - "name" : "Speak Text", - "link" : "/ia7/house/speaktext.shtml" + "107" : { + "keys" : "true", + "icon" : "fa-book", + "name" : "Address Book", + "link" : "/organizer/contacts.pl" }, - "97" : { - "link" : "/ia7/#path=/print_speaklog", - "name" : "View Speech Log", - "icon" : "fa-bullhorn" + "34" : { + "name" : "Radiobutton Widgets", + "mode" : "advanced", + "icon" : "fa-dot-circle-o", + "link" : "/ia7/widgets_radiobutton" }, - "61" : { - "children" : [ - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125 - ], - "name" : "Weather Station", - "icon" : "fa-bolt" + "125" : { + "name" : "Baro Pressure", + "icon" : "wi-cloud-refresh", + "link" : "/ia7/#path=/rrd?now-6hour?outside_press" }, - "51" : { - "icon" : "fa-group", - "name" : "Browse Groups", - "link" : "/ia7/#path=/objects&type=Group" + "118" : { + "icon" : "wi-thermometer", + "name" : "Indoor Temp", + "link" : "/ia7/#path=/rrd?now-6hour?indoor_temp" }, - "14" : { - "link" : "/ia7/house/aboutaudrey.shtml", - "name" : "About 3Com Audrey", - "icon" : "fa-desktop" + "52" : { + "link" : "/ia7/#path=objects&type=X10_Item", + "icon" : "fa-info", + "name" : "Control X10 Items" }, "31" : { "link" : "/ia7/widgets", "icon" : "fa-cogs", "name" : "All Widgets" }, - "56" : { - "link" : "/ia7/#path=/objects&parents=Appliances", - "icon" : "fa-sitemap", - "name" : "Browse Appliances" - }, - "6" : { - "icon" : "fa-video-camera", - "name" : "Security Cameras", - "children" : [ - 68, - 70, - 72, - 73, - 74, - 75, - 76, - 69, - 71 - ] - }, - "84" : { - "icon" : "fa-sort-alpha-desc", - "name" : "Reverse Lookup", - "external" : "http://www.whitepages.com/find_person.pl?fid=p" - }, - "74" : { - "link" : "/ia5/security/desktopcam.shtml", - "name" : "Desktop Camera", - "icon" : "fa-desktop" - }, - "33" : { - "link" : "/ia7/widgets_entry", - "icon" : "fa-pencil-square-o", - "name" : "Entry Widgets" + "14" : { + "icon" : "fa-desktop", + "name" : "About 3Com Audrey", + "link" : "/ia7/house/aboutaudrey.shtml" }, - "103" : { - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Photos", - "icon" : "fa-archive", - "name" : "Browse Photos" + "70" : { + "icon" : "fa-clock-o", + "name" : "Time Lapse Viewer", + "link" : "/ia5/security/wc_sshow.shtml" }, - "65" : { - "icon" : "fa-fire", - "name" : "Iridium Flares", + "40" : { + "link" : "/ia5/statistics/webstats.shtml", + "name" : "WebServer Statistics", "mode" : "advanced", - "link" : "/ia5/outside/sattelite.shtml" + "icon" : "fa-link" }, - "35" : { - "link" : "/ia7/widgets_checkbox", - "icon" : "fa-check-square-o", - "name" : "Checkbox Widgets" + "117" : { + "link" : "/ia7/#path=/rrd?now-6hour?outdoor_temp", + "name" : "Outdoor Temp", + "icon" : "fa-leaf" }, - "0" : { - "name" : "Home", - "children" : [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12 - ], - "icon" : "fa-home" + "51" : { + "icon" : "fa-group", + "name" : "Browse Groups", + "link" : "/ia7/#path=/objects&type=Group" }, - "86" : { - "icon" : "fa-music", - "name" : "MP3 Jukebox", - "link" : "/misc/mp3.html" + "123" : { + "link" : "/ia7/#path=/rrd?now-6hour?rain", + "icon" : "wi-rain", + "name" : "Total Rain" }, - "43" : { - "link" : "/ia5/news/main.shtml", - "name" : "Read e-mail", - "icon" : "fa-envelope" + "55" : { + "link" : "/ia7/#path=/objects&parents=All_Lights", + "name" : "Browse All Lights", + "icon" : "fa-lightbulb-o" }, - "88" : { - "link" : "/tv", - "icon" : "fa-calendar-o", - "name" : "TV Today" + "126" : { + "link" : "/ia7/#path=/security", + "icon" : "fa-users", + "name" : "Users and Groups" }, - "36" : { - "icon" : "fa-list", - "name" : "View Print Log", - "link" : "/ia7/#path=/print_log" + "112" : { + "name" : "Timers", + "icon" : "fa-exclamation", + "mode" : "advanced", + "link" : "/misc/timers.shtml" }, - "24" : { - "name" : "Edit Items", - "icon" : "fa-list", - "link" : "/bin/items.pl" + "600" : { + "link" : "/ia7/#path=/objects&parents=ia7_status_items", + "name" : "Status" }, - "123" : { - "link" : "/ia7/#path=/rrd?now-6hour?rain", - "name" : "Total Rain", - "icon" : "wi-rain" + "109" : { + "icon" : "fa-shopping-cart", + "name" : "Shopping List", + "link" : "/bin/shopping_list.pl" }, - "48" : { - "name" : "Control Modes & Events", - "icon" : "fa-tasks", - "link" : "/ia7/house/modes.shtml" + "58" : { + "name" : "Weather Underground", + "icon" : "fa-sun-o", + "iframe" : "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=91403" }, "87" : { "icon" : "fa-play-circle", "name" : "Media Center", "link" : "/media/mhmedia.html" }, - "9" : { - "name" : "Speech", - "children" : [ - 97, - 98, - 100, - 99 - ], - "icon" : "fa-microphone" + "18" : { + "link" : "/ia7/#path=/objects&type=Type", + "icon" : "fa-info", + "name" : "Browse Items" }, - "66" : { - "name" : "GPS/APRS Tracking", + "76" : { + "link" : "/ia5/security/floorplan.shtml", + "name" : "Floorplan View2", "mode" : "advanced", - "icon" : "fa-road", - "link" : "/ia5/outside/tracking.shtml" + "icon" : "fa-building-o" }, "95" : { "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Entertainment", "icon" : "fa-gamepad", "name" : "Browse Entertainment" }, - "106" : { - "link" : "/organizer/calendar.pl", - "keys" : "true", - "name" : "Calendar", - "icon" : "fa-calendar" + "21" : { + "link" : "/bin/code_select.pl", + "name" : "Common Code Activation", + "icon" : "fa-code" }, - "58" : { - "name" : "Weather Underground", - "iframe" : "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=91403", - "icon" : "fa-sun-o" + "124" : { + "icon" : "wi-sprinkle", + "name" : "Rain Rate", + "link" : "/ia7/#path=/rrd?now-6hour?rain_rate" }, - "67" : { - "link" : "/ia5/outside/browse.shtml", - "icon" : "fa-archive", - "name" : "Browse Category" + "27" : { + "link" : "/bin/headercontrol.pl", + "name" : "Header Control", + "mode" : "advanced", + "icon" : "fa-wrench" }, - "49" : { + "23" : { + "icon" : "fa-clock-o", + "name" : "Edit Triggers", + "link" : "/ia7/#path=triggers" + }, + "94" : { + "link" : "/ia5/entertain/internetradio.shtml", + "icon" : "fa-sitemap", + "name" : "Internet Radio" + }, + "38" : { + "link" : "/ia5/statistics/errorlog.shtml", + "name" : "View Error Log", + "icon" : "fa-warning" + }, + "110" : { + "link" : "/bin/ListManager.pl", "icon" : "fa-list-alt", + "name" : "List Manager" + }, + "19" : { + "children" : [ + 31, + 32, + 33, + 34, + 35 + ], "mode" : "advanced", - "name" : "Menu Control", - "link" : "/bin/menu.pl" + "icon" : "fa-gears", + "name" : "Browse Widgets" }, - "71" : { - "link" : "/cameras/", - "name" : "Camera Files", + "82" : { + "icon" : "fa-building-o", + "external" : "http://www.yellowpages.com/", + "name" : "Yellow Pages" + }, + "66" : { + "icon" : "fa-road", "mode" : "advanced", - "icon" : "fa-film" + "name" : "GPS/APRS Tracking", + "link" : "/ia5/outside/tracking.shtml" }, - "18" : { - "icon" : "fa-info", - "name" : "Browse Items", - "link" : "/ia7/#path=/objects&type=Type" + "100" : { + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Speech", + "name" : "Browse Speech", + "icon" : "fa-microphone" }, - "8" : { - "icon" : "fa-music", - "children" : [ - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96 - ], - "name" : "TV/Radio Guide & MP3 Music" + "16" : { + "name" : "Browse Categories", + "icon" : "fa-archive", + "link" : "/ia7/#path=/objects&type=Category" }, - "23" : { - "icon" : "fa-clock-o", - "name" : "Edit Triggers", - "link" : "/bin/triggers.pl" + "105" : { + "link" : "/ia5/calendar/main.shtml", + "icon" : "fa-check", + "name" : "Calendar Facts" }, - "47" : { - "link" : "/ia5/news/browse.shtml", - "icon" : "fa-list-alt", - "name" : "Browse News" + "85" : { + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Phone", + "icon" : "fa-archive", + "name" : "Browse Phone" + }, + "39" : { + "name" : "View Backup Log", + "icon" : "fa-floppy-o", + "link" : "/ia5/statistics/backuplog.shtml" + }, + "57" : { + "icon" : "fa-home", + "name" : "Floorplan View", + "link" : "/ia7/#path=/floorplan?Sample_Floorplan" }, "32" : { "link" : "/ia7/widgets_label", "name" : "Label Widgets", "icon" : "fa-square-o" }, - "1" : { - "icon" : "fa-home", + "93" : { + "icon" : "fa-signal", + "external" : "http://realguide.real.com/", + "name" : "Radio Guide" + }, + "12" : { "children" : [ - 13, - 15, - 16, - 17, - 18, - 20, - 29, - 30, - 19 + 36, + 37, + 38, + 39, + 40, + 41, + 42 ], - "name" : "Mr. House Home" + "name" : "Statistics & Logged Data", + "icon" : "fa-bar-chart-o" }, - "52" : { - "icon" : "fa-info", - "name" : "Control X10 Items", - "link" : "/ia7/#path=objects&type=X10_Item" + "122" : { + "link" : "/ia7/#path=/rrd?now-6hour?wind_dir", + "icon" : "fa-arrows", + "name" : "Wind Direction" }, - "90" : { - "external" : "http://tvguide.com/tv", - "name" : "What's On Now", - "icon" : "fa-desktop" + "33" : { + "name" : "Entry Widgets", + "icon" : "fa-pencil-square-o", + "link" : "/ia7/widgets_entry" }, - "96" : { - "name" : "Browse Music", - "icon" : "fa-headphones", - "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Music" + "108" : { + "name" : "TODO List", + "icon" : "fa-list", + "link" : "/organizer/tasks.pl", + "keys" : "true" }, - "25" : { - "icon" : "fa-table", - "name" : "INI Editor", - "link" : "/bin/iniedit.pl" + "24" : { + "link" : "/bin/items.pl", + "name" : "Edit Items", + "icon" : "fa-list" }, - "45" : { - "external" : "http://groups.google.com/grphp", - "icon" : "fa-group", - "name" : "Newsgroups" + "53" : { + "link" : "/ia7/#path=objects&type=X10_Appliance", + "icon" : "fa-sitemap", + "name" : "Control X10 Appliances" }, "111" : { - "link" : "/bin/triggers.pl", + "link" : "/ia7/#path=triggers", "name" : "Alarms", "icon" : "fa-bell-o" }, - "2" : { - "icon" : "fa-envelope", + "4" : { "children" : [ - 43, - 44, - 45, - 46, - 47 + 51, + 52, + 53, + 54, + 55, + 56, + 57 ], - "name" : "Mail and News" - }, - "62" : { - "link" : "/ia7/#path=/objects&parents=HVAC", - "name" : "HVAC", - "icon" : "fa-dashboard" + "name" : "Lights & Appliances", + "icon" : "fa-lightbulb-o" }, - "21" : { - "icon" : "fa-code", - "name" : "Common Code Activation", - "link" : "/bin/code_select.pl" + "11" : { + "children" : [ + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 113, + 115, + 112, + 114 + ], + "name" : "Events, Calendar, & Clock", + "icon" : "fa-calendar" }, - "122" : { - "link" : "/ia7/#path=/rrd?now-6hour?wind_dir", - "icon" : "fa-arrows", - "name" : "Wind Direction" + "103" : { + "name" : "Browse Photos", + "icon" : "fa-archive", + "link" : "/ia7/#path=/objects&type=Voice_Cmd&category=Photos" }, - "76" : { - "link" : "/ia5/security/floorplan.shtml", - "mode" : "advanced", - "name" : "Floorplan View2", - "icon" : "fa-building-o" + "13" : { + "link" : "/ia7/house/main.shtml", + "name" : "About MrHouse", + "icon" : "fa-home" }, - "101" : { - "name" : "Daily Comics", - "icon" : "fa-picture-o", - "link" : "/comics/index.html" + "0" : { + "children" : [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12 + ], + "icon" : "fa-home", + "name" : "Home" }, - "10" : { - "icon" : "fa-picture-o", + "7" : { "children" : [ - 101, - 102, - 103, - 104 + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85 ], - "name" : "Comics & Pictures" + "icon" : "fa-phone", + "name" : "Phone Calls & VoiceMail Msgs" }, - "39" : { - "link" : "/ia5/statistics/backuplog.shtml", - "name" : "View Backup Log", - "icon" : "fa-floppy-o" + "37" : { + "link" : "/ia7/#path=/print_speaklog", + "icon" : "fa-bullhorn", + "name" : "View Speech Log" + }, + "84" : { + "icon" : "fa-sort-alpha-desc", + "external" : "http://www.whitepages.com/find_person.pl?fid=p", + "name" : "Reverse Lookup" + }, + "43" : { + "link" : "/ia5/news/main.shtml", + "name" : "Read e-mail", + "icon" : "fa-envelope" + }, + "2" : { + "name" : "Mail and News", + "icon" : "fa-envelope", + "children" : [ + 43, + 44, + 45, + 46, + 47 + ] + }, + "500" : { + "icon" : "fa-home", + "name" : "Gear Settings", + "children" : [ + 700 + ] } } diff --git a/lib/BondHome.pm b/lib/BondHome.pm index 671bcaf97..036d116be 100644 --- a/lib/BondHome.pm +++ b/lib/BondHome.pm @@ -8,8 +8,13 @@ Module for interfacing with the BondHome Hub to control devices configured in it =head2 CONFIGURATION -At minimum, you must define the Interface and one BondHome_Device object. -This allows for the display and control of these objects as separate items +At minimum, you must define the BondHome_ip in the mh.private.ini and the Interface +object. Once they are configured you can restart MH, then restart the Bond Hub once +MH is back up, next set the Bond Hub interface object to "GetToken" within a few min of the +Bond Hub reboot. Once the token is successfuly retreved, set the Bond Hub interface object to "LogDevs" +and copy the device code from the MH logs and paste into a MH .mht file. You will need all the devices +preconfigured in the BondHome Hub because MH pulls them including the names from it. +The BondHome_Device objects allow for the display and control of these objects as separate items in the MH interface and allows users to interact directly with these objects using the basic Generic_Item functions such as tie_event. @@ -19,7 +24,7 @@ devices configured in the bondhome hub. Misterhouse loads all devices and device commands from BondHome when it is started. You must reload the BondHome device and trigger and retrieve an auth token by setting the -parent object to "GETTOKEN" with in 1 min after the reboot. +parent object to "GetToken" with in 1 min after the reboot. =head2 Interface Configuration @@ -34,7 +39,7 @@ The IP of the BondHome Hub: BondHome_ip=192.168.1.50 -Max command retry: +Max command retrys when a command fails to send to the BondHome Hub: BondHome_maxretry=4 @@ -45,21 +50,14 @@ object. The object can be defined in the user code. In user code: - $BondHome = new BondHome('BondHome'); + $BondHomeHub = new BondHome('BondHome'); Wherein the format for the definition is: - $BondHome = new BondHome(INSTANCE); - -=head2 Device Object - - $BondHome_Device = new BondHome_Device('BondHome'); - -Wherein the format for the definition is: - $BondHome_Device = new BondHome_Device(INSTANCE); + $BondHomeHub = new BondHome(INSTANCE); States: -Dynamic from the bond home +GetToken,Reboot,LogDevs,ReloadCache @@ -75,7 +73,7 @@ An example user code: #noloop=start use BondHome; - $BondHome = new BondHome('BondHome'); + $BondHomeHub = new BondHome('BondHome'); $MasterFan = new BondHome_Device('BondHome','MasterFan'); #noloop=stop @@ -94,7 +92,7 @@ package BondHome; @BondHome::ISA = ('Generic_Item'); use Data::Dumper; -use JSON qw(decode_json); +use JSON; sub new { my ( $class, $instance ) = @_; @@ -109,14 +107,15 @@ sub new { $$self{ip} = $::config_parms{ $instance . '_ip' }; my $year_mon = &::time_date_stamp( 10, time ); $$self{log_file} = $::config_parms{'data_dir'} . "/logs/BondHome.$year_mon.log"; + $$self{token_file} = $::config_parms{'data_dir'} . "/.bh-$instance"; bless $self, $class; #Store Object with Instance Name $self->_set_object_instance($instance); - $self->restore_data( 'token' ); - $$self{token} = '4b8d109022195f1b'; - @{$$self{states}} = ('gettoken','reboot','logdevs','reloadcache'); + #$self->restore_data( 'token' ); + $$self{token} = $self->get_data($$self{token_file}); #The normal restore_data happens after new is called, so we have to save to a file. + @{$$self{states}} = ('GetToken','Reboot','LogDevs','ReloadCache', 'LogVersion', 'ScanRF', 'ScanIR', 'ScanStop', 'ScanCheck'); return $self; } @@ -143,68 +142,88 @@ Used to associate child objects with the interface. sub register { my ( $self, $object, $class ) = @_; if ( $object->isa('BondHome_Device') ) { - ::print_log("Registering Child Object for BondHome Device"); - push @{ $self->{device_object} }, $object; + ::print_log("Registering BondHome Device Child Object: ".$object->get_object_name." for interface: ".$self->get_object_name ); + push @{ $self->{device_object} }, $object; + } elsif ( $object->isa('BondHome_Manual') ) { + ::print_log("Registering BondHome Manual Child Object: ".$object->get_object_name." for interface: ".$self->get_object_name ); + push @{ $self->{manual_object} }, $object; } } - + sub set { my ( $self, $p_state, $p_setby, $p_response ) = @_; - ::print_log( "[BOND] Unknown request " . $p_state . " for " . $self->get_object_name ); - if ( $p_state eq 'GETTOKEN' ) { - ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + if ( uc $p_state eq 'GETTOKEN' ) { + ::print_log( "[BondHome] Received request " . $p_state . " for " . $self->get_object_name ); $self->SUPER::set( $p_state, $p_setby ); $self->gettoken( $object, $class ); - } - elsif ( $p_state eq 'REBOOT' ) { - ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + } elsif ( uc $p_state eq 'REBOOT' ) { + ::print_log( "[BondHome] Received request " . $p_state . " for " . $self->get_object_name ); $self->SUPER::set( $p_state, $p_setby ); - $self->reboot( $object, $class ); - } - elsif ( $p_state eq 'LOGDEVS' ) { - ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + return unless $self->tokencheck; + $self->bondcmd( $class, '/v2/sys/reboot', 'PUT', '{}' ); + } elsif ( uc $p_state eq 'LOGDEVS' ) { + ::print_log( "[BondHome] Received request " . $p_state . " for " . $self->get_object_name ); $self->SUPER::set( $p_state, $p_setby ); - $self->logdevs( $object, $class ); - } - elsif ( $p_state eq 'RELOADCACHE' ) { - ::print_log( "[BOND] Received request " . $p_state . " for " . $self->get_object_name ); + $self->logdevs( $object, $class ); + } elsif ( uc $p_state eq 'LOGVERSION' ) { + ::print_log( "[BondHome] Received request " . $p_state . " for " . $self->get_object_name ); $self->SUPER::set( $p_state, $p_setby ); - $self->reloadcache( $object, $class ); - } - else { - ::print_log( "[BondHome] Unknown request " . $p_state . " for " . $self->get_object_name ); + return unless $self->tokencheck; + ::print_log "[BondHome] version ".$self->bondcmd( $class, '/v2/sys/version', 'GET')->{_content}; + } elsif ( uc $p_state eq 'RELOADCACHE' ) { + ::print_log( "[BondHome] Received request " . $p_state . " for " . $self->get_object_name ); + $self->SUPER::set( $p_state, $p_setby ); + return unless $self->tokencheck; + ::print_log "[BondHome] Reloading local device cache from Bond"; + delete $self->{devicehash}->{devicename} if $self->{devicehash}->{devicename}; + $self->getbonddevs( $object, $class ); + foreach my $child ( @{ $self->{device_object} } ) { + $child->updatestates($class); + } + } elsif ( uc $p_state eq 'SCANRF' ) { + $self->scan('RF',$class ); + } elsif ( uc $p_state eq 'SCANIR' ) { + $self->scan('IR',$class ); + } elsif ( uc $p_state eq 'SCANSTOP' ) { + $self->scan('STOP',$class ); + } elsif ( uc $p_state eq 'SCANCHECK' ) { + $self->scan('CHECK',$class ); + } else { + ::print_log( "[BondHome] Unknown request " . $p_state . " for " . $self->get_object_name ); } } - +sub tokencheck { + my ( $self ) = @_; + unless ( $$self{token} ) { + ::print_log "[BondHome] You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; + return; + } + return 1; +} sub devexists { - my ( $self, $object, $devicename, $class ) = @_; - my $ip = $$self{ip}; + my ( $self, $object, $class ) = @_; my $token = $$self{token}; + my $devicename = $$object{devicename}; - unless ( $token ) { - ::print_log ("[BOND] (Sub devexists) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"); - return; - } + return unless $self->tokencheck; unless ( $self->{devicehash}->{devicename} ) { - ::print_log ("[BOND] There are no devices in cache, running reload cache ". $self->get_object_name ); - $self->reloadcache( $object, $class ); + ::print_log ("[BondHome] There are no devices in cache, running reload cache ". $self->get_object_name ); + $self->getbonddevs( $object, $class ); - #::print_log Dumper $self; - unless ( $self->{devicehash}->{devicename} ) { - ::print_log ("[BOND] (Sub devexists) There are no devices in cache after reloading the cache, something went wrong"); + ::print_log ("[BondHome] (Sub devexists) There are no devices in cache after reloading the cache, something went wrong"); return; } } return 1 if ( exists $self->{devicehash}->{devicename}->{$devicename} ); - ::print_log ( "[BOND] there is no device with the name \"$devicename\" configured on the BondHome Hub " . $self->get_object_name ); + ::print_log ( "[BondHome] there is no device with the name \"$devicename\" configured on the BondHome Hub " . $self->get_object_name ); return; } @@ -214,108 +233,154 @@ sub getdevstates { my $token = $$self{token}; my $devicename = $$object{devicename}; - unless ( $token ) { - ::print_log "[BOND] (Sub getdevstates) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; - return; - } + return unless $self->tokencheck; unless ( $self->{devicehash}->{devicename} ) { - ::print_log "[BOND] There are no devices in cache, running reload cache"; - $self->reloadcache( $object, $class ); + ::print_log "[BondHome] There are no devices in cache, running reload cache"; + $self->getbonddevs( $object, $class ); unless ( $self->{devicehash}->{devicename} ) { - ::print_log "[BOND] (Sub getdevstates) There are no devices in cache after reloading the cache, something went wrong"; + ::print_log "[BondHome] (Sub getdevstates) There are no devices in cache after reloading the cache, something went wrong"; return; } } my @states; + my @speeds; foreach my $command (keys %{$self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}} ) { - push @states, cleanstring($command); + push @states, ucfirst($command); + if ( normalize($command) =~ /speed(\d)/ ) { + push @speeds, $1; + } } + @speeds = sort @speeds; + push @states, @speeds; return @states; } -sub sendcmd { - my ( $self, $object, $devicename, $cmd, $class ) = @_; - - unless ( $$self{token} ) { - ::print_log "[BOND] (Sub sendcmd) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; - return; - } +sub normalize { + my ( $string ) = @_; + $string = lc $string; + $string =~ s/ //g; + return $string; +} - my $maxretry = $$self{maxretry}; - $devicename = cleanstring($devicename); - $cmd = cleanstring($cmd); - - if ( $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmd}->{id} ) { - my $deviceid = $self->{devicehash}->{devicename}->{$devicename}->{id}; - my $cmdid = $self->{devicehash}->{devicename}->{device}->{commands}->{name}->{$cmd}->{id}; - for (0..$maxretry) { - my $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands/$cmdid/tx" ); - last if $response; - } - } else { - ::print_log "[BOND] Invalid command: $cmd for " . $self->get_object_name; +sub get_data { + my ( $self, $file ) = @_; + if( -e $file ) { + open my $FH, '<', $file or ::print_log "[BondHome] failed to open token file $!"; + while (my $line = <$FH>) { + $line =~ s/^\s+//; + $line =~ s/\s+$//; + if ( length($line) > 1 ) { + close $FH; + return $line; + } + } + close $FH; } - } -sub cleanstring { - my ( $string ) = @_; - $string = uc $string; - $string =~ s/ //g; - return $string; + +sub save_data { + my ( $self, $file, $data ) = @_; + ::print_log "[BondHome] saving token to $file"; + open my $FH, '>', $file or ::print_log "[BondHome] failed to save token to file $!"; + print $FH $data; + close($FH); } -sub reboot { - my ( $self, $object, $class ) = @_; +sub scan { + my ( $self, $type, $class ) = @_; my $token = $$self{token}; - unless ( $token ) { - ::print_log "[BOND] (Sub reboot) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; - return; + my $instance = $$self{instance}; + my $http; + my $content; + my $url = '/v2/signal/scan'; + + if ($type eq 'IR') { + $http = 'PUT'; + $content = '{ "freq": 38, "modulation": "OOK" }'; + } elsif ($type eq 'RF') { + $http = 'PUT'; + $content = '{ "modulation": "OOK" }'; #RF all frequencies + } elsif ($type eq 'STOP') { + $http = 'DELETE'; + } elsif ($type eq 'CHECK') { + $http = 'GET'; + } + + return unless $self->tokencheck; + + my $response = $self->bondcmd( $class, $url, $http, $content ); + + #BONDHOME_MANUAL, masterfan, BondHome + #BONDHOME_MANUAL_CMD, masterfan, power, 434000, OOK, cq, 1000, 12, 110100110110H + + + if ($type eq 'CHECK') { + return unless $response; + my $message = $response->decoded_content; + eval { $message = decode_json($message) }; + if ( $message->{success} ) { + my $response2 = $self->bondcmd( $class, $url.'/signal', 'GET' ); + return unless $response2; + my $message2 = $response2->decoded_content; + eval { $message2 = decode_json($message2) }; + + ::print_log "[BondHome] Listing mht code for manual device command"; + my $msg = "\nBONDHOME_MANUAL, dev_name_update_me, $instance"; + $msg .= "\nBONDHOME_MANUAL_CMD, dev_name_update_me, cmd_name_update_me, ".$message2->{freq}.", ".$message2->{modulation}.", ".$message2->{encoding}.", ".$message2->{bps}.", ".$message2->{reps}.", ".$message2->{data}; + ::print_log $msg; + } elsif ( $message->{running} ) { + ::print_log "[BondHome] Scan is still running and no signals have been seen"; + } else { + ::print_log "[BondHome] Scan has timed out and no signals have been seen"; + } } - $self->bondcmd( $object, $class, '/v2/sys/reboot' ); } + sub logdevs { my ( $self, $object, $class ) = @_; my $token = $$self{token}; + my $instance = $$self{instance}; + my $name = $self->get_object_name; + $name =~ s/\$//; - unless ( $token ) { - ::print_log "[BOND] (Sub logdevs) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; - return; - } + return unless $self->tokencheck; unless ( $self->{devicehash}->{devicename} ) { - ::print_log "[BOND] There are no devices in cache, running reload cache"; - $self->reloadcache( $self, $object, $class ); + ::print_log "[BondHome] There are no devices in cache, running reload cache"; + $self->getbonddevs( $object, $class ); } + ::print_log "[BondHome] Listing mht code for Bond devices"; + my $message = "\nBONDHOME, $name, $instance"; foreach my $devicename (keys %{$self->{devicehash}->{devicename}}) { - ::print_log "[BOND] Device: $device"; - ::print_log "[BOND] ---- Commands:"; - foreach my $command (keys %{$self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}} ) { - ::print_log "[BOND] ---- $command"; - } + $message .= "\nBONDHOME_DEVICE, $devicename, $instance, $devicename"; } + ::print_log $message; } sub gettoken { my ( $self, $object, $class ) = @_; - ::print_log "[BOND] Getting Token"; - my $response = $self->bondcmd( $object, $class, '/v2/token' ); + ::print_log "[BondHome] Getting Token"; + my $response = $self->bondcmd( $class, '/v2/token', 'GET' ); my $message = $response->decoded_content; eval { $message = decode_json($message) }; if ( $message->{locked} ) { - ::print_log "[BOND] You must reboot the Bond Home before running gettoken"; + ::print_log "[BondHome] You must reboot the Bond Home before running gettoken"; return; } $$self{token} = $message->{token}; + my $token = $message->{token}; + $self->save_data( $$self{token_file}, $token ); + ::print_log "[BondHome] Got Token: $$self{token}" if $$self{token}; delete $self->{devicehash} if $self->{devicehash}; $self->{devicehash} = $self->getbonddevs( $object, $class); } @@ -324,45 +389,47 @@ sub gettoken { sub reloadcache { my ( $self, $object, $class ) = @_; - ::print_log "[BOND] Reloading local device cache from Bond"; + ::print_log "[BondHome] Reloading local device cache from Bond"; delete $self->{devicehash}->{devicename} if $self->{devicehash}->{devicename}; $self->getbonddevs( $object, $class ); } sub getbonddevs { - my ( $self, $object, $class ) = @_; + my ( $self, $class ) = @_; my $maxretry = $$self{maxretry}; my $token = $$self{token}; my $response; - unless ( $token ) { - ::print_log "[BOND] (Sub getbonddevs) You must reboot the Bond Home and set the parent BondHome object to gettoken to get/set the token"; - return; - } + return unless $self->tokencheck; for (0..$maxretry) { - $response = $self->bondcmd( $object, $class, '/v2/devices' ); + $response = $self->bondcmd( $class, '/v2/devices', 'GET' ); } return unless $response; my $message = $response->decoded_content; eval { $message = decode_json($message) }; - ::print_log("[BOND] reloading MH device cache from bond"); + ::print_log("[BondHome] reloading MH device cache from bond"); foreach my $deviceid (keys %{$message}) { next if $deviceid =~ /_/; for (0..$maxretry) { - $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid" ); + $response = $self->bondcmd( $class, "/v2/devices/$deviceid", 'GET' ); last if $response; } next unless $response; my $message = $response->decoded_content; eval { $message = decode_json($message) }; - my $devicename = $message->{name}; + my $devicename = normalize($message->{name}); $self->{devicehash}->{devicename}->{$devicename}->{id}=$deviceid; + + foreach my $action ( @{$message->{actions}} ) { + next if ( $action =~ /^Set/ ); #Skip the set actions because the require arguments. + $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{normalize($action)}->{action}=$action; + } #::print_log Dumper $message; my $response2; for (0..$maxretry) { - $response2 = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands" ); + $response2 = $self->bondcmd( $class, "/v2/devices/$deviceid/commands", 'GET' ); last if $response2; } next unless $response2; @@ -371,23 +438,60 @@ sub getbonddevs { foreach my $cmdid (keys %{$message2}) { next if $cmdid =~ /_/; for (0..$maxretry) { - $response = $self->bondcmd( $object, $class, "/v2/devices/$deviceid/commands/$cmdid" ); + $response = $self->bondcmd( $class, "/v2/devices/$deviceid/commands/$cmdid", 'GET' ); last if $response; } next unless $response; my $message = $response->decoded_content; eval { $message = decode_json($message) }; - $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$message->{name}}->{id}=$cmdid; + my $cmdname = normalize($message->{name}); + $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmdname}->{id}=$cmdid; #::print_log Dumper $$self{devicehash}; } - ::print_log "[BOND] \n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n" if $debug; + ::print_log "[BondHome] \n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n" if $debug; + } +} + + + +sub sendcmd { + my ( $self, $object, $cmdname, $class, $argument ) = @_; + + return unless $self->tokencheck; + + my $maxretry = $$self{maxretry}; + my $devicename = $$object{devicename}; + $cmdname = normalize($cmdname); + + if ( exists $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmdname}->{id} ) { + my $deviceid = $self->{devicehash}->{devicename}->{$devicename}->{id}; + my $cmdid = $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmdname}->{id}; + for (0..$maxretry) { + my $response = $self->bondcmd( $class, "/v2/devices/$deviceid/commands/$cmdid/tx", 'PUT', '{}' ); + last if $response; + } + } elsif ( exists $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmdname}->{action} ) { + my $deviceid = $self->{devicehash}->{devicename}->{$devicename}->{id}; + my $action = $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{$cmdname}->{action}; + if ( $argument ) { + $argument = '{"argument": '.$argument.'}'; + } else { + $argument = '{}'; + } + for (0..$maxretry) { + my $response = $self->bondcmd( $class, "/v2/devices/$deviceid/actions/$action", 'PUT', $argument ); + last if $response; + } + } else { + ::print_log "[BondHome] Invalid command: $cmdname for " . $self->get_object_name; } + } sub bondcmd { - my ( $self, $object, $class, $url ) = @_; + my ( $self, $class, $url, $function, $content ) = @_; use LWP::UserAgent; use HTTP::Request; my $ip = $$self{ip}; @@ -395,10 +499,16 @@ sub bondcmd { my $userAgent = LWP::UserAgent->new(); $userAgent->timeout(1); my $request; - if ( ($url =~ /\/tx$/) or ($url =~ /\/reboot$/) ) { + + if ( $function eq 'PUT' ) { $request = HTTP::Request->new(PUT => 'http://'.$ip.$url); - $request->content('{}'); - } else { + $request->content($content); + } elsif ( $function eq 'POST' ) { + $request = HTTP::Request->new(POST => 'http://'.$ip.$url); + $request->content($content); + } elsif ( $function eq 'DELETE' ) { + $request = HTTP::Request->new(DELETE => 'http://'.$ip.$url); + } elsif ( $function eq 'GET' ) { $request = HTTP::Request->new(GET => 'http://'.$ip.$url); } @@ -407,11 +517,12 @@ sub bondcmd { $request->header('BOND-Token' => "$token"); } + #::print_log("[BondHome] request: ".Dumper $request); my $response = $userAgent->request($request); if ($response->is_error) { - ::print_log("[BOND] http request: http://$ip$url failed - ". $response->status_line); + ::print_log("[BondHome] http request: http://$ip$url failed - ". $response->status_line ." ". $response ->decoded_content); if ($response->status_line =~ /read timeout/) { - ::print_log("[BOND] retrying request: http://$ip$url"); + ::print_log("[BondHome] retrying request: http://$ip$url"); } return 0; } @@ -426,10 +537,13 @@ sub bondcmd { User code: - $BondHome_Device = new BondHome_Device('BondHome'); + $LivingRoomFan = new BondHome_Device('BondHome','livingroomfan'); Wherein the format for the definition is: - $BondHome_Device = new BondHome_Device(INSTANCE); + $BondHome_Device = new BondHome_Device(INSTANCE,BondHomeDeviceName); + +States: +Dynamic from BondHome Hub See C for a more detailed description of the arguments. @@ -463,30 +577,32 @@ $devicename = The name of the device used on the bondhome hub sub new { - my ( $class, $instance, $devicename ) = @_; - my $self = new Generic_Item(); - bless $self, $class; - $$self{parent} = BondHome::get_object_by_instance($instance); - $$self{parent}->register( $self, $class ); - $$self{devicename} = $devicename; + my ( $class, $instance, $devicename ) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = BondHome::get_object_by_instance($instance); + $$self{parent}->register( $self, $class ); + $$self{devicename} = normalize($devicename); - $$self{parent}->devexists( $self, $devicename, $class ); + $$self{parent}->devexists( $self, $class ); - #@{ $$self{states} } = ('ON','OFF'); @{ $$self{states} } = $$self{parent}->getdevstates( $self, $class ); - return $self; + return $self; } sub set { my ( $self, $p_state, $p_setby, $p_response ) = @_; - ::print_log( "[BOND] Unknown request " . $p_state . " for " . $self->get_object_name ); + $p_state = normalize($p_state); + if ( $p_state =~ /^\d+$/ ) { + $p_state = "speed$p_state"; + } if ( $self->validstate( $p_state ) ) { ::print_log( "[BondHome::Device] Received request " . $p_state . " for " . $self->get_object_name ); $self->SUPER::set( $p_state, $p_setby ); - $$self{parent}->sendcmd( $self, $$self{devicename}, $cmd, $class ) + $$self{parent}->sendcmd( $self, normalize($p_state), $class ); } else { ::print_log( "[BondHome::Device] Received INVALID request " . $p_state . " for " . $self->get_object_name ); @@ -497,19 +613,193 @@ sub validstate { my ( $self, $p_state ) = @_; foreach my $state ( @{ $$self{states} } ) { - if ( $state eq $p_state ) { - return 1; + if ( normalize($state) eq $p_state ) { + return $p_state; } + } return 0; } +sub getcmd { + my ( $self, $cmd ) = @_; + + foreach my $state ( @{ $$self{states} } ) { + if ( normalize($state) =~ /$cmd/ ) { + return normalize($state); + } + } + return 0; +} + sub updatestates { my ( $self, $class ) = @_; @{ $$self{states} } = $$self{parent}->getdevstates( $self, $class ); } + +sub normalize { + my ( $string ) = @_; + $string = lc $string; + $string =~ s/ //g; + return $string; +} + + +=back + +=head1 B + +=head2 SYNOPSIS + +User code: + + $LivingRoomFan = new BondHome_Manual('BondHome'); + + Wherein the format for the definition is: + $BondHomeManualObject = new BondHome_Manual(INSTANCE); + + Add discovered commands with: + $LivingRoomFan->addcmd('power', '434000', 'OOK', 'cq', '1000', '12', '110100110110H'); + + Wherein the format for the definition is: + $BondHomeManualObject->addcmd(CommandName, Frequency, Modulation, Encoding, Bps, Reps, Data); + +mht file code: + + BONDHOME_MANUAL, LivingRoomFan, BondHome + + Wherein the format for the definition is: + BONDHOME_MANUAL, ObjectName, INSTANCE + + Add discovered commands with: + BONDHOME_MANUAL_CMD, LivingRoomFan, power, 434000, OOK, cq, 1000, 12, 110100110110H + + Wherein the format for the definition is: + BONDHOME_MANUAL_CMD, BondHomeManualObject, CommandName, Frequency, Modulation, Encoding, Bps, Reps, Data + +States: +Created from the BondHome addcmd sub routine + + +See C for a more detailed description of the arguments. + + +=head2 DESCRIPTION + + Configures a device from the BondHome to be controlled by MH. + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + +package BondHome_Manual; +@BondHome_Manual::ISA = ('Generic_Item'); + +=item C + +Instantiates a new object. + +$instance = The instance of the parent BondHome hub object that this device is found on + + +=cut + +use Data::Dumper; +use JSON; + +sub new { + my ( $class, $instance ) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = BondHome::get_object_by_instance($instance); + $$self{parent}->register( $self, $class ); + @{$$self{states}} = (' '); + + return $self; +} + + +sub set { + my ( $self, $p_state, $p_setby, $p_response ) = @_; + + $p_state = normalize($p_state); + if ( exists $self->{devicehash}->{commands}->{name}->{$p_state} ) { + ::print_log( "[BondHome::Manual] Received request " . $p_state . " for " . $self->get_object_name ); + + my $content; + $content->{freq} = $self->{devicehash}->{commands}->{name}->{$p_state}->{freq}; + $content->{modulation} = $self->{devicehash}->{commands}->{name}->{$p_state}->{modulation}; + $content->{data} = $self->{devicehash}->{commands}->{name}->{$p_state}->{data}; + $content->{encoding} = $self->{devicehash}->{commands}->{name}->{$p_state}->{encoding}; + $content->{bps} = $self->{devicehash}->{commands}->{name}->{$p_state}->{bps}; + $content->{reps} = $self->{devicehash}->{commands}->{name}->{$p_state}->{reps}; + $content->{use_scan}='false'; + + $content = encode_json($content); + ::print_log( "[BondHome::Manual] content: $content" ); + $$self{parent}->bondcmd( $class, '/v2/signal/tx', 'PUT', $content ); + + $self->SUPER::set( $p_state, $p_setby ); + + } + else { + ::print_log( "[BondHome::Manual] Received INVALID request " . $p_state . " for " . $self->get_object_name ); + } +} + + +sub addcmd { + my ($self, $cmdname, $frequency, $modulation, $encoding, $bps, $reps, $data) = @_; + + #::print_log "[BondHome::Manual] ". Dumper Dumper $self; + ::print_log( "[BondHome::Manual] adding new command $cmdname" ); + $cmdname = normalize($cmdname); + $self->{devicehash}->{commands}->{name}->{$cmdname}->{modulation} = $modulation; + $self->{devicehash}->{commands}->{name}->{$cmdname}->{data} = $data; + $self->{devicehash}->{commands}->{name}->{$cmdname}->{freq} = $frequency; + $self->{devicehash}->{commands}->{name}->{$cmdname}->{encoding} = $encoding; + $self->{devicehash}->{commands}->{name}->{$cmdname}->{bps} = $bps; + $self->{devicehash}->{commands}->{name}->{$cmdname}->{reps} = $reps; + + $self->{ 'cmdtimer' } = ::Timer::new(); + $self->{ 'cmdtimer' }->set( + 5, + sub { $self->updatestates; } + ); + +} + + +sub updatestates { + my ( $self ) = @_; + my @speeds; + foreach my $command (keys %{$self->{devicehash}->{commands}->{name}} ) { + push @{ $$self{states} }, ucfirst($command); + if ( normalize($command) =~ /speed(\d)/ ) { + push @speeds, $1; + } + } + @speeds = sort @speeds; + push @{ $$self{states} }, @speeds; + +} + + +sub normalize { + my ( $string ) = @_; + $string = lc $string; + $string =~ s/ //g; + return $string; +} + =back =head2 INI PARAMETERS diff --git a/lib/read_table_A.pl b/lib/read_table_A.pl index b119b2c7e..cac45521c 100644 --- a/lib/read_table_A.pl +++ b/lib/read_table_A.pl @@ -1840,7 +1840,24 @@ sub read_table_A { $object = ''; } #-------------- End Alexa Objects ---------------- - + #-------------- BondHome Objects ----------------- + elsif ( $type eq "BONDHOME" ) { + ## + require 'BondHome.pm'; + my ($instance); + ( $name, $instance, $grouplist, @other ) = @item_info; + $other = join ', ', ( map { "'$_'" } @other ); # Quote data + $object = "BondHome('$instance','$other')"; + } + elsif ( $type eq "BONDHOME_DEVICE" ) { + ## + require 'BondHome.pm'; + my ($instance, $bonddevname); + ( $name, $instance, $bonddevname, $grouplist, @other ) = @item_info; + $other = join ', ', ( map { "'$_'" } @other ); + $object = "BondHome_Device('$instance','$bonddevname')"; + } + #-------------- End BondHome Objects ----------------- #-------------- AoGSmartHome Objects ----------------- elsif ( $type eq "AOGSMARTHOME_ITEMS" ) { ## From 402ba5427506b25b3c132945411e7e6f2c599983 Mon Sep 17 00:00:00 2001 From: waynieack Date: Sun, 19 Jul 2020 15:21:01 -0500 Subject: [PATCH 3/3] Added BondHome_Manual object to read_table_A.pl and updated docs --- lib/BondHome.pm | 58 ++++++++++++++++++++++++++++++++++----------- lib/read_table_A.pl | 27 ++++++++++++++++++--- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/lib/BondHome.pm b/lib/BondHome.pm index 036d116be..4ace3ea83 100644 --- a/lib/BondHome.pm +++ b/lib/BondHome.pm @@ -4,27 +4,38 @@ =head2 DESCRIPTION -Module for interfacing with the BondHome Hub to control devices configured in it. +Module for interfacing with the BondHome Hub to control IR/RF devices such as fans. =head2 CONFIGURATION At minimum, you must define the BondHome_ip in the mh.private.ini and the Interface -object. Once they are configured you can restart MH, then restart the Bond Hub once -MH is back up, next set the Bond Hub interface object to "GetToken" within a few min of the -Bond Hub reboot. Once the token is successfuly retreved, set the Bond Hub interface object to "LogDevs" -and copy the device code from the MH logs and paste into a MH .mht file. You will need all the devices -preconfigured in the BondHome Hub because MH pulls them including the names from it. +object. Once they are configured you can restart MH, once MH is back up restart the Bond Hub, +next set the Bond Hub interface object to "GetToken" within a few min after the Bond Hub reboot. +Once the token is successfuly retreved, set the Bond Hub interface object to "LogDevs" +and copy the device code from the MH logs and paste into a MH .mht file in your code directory. +You will need all the devices preconfigured in the BondHome Hub because MH pulls them including the names from it. The BondHome_Device objects allow for the display and control of these objects as separate items in the MH interface and allows users to interact directly with these objects using the basic Generic_Item functions such as tie_event. The BondHome_Device object is for tracking the state of and controlling -devices configured in the bondhome hub. +devices configured in the BondHome hub through the BondHome app and stored in +the local BondHome hub database. These devices are pulled from the BondHome Hub +with the local api. Misterhouse loads all devices and device commands from BondHome when it is started. You must reload the BondHome device and trigger and retrieve an auth token by setting the -parent object to "GetToken" with in 1 min after the reboot. +parent object to "GetToken" with in a few min after the reboot. + +The BondHome_Manual object is for manually recording remote signals and sending them from Misterhouse. +This bypasses the BondHome Hub database and just tells the BondHome Hub what signal to transmit directly. +To record a signal, set the Bond Hub interface object to ScanRF or ScanIR depending on what kind of remote +you are recording. Once scan is enabled, put the remote close to the hub and push the button you want to +record a few times and watch for the hub lights to change colors (This is the same process as the initial hub setup +through the app). Next set the Bond Hub interface object to ScanCheck and the .mht code for the recorded +command will be logged, update the device name IE: MasterFan and the command name IE: PowerOff and +paste the code in your .mht file. =head2 Interface Configuration @@ -57,7 +68,7 @@ Wherein the format for the definition is: $BondHomeHub = new BondHome(INSTANCE); States: -GetToken,Reboot,LogDevs,ReloadCache +GetToken,Reboot,LogDevs,ReloadCache,LogVersion,ScanRF,ScanIR,ScanStop,ScanCheck @@ -72,10 +83,27 @@ An example mh.private.ini: An example user code: #noloop=start + use BondHome; + $BondHomeHub = new BondHome('BondHome'); + $MasterFan = new BondHome_Device('BondHome','MasterFan'); + + $TV = new BondHome_Manual('BondHome'); + $TV->addcmd('power', '38', 'OOK', 'hex', '40000', '1', '00000'); + #noloop=stop + + +An example .mht code: + BONDHOME, BondHome, BondHome + BONDHOME_DEVICE, masterfan, BondHome, masterfan + BONDHOME_DEVICE, guestfan, BondHome, guestfan + + + BONDHOME_MANUAL, TV, BondHome + BONDHOME_MANUAL_CMD, TV, power, 38, OOK, hex, 40000, 1, 0000000 =head2 INHERITS @@ -103,7 +131,7 @@ sub new { # Initialize Variables $$self{instance} = $instance; - $$self{maxretry} = $::config_parms{ $instance . '_maxretry' }; + $$self{maxretry} = $::config_parms{ $instance . '_maxretry' } || 4; $$self{ip} = $::config_parms{ $instance . '_ip' }; my $year_mon = &::time_date_stamp( 10, time ); $$self{log_file} = $::config_parms{'data_dir'} . "/logs/BondHome.$year_mon.log"; @@ -142,10 +170,10 @@ Used to associate child objects with the interface. sub register { my ( $self, $object, $class ) = @_; if ( $object->isa('BondHome_Device') ) { - ::print_log("Registering BondHome Device Child Object: ".$object->get_object_name." for interface: ".$self->get_object_name ); + ::print_log("Registering BondHome Device Child Object"); push @{ $self->{device_object} }, $object; } elsif ( $object->isa('BondHome_Manual') ) { - ::print_log("Registering BondHome Manual Child Object: ".$object->get_object_name." for interface: ".$self->get_object_name ); + ::print_log("Registering BondHome Manual Child Object" ); push @{ $self->{manual_object} }, $object; } } @@ -423,7 +451,9 @@ sub getbonddevs { $self->{devicehash}->{devicename}->{$devicename}->{id}=$deviceid; foreach my $action ( @{$message->{actions}} ) { - next if ( $action =~ /^Set/ ); #Skip the set actions because the require arguments. + next if ( $action =~ /^Set/ ); #Skip the Set actions because they require arguments. + next if ( $action =~ /^IncreaseSpeed/ ); #Skip the IncreaseSpeed actions because they require arguments. + next if ( $action =~ /^DecreaseSpeed/ ); #Skip the DecreaseSpeed actions because they require arguments. $self->{devicehash}->{devicename}->{$devicename}->{commands}->{name}->{normalize($action)}->{action}=$action; } #::print_log Dumper $message; @@ -744,7 +774,7 @@ sub set { $content->{use_scan}='false'; $content = encode_json($content); - ::print_log( "[BondHome::Manual] content: $content" ); + #::print_log( "[BondHome::Manual] content: $content" ); $$self{parent}->bondcmd( $class, '/v2/signal/tx', 'PUT', $content ); $self->SUPER::set( $p_state, $p_setby ); diff --git a/lib/read_table_A.pl b/lib/read_table_A.pl index cac45521c..89b56bc11 100644 --- a/lib/read_table_A.pl +++ b/lib/read_table_A.pl @@ -1844,18 +1844,39 @@ sub read_table_A { elsif ( $type eq "BONDHOME" ) { ## require 'BondHome.pm'; + $code .= '#noloop=start'."\n"; my ($instance); ( $name, $instance, $grouplist, @other ) = @item_info; $other = join ', ', ( map { "'$_'" } @other ); # Quote data - $object = "BondHome('$instance','$other')"; + $object = "BondHome('$instance','$other')".';'."\n".'#noloop=stop'."\n"; } elsif ( $type eq "BONDHOME_DEVICE" ) { ## - require 'BondHome.pm'; + require 'BondHome.pm'; + $code .= '#noloop=start'."\n"; my ($instance, $bonddevname); ( $name, $instance, $bonddevname, $grouplist, @other ) = @item_info; $other = join ', ', ( map { "'$_'" } @other ); - $object = "BondHome_Device('$instance','$bonddevname')"; + $object = "BondHome_Device('$instance','$bonddevname')".';'."\n".'#noloop=stop'."\n"; + $code .= '#noloop=stop'."\n"; + } + elsif ( $type eq "BONDHOME_MANUAL" ) { + ## + require 'BondHome.pm'; + $code .= '#noloop=start'."\n"; + my ($instance); + ( $name, $instance, $grouplist, @other ) = @item_info; + $other = join ', ', ( map { "'$_'" } @other ); + $object = "BondHome_Manual('$instance')".';'."\n".'#noloop=stop'."\n"; + $code .= '#noloop=stop'."\n"; + } + elsif ( $type eq "BONDHOME_MANUAL_CMD" ) { + ## + $code .= '#noloop=start'."\n"; + my ($parent, $cmdname, $frequency, $modulation, $encoding, $bps, $reps, $data) = @item_info;; + $code .= sprintf "\$%-35s -> addcmd('$cmdname', '$frequency', '$modulation', '$encoding', '$bps', '$reps', '$data');\n", $parent; + $object = ''; + $code .= '#noloop=stop'."\n"; } #-------------- End BondHome Objects ----------------- #-------------- AoGSmartHome Objects -----------------