From 1408caa17aa113a52ed870931b12ad6ae196f1c3 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 16 Apr 2014 21:04:13 -0700 Subject: [PATCH 01/13] Insteon: Add Methods to Support Delayed Deaf Device Communication --- lib/Insteon/BaseInsteon.pm | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 93cbc9e0d..bd8054992 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -1343,7 +1343,7 @@ sub new $self->restore_data('devcat', 'firmware', 'level', 'retry_count_log', 'fail_count_log', 'outgoing_count_log', 'incoming_count_log', 'corrupt_count_log', 'dupe_count_log', 'hops_left_count', 'max_hops_count', - 'outgoing_hop_count'); + 'outgoing_hop_count', 'awake_time'); $self->initialize(); $self->rate(undef); @@ -1385,6 +1385,53 @@ sub initialize # persist local, simple attribs } +=item C + +Used to store and return the associated awake time of a device. This only +applies to deaf devices. + +If provided, stores awake time. + +=cut + +sub awake_time +{ + my ($self, $p_time) = @_; + $$self{awake_time} = $p_time if $p_time; + return $$self{awake_time}; +} + +=item C + +Used to store and return the time of the last contact from a device. This only +applies to deaf devices. + +=cut + +sub last_contact +{ + my ($self, $p_time) = @_; + $$self{last_contact} = $p_time if $p_time; + return $$self{last_contact}; +} + +=item C + +Used to manually flag the device as awake for a period of time. This will +cause all messages from MH to be immediately sent to the device instead of +being held until next contact. This should be used when you have manually +set the device to be awake such as by holding the set button until the device +beeps. Used only for deaf devices. + +=cut + +sub manual_awake +{ + my ($self, $p_time) = @_; + $$self{manual_awake} = $p_time if $p_time; + return $$self{manual_awake}; +} + =item C Used to store and return the associated ramp rate of a device. From bd9f0a0c5fbe170abc2a300cd7fdf1867de3b0f8 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 16 Apr 2014 21:28:24 -0700 Subject: [PATCH 02/13] Insteon: Do Not Send Commands to Deaf Devices Not Awake --- lib/Insteon/BaseInsteon.pm | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index bd8054992..8116ce563 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -982,7 +982,7 @@ sub _process_command_stack # for now, be "dumb" and just unset it $$self{awaiting_ack} = 0; } - if (!($$self{awaiting_ack})) { + if (!($$self{awaiting_ack}) && $self->is_awake) { my $callback = undef; my $message = pop(@{$$self{command_stack}}); # convert ptr to cmd hash @@ -1035,7 +1035,11 @@ sub _process_command_stack if $@ and $self->debuglevel(1, 'insteon'); package Insteon::BaseObject; } - } else { + } elsif (!$self->is_awake){ + ::print_log("[Insteon::BaseObject] ". $self->get_object_name . + " is deaf and not currently awake. Queuing commands" . + " until device wakes up."); + }else { # &::print_log("[Insteon_Device] " . $self->get_object_name . " command queued but not yet sent; awaiting ack from prior command") if $self->debuglevel(1, 'insteon'); } } @@ -1428,10 +1432,29 @@ beeps. Used only for deaf devices. sub manual_awake { my ($self, $p_time) = @_; - $$self{manual_awake} = $p_time if $p_time; + $$self{manual_awake} = time + $p_time if $p_time; return $$self{manual_awake}; } +=item C + +Returns true if the device has made contact within the time allowed by +C or if time allowed for C= time){ + $is_awake = 1; + } + $is_awake = 1 unless ($self->is_deaf); + return $is_awake; +} + =item C Used to store and return the associated ramp rate of a device. From 01090cd4beeace9fd0e869890bbdf082ce1b1a07 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 16 Apr 2014 21:35:40 -0700 Subject: [PATCH 03/13] Insteon: Set Last Contact on Received Message --- lib/Insteon/BaseInterface.pm | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index 61900f687..2e453504a 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -607,6 +607,7 @@ sub on_standard_insteon_received $object->max_hops_count($msg{maxhops}) if $object->can('max_hops_count'); $object->hops_left_count($msg{hopsleft}) if $object->can('hops_left_count'); $object->incoming_count_log(1) if $object->can('incoming_count_log'); + $object->last_contact(time); if ($msg{type} ne 'broadcast') { $msg{command} = $object->message_type($msg{cmd_code}); @@ -739,6 +740,11 @@ sub on_standard_insteon_received # ask the object to process the received message and update its state $object->_process_message($self, %msg); } + if ($self->is_deaf){ + #See if deaf device has commands waiting to be + #sent + $self->_process_command_stack(); + } } else { @@ -788,6 +794,7 @@ sub on_extended_insteon_received $object->max_hops_count($msg{maxhops}) if $object->can('max_hops_count'); $object->hops_left_count($msg{hopsleft}) if $object->can('hops_left_count'); $object->incoming_count_log(1) if $object->can('incoming_count_log'); + $object->last_contact(time); if ($msg{type} ne 'broadcast') { $msg{command} = $object->message_type($msg{cmd_code}); @@ -808,6 +815,11 @@ sub on_extended_insteon_received } $self->clear_active_message(); } + if ($self->is_deaf){ + #See if deaf device has commands waiting to be + #sent + $self->_process_command_stack(); + } } else { From 6ecde9cdf4de1c905bf9d35e14bca3baf0e8bcb7 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 16 Apr 2014 21:39:06 -0700 Subject: [PATCH 04/13] Insteon: Set Default Awake Time --- lib/Insteon/BaseInsteon.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 8116ce563..0c7f4b705 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -1369,6 +1369,7 @@ sub new $$self{hops_left_count} = 0; $$self{max_hops_count} = 0; $$self{outgoing_hop_count} = 0; + $$self{awake_time} = 2 unless $$self{awake_time}; return $self; From 1521a3c0ed3546f8fefd7fc7248313931b2848ae Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 16 Apr 2014 22:06:54 -0700 Subject: [PATCH 05/13] Insteon: Move is_awake to BaseObject; Fix Coding Error in BaseInterface -_process_command_stack is in BaseObject, which requires is_awake to be there too, otherwise we get errors with InterfaceObjects -Fix stupid error s/$self/$object/ in BaseInterface --- lib/Insteon/BaseInsteon.pm | 39 ++++++++++++++++++------------------ lib/Insteon/BaseInterface.pm | 10 ++++----- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 0c7f4b705..62c665233 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -1205,6 +1205,26 @@ sub is_responder } } +=item C + +Returns true if the device has made contact within the time allowed by +C or if time allowed for Cisa('Insteon::BaseDevice'); + my $is_awake = 0; + if (((time - $$self{last_contact}) <= $$self{awake_time}) || + $$self{manual_awake} >= time){ + $is_awake = 1; + } + $is_awake = 1 unless ($self->is_deaf); + return $is_awake; +} + =back =head2 INI PARAMETERS @@ -1437,25 +1457,6 @@ sub manual_awake return $$self{manual_awake}; } -=item C - -Returns true if the device has made contact within the time allowed by -C or if time allowed for C= time){ - $is_awake = 1; - } - $is_awake = 1 unless ($self->is_deaf); - return $is_awake; -} - =item C Used to store and return the associated ramp rate of a device. diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index 2e453504a..79df66673 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -740,11 +740,11 @@ sub on_standard_insteon_received # ask the object to process the received message and update its state $object->_process_message($self, %msg); } - if ($self->is_deaf){ + if ($object->is_deaf){ #See if deaf device has commands waiting to be #sent - $self->_process_command_stack(); - } + $object->_process_command_stack(); + } } else { @@ -815,10 +815,10 @@ sub on_extended_insteon_received } $self->clear_active_message(); } - if ($self->is_deaf){ + if ($object->is_deaf){ #See if deaf device has commands waiting to be #sent - $self->_process_command_stack(); + $object->_process_command_stack(); } } else From 3ce31270bd709c170718f43f3448bfbb6a6da23e Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 17:35:00 -0700 Subject: [PATCH 06/13] Insteon: Move ALDB Delta Checking in Batch Scan to BaseDevice This is needed in order to allow for delayed scanning of deaf devices. As a side effect, it makes the code much more condensed and readable. --- lib/Insteon.pm | 31 ++++++++++--------------------- lib/Insteon/BaseInsteon.pm | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index f162a48e5..e4bafcd49 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -451,31 +451,20 @@ Gets the next device to scan. sub _get_next_linkscan { my($skip_unchanged, $changed_device) = @_; - my $checking = 0; - if (!defined($changed_device)) { - $current_scan_device = shift @_scan_devices; - if ($skip_unchanged && $current_scan_device && ($current_scan_device != &Insteon::active_interface)){ - ## check if aldb_delta has changed; - $current_scan_device->_aldb->{_aldb_unchanged_callback} = '&Insteon::_get_next_linkscan('.$skip_unchanged.')'; - $current_scan_device->_aldb->{_aldb_changed_callback} = '&Insteon::_get_next_linkscan('.$skip_unchanged.', '.$current_scan_device->get_object_name.')'; - $current_scan_device->_aldb->{_failure_callback} = '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')'; - $current_scan_device->_aldb->query_aldb_delta("check"); - $checking = 1; - } - } else { - $current_scan_device = $changed_device; - } - if ($current_scan_device && ($checking == 0)) - { - &main::print_log("[Scan all link tables] Now scanning: " + $current_scan_device = shift @_scan_devices; + if ($current_scan_device) { + ::print_log("[Scan all link tables] Now scanning: " . $current_scan_device->get_object_name . " (" . ($_scan_cnt - scalar @_scan_devices) . " of $_scan_cnt)"); # pass first the success callback followed by the failure callback - $current_scan_device->scan_link_table('&Insteon::_get_next_linkscan('.$skip_unchanged.')','&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')'); - } elsif (scalar(@_scan_devices) == 0 && ($checking == 0)) - { - &main::print_log("[Scan all link tables] All tables have completed scanning"); + $current_scan_device->scan_link_table( + '&Insteon::_get_next_linkscan('.$skip_unchanged.')', + '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')', + $skip_unchanged); + } + else { + ::print_log("[Scan all link tables] All tables have completed scanning"); my $_scan_failure_cnt = scalar @_scan_device_failures; if ($_scan_failure_cnt){ my $obj_list; diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 62c665233..268873416 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -1943,13 +1943,19 @@ Scans a device link table and caches a copy. sub scan_link_table { - my ($self, $success_callback, $failure_callback) = @_; - my $aldb = $self->get_root()->_aldb; - if ($aldb) - { - return $aldb->scan_link_table($success_callback, $failure_callback); + my ($self, $success_callback, $failure_callback, $skip_unchanged) = @_; + my $aldb = $self->get_root()->_aldb; + if ($skip_unchanged) { + my $self_name = $self->get_object_name(); + ## check if aldb_delta has changed; + $aldb->{_aldb_unchanged_callback} = $success_callback; + $aldb->{_aldb_changed_callback} = $self_name."->scan_link_table( + '$success_callback', '$failure_callback')"; + $aldb->{_failure_callback} = $failure_callback; + $aldb->query_aldb_delta("check"); + } else { + $aldb->scan_link_table($success_callback, $failure_callback); } - } =item C From a010c9bee5c230399ba58f8c5d02c517d9612855 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 17:40:00 -0700 Subject: [PATCH 07/13] Insteon: Queue Scans of Deaf Devices Last - Add log message explaining what is happening - Send command to scan each deaf device individually, this should result in the requests being queued, unless the device is awake - Because these commands will be fired off when the various devices are randomly awake, these have to be individual "uncoordinated" scans. In the worst case scenario, this will cause the scanning of numerous devices to happen at the same time. While not ideal, this can't simply be avoided, however, the stack should be robust enough to handle this without error now, although it may be confusing to the user. --- lib/Insteon.pm | 52 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index e4bafcd49..52122ad48 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -311,7 +311,7 @@ Resets the message stats back to 0 for this device. my (@_insteon_plm,@_insteon_device,@_insteon_link,@_scannable_link,$_scan_cnt,$_sync_cnt,$_sync_failure_cnt); my $init_complete; -my (@_scan_devices,@_scan_device_failures,$current_scan_device); +my (@_scan_devices,@_scan_deaf_devices,@_scan_device_failures,$current_scan_device); my (@_sync_devices,@_sync_device_failures,$current_sync_device); my ($_stress_test_count, $_stress_test_one_pass, @_stress_test_devices); my ($_ping_count, @_ping_devices); @@ -386,6 +386,7 @@ sub scan_all_linktables my @candidate_devices = (); # clear @_scan_devices @_scan_devices = (); + @_scan_deaf_devices = (); @_scan_device_failures = (); $current_scan_device = undef; # alwayws include the active interface (e.g., plm) @@ -400,8 +401,7 @@ sub scan_all_linktables { my $candidate_object = $_; if ($candidate_object->is_root and - !($candidate_object->is_deaf - or $candidate_object->isa('Insteon::InterfaceController'))) + !($candidate_object->isa('Insteon::InterfaceController'))) { push @_scan_devices, $candidate_object; &main::print_log("[Scan all linktables] INFO1: " @@ -453,26 +453,44 @@ sub _get_next_linkscan my($skip_unchanged, $changed_device) = @_; $current_scan_device = shift @_scan_devices; if ($current_scan_device) { - ::print_log("[Scan all link tables] Now scanning: " - . $current_scan_device->get_object_name . " (" - . ($_scan_cnt - scalar @_scan_devices) - . " of $_scan_cnt)"); - # pass first the success callback followed by the failure callback - $current_scan_device->scan_link_table( - '&Insteon::_get_next_linkscan('.$skip_unchanged.')', - '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')', - $skip_unchanged); + if ($current_scan_device->is_deaf){ + # Store deaf devices for scanning at the end + push(@_scan_deaf_devices, $current_scan_device); + &Insteon::_get_next_linkscan($skip_unchanged); + } + else { + ::print_log("[Scan all link tables] Now scanning: " + . $current_scan_device->get_object_name . " (" + . ($_scan_cnt - scalar @_scan_devices) + . " of $_scan_cnt)"); + # pass first the success callback followed by the failure callback + $current_scan_device->scan_link_table( + '&Insteon::_get_next_linkscan('.$skip_unchanged.')', + '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')', + $skip_unchanged); + } } else { - ::print_log("[Scan all link tables] All tables have completed scanning"); - my $_scan_failure_cnt = scalar @_scan_device_failures; - if ($_scan_failure_cnt){ + ::print_log("[Scan all link tables] Completed scanning of all regular items."); + if (scalar @_scan_device_failures){ my $obj_list; for my $failed_obj (@_scan_device_failures){ $obj_list .= $failed_obj->get_object_name .", "; } - ::print_log("[Scan all link tables] However, some failures " - ."were noted with the following devices: $obj_list"); + ::print_log("[Scan all link tables] WARN, unable to " + ." complete a scan of the following devices: $obj_list"); + } + if (scalar @_scan_deaf_devices){ + my $obj_list; + for my $deaf_obj (@_scan_deaf_devices){ + $obj_list .= $deaf_obj->get_object_name .", "; + } + ::print_log("[Scan all link tables] Will attempt to scan" + ." the following deaf devices the next time they" + ." wakeup: $obj_list"); + for my $deaf_obj (@_scan_deaf_devices){ + $deaf_obj->scan_link_table('','',$skip_unchanged); + } } } } From 8b7be27b64ae7f031453ebdea7fdf916ecd38f22 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 17:45:00 -0700 Subject: [PATCH 08/13] Insteon: Add Log Messages to Appear at Conclusion of Delayed Scan --- lib/Insteon.pm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 52122ad48..552d96a28 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -489,7 +489,16 @@ sub _get_next_linkscan ." the following deaf devices the next time they" ." wakeup: $obj_list"); for my $deaf_obj (@_scan_deaf_devices){ - $deaf_obj->scan_link_table('','',$skip_unchanged); + my $success_callback = "::print_log(qq|[Scan all". + " link tables] Delayed scan successfully". + " completed for: " . + $deaf_obj->get_object_name . "|)"; + my $failure_callback = "::print_log(qq|[Scan all". + " link tables] WARN: The delayed scan ". + " failed for: " . + $deaf_obj->get_object_name . "|)"; + $deaf_obj->scan_link_table($success_callback, + $failure_callback,$skip_unchanged); } } } From 20b85c864ed6d9899ca41cb76fe181a11b351e33 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 17:55:00 -0700 Subject: [PATCH 09/13] Insteon: Queue Sync Requests for Deaf Devices for Later Delivery Sync these links when the device wakes up Also clean up Sync_All_Links routine a bit --- lib/Insteon.pm | 21 ++++------ lib/Insteon/BaseInsteon.pm | 85 +++++++++++++++++++++++++++++++------- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 552d96a28..067a416e7 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -548,31 +548,27 @@ call _get_next_linksync_failure() if sync_links() fails. sub _get_next_linksync { - $current_scan_device = shift @_scan_devices; my $sync_req_ptr = shift(@_sync_devices); my %sync_req = ($sync_req_ptr) ? %$sync_req_ptr : undef; - if (%sync_req) - { - + if (%sync_req) { $current_sync_device = $sync_req{'sync_object'}; } - else - { + else { $current_sync_device = undef; } - if ($current_sync_device) - { + if ($current_sync_device) { &main::print_log("[Sync all links] Now syncing: " . $current_sync_device->get_object_name . " (" . ($_sync_cnt - scalar @_sync_devices) . " of $_sync_cnt)"); my $skip_deaf = 1; # pass first the success callback followed by the failure callback - $current_sync_device->sync_links($sync_req{'audit_mode'}, '&Insteon::_get_next_linksync()','&Insteon::_get_next_linksync_failure()', $skip_deaf); + $current_sync_device->sync_links($sync_req{'audit_mode'}, + '&Insteon::_get_next_linksync()', + '&Insteon::_get_next_linksync_failure()', $skip_deaf); } - else - { + else { &main::print_log("[Sync all links] All links have completed syncing"); my $_sync_failure_cnt = scalar @_sync_device_failures; if ($_sync_failure_cnt){ @@ -581,7 +577,8 @@ sub _get_next_linksync $obj_list .= $failed_obj->get_object_name .", "; } ::print_log("[Sync all links] WARN! Failures occured, " - ."some links involving the following objects remain out-of-sync: $obj_list"); + ."some links involving the following objects " + ."remain out-of-sync: $obj_list"); } } diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 268873416..82570c398 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -3145,13 +3145,7 @@ sub sync_links # Warn if device is deaf or ALDB out of sync my $insteon_object_is_syncable = 1; - if ($insteon_object->is_deaf && $skip_deaf) { - ::print_log("[Insteon::BaseController] $self_link_name is deaf, only responder links will be added to devices " - ."controlled by this device. To sync links on this device, put it in awake mode and run the 'Sync Links' " - ."command on this specific device."); - $insteon_object_is_syncable = 0; - } - elsif ($insteon_object->_aldb->health ne 'good' && $insteon_object->_aldb->health ne 'empty'){ + if ($insteon_object->_aldb->health ne 'good' && $insteon_object->_aldb->health ne 'empty'){ ::print_log("[Insteon::BaseController] WARN! The ALDB of $self_link_name is ".$insteon_object->_aldb->health .", links will be added to devices " ."linked to this device, but no links will be added to $self_link_name. Please rescan this device and attempt " @@ -3350,13 +3344,17 @@ sub _process_sync_queue { if ($num_sync_queue) { my $link_req_ptr = shift(@{$$self{sync_queue}}); my %link_req = %$link_req_ptr; - if ($link_req{cmd} eq 'update') { - my $link_member = $link_req{member}; - $link_member->update_link(%link_req); - } elsif ($link_req{cmd} eq 'add') { - my $link_member = $link_req{member}; - $link_member->add_link(%link_req); - } + my $link_member = $link_req{member}; + if ($link_member->is_deaf){ + $link_member->_build_deaf_sync_queue($link_req_ptr); + $self->_process_sync_queue(); + } + elsif ($link_req{cmd} eq 'update') { + $link_member->update_link(%link_req); + } + elsif ($link_req{cmd} eq 'add') { + $link_member->add_link(%link_req); + } } elsif ($$self{sync_queue_callback}) { my $callback = $$self{sync_queue_callback}; if ($$self{sync_queue_failure}){ @@ -3364,14 +3362,69 @@ sub _process_sync_queue { } package main; eval ($callback); - &::print_log("[Insteon::BaseController] error in sync links callback: " . $@) + ::print_log("[Insteon::BaseController] ERROR in sync links callback: " . $@) if $@ and $self->debuglevel(1, 'insteon'); package Insteon::BaseController; } else { - main::print_log($self->get_object_name." completed sync links"); + ::print_log("[Insteon::BaseController] Completed sync links for: " + .$self->get_object_name); + } +} + +###### +# +# The following three routines are used to queue links to be synced on deaf +# devices these requests will be processed the next time the device wakes up +# +###### + +sub _build_deaf_sync_queue { + my ($self, $link_req_ptr) = @_; + my %link_req = %$link_req_ptr; + my $self_link_name = $self->get_object_name; + %link_req = ( callback => "$self_link_name->_process_deaf_sync_queue()", + failure_callback => "$self_link_name->_process_deaf_sync_queue_failure()"); + push @{$$self{deaf_sync_queue}}, \%link_req; + if (!$$self{deaf_sync_queue_flag}){ + ::print_log("[Insteon::BaseController] Sync requests for " + .$self_link_name. " will not be processed until it is awake."); + $self->_process_deaf_sync_queue(); + } +} + +sub _process_deaf_sync_queue { + my ($self) = @_; + my $num_sync_queue = @{$$self{deaf_sync_queue}}; + if ($num_sync_queue) { + $$self{deaf_sync_queue_flag} = 1; #Sync requests are pending + my $link_req_ptr = shift(@{$$self{deaf_sync_queue}}); + my %link_req = %$link_req_ptr; + my $link_member = $link_req{member}; + if ($link_req{cmd} eq 'update') { + $link_member->update_link(%link_req); + } + elsif ($link_req{cmd} eq 'add') { + $link_member->add_link(%link_req); + } + } + else { + ::print_log($self->get_object_name." completed the delayed " + ."sync links request"); + if ($$self{deaf_sync_queue_failure}) { + ::print_log("However, some failures occured while " + ."syncing links on " . $self->get_object_name); + } + $$self{deaf_sync_queue_flag} = 0; + $$self{deaf_sync_queue_failure} = 0; } } +sub _process_deaf_sync_queue_failure { + my ($self) = @_; + $$self{deaf_sync_queue_failure} = 1; + $self->_process_deaf_sync_queue(); +} + =item C Checks each linked member of device. If the linked member is a C, From a9bae32c1fc5c62fd65b340885f7923909aed149 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 20:25:03 -0700 Subject: [PATCH 10/13] Insteon: Revert Including Deaf Devices in Batch Commands - On further thought, including deaf devices in batch commands is a bad idea. Many deaf devices are rarely contacted, yet a user may run the batch commands frequently. This may result in a TON of queued messages for a deaf device, which may result in too much traffic when the device is finally contacted. - The better course of action seems to be to force users to run these same commands on the specific deaf devices. --- lib/Insteon.pm | 70 ++++++++++++-------------------------- lib/Insteon/BaseInsteon.pm | 29 ++++++++++------ 2 files changed, 40 insertions(+), 59 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 067a416e7..92bf173df 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -311,7 +311,7 @@ Resets the message stats back to 0 for this device. my (@_insteon_plm,@_insteon_device,@_insteon_link,@_scannable_link,$_scan_cnt,$_sync_cnt,$_sync_failure_cnt); my $init_complete; -my (@_scan_devices,@_scan_deaf_devices,@_scan_device_failures,$current_scan_device); +my (@_scan_devices,@_scan_device_failures,$current_scan_device); my (@_sync_devices,@_sync_device_failures,$current_sync_device); my ($_stress_test_count, $_stress_test_one_pass, @_stress_test_devices); my ($_ping_count, @_ping_devices); @@ -386,7 +386,6 @@ sub scan_all_linktables my @candidate_devices = (); # clear @_scan_devices @_scan_devices = (); - @_scan_deaf_devices = (); @_scan_device_failures = (); $current_scan_device = undef; # alwayws include the active interface (e.g., plm) @@ -395,12 +394,17 @@ sub scan_all_linktables push @candidate_devices, &Insteon::find_members("Insteon::BaseDevice"); # don't try to scan devices that are not responders - if (@candidate_devices) - { - foreach (@candidate_devices) - { + if (@candidate_devices) { + foreach (@candidate_devices) { my $candidate_object = $_; - if ($candidate_object->is_root and + if ($candidate_object->is_deaf){ + ::print_log("[Scan all linktables] INFO: !!! " + . $candidate_object->get_object_name + . " is deaf. To scan this object you" + . " must run 'Scan Link Table' on it" + . " directly."); + } + elsif ($candidate_object->is_root and !($candidate_object->isa('Insteon::InterfaceController'))) { push @_scan_devices, $candidate_object; @@ -408,16 +412,14 @@ sub scan_all_linktables . $candidate_object->get_object_name . " will be scanned.") if $candidate_object->debuglevel(1, 'insteon'); } - else - { + else { &main::print_log("[Scan all linktables] INFO: !!! " . $candidate_object->get_object_name . " is NOT a candidate for scanning."); } } } - else - { + else { &main::print_log("[Scan all linktables] WARN: No insteon devices could be found"); } $_scan_cnt = scalar @_scan_devices; @@ -453,22 +455,15 @@ sub _get_next_linkscan my($skip_unchanged, $changed_device) = @_; $current_scan_device = shift @_scan_devices; if ($current_scan_device) { - if ($current_scan_device->is_deaf){ - # Store deaf devices for scanning at the end - push(@_scan_deaf_devices, $current_scan_device); - &Insteon::_get_next_linkscan($skip_unchanged); - } - else { - ::print_log("[Scan all link tables] Now scanning: " - . $current_scan_device->get_object_name . " (" - . ($_scan_cnt - scalar @_scan_devices) - . " of $_scan_cnt)"); - # pass first the success callback followed by the failure callback - $current_scan_device->scan_link_table( - '&Insteon::_get_next_linkscan('.$skip_unchanged.')', - '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')', - $skip_unchanged); - } + ::print_log("[Scan all link tables] Now scanning: " + . $current_scan_device->get_object_name . " (" + . ($_scan_cnt - scalar @_scan_devices) + . " of $_scan_cnt)"); + # pass first the success callback followed by the failure callback + $current_scan_device->scan_link_table( + '&Insteon::_get_next_linkscan('.$skip_unchanged.')', + '&Insteon::_get_next_linkscan_failure('.$skip_unchanged.')', + $skip_unchanged); } else { ::print_log("[Scan all link tables] Completed scanning of all regular items."); @@ -480,27 +475,6 @@ sub _get_next_linkscan ::print_log("[Scan all link tables] WARN, unable to " ." complete a scan of the following devices: $obj_list"); } - if (scalar @_scan_deaf_devices){ - my $obj_list; - for my $deaf_obj (@_scan_deaf_devices){ - $obj_list .= $deaf_obj->get_object_name .", "; - } - ::print_log("[Scan all link tables] Will attempt to scan" - ." the following deaf devices the next time they" - ." wakeup: $obj_list"); - for my $deaf_obj (@_scan_deaf_devices){ - my $success_callback = "::print_log(qq|[Scan all". - " link tables] Delayed scan successfully". - " completed for: " . - $deaf_obj->get_object_name . "|)"; - my $failure_callback = "::print_log(qq|[Scan all". - " link tables] WARN: The delayed scan ". - " failed for: " . - $deaf_obj->get_object_name . "|)"; - $deaf_obj->scan_link_table($success_callback, - $failure_callback,$skip_unchanged); - } - } } } diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 82570c398..eac142a2e 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -3145,7 +3145,13 @@ sub sync_links # Warn if device is deaf or ALDB out of sync my $insteon_object_is_syncable = 1; - if ($insteon_object->_aldb->health ne 'good' && $insteon_object->_aldb->health ne 'empty'){ + if ($insteon_object->is_deaf && $skip_deaf) { + ::print_log("[Insteon::BaseController] $self_link_name is deaf, only responder links will be added to devices " + ."controlled by this device. To sync links on this device, put it in awake mode and run the 'Sync Links' " + ."command on this specific device."); + $insteon_object_is_syncable = 0; + } + elsif ($insteon_object->_aldb->health ne 'good' && $insteon_object->_aldb->health ne 'empty'){ ::print_log("[Insteon::BaseController] WARN! The ALDB of $self_link_name is ".$insteon_object->_aldb->health .", links will be added to devices " ."linked to this device, but no links will be added to $self_link_name. Please rescan this device and attempt " @@ -3368,6 +3374,12 @@ sub _process_sync_queue { } else { ::print_log("[Insteon::BaseController] Completed sync links for: " .$self->get_object_name); + if ($$self{deaf_sync_queue_flag}){ + ::print_log("[Insteon::BaseController] Links on " + .$self->get_object_name . " will sync the next " + ."time the device is awake."); + $self->_process_deaf_sync_queue(); + } } } @@ -3382,21 +3394,16 @@ sub _build_deaf_sync_queue { my ($self, $link_req_ptr) = @_; my %link_req = %$link_req_ptr; my $self_link_name = $self->get_object_name; + $$self{deaf_sync_queue_flag} = 1; #Sync requests are pending %link_req = ( callback => "$self_link_name->_process_deaf_sync_queue()", failure_callback => "$self_link_name->_process_deaf_sync_queue_failure()"); push @{$$self{deaf_sync_queue}}, \%link_req; - if (!$$self{deaf_sync_queue_flag}){ - ::print_log("[Insteon::BaseController] Sync requests for " - .$self_link_name. " will not be processed until it is awake."); - $self->_process_deaf_sync_queue(); - } } sub _process_deaf_sync_queue { my ($self) = @_; my $num_sync_queue = @{$$self{deaf_sync_queue}}; if ($num_sync_queue) { - $$self{deaf_sync_queue_flag} = 1; #Sync requests are pending my $link_req_ptr = shift(@{$$self{deaf_sync_queue}}); my %link_req = %$link_req_ptr; my $link_member = $link_req{member}; @@ -3408,11 +3415,11 @@ sub _process_deaf_sync_queue { } } else { - ::print_log($self->get_object_name." completed the delayed " - ."sync links request"); + ::print_log("[Insteon::BaseController] Completed the delayed " + ."sync links request on " . $self->get_object_name); if ($$self{deaf_sync_queue_failure}) { - ::print_log("However, some failures occured while " - ."syncing links on " . $self->get_object_name); + ::print_log("[Insteon::BaseController] However, some " + ."failures occured."); } $$self{deaf_sync_queue_flag} = 0; $$self{deaf_sync_queue_failure} = 0; From 95db432a1d3fc812fcd4e0f08ff366bc8be94191 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 20:28:52 -0700 Subject: [PATCH 11/13] Insteon: Allow Delete Orphans to Be Run on a Single Device. Using Delete Orphans on a single device is not generally advisable. Delete orphans works on the device level and not the link level, meaning it only cleans up a specific device, which may result in "half-links" Now, this shouldn't be too much of an issue for deaf devices as the batch command of Delete Orphans will allow "half links" to be created by deleting responder records from deaf devices. As a result, the command on a deaf device will simply clean up the half links. (I hope that makes sense) --- lib/Insteon/AllLinkDatabase.pm | 25 +++++++++++++++---------- lib/Insteon/BaseInsteon.pm | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/Insteon/AllLinkDatabase.pm b/lib/Insteon/AllLinkDatabase.pm index b8bc0cd40..da816e5bc 100644 --- a/lib/Insteon/AllLinkDatabase.pm +++ b/lib/Insteon/AllLinkDatabase.pm @@ -435,19 +435,21 @@ scanned and processed. sub delete_orphan_links { - my ($self, $audit_mode, $failure_callback) = @_; + my ($self, $audit_mode, $failure_callback, $is_batch_mode) = @_; @{$$self{delete_queue}} = (); # reset the work queue $$self{delete_queue_processed} = 0; my $selfname = $$self{device}->get_object_name; # first, make sure that the health of ALDB is ok - if ($self->health ne 'good' || $$self{device}->is_deaf) { + if ($self->health ne 'good' || ($$self{device}->is_deaf && $is_batch_mode)) { my $sent_to_failure = 0; if ($$self{device}->is_deaf) { - ::print_log("[Insteon::AllLinkDatabase] Delete orphan links: Will not delete links on deaf device: $selfname"); + ::print_log("[Insteon::AllLinkDatabase] Will not delete ". + "links on deaf device: $selfname. Run 'Delete ". + "Orphan Links' directly on the device to do this."); } elsif ($self->health eq 'empty'){ - ::print_log("[Insteon::AllLinkDatabase] Delete orphan links: Skipping $selfname, because it has no links"); + ::print_log("[Insteon::AllLinkDatabase] Skipping $selfname, because it has no links"); } else { ::print_log("[Insteon::AllLinkDatabase] Delete orphan links: skipping $selfname because health: " @@ -464,7 +466,7 @@ sub delete_orphan_links } } if (!$$self{device}->isa('Insteon_PLM') && !$sent_to_failure){ - $self->_process_delete_queue(); + $self->_process_delete_queue($is_batch_mode); } return; } @@ -475,7 +477,7 @@ sub delete_orphan_links next LINKKEY if ($linkkey eq 'empty'); # Define delete request - my %delete_req = (callback => "$selfname->_aldb->_process_delete_queue()", + my %delete_req = (callback => "$selfname->_aldb->_process_delete_queue($is_batch_mode)", failure_callback => $failure_callback); # Delete duplicate entries @@ -661,12 +663,12 @@ sub delete_orphan_links ::print_log("[Insteon::AllLinkDatabase] ## Begin processing delete queue for: $selfname"); } if (!$$self{device}->isa('Insteon_PLM')) { - $self->_process_delete_queue(); + $self->_process_delete_queue($is_batch_mode); } } sub _process_delete_queue { - my ($self) = @_; + my ($self, $is_batch_mode) = @_; my $num_in_queue = @{$$self{delete_queue}}; if ($num_in_queue) { @@ -691,7 +693,9 @@ sub _process_delete_queue { { &::print_log("[Insteon::AllLinkDatabase] Nothing else to do for " . $$self{device}->get_object_name . " after deleting " . $$self{delete_queue_processed} . " links") if $self->{device}->debuglevel(1, 'insteon'); - $$self{device}->interface->_aldb->_process_delete_queue($$self{delete_queue_processed}); + if ($is_batch_mode){ + $$self{device}->interface->_aldb->_process_delete_queue($$self{delete_queue_processed}); + } } } @@ -2661,7 +2665,8 @@ sub _process_delete_queue { if ($delete_req{'root_object'}) { $$self{current_delete_device} = $delete_req{'root_object'}->get_object_name; - $delete_req{'root_object'}->delete_orphan_links(($delete_req{'audit_mode'}) ? 1 : 0, $failure_callback); + my $is_batch_mode = 1; + $delete_req{'root_object'}->delete_orphan_links(($delete_req{'audit_mode'}) ? 1 : 0, $failure_callback, $is_batch_mode); } else { diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index eac142a2e..b5ac971a5 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -2274,8 +2274,8 @@ does nothing. sub delete_orphan_links { - my ($self, $audit_mode, $failure_callback) = @_; - return $self->_aldb->delete_orphan_links($audit_mode, $failure_callback) if $self->_aldb; + my ($self, $audit_mode, $failure_callback, $is_batch_mode) = @_; + return $self->_aldb->delete_orphan_links($audit_mode, $failure_callback,$is_batch_mode) if $self->_aldb; } sub _process_delete_queue { From 56aecdeff10e2f495c133ff376a986f8c2344d75 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 21:26:38 -0700 Subject: [PATCH 12/13] Insteon: Add Voice Commands for Deaf Devices Since batch commands will not work on deaf devices, needed to add two AUDIT commands Also add a Delete Orphans command --- lib/Insteon/BaseInsteon.pm | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index b5ac971a5..2a497fada 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -925,6 +925,7 @@ sub _process_message } elsif ($msg{type} eq 'broadcast') { $self->devcat($msg{devcat}); $self->firmware($msg{firmware}); + $self->manual_awake(240); &::print_log("[Insteon::BaseObject] device category: $msg{devcat}" . " firmware: $msg{firmware} received for " . $self->{object_name}); } else { @@ -1139,6 +1140,14 @@ sub get_voice_cmds #here 'sync links' => $self->get_object_name . '->sync_links(0)' ); + # for deaf devices, the device level is the only version of sync links + # so add an audit command + if ($self->is_deaf && $self->is_root){ + %voice_cmds = ( + %voice_cmds, + '(AUDIT) sync links' => $self->get_object_name . '->sync_links(1)' + ); + } return \%voice_cmds; } @@ -2800,7 +2809,14 @@ sub get_voice_cmds 'run stress test' => "$object_name->stress_test(5)", 'run ping test' => "$object_name->ping(5)", 'log links' => "$object_name->log_alllink_table()" - ) + ); + if ($self->is_deaf){ + %voice_cmds = ( + %voice_cmds, + 'delete orphan links' => "$object_name->delete_orphan_links(0)", + '(AUDIT) delete orphan links' => "$object_name->delete_orphan_links(1)", + ); + } } return \%voice_cmds; } From 985581d4fbec89fc71b50e627c29e708dbee6175 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Thu, 17 Apr 2014 21:48:44 -0700 Subject: [PATCH 13/13] Insteon: Add voice commands for Deaf Devices; Monitor Mode Enabling Monitor Mode has other benefits. Adding it as a voice command option. Adding POD documentation for voice commands. --- lib/Insteon.pm | 71 ++++++++++++++++++++++++++++++++++++ lib/Insteon/BaseInsteon.pm | 1 + lib/Insteon/BaseInterface.pm | 4 +- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 92bf173df..9792636f7 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -78,6 +78,10 @@ Deleting the orphan links will make your devices happier. If you have unintende links on your devices, they can run slower and may unnecessarily increase the number of messages sent on your network. +B This command will not run on deaf devices such as motion sensors or +remotelincs. These devices need to be "awake" to receive commands. So please +run this same command on deaf devices directly. + =item C Does the same thing as C but doesn't actually delete anything @@ -102,6 +106,10 @@ up. See the workflow described in C. +B This command will not run on deaf devices such as motion sensors or +remotelincs. These devices need to be "awake" to receive commands. So please +run this same command on deaf devices directly. + =item C Same as C but prints what it would do to the log, without doing @@ -134,6 +142,31 @@ Logs some details about each device to the log. See C =back +=item C + +Places the PLM into "Monitor Mode." The documentation is a little unclear on +what this does. In practice, this enables the PLM to receive B +messages from devices which are in the PLM's link database. So far, I have +encountered two important broadcast messages, 1) EZFlora (EZRain) can send +out broadcast messages whenever a valve changes state, 2) Each device will +send out a broadcast message whenever you hold down the set button for 10 +seconds. Within MisterHouse this message is used to mark a deaf device as +awake for 4 minutes. If Monitor Mode is not enabled, MisterHouse will not +see either of these messages. + +Please be warned, since the documentation is rather vague on what this setting +does, please consider this setting a B feature. Other users with other +setups or devices may discover problems with this setting, but at least for me +I do not see a downside to enabling this feature. + +=back + +=item C + +Disables B defined above. + +=back + =head3 Devices =over @@ -151,6 +184,11 @@ Turns the device off. Similar to C above, but this will only add links that are related to this device. Useful when adding a new device. +On deaf devices, this command will perform its tasks the next time the device +is awake. You can awaken a device temporarily by triggering it (walk in front +of a motion sensor, press a button on a remotelinc). Or you can place the +device in awake mode for a longer period of time. See B + =item C Will create the controller/responder links between the device and the PLM. @@ -300,6 +338,39 @@ controlled by MH and is not reset by calling C Resets the message stats back to 0 for this device. +=item C + +Flags the device as being awake for 4 minutes. Only applicable to deaf devices +such as motion sensors and remotelincs. You must first press and hold the set +button for 10 seconds on the device to put it into awake mode. Then tell +MisterHouse that you have done this by using this command. Alternatively, if +you enable B on the PLM, MisterHouse will see when you press the +set button of a device for ten seconds, and automatically mark it as awake for +you. + +=item C<(AUDIT) Sync Links> + +Only available on deaf devices such as motion sensors and remotelincs. Similar +to C above in the PLM, but this will only report links +that B be added to this device should you run B. + +=item C<(AUDIT) Delete Orphan Links> + +Only available on deaf devices such as motion sensors and remotelincs. Similar +to C above in the PLM, but this will only report links +that B be deleted from this device should you run B. + +=item C + +Only available on deaf devices such as motion sensors and remotelincs. Similar +to C above in the PLM, but this will only delete links +from this device that are unnecessary or no longer used. + +On deaf devices, this command will perform its tasks the next time the device +is awake. You can awaken a device temporarily by triggering it (walk in front +of a motion sensor, press a button on a remotelinc). Or you can place the +device in awake mode for a longer period of time. See B + =back =head2 METHODS diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index 2a497fada..11cd765b3 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -2815,6 +2815,7 @@ sub get_voice_cmds %voice_cmds, 'delete orphan links' => "$object_name->delete_orphan_links(0)", '(AUDIT) delete orphan links' => "$object_name->delete_orphan_links(1)", + 'mark as manually awake' => "$object_name->manual_awake(240)" ); } } diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index 79df66673..3ca403ea1 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -1017,7 +1017,9 @@ sub get_voice_cmds 'reset all message stats' => "Insteon::reset_all_message_stats", 'stress test ALL devices' => "Insteon::stress_test_all(5,1)", 'ping test ALL devices' => "Insteon::ping_all(5)", - 'log all device ALDB status' => "Insteon::log_all_ADLB_status" + 'log all device ALDB status' => "Insteon::log_all_ADLB_status", + 'enable monitor mode' => "$object_name->enable_monitor_mode(1)", + 'disable monitor mode' => "$object_name->enable_monitor_mode(0)", ); return \%voice_cmds; }