From 2f42a0c8d3b3245cdff4cce6443ff9f5b9b2dc5a Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 9 Sep 2014 17:47:00 -0700 Subject: [PATCH 01/34] Insteon: Move Insertion of PLM ALDB Record to PLM Package This way, the record is added to the PLM cache no matter how the record was added to the PLM. This includes manual linking. --- lib/Insteon_PLM.pm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index 5b91483ae..5d03b0651 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -926,6 +926,14 @@ sub _parse_data { my $device_object = Insteon::get_object($link_address); $device_object->devcat(substr($message_data,10,4)); $device_object->firmware(substr($message_data,14,2)); + + #Insert the record into MH cache of the PLM's link table + my $data1 = substr($device_object->devcat, 0, 2); + my $data2 = substr($device_object->devcat, 2, 2); + my $data3 = $device_object->firmware; + $self->_aldb->add_link_to_hash('E2', '00', '1', $link_address, $data1, $data2, $data3); + + #Run success callback if it exists if (ref $self->active_message) { if ($self->active_message->success_callback){ main::print_log("[Insteon::Insteon_PLM] DEBUG4: Now calling message success callback: " From 95ed220eb3576bfe022269a3d18afd49cd90ee95 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 9 Sep 2014 17:57:00 -0700 Subject: [PATCH 02/34] Insteon: Better Handling of PLM Link Complete; Mark PLM ALDB Out of Sync; Handle both responder and controller links. Handle manually delete links. Mark out of sync if manually reset. --- lib/Insteon/AllLinkDatabase.pm | 9 +++++---- lib/Insteon_PLM.pm | 24 ++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/Insteon/AllLinkDatabase.pm b/lib/Insteon/AllLinkDatabase.pm index da816e5bc..9eeb529f9 100644 --- a/lib/Insteon/AllLinkDatabase.pm +++ b/lib/Insteon/AllLinkDatabase.pm @@ -2861,10 +2861,11 @@ sub add_link =item C -This is used by the C routine. -This routine manually adds a record to MH's cache of the PLM ALDB. Normally -you only want to add records during a scan of the ALDB, so use this routine -with caution. +This is used in response to an all_link_complete command received by the PLM. +This may be from the C routine, +or it may be as a result of a manual link creation. This routine manually adds +a record to MH's cache of the PLM ALDB. Normally you only want to add records +during a scan of the ALDB, so use this routine with caution. =cut diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index 5d03b0651..66ff4538a 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -931,7 +931,27 @@ sub _parse_data { my $data1 = substr($device_object->devcat, 0, 2); my $data2 = substr($device_object->devcat, 2, 2); my $data3 = $device_object->firmware; - $self->_aldb->add_link_to_hash('E2', '00', '1', $link_address, $data1, $data2, $data3); + my $type = substr($message_data,0,2); + my $group = substr($message_data,2,2); + + #Select type of link (00 - responder, 01 - master, ff - delete) + if ($type eq '00'){ + $self->_aldb->add_link_to_hash('A2', $group, '0', $link_address, $data1, $data2, $data3); + } + elsif ($type eq '01'){ + $self->_aldb->add_link_to_hash('E2', $group, '1', $link_address, $data1, $data2, $data3); + } + elsif (lc($type) eq 'ff'){ + # This is a delete request. + # The problem is that the message from the PLM + # does not identify whether the link deleted was + # a responder or controller. We could guess, b/c + # it is unlikely that d1-d3 would be identical. + # However, that seems sloppy. For the time being + # simply mark PLM aldb as unhealthy, and move on. + + $self->_aldb->health('out-of-sync'); + } #Run success callback if it exists if (ref $self->active_message) { @@ -1009,7 +1029,7 @@ sub _parse_data { &::print_log( "[Insteon_PLM] DEBUG4:\n".Insteon::MessageDecoder::plm_decode($data)) if $self->debuglevel(4, 'insteon'); main::print_log("[Insteon_PLM] Detected PLM user reset to factory defaults"); - + $self->_aldb->health('out-of-sync'); $data = substr($data, 4); } elsif ($record_type eq $prefix{all_link_clean_status} and (length($data) >= 6)) { From 5f1e35d7f621cbf5b515591992b7e8442fd9234f Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 9 Sep 2014 17:57:00 -0700 Subject: [PATCH 03/34] Insteon: Distinguish Between MH and non-MH Initiated Delete Requests --- lib/Insteon_PLM.pm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index 66ff4538a..f888f2b4e 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -949,8 +949,15 @@ sub _parse_data { # it is unlikely that d1-d3 would be identical. # However, that seems sloppy. For the time being # simply mark PLM aldb as unhealthy, and move on. - - $self->_aldb->health('out-of-sync'); + if (ref $self->active_message && $self->active_message->success_callback){ + # This is LIKELY a delete in response to a MH + # request. This is a bad way to check for + # this, but not sure what else to do. + # As a result, don't change health status + } + else { + $self->_aldb->health('out-of-sync'); + } } #Run success callback if it exists From 5fe3cc16f08da80528b25079357474a60d5a1070 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 9 Sep 2014 18:07:00 -0700 Subject: [PATCH 04/34] Insteon: Catch Software Reset ACK While not specifically enabled in the code, it may one day be, or an advanced user may somehow trigger it. --- lib/Insteon_PLM.pm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index f888f2b4e..55f167a01 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -1039,6 +1039,19 @@ sub _parse_data { $self->_aldb->health('out-of-sync'); $data = substr($data, 4); } + elsif ($record_type eq $prefix{plm_reset} and (length($data) >= 6)) { + &::print_log( "[Insteon_PLM] DEBUG4:\n".Insteon::MessageDecoder::plm_decode($data)) + if $self->debuglevel(4, 'insteon'); + if (substr($data,4,2) eq '06'){ + ::print_log("[Insteon_PLM] Received ACK to software reset request"); + $self->_aldb->health('out-of-sync'); + } + else { + ::print_log("[Insteon_PLM] ERROR Received NACK to software reset request"); + } + + $data = substr($data, 6); + } elsif ($record_type eq $prefix{all_link_clean_status} and (length($data) >= 6)) { #ALL-Link Cleanup Status Report my $message_data = substr($data,4,2); From 526e300f9502f8e8cc55be2cb2a571d0e099b5ab Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 10 Sep 2014 17:37:00 -0700 Subject: [PATCH 05/34] Insteon: Skip PLM Scan if Healthy During Scan All Changed --- lib/Insteon/AllLinkDatabase.pm | 1 + lib/Insteon_PLM.pm | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/Insteon/AllLinkDatabase.pm b/lib/Insteon/AllLinkDatabase.pm index 9eeb529f9..7bde210b9 100644 --- a/lib/Insteon/AllLinkDatabase.pm +++ b/lib/Insteon/AllLinkDatabase.pm @@ -2702,6 +2702,7 @@ sub _process_delete_queue_failure { push @{$$self{_delete_device_failures}}, $$self{current_delete_device}; ::print_log("[Insteon::ALDB_PLM] WARN: failure occurred when deleting orphan links from: " . $$self{current_delete_device} . ". Moving on..."); + $self->health('out-of-sync'); $self->_process_delete_queue; } diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index 55f167a01..db38a7994 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -379,12 +379,23 @@ Causes MisterHouse to scan the link table of the PLM only. sub scan_link_table { - my ($self,$callback) = @_; + my ($self,$callback, $failure, $skip_unchanged) = @_; #$$self{links} = undef; # clear out the old - $$self{aldb} = new Insteon::ALDB_PLM($self); - $$self{_mem_activity} = 'scan'; - $$self{_mem_callback} = ($callback) ? $callback : undef; - $self->_aldb->get_first_alllink(); + if ($skip_unchanged || $self->_aldb->health =~ /(empty)|(good)/) { + ::print_log('[Scan Link Tables] - PLM Link Table is good, skipping.'); + package main; + eval ($callback); + &::print_log("[Insteon_PLM] WARN1: Error encountered during scan callback: " . $@) + if $@ and $self->debuglevel(1, 'insteon'); + package Insteon_PLM; + } + else { + #ALDB Cache is unhealthy, or scan forced + $$self{aldb} = new Insteon::ALDB_PLM($self); + $$self{_mem_activity} = 'scan'; + $$self{_mem_callback} = ($callback) ? $callback : undef; + $self->_aldb->get_first_alllink(); + } } =item C From 51d9878a15d5b19817a7e030f19dae8e77b79fed Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 10 Sep 2014 17:57:00 -0700 Subject: [PATCH 06/34] Insteon: Change the Nomeclature of ALDB Health It was confusing to list both links and link tables as "out-of-sync." At least for link tables, the likely command would be "sync ...." but in fact it is "scan ...". Now, link tables can be unknown|empty|changed|unchanged. This fits with the prior voice command "scan changed link tables." Hopefully this will lead to less confusion. --- lib/Insteon/AllLinkDatabase.pm | 58 ++++++++++++++++------------------ lib/Insteon/BaseInsteon.pm | 18 +++++------ lib/Insteon_PLM.pm | 14 ++++---- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/lib/Insteon/AllLinkDatabase.pm b/lib/Insteon/AllLinkDatabase.pm index 7bde210b9..27d630eef 100644 --- a/lib/Insteon/AllLinkDatabase.pm +++ b/lib/Insteon/AllLinkDatabase.pm @@ -63,7 +63,7 @@ sub aldb_version return $$self{aldb_version}; } -=item C +=item C Used to track the health of MisterHouse's copy of a device's ALDB. @@ -82,11 +82,7 @@ sub health =item C -Used to track the health of MisterHouse's copy of a device's ALDB. - -If provided, saves status to memory. - -Returns the saved health status. +Returns the key in the ALDB hash that identifies this link. =cut @@ -164,9 +160,9 @@ sub query_aldb_delta { my ($self, $action) = @_; $$self{aldb_delta_action} = $action; - if ($action eq "check" && $self->health ne "good" && $self->health ne "empty"){ + if ($action eq "check" && $self->health ne "unchanged" && $self->health ne "empty"){ &::print_log("[Insteon::AllLinkDatabase] WARN The link table for " - . $self->{device}->get_object_name . " is out-of-sync."); + . $self->{device}->get_object_name . " has changed."); if (defined $self->{_aldb_changed_callback}) { package main; my $callback = $self->{_aldb_changed_callback}; @@ -314,7 +310,7 @@ sub scan_link_table $$self{_mem_activity} = 'scan'; $$self{_success_callback} = ($success_callback) ? $success_callback : undef; $$self{_failure_callback} = ($failure_callback) ? $failure_callback : undef; - $self->health('out-of-sync'); # allow acknowledge to set otherwise + $self->health('changed'); # allow acknowledge to set otherwise if($self->isa('Insteon::ALDB_i1')) { $self->_peek('0FF8',0); } else { @@ -441,7 +437,7 @@ sub delete_orphan_links 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 && $is_batch_mode)) { + if ($self->health ne 'unchanged' || ($$self{device}->is_deaf && $is_batch_mode)) { my $sent_to_failure = 0; if ($$self{device}->is_deaf) { ::print_log("[Insteon::AllLinkDatabase] Will not delete ". @@ -452,7 +448,7 @@ sub delete_orphan_links ::print_log("[Insteon::AllLinkDatabase] Skipping $selfname, because it has no links"); } else { - ::print_log("[Insteon::AllLinkDatabase] Delete orphan links: skipping $selfname because health: " + ::print_log("[Insteon::AllLinkDatabase] Delete orphan links: skipping $selfname because the link table is: " . $self->health . ". Please rescan the link table of this device and rerun delete " . "orphans if necessary"); #log the failure @@ -607,10 +603,10 @@ sub delete_orphan_links } # Do not delete links to unhealthy devices - if ($linked_device->_aldb->health ne 'good' && $linked_device->_aldb->health ne 'empty') { + if ($linked_device->_aldb->health ne 'unchanged' && $linked_device->_aldb->health ne 'empty') { $delete_req{skip} = "$selfname -- Skipping check for reciprocal links on " - . $linked_device->get_object_name . " because aldb health of that device is " - . $linked_device->_aldb->health . ". Please rescan this device."; + . $linked_device->get_object_name . " because link table of that device has " + . $linked_device->_aldb->health . ". Please rescan the link table on this device."; next LINKKEY; } @@ -1047,7 +1043,7 @@ sub log_alllink_table &::print_log("[Insteon::AllLinkDatabase] Link table for " . $$self{device}->get_object_name - . " health: " . $self->health); + . " is: " . $self->health); # We want to log links sorted by ALDB address. Since the ALDB # addresses are scattered throughout the %{$$self{aldb}} hash, @@ -1084,7 +1080,7 @@ sub log_alllink_table } # Finally traverse the ALDB, but this time sorted by ALDB address - if ($self->health eq 'good') + if ($self->health eq 'unchanged') { foreach my $address (sort keys %aldb) { @@ -1164,7 +1160,7 @@ sub log_alllink_table } else { - main::print_log("[Insteon::AllLinkDatabase] ALDB is ".$self->health." and will not be listed"); + main::print_log("[Insteon::AllLinkDatabase] The link table is ".$self->health." and will not be listed"); } } @@ -1334,11 +1330,11 @@ sub _on_poke $$self{aldb}{$aldbkey}{deviceid} = lc $$self{pending_aldb}{deviceid}; $$self{aldb}{$aldbkey}{group} = lc $$self{pending_aldb}{group}; $$self{aldb}{$aldbkey}{address} = $$self{pending_aldb}{address}; - $self->health("good"); + $self->health("unchanged"); } # clear out mem_activity flag $$self{_mem_activity} = undef; - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } @@ -1388,7 +1384,7 @@ sub _on_poke $$self{pending_aldb}{data3}); delete $$self{aldb}{$key}; } - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } @@ -1502,12 +1498,12 @@ sub _on_peek } else { - $self->health("good"); + $self->health("unchanged"); } &::print_log("[Insteon::ALDB_i1] " . $$self{device}->get_object_name . " completed link memory scan") if $self->{device}->debuglevel(1, 'insteon'); - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } @@ -2138,13 +2134,13 @@ sub on_read_write_aldb } else { - $self->health("good"); + $self->health("unchanged"); } &::print_log("[Insteon::ALDB_i2] " . $$self{device}->get_object_name - . " completed link memory scan: status: " . $self->health()) + . " completed link memory scan. Status: " . $self->health()) if $self->{device}->debuglevel(1, 'insteon'); - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } @@ -2218,7 +2214,7 @@ sub on_read_write_aldb main::print_log("[Insteon::ALDB_i2] DEBUG3: " . $$self{device}->get_object_name . " link write completed for [".$$self{aldb}{$aldbkey}{address}."]") if $self->{device}->debuglevel(3, 'insteon'); - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } else { @@ -2236,7 +2232,7 @@ sub on_read_write_aldb $$self{pending_aldb}{data3}); delete $$self{aldb}{$key}; } - $self->health("good"); + $self->health("unchanged"); # Put the new ALDB Delta into memory $self->query_aldb_delta('set'); } @@ -2604,7 +2600,7 @@ Sends the request for the first alllink entry on the PLM. sub get_first_alllink { my ($self) = @_; - $self->health('out-of-sync'); # set as corrupt and allow acknowledge to set otherwise + $self->health('changed'); # set as corrupt and allow acknowledge to set otherwise $$self{device}->queue_message(new Insteon::InsteonMessage('all_link_first_rec', $$self{device})); } @@ -2702,7 +2698,7 @@ sub _process_delete_queue_failure { push @{$$self{_delete_device_failures}}, $$self{current_delete_device}; ::print_log("[Insteon::ALDB_PLM] WARN: failure occurred when deleting orphan links from: " . $$self{current_delete_device} . ". Moving on..."); - $self->health('out-of-sync'); + $self->health('changed'); $self->_process_delete_queue; } @@ -2850,7 +2846,7 @@ sub add_link $$self{aldb}{$linkkey}{data2} = lc $data2; $$self{aldb}{$linkkey}{data3} = lc $data3; $$self{aldb}{$linkkey}{inuse} = 1; - $self->health('good') if($self->health() eq 'empty'); + $self->health('unchanged') if($self->health() eq 'empty'); my $message = new Insteon::InsteonMessage('all_link_manage_rec', $$self{device}); $message->interface_data($cmd); $$self{_success_callback} = ($link_parms{callback}) ? $link_parms{callback} : undef; @@ -2882,7 +2878,7 @@ sub add_link_to_hash { $$self{aldb}{$linkkey}{data2} = lc $data2; $$self{aldb}{$linkkey}{data3} = lc $data3; $$self{aldb}{$linkkey}{inuse} = 1; - $self->health('good') if($self->health() eq 'empty'); + $self->health('unchanged') if($self->health() eq 'empty'); return; } diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index d512267d0..66de39105 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -707,7 +707,7 @@ sub _is_info_request { if ($self->_aldb->aldb_delta() eq $msg{cmd_code}){ &::print_log("[Insteon::BaseObject] The link table for " - . $self->{object_name} . " is in sync."); + . $self->{object_name} . " is unchanged."); #Link Table Scan Successful, Record Current Time $self->_aldb->scandatetime(&main::get_tickcount); if (defined $self->_aldb->{_aldb_unchanged_callback}) { @@ -716,8 +716,8 @@ sub _is_info_request } } else { &::print_log("[Insteon::BaseObject] WARN The link table for " - . $self->{object_name} . " is out-of-sync."); - $self->_aldb->health('out-of-sync'); + . $self->{object_name} . " has changed."); + $self->_aldb->health('changed'); if (defined $self->_aldb->{_aldb_changed_callback}) { $callback = $self->_aldb->{_aldb_changed_callback}; $self->_aldb->{_aldb_changed_callback} = undef; @@ -725,7 +725,7 @@ sub _is_info_request } } $self->_aldb->{aldb_delta_action} = undef; - $self->_aldb->health('out-of-sync') if($self->_aldb->aldb_delta() ne $msg{cmd_code}); + $self->_aldb->health('changed') if($self->_aldb->aldb_delta() ne $msg{cmd_code}); if ($callback){ package main; eval ($callback); @@ -841,7 +841,7 @@ sub _process_message else { if (($pending_cmd eq 'do_read_ee') && - ($self->_aldb->health eq "good" || $self->_aldb->health eq "empty") && + ($self->_aldb->health eq "unchanged" || $self->_aldb->health eq "empty") && ($self->isa('Insteon::KeyPadLincRelay') || $self->isa('Insteon::KeyPadLinc'))){ ## Update_Flags ends up here, set aldb_delta to new value $self->_aldb->query_aldb_delta("set"); @@ -3172,7 +3172,7 @@ sub 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'){ + elsif ($insteon_object->_aldb->health ne 'unchanged' && $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 " @@ -3244,7 +3244,7 @@ sub sync_links # 3. Does the responder link exist if (!$member_root->has_link($insteon_object, $self->group, 0, $member->group) && - ($member_root->_aldb->health eq 'good' || $member_root->_aldb->health eq 'empty')){ + ($member_root->_aldb->health eq 'unchanged' || $member_root->_aldb->health eq 'empty')){ my %link_req = ( member => $member, cmd => 'add', object => $insteon_object, group => $self->group, is_controller => 0, on_level => $tgt_on_level, ramp_rate => $tgt_ramp_rate, @@ -3255,7 +3255,7 @@ sub sync_links push @{$$self{sync_queue}}, \%link_req; $has_link = 0; } - elsif ($member_root->_aldb->health ne 'good' && $member_root->_aldb->health ne 'empty'){ + elsif ($member_root->_aldb->health ne 'unchanged' && $member_root->_aldb->health ne 'empty'){ my %link_req = ( member => $member, cmd => 'add', object => $insteon_object, group => $self->group, is_controller => 0, on_level => $tgt_on_level, ramp_rate => $tgt_ramp_rate, @@ -3306,7 +3306,7 @@ sub sync_links } } if ($requires_update && - ($member_root->_aldb->health eq 'good' || $member_root->_aldb->health eq 'empty')) { + ($member_root->_aldb->health eq 'unchanged' || $member_root->_aldb->health eq 'empty')) { my %link_req = ( member => $member, cmd => 'update', object => $insteon_object, group => $self->group, is_controller => 0, on_level => $tgt_on_level, ramp_rate => $tgt_ramp_rate, diff --git a/lib/Insteon_PLM.pm b/lib/Insteon_PLM.pm index db38a7994..d775d280e 100644 --- a/lib/Insteon_PLM.pm +++ b/lib/Insteon_PLM.pm @@ -381,8 +381,8 @@ sub scan_link_table { my ($self,$callback, $failure, $skip_unchanged) = @_; #$$self{links} = undef; # clear out the old - if ($skip_unchanged || $self->_aldb->health =~ /(empty)|(good)/) { - ::print_log('[Scan Link Tables] - PLM Link Table is good, skipping.'); + if ($skip_unchanged && $self->_aldb->health =~ /(empty)|(unchanged)/) { + ::print_log('[Scan Link Tables] - PLM Link Table is unchanged, skipping.'); package main; eval ($callback); &::print_log("[Insteon_PLM] WARN1: Error encountered during scan callback: " . $@) @@ -772,11 +772,11 @@ sub _parse_data { $self->_aldb->health("empty"); } else { - $self->_aldb->health("good"); + $self->_aldb->health("unchanged"); } $self->_aldb->scandatetime(&main::get_tickcount); &::print_log("[Insteon_PLM] " . $self->get_object_name - . " completed link memory scan: status: " . $self->_aldb->health()) + . " completed link memory scan. Status: " . $self->_aldb->health()) if $self->debuglevel(1, 'insteon'); if ($$self{_mem_callback}) { my $callback = $$self{_mem_callback}; @@ -967,7 +967,7 @@ sub _parse_data { # As a result, don't change health status } else { - $self->_aldb->health('out-of-sync'); + $self->_aldb->health('changed'); } } @@ -1047,7 +1047,7 @@ sub _parse_data { &::print_log( "[Insteon_PLM] DEBUG4:\n".Insteon::MessageDecoder::plm_decode($data)) if $self->debuglevel(4, 'insteon'); main::print_log("[Insteon_PLM] Detected PLM user reset to factory defaults"); - $self->_aldb->health('out-of-sync'); + $self->_aldb->health('changed'); $data = substr($data, 4); } elsif ($record_type eq $prefix{plm_reset} and (length($data) >= 6)) { @@ -1055,7 +1055,7 @@ sub _parse_data { if $self->debuglevel(4, 'insteon'); if (substr($data,4,2) eq '06'){ ::print_log("[Insteon_PLM] Received ACK to software reset request"); - $self->_aldb->health('out-of-sync'); + $self->_aldb->health('changed'); } else { ::print_log("[Insteon_PLM] ERROR Received NACK to software reset request"); From f04339e6d95e715bf5d1390fa1b64d7b55ac61be Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 10 Sep 2014 17:57:00 -0700 Subject: [PATCH 07/34] Insteon: Rename PLM Voice Commands for Comprehension Added a prefix to distinguish PLM/Global voice commands. Changed the name of 'scan all device link tables' to 'force scan...' to try and underscore that it is not a routine function --- lib/Insteon/BaseInterface.pm | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index cb49b427d..034b67fc5 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -1007,25 +1007,25 @@ sub get_voice_cmds my ($self) = @_; my $object_name = $self->get_object_name; my %voice_cmds = ( - 'complete linking as responder' => "$object_name->complete_linking_as_responder", - 'initiate linking as controller' => "$object_name->initiate_linking_as_controller", - 'initiate unlinking' => "$object_name->initiate_unlinking_as_controller", - 'cancel linking' => "$object_name->cancel_linking", - 'log links' => "$object_name->log_alllink_table", - 'scan link table' => "$object_name->scan_link_table(\"" . '\$self->log_alllink_table' . "\")", - 'scan changed device link tables' => "Insteon::scan_all_linktables(1)", - 'delete orphan links' => "$object_name->delete_orphan_links", - 'AUDIT - delete orphan links' => "$object_name->delete_orphan_links(1)", - 'scan all device link tables' => "Insteon::scan_all_linktables", - 'sync all links' => "Insteon::sync_all_links(0)", - 'AUDIT - sync all links' => "Insteon::sync_all_links(1)", - 'print all message stats' => "Insteon::print_all_message_stats", - '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", - 'enable monitor mode' => "$object_name->enable_monitor_mode(1)", - 'disable monitor mode' => "$object_name->enable_monitor_mode(0)", + 'plm - complete linking as responder' => "$object_name->complete_linking_as_responder", + 'plm - initiate linking as controller' => "$object_name->initiate_linking_as_controller", + 'plm - initiate unlinking' => "$object_name->initiate_unlinking_as_controller", + 'plm - cancel linking' => "$object_name->cancel_linking", + 'plm - log links' => "$object_name->log_alllink_table", + 'plm - scan PLM link table' => "$object_name->scan_link_table(\"" . '\$self->log_alllink_table' . "\")", + 'plm - scan changed device link tables' => "Insteon::scan_all_linktables(1)", + 'global - delete orphan links' => "$object_name->delete_orphan_links", + 'global - audit delete orphan links' => "$object_name->delete_orphan_links(1)", + 'global - force scan all device link tables' => "Insteon::scan_all_linktables", + 'global - sync all links' => "Insteon::sync_all_links(0)", + 'global - audit sync all links' => "Insteon::sync_all_links(1)", + 'global - print all message stats' => "Insteon::print_all_message_stats", + 'global - reset all message stats' => "Insteon::reset_all_message_stats", + 'global - stress test ALL devices' => "Insteon::stress_test_all(5,1)", + 'global - ping test ALL devices' => "Insteon::ping_all(5)", + 'global - log all device ALDB status' => "Insteon::log_all_ADLB_status", + 'plm - enable monitor mode' => "$object_name->enable_monitor_mode(1)", + 'plm - disable monitor mode' => "$object_name->enable_monitor_mode(0)", ); return \%voice_cmds; } From 64a10360c76022b1030d7c18d007db2dd799dd54 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Mon, 15 Sep 2014 17:57:00 -0700 Subject: [PATCH 08/34] Insteon: Fix a Few Instances of Sync->Unchanged; Fix Global PLM Voice Command Reference --- lib/Insteon/AllLinkDatabase.pm | 8 ++++---- lib/Insteon/BaseInterface.pm | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Insteon/AllLinkDatabase.pm b/lib/Insteon/AllLinkDatabase.pm index 27d630eef..49c20fb3b 100644 --- a/lib/Insteon/AllLinkDatabase.pm +++ b/lib/Insteon/AllLinkDatabase.pm @@ -175,7 +175,7 @@ sub query_aldb_delta } elsif ($action eq "check" && ((&main::get_tickcount - $self->scandatetime()) <= 2000)){ #if we just did a aldb_query less than 2 seconds ago, don't repeat &::print_log("[Insteon::AllLinkDatabase] The link table for " - . $self->{device}->get_object_name . " is in sync."); + . $self->{device}->get_object_name . " is unchanged."); #Further extend Scan Time in case of serial aldb requests $self->scandatetime(&main::get_tickcount); if (defined $self->{_aldb_unchanged_callback}) { @@ -345,7 +345,7 @@ sub delete_link $$self{_success_callback} = ($link_parms{callback}) ? $link_parms{callback} : undef; $$self{_failure_callback} = ($link_parms{failure_callback}) ? $link_parms{failure_callback} : undef; if (!defined($link_parms{aldb_check}) && (!$$self{device}->isa('Insteon_PLM'))){ - ## Check whether ALDB is in sync + ## Check whether ALDB has changed $self->{callback_parms} = \%link_parms; $$self{_aldb_unchanged_callback} = '&Insteon::AllLinkDatabase::delete_link('.$$self{device}->{object_name}."->_aldb, 'ok')"; $$self{_aldb_changed_callback} = '&Insteon::AllLinkDatabase::delete_link('.$$self{device}->{object_name}."->_aldb, 'fail')"; @@ -866,7 +866,7 @@ sub add_link $$self{_success_callback} = ($link_parms{callback}) ? $link_parms{callback} : undef; $$self{_failure_callback} = ($link_parms{failure_callback}) ? $link_parms{failure_callback} : undef; if (!defined($link_parms{aldb_check}) && (!$$self{device}->isa('Insteon_PLM'))){ - ## Check whether ALDB is in sync + ## Check whether ALDB has changed $self->{callback_parms} = \%link_parms; $$self{_aldb_unchanged_callback} = '&Insteon::AllLinkDatabase::add_link('.$$self{device}->{object_name}."->_aldb, 'ok')"; $$self{_aldb_changed_callback} = '&Insteon::AllLinkDatabase::add_link('.$$self{device}->{object_name}."->_aldb, 'fail')"; @@ -991,7 +991,7 @@ sub update_link my $deviceid = $insteon_object->device_id; my $key = $self->get_linkkey($deviceid, $group, $is_controller, $data3); if (!defined($link_parms{aldb_check}) && (!$$self{device}->isa('Insteon_PLM'))){ - ## Check whether ALDB is in sync + ## Check whether ALDB has changed $self->{callback_parms} = \%link_parms; $$self{_aldb_unchanged_callback} = '&Insteon::AllLinkDatabase::update_link('.$$self{device}->{object_name}."->_aldb, 'ok')"; $$self{_aldb_changed_callback} = '&Insteon::AllLinkDatabase::update_link('.$$self{device}->{object_name}."->_aldb, 'fail')"; diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index 034b67fc5..96898571a 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -1013,7 +1013,7 @@ sub get_voice_cmds 'plm - cancel linking' => "$object_name->cancel_linking", 'plm - log links' => "$object_name->log_alllink_table", 'plm - scan PLM link table' => "$object_name->scan_link_table(\"" . '\$self->log_alllink_table' . "\")", - 'plm - scan changed device link tables' => "Insteon::scan_all_linktables(1)", + 'global - scan changed device link tables' => "Insteon::scan_all_linktables(1)", 'global - delete orphan links' => "$object_name->delete_orphan_links", 'global - audit delete orphan links' => "$object_name->delete_orphan_links(1)", 'global - force scan all device link tables' => "Insteon::scan_all_linktables", From 44d2a9e40748d6c278639a2ae4bce5b14d0d7419 Mon Sep 17 00:00:00 2001 From: Lieven Hollevoet Date: Sun, 22 Mar 2015 21:05:51 +0100 Subject: [PATCH 09/34] Added first support for Lux lights. Todo: add proper dimming support that automatically works in the web interface too. --- lib/Philips_Hue.pm | 52 +++++++++++++++++++++++++++++++++++++++++++++ lib/read_table_A.pl | 13 ++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/Philips_Hue.pm b/lib/Philips_Hue.pm index 4dd81f218..1819317e0 100644 --- a/lib/Philips_Hue.pm +++ b/lib/Philips_Hue.pm @@ -276,4 +276,56 @@ sub _calc_bri_command # #} + +=head1 B + +=head2 SYNOPSIS + +Support for the Philips Lux devices + +=head2 DESCRIPTION + +This module inherits from Philips_Hue and disables the features that are not available on a Lux light. Basically this means everything that has to do with color settings. + +=cut + +package Philips_Lux; + +@Philips_Lux::ISA = ('Philips_Hue'); + +sub effect +{ + my $self = shift(); + + return; + +} + + +sub ct_k +{ + my ($self, $value) = @_; + + ::print_log('hue', "Setting color temperature not supported on Lux light"); + + return; + +} + +sub hs +{ + my ($self, $hue, $sat) = @_; + + ::print_log('hue', "Setting hue and saturation not supported on Lux light"); + +} + +sub hsb +{ + my ($self, $hue, $sat, $bri) = @_; + + ::print_log('hue', "Setting hue, saturation and brightness not supported on Lux light"); + +} + 1; diff --git a/lib/read_table_A.pl b/lib/read_table_A.pl index bf04f3e5f..2aa5ffc59 100644 --- a/lib/read_table_A.pl +++ b/lib/read_table_A.pl @@ -1103,6 +1103,19 @@ sub read_table_A { $code .= "use Philips_Hue;\n"; } } + elsif ($type eq "PHILIPS_LUX"){ + ($address, $name, $grouplist, @other) = @item_info; + $other = join ', ', (map {"'$_'"} @other); # Quote data + if($other){ + $object = "Philips_Lux('$address',$other)"; + } + else{ + $object = "Philips_Lux('$address')"; + } + if( ! $packages{Philips_Hue}++ ) { # first time for this object type? + $code .= "use Philips_Hue;\n"; + } + } #-------------- AD2 Objects ----------------- elsif($type eq "AD2_INTERFACE") { require AD2; From dc6f6cedcc017d3188d9ca5f3f78f7a03dc2c67e Mon Sep 17 00:00:00 2001 From: Lieven Hollevoet Date: Sun, 22 Mar 2015 22:05:37 +0100 Subject: [PATCH 10/34] Bugfix: in case the light state is not defined, the set off command causes a loop of sending on/off commands --- lib/Philips_Hue.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Philips_Hue.pm b/lib/Philips_Hue.pm index 1819317e0..786c22ac8 100644 --- a/lib/Philips_Hue.pm +++ b/lib/Philips_Hue.pm @@ -164,6 +164,9 @@ sub effect ::print_log('hue', "Effect '$effect' request, current lamp state is $light_state"); + # Do not continue if state is undefined to avoid loops. + return if ($light_state eq ""); + # Light needs to be on to be able to program an effect $self->set('on'); From 7e39a29236afd34d5aea8f4e36a9e9bc7db4d7ac Mon Sep 17 00:00:00 2001 From: Lieven Hollevoet Date: Mon, 23 Mar 2015 20:31:10 +0100 Subject: [PATCH 11/34] Add a few predefined dim levels for the web interface --- lib/Philips_Hue.pm | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/Philips_Hue.pm b/lib/Philips_Hue.pm index 786c22ac8..b7653960d 100644 --- a/lib/Philips_Hue.pm +++ b/lib/Philips_Hue.pm @@ -118,7 +118,7 @@ sub new { $$self{hue} = new Device::Hue('bridge' => $$self{gateway}, 'key' => $$self{apikey}, 'debug' => 0); $$self{light} = $$self{hue}->light($$self{lamp_id}); - $self->addStates ('on', 'off'); + $self->addStates ('on', 'off', '20%', '40%', '60%', '80%'); return $self; } @@ -139,6 +139,7 @@ sub default_setstate my ($self, $state, $substate, $set_by) = @_; my $cmnd = ($state =~ /^off/i) ? 'off' : 'on'; + $cmnd = $state if ($state =~ /\%/); return -1 if ($self->state eq $state); # Don't propagate state unless it has changed. @@ -150,8 +151,14 @@ sub default_setstate $self->effect('none'); } - $$self{light}->$cmnd; - + if ($cmnd =~ /(\d+)\%/) { + ::print_log('hue', "Sending brightness $1"); + $self->bri($1); + } else { + ::print_log('hue', "Sending command $cmnd"); + $$self{light}->$cmnd; + } + return; } From a118c69eaaee9286dc2fb8c8aa0a5f3442d4d254 Mon Sep 17 00:00:00 2001 From: Lieven Hollevoet Date: Mon, 23 Mar 2015 21:01:32 +0100 Subject: [PATCH 12/34] Another fix to avoid loops. Command needs to be sent to device directly and not through the internal MisterHouse set command --- lib/Philips_Hue.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Philips_Hue.pm b/lib/Philips_Hue.pm index b7653960d..b0d6d60a4 100644 --- a/lib/Philips_Hue.pm +++ b/lib/Philips_Hue.pm @@ -175,10 +175,10 @@ sub effect return if ($light_state eq ""); # Light needs to be on to be able to program an effect - $self->set('on'); + $$self{light}->on; # Send effect command - ::print_log('hue', "Sending effect command"); + ::print_log('hue', "Sending effect command '$effect'"); if ($effect ne 'off') { $$self{light}->set_state({'effect' => $effect}); } else { @@ -186,9 +186,9 @@ sub effect } # If the light was off and effect is none, ensure it is back off after we sent the command - if ($light_state ne 'on' && $effect ne 'on') { + if ($light_state eq 'off') { ::print_log('hue', "Restoring light state to off"); - $self->set('off'); + $$self{light}->off; } } From f4cbcc3a61f462bbf7c57d90202abbb385e2a1b2 Mon Sep 17 00:00:00 2001 From: Lieven Hollevoet Date: Mon, 23 Mar 2015 21:29:36 +0100 Subject: [PATCH 13/34] Added extra 100% state and some fixes in case an effect is in place --- lib/Philips_Hue.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Philips_Hue.pm b/lib/Philips_Hue.pm index b0d6d60a4..062e77286 100644 --- a/lib/Philips_Hue.pm +++ b/lib/Philips_Hue.pm @@ -118,7 +118,7 @@ sub new { $$self{hue} = new Device::Hue('bridge' => $$self{gateway}, 'key' => $$self{apikey}, 'debug' => 0); $$self{light} = $$self{hue}->light($$self{lamp_id}); - $self->addStates ('on', 'off', '20%', '40%', '60%', '80%'); + $self->addStates ('on', 'off', '20%', '40%', '60%', '80%', '100%'); return $self; } @@ -147,7 +147,8 @@ sub default_setstate ::print_log('hue', "Command settings: '" . $$self{gateway} . "' - '" . $$self{apikey} . "' - '" . $$self{lamp_id} . "' : '" . $cmnd. "'"); # Disable the effect commands when we turn off the light - if ($cmnd eq 'off') { + if ($cmnd eq 'off' || $cmnd eq 'on') { + ::print_log('hue', "Sending effect 'none'"); $self->effect('none'); } @@ -186,7 +187,7 @@ sub effect } # If the light was off and effect is none, ensure it is back off after we sent the command - if ($light_state eq 'off') { + if ($light_state eq 'off' && $effect eq 'none') { ::print_log('hue', "Restoring light state to off"); $$self{light}->off; } From 9234f2c636537f5c137e1ef5208f31344177ab51 Mon Sep 17 00:00:00 2001 From: "H.Plato" Date: Sat, 2 May 2015 15:35:40 -0600 Subject: [PATCH 14/34] v1.0.200 Added in a config file to provide easier customization. Enable/disable header button, object log, and number of log entries Look for a mh.ini param ia7_data_dir which is a user defined override for collections.json and ia7_config.json --- data/web/ia7_config.json | 8 +++++++ lib/json_server.pl | 22 ++++++++++++++++++- web/ia7/house/main.shtml | 2 +- web/ia7/include/javascript.js | 41 ++++++++++++++++++++++++++--------- 4 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 data/web/ia7_config.json diff --git a/data/web/ia7_config.json b/data/web/ia7_config.json new file mode 100644 index 000000000..fac02f5b4 --- /dev/null +++ b/data/web/ia7_config.json @@ -0,0 +1,8 @@ +{ + "prefs" : { + "header_button" : "yes", + "state_log_show" : "yes", + "state_log_entries" : "4", + "always_double_buttons" : "no" + } +} \ No newline at end of file diff --git a/lib/json_server.pl b/lib/json_server.pl index fd7403a7a..67ce4f180 100644 --- a/lib/json_server.pl +++ b/lib/json_server.pl @@ -162,7 +162,8 @@ sub json_get { my $collection_file = "$Pgm_Root/data/web/collections.json"; $collection_file = "$config_parms{data_dir}/web/collections.json" if -e "$config_parms{data_dir}/web/collections.json"; - # Consider copying the source file to the user data dir here. + $collection_file = "$config_parms{ia7_data_dir}/collections.json" + if -e "$config_parms{ia7_data_dir}/collections.json"; eval { my $json_collections = file_read($collection_file); @@ -175,6 +176,25 @@ sub json_get { } } + # List ia7 preferences + if ($path[0] eq 'ia7_config' || $path[0] eq '') { + my $prefs_file = "$Pgm_Root/data/web/ia7_config.json"; + $prefs_file = "$config_parms{data_dir}/web/ia7_config.json" + if -e "$config_parms{data_dir}/web/ia7_config.json"; + $prefs_file = "$config_parms{ia7_data_dir}/ia7_config.json" + if -e "$config_parms{ia7_data_dir}/ia7_config.json"; + + eval { + my $prefs = file_read($prefs_file); + $json_data{'ia7_config'} = decode_json($prefs); #HP, wrap this in eval to prevent MH crashes + }; + if ($@){ + print_log "Json_Server.pl: WARNING: decode_json failed for ia7_config.json. Please check this file!"; + $json_data{'ia7_config'} = decode_json('{ "prefs" : { "status" : "error" } }'); #write a blank collection + + } + } + # List objects if ($path[0] eq 'objects' || $path[0] eq '') { $json_data{objects} = {}; diff --git a/web/ia7/house/main.shtml b/web/ia7/house/main.shtml index bf50bd1ad..107e99e33 100644 --- a/web/ia7/house/main.shtml +++ b/web/ia7/house/main.shtml @@ -72,7 +72,7 @@

MisterHouse was created by Bruce Winter. Ron Klinkien developed the v2.3 web interface. Kevin Robert Keegan - developed the v4 IA7 web interface, updates by H.Plato. IA7 v1.0.100

+ developed the v4 IA7 web interface, updates by H.Plato. IA7 v1.0.200

\ No newline at end of file diff --git a/web/ia7/include/javascript.js b/web/ia7/include/javascript.js index 09827b304..efc10f15b 100644 --- a/web/ia7/include/javascript.js +++ b/web/ia7/include/javascript.js @@ -123,6 +123,26 @@ function changePage (){ // collections URLHash.path = "collections"; } + if (getJSONDataByPath("ia7_config") === undefined){ + // We need at minimum the basic collections data to render all pages + // (the breadcrumb) + // NOTE may want to think about how to handle dynamic changes to the + // collections list + $.ajax({ + type: "GET", + url: "/json/ia7_config", + dataType: "json", + success: function( json ) { + JSONStore(json); + changePage(); + } + }); + } else { + if (json_store.ia7_config.prefs.header_button == "no") { + //console.log("remove header button"); + $("#mhstatus").remove(); + } + } if (getJSONDataByPath("collections") === undefined){ // We need at minimum the basic collections data to render all pages // (the breadcrumb) @@ -1096,17 +1116,18 @@ var create_state_modal = function(entity) { //remove states from anything that doesn't have more than 1 state $('#control').find('.states').find('.btn-group').remove(); } - //state log show last 4 (separate out set_by as advanced) - keeps being added to each time it opens - // could load all log items, and only unhide the last 4 -- maybe later - $('#control').find('.modal-body').find('.obj_log').remove(); + if (json_store.ia7_config.prefs.state_log_show !== "no") { + //state log show last 4 (separate out set_by as advanced) - keeps being added to each time it opens + // could load all log items, and only unhide the last 4 -- maybe later + $('#control').find('.modal-body').find('.obj_log').remove(); - $('#control').find('.modal-body').append("

Object Log

"); - for (var i = 0; i < 4; i++) { - if (json_store.objects[entity].state_log[i] == undefined) continue; - var slog = json_store.objects[entity].state_log[i].split("set_by="); - $('#control').find('.obj_log').append(slog[0]+"
"); - } - + $('#control').find('.modal-body').append("

Object Log

"); + for (var i = 0; i < json_store.ia7_config.prefs.state_log_entries; i++) { + if (json_store.objects[entity].state_log[i] == undefined) continue; + var slog = json_store.objects[entity].state_log[i].split("set_by="); + $('#control').find('.obj_log').append(slog[0]+"
"); + } + } $('.mhstatemode').on('click', function(){ $('#control').find('.states').find('.btn').removeClass('hidden'); $('#control').find('.mh_set_by').removeClass('hidden'); From 78513f3cdc9e45c9572a3274a9fc8ee326d4e193 Mon Sep 17 00:00:00 2001 From: "H.Plato" Date: Sat, 2 May 2015 15:37:50 -0600 Subject: [PATCH 15/34] Removed hardcoded version 4.0 from main.html. Would still like to figure out a way for v4 version details to show up for those of us that will be running master after stable is released --- web/ia7/house/main.shtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ia7/house/main.shtml b/web/ia7/house/main.shtml index 107e99e33..bd8bf5c32 100644 --- a/web/ia7/house/main.shtml +++ b/web/ia7/house/main.shtml @@ -1,7 +1,7 @@
-

Version: 4.0
+

Version:
Modified:
OS:
Perl:
From ea5c45bf94db7029090d97682cfdd37b401998ee Mon Sep 17 00:00:00 2001 From: "H.Plato" Date: Sun, 3 May 2015 20:02:40 -0600 Subject: [PATCH 16/34] Big update, added ability to add a controllable item to the collection screen Scaled buttons up a little bit such that collection button = 2x item button Added a ia7_config item to make double button the default --- web/ia7/house/main.shtml | 2 +- web/ia7/include/javascript.js | 131 ++++++++++++++++++++++++---------- web/ia7/index.shtml | 7 +- 3 files changed, 102 insertions(+), 38 deletions(-) diff --git a/web/ia7/house/main.shtml b/web/ia7/house/main.shtml index bd8bf5c32..51e47da1f 100644 --- a/web/ia7/house/main.shtml +++ b/web/ia7/house/main.shtml @@ -72,7 +72,7 @@

MisterHouse was created by Bruce Winter. Ron Klinkien developed the v2.3 web interface. Kevin Robert Keegan - developed the v4 IA7 web interface, updates by H.Plato. IA7 v1.0.200

+ developed the v4 IA7 web interface, updates by H.Plato. IA7 v1.0.300

\ No newline at end of file diff --git a/web/ia7/include/javascript.js b/web/ia7/include/javascript.js index efc10f15b..e148a991b 100644 --- a/web/ia7/include/javascript.js +++ b/web/ia7/include/javascript.js @@ -1,8 +1,6 @@ // Optimization opportunity -// common code for setting states, should move to subroutine -// Should be able to merge print_log, print_speaklog and the non-existant print_errorlog into a -// single method -// updateStaticPage has lots of copy paste +// add print_errorlog +// updateStaticPage has lots of copy paste var entity_store = {}; //global storage of entities @@ -138,10 +136,11 @@ function changePage (){ } }); } else { + //console.log("x "+json_store.ia7_config.prefs.substate_percentages); if (json_store.ia7_config.prefs.header_button == "no") { - //console.log("remove header button"); $("#mhstatus").remove(); } + if (json_store.ia7_config.prefs.substate_percentages === undefined) json_store.ia7_config.prefs.substate_percentages = 20; } if (getJSONDataByPath("collections") === undefined){ // We need at minimum the basic collections data to render all pages @@ -427,9 +426,13 @@ var loadList = function() { var color = getButtonColor(json_store.objects[entity].state); if (json_store.objects[entity].label !== undefined) name = json_store.objects[entity].label; //Put objects into button + var dbl_btn = ""; + if (json_store.ia7_config.prefs.always_double_buttons == "yes") { + if (name.length < 30) dbl_btn = "
"; + } button_html = "
"; + button_html += name+dbl_btn+""+json_store.objects[entity].state+"
"; entity_arr.push(button_html); } }//entity each loop @@ -503,7 +506,7 @@ var filterSubstate = function (state) { if (state.indexOf('%') >= 0) { var number = parseInt(state, 10) - if (number % 20 != 0) { + if (number % json_store.ia7_config.prefs.substate_percentages != 0) { filter = 1 } } @@ -622,13 +625,10 @@ var updateItem = function(item,link,time) { if (time === undefined) { time = ""; } - var split_path = HashtoJSONArgs(URLHash).split("?"); - var path_str = split_path[0]; - var arg_str = split_path[1]; - path_str = "/objects" // override, for now, would be good to add voice_cmds + var path_str = "/objects" // override, for now, would be good to add voice_cmds //arg_str=link=%2Fia7%2Fhouse%2Fgarage.shtml&fields=state%2Ctype&long_poll=true&time=1426011733833.94 //arg_str = "fields=state,states,label&long_poll=true&time="+time; - arg_str = "fields=state&long_poll=true&items="+item+"&time="+time; + var arg_str = "fields=state,states,label,state_log&long_poll=true&items="+item+"&time="+time; //alert("path_str="+path_str+" arg_str="+arg_str) updateSocket = $.ajax({ type: "GET", @@ -741,6 +741,7 @@ var loadCollection = function(collection_keys) { var collection_keys_arr = collection_keys.split(","); var last_collection_key = collection_keys_arr[collection_keys_arr.length-1]; var entity_arr = []; + var items = ""; var entity_sort = json_store.collections[last_collection_key].children; if (entity_sort.length <= 0){ entity_arr.push("Childless Collection"); @@ -753,28 +754,57 @@ var loadCollection = function(collection_keys) { var name = json_store.collections[collection].name; var mode = json_store.collections[collection].mode; var keys = json_store.collections[collection].keys; //for legacy CGI scripts to recreate proper URL + var item = json_store.collections[collection].item; - if (json_store.collections[collection].iframe !== undefined) { - link = "/ia7/include/iframe.shtml?"+json_store.collections[collection].iframe; - } - var hidden = ""; - if (mode != display_mode && mode != undefined ) hidden = "hidden"; //Hide any simple/advanced buttons - var next_collection_keys = collection_keys + "," + entity_sort[i]; - if (keys === "true") { - var arg = "?"; - if (link.indexOf("?") >= 0 ) { //already has arguments, so just add one on - arg = "&"; + if (item !== undefined) { + if (json_store.objects[item] === undefined) { + var path_str = "/objects"; + var arg_str = "fields=state,states,label,state_log&items="+item; + $.ajax({ + type: "GET", + url: "/json"+path_str+"?"+arg_str, + dataType: "json", + success: function( json ) { + JSONStore(json); + loadCollection(collection_keys); + } + }); + } else { + var name = item; + var color = getButtonColor(json_store.objects[item].state); + if (json_store.objects[item].label !== undefined) name = json_store.objects[item].label; + var dbl_btn = ""; + if (name.length < 30) dbl_btn = "
"; + var button_html = "
"; + entity_arr.push(button_html); + items += item+","; } - link += arg+"ia7="+next_collection_keys; - } - link = buildLink (link, next_collection_keys); - if (json_store.collections[collection].external !== undefined) { - link = json_store.collections[collection].external; + + } else { + if (json_store.collections[collection].iframe !== undefined) { + link = "/ia7/include/iframe.shtml?"+json_store.collections[collection].iframe; + } + var hidden = ""; + if (mode != display_mode && mode != undefined ) hidden = "hidden"; //Hide any simple/advanced buttons + var next_collection_keys = collection_keys + "," + entity_sort[i]; + if (keys === "true") { + var arg = "?"; + if (link.indexOf("?") >= 0 ) { //already has arguments, so just add one on + arg = "&"; + } + link += arg+"ia7="+next_collection_keys; + } + link = buildLink (link, next_collection_keys); + if (json_store.collections[collection].external !== undefined) { + link = json_store.collections[collection].external; + } + var icon_set = "fa"; + if (icon.indexOf("wi-") !=-1) icon_set = "wi"; + var button_html = ""+name+""; + entity_arr.push(button_html); } - var icon_set = "fa"; - if (icon.indexOf("wi-") !=-1) icon_set = "wi"; - var button_html = ""+name+""; - entity_arr.push(button_html); } //loop through array and print buttons var row = 0; @@ -794,6 +824,19 @@ var loadCollection = function(collection_keys) { } column++; } + // if any items present, then create modals and activate updateItem... + if (items !== "") { + items = items.slice(0,-1); //remove last comma + //console.log("items="+items); + $('.btn-state-cmd').click( function () { + var entity = $(this).attr("entity"); + //console.log("entity="+entity); + create_state_modal(entity); + }); +// test multiple items at some point + updateItem(items); + } + }; //Constructs a link, likely should be replaced by HashToURL @@ -1088,14 +1131,15 @@ var create_state_modal = function(entity) { var advanced_html = ""; for (var i = 0; i < modal_states.length; i++){ if (filterSubstate(modal_states[i]) == 1) { - advanced_html += ""; + advanced_html += ""; continue } else { - buttonlength += 2 + modal_states[i].length //TODO: Maybe just count buttons to create groups. +//TODO: Maybe just count buttons to create groups, organize them a bit better, <4 buttons, do a block? + buttonlength += 2 + modal_states[i].length } if (buttonlength >= 25) { stategrp++; - $('#control').find('.states').append("
"); + $('#control').find('.states').append("
"); buttonlength = 0; } var color = getButtonColor(modal_states[i]) @@ -1103,7 +1147,7 @@ var create_state_modal = function(entity) { if (modal_states[i] == json_store.objects[entity].state) { disabled = "disabled"; } - $('#control').find('.states').find(".stategrp"+stategrp).append(""); + $('#control').find('.states').find(".stategrp"+stategrp).append(""); } $('#control').find('.states').append("
"+advanced_html+"
"); @@ -1263,4 +1307,19 @@ $(document).ready(function() { $('#optionsModal').modal('hide'); }); }); -}); \ No newline at end of file +}); + +// +// 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. +// diff --git a/web/ia7/index.shtml b/web/ia7/index.shtml index 2b7bac475..47f486d5f 100644 --- a/web/ia7/index.shtml +++ b/web/ia7/index.shtml @@ -101,7 +101,12 @@ } .toolbar-right-end { margin-right: 15px; - } + } + .icon-larger:before + { + font-size: 46px; + margin-left: -10px; + } From 105486a448acbbb824470080253bd14c98f0cbfc Mon Sep 17 00:00:00 2001 From: H Plato Date: Fri, 8 May 2015 19:41:29 -0600 Subject: [PATCH 17/34] First take at Opensprinkler module --- lib/Opensprinkler.pm | 698 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 lib/Opensprinkler.pm diff --git a/lib/Opensprinkler.pm b/lib/Opensprinkler.pm new file mode 100644 index 000000000..8fd9f70e4 --- /dev/null +++ b/lib/Opensprinkler.pm @@ -0,0 +1,698 @@ +package OpenSprinkler; + +use strict; +use warnings; + +use LWP::UserAgent; +use HTTP::Request::Common qw(POST); +use JSON::XS; +use Data::Dumper; + + + + +# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); +# +# $os_wl = new OpenSprinkler_Waterlevel($os1); +# $os_rs = new OpenSprinkler_Rainstatus($os1); +# $front_garden = new OpenSprinkler_Station($os1,0,60); +# +# $os1_comm = new OpenSprinkler_Comm($os1); +# methods +# -set disable +# -reboot +# -reset +# - get_waterlevel + + +#todo +# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. +#?? disabling the opensprinkler doesn't turn off the stations? +#?? parse return codes better, +#?? print logs +# # make the data poll non-blocking, turn off timer +# +# State can only be set by stat. Set mode will change the mode. + + +@OpenSprinkler::ISA = ('Generic_Item'); + + +# -------------------- START OF SUBROUTINES -------------------- +# -------------------------------------------------------------- + +our %rest; + +$rest{get_vars} = "jc"; +$rest{set_vars} = "cv"; + +$rest{get_options} = "jo"; +$rest{set_options} = "co"; +$rest{station_info} = "jn"; +$rest{get_stations} = "js"; +$rest{set_stations} = "cs"; +$rest{test_station} = "cm"; + +$rest{get_log} = "jl"; + + +our %result; +$result{1} = "Success"; +$result{2} = "Unauthorized"; +$result{3} = "Mismatch"; +$result{4} = "Data Missing"; +$result{5} = "Out of Range"; +$result{6} = "Data Format"; +$result{7} = "Error Page Not Found"; +$result{8} = "Not Permitted"; + +sub new { + my ($class, $host,$pwd,$poll) = @_; + my $self = {}; + bless $self, $class; + $self->{data} = undef; + $self->{child_object} = undef; + $self->{config}->{cache_time} = 5; #TODO fix cache timeouts + $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; + $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes + $self->{config}->{poll_seconds} = 10; + $self->{config}->{poll_seconds} = $poll if ($poll); + $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); + $self->{updating} = 0; + $self->{data}->{retry} = 0; + $self->{data}->{stations} = (); + $self->{host} = $host; + $self->{password} = $pwd; + $self->{debug} = 0; + $self->{loglevel} = 1; + $self->{timeout} = 15; #300; + push(@{$$self{states}}, 'enabled', 'disabled'); + + $self->_init; + $self->{timer} = new Timer; + $self->start_timer; + return $self; +} + +sub _poll_check { + my ($self) = @_; + #main::print_log("[OpenSprinkler] _poll_check initiated"); + #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background + $self->get_data(); +} + +sub get_data { + my ($self) = @_; + #main::print_log("[OpenSprinkler] get_data initiated"); + $self->poll; + $self->process_data; +} + +sub _init { + my ($self) = @_; + + my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); + + if ($isSuccessResponse1) { + + if ($osp) { #->{fwv} > 213) { + + main::print_log("[OpenSprinkler] OpenSprinkler hardware $osp->{hwv} found with firmware $osp->{fmv}"); + my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); + for my $index (0 .. $#{$stations->{snames}}) { + #print "$index: $stations->{snames}[$index]\n"; + $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; + } + $self->{previous}->{info}->{waterlevel} = $osp->{wl}; + $self->{previous}->{info}->{rs} = "init"; + $self->{previous}->{info}->{state} = "disabled"; + $self->{previous}->{info}->{adjustment_method} = "init"; + $self->{previous}->{info}->{rain_sensor_status} = "init"; + $self->{previous}->{info}->{sunrise} = 0; + $self->{previous}->{info}->{sunset} = 0; + if ($self->poll()) { + main::print_log("[OpenSprinkler] Data Successfully Retrieved"); + $self->{active} = 1; + $self->print_info(); + $self->set($self->{data}->{info}->{state},'poll'); + + } else { + main::print_log("[OpenSprinkler] Problem retrieving initial data"); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); + $self->{active} = 0; + return ('1'); + } +} + +sub poll { + my ($self) = @_; + + main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); + + my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); + my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); + my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); + + + if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { + $self->{data}->{name} = $vars->{loc}; + $self->{data}->{loc} = $vars->{loc}; + $self->{data}->{options} = $options; + $self->{data}->{vars} = $vars; + $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; + $self->{data}->{info}->{waterlevel} = $options->{wl}; + $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; + $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; + $self->{data}->{info}->{sunrise} = $vars->{sunrise}; + $self->{data}->{info}->{sunset} = $vars->{sunset}; + + for my $index (0 .. $#{$stations->{sn}}) { + print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); + $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; + } + $self->{data}->{nstations} = $stations->{nstations}; + $self->{data}->{timestamp} = time; + $self->{data}->{retry} = 0; +#print Dumper $self; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + return ('1'); + } else { + main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); + $self->{data}->{retry}++; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } + +} + +#------------------------------------------------------------------------------------ +sub _get_JSON_data { + my ($self, $mode, $cmd) = @_; + + unless ($self->{updating}) { + $cmd = "" unless ($cmd); + $self->{updating} = 1; + my $ua = new LWP::UserAgent(keep_alive=>1); + $ua->timeout($self->{timeout}); + + my $host = $self->{host}; + my $password = $self->{password}; + print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); + my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); + #$request->content_type("application/x-www-form-urlencoded"); + + my $responseObj = $ua->request($request); + print $responseObj->content."\n--------------------\n" if $self->{debug}; + + my $responseCode = $responseObj->code; + print 'Response code: ' . $responseCode . "\n" if $self->{debug}; + my $isSuccessResponse = $responseCode < 400; + $self->{updating} = 0; + if (! $isSuccessResponse ) { + main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } else { + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + } + my $response; + eval { + $response = JSON::XS->new->decode ($responseObj->content); + }; + # catch crashes: + if($@){ + print "[OpenSprinkler:" . $self->{data}->{name} ."] ERROR! JSON parser crashed! $@\n"; + return ('0'); + } else { + return ($isSuccessResponse, $response) + } + } else { + main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); + return ('0'); + } +} + +sub register { + my ($self, $object, $type, $number ) = @_; + #my $name; + #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? + if (lc $type eq "station") { + &main::print_log("[OpenSprinkler] Registering station $number child object"); + $self->{child_object}->{station}->{$number} = $object; + $object->set_label($self->{data}->{stations}->[$number]->{name}); + } else { + &main::print_log("[OpenSprinkler] Registering $type child object"); + + $self->{child_object}->{$type} = $object; + } + + } + +sub stop_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->stop() if ($self->{timer}->active()); + } else { + main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); + } +} + +sub start_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); + } else { + main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); + } +} + +sub print_info { + my ($self) = @_; + + my (@state,@enabled,@rd,@rs,@pwenabled); + $state[0] = "off"; + $state[1] = "on"; + $enabled[1] = "ENABLED"; + $enabled[0] = "DISABLED"; + $pwenabled[0] = "ENABLED"; + $pwenabled[1] = "DISABLED"; + $rd[0] = "rain delay is currently in effect"; + $rd[1] = "no rain delay"; + $rs[0] = "rain is detected from rain sensor"; + $rs[1] = "no rain detected"; + + main::print_log("[OpenSprinkler] Device Hardware v" . $self->{data}->{options}->{hwv} . " with firmware " . $self->{data}->{options}->{fwv}); + main::print_log("[OpenSprinkler] *Mode is " . $self->{data}->{info}->{state}); + main::print_log("[OpenSprinkler] Time Zone is " . $self->get_tz()); + main::print_log("[OpenSprinkler] NTP Sync " . $state[$self->{data}->{options}->{ntp}]); + main::print_log("[OpenSprinkler] Use DHCP " . $state[$self->{data}->{options}->{dhcp}]); + main::print_log("[OpenSprinkler] Number of expansion boards " . $self->{data}->{options}->{ext}); + main::print_log("[OpenSprinkler] Station delay time " . $self->{data}->{options}->{sdt}); + main::print_log("[OpenSprinkler] Master station " . $self->{data}->{options}->{mas}); + main::print_log("[OpenSprinkler] master on time " . $self->{data}->{options}->{mton}); + main::print_log("[OpenSprinkler] master off time " . $self->{data}->{options}->{mtof}); + main::print_log("[OpenSprinkler] Rain Sensor " . $state[$self->{data}->{options}->{urs}]); + main::print_log("[OpenSprinkler] *Water Level " . $self->{data}->{info}->{waterlevel}); + main::print_log("[OpenSprinkler] Password is " . $pwenabled[$self->{data}->{options}->{ipas}]); + main::print_log("[OpenSprinkler] Device ID " . $self->{data}->{options}->{devid}) if defined ($self->{data}->{options}->{devid}); + main::print_log("[OpenSprinkler] LCD Contrast " . $self->{data}->{options}->{con}); + main::print_log("[OpenSprinkler] LCD Backlight " . $self->{data}->{options}->{lit}); + main::print_log("[OpenSprinkler] LCD Dimming " . $self->{data}->{options}->{dim}); + main::print_log("[OpenSprinkler] Relay Pulse Time " . $self->{data}->{options}->{rlp}) if defined ($self->{data}->{options}->{rlp}); + main::print_log("[OpenSprinkler] *Weather adjustment Method " . $self->{data}->{info}->{adjustment_method}); + main::print_log("[OpenSprinkler] Logging " . $enabled[$self->{data}->{options}->{lg}]); + main::print_log("[OpenSprinkler] Zone exapnsion boards " . $self->{data}->{options}->{dexp}); + main::print_log("[OpenSprinkler] Max zone expansion boards " . $self->{data}->{options}->{mexp}); + + main::print_log("[OpenSprinkler] Device Time " . localtime($self->{data}->{vars}->{devt})); + main::print_log("[OpenSprinkler] Number of 8 station boards " . $self->{data}->{vars}->{nbrd}); + main::print_log("[OpenSprinkler] Rain delay " . $self->{data}->{vars}->{rd}); + main::print_log("[OpenSprinkler] *Rain sensor status " . $self->{data}->{info}->{rain_sensor_status}); + main::print_log("[OpenSprinkler] Location " . $self->{data}->{vars}->{loc}); + main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); + main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); + main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); + +} + +sub process_data { + my ($self) = @_; + # Main core of processing + # set state of self for state + # for any registered child selfs, update their state if + + + for my $index (0 .. $#{$self->{data}->{stations}}) { + my $previous = "init"; + $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); + if ($previous ne $self->{data}->{stations}->[$index]->{state}) { + main::print_log("[OpenSprinkler] Station $index $self->{data}->{stations}->[$index]->{name} changed from $previous to $self->{data}->{stations}->[$index]->{state}") if ($self->{loglevel}); + $self->{previous}->{data}->{stations}->[$index]->{state} = $self->{data}->{stations}->[$index]->{state}; + if (defined $self->{child_object}->{station}->{$index}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{station}->{$index}->set($self->{data}->{stations}->[$index]->{state},'poll'); + } + } + } + + if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { + main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); + $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; + $self->set($self->{data}->{info}->{state},'poll'); + } + + if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { + main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); + $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; + if (defined $self->{child_object}->{waterlevel}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); + } + } + + if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { + main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); + $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; + if (defined $self->{child_object}->{rain_sensor_status}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); + } + } + + if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { + main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; + } + + if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { + main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; + } + + if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { + main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); + $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; + } + +} + + +sub print_logs { + my ($self) = @_; + my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); + + for my $tstamp (0..$#{$data->{runtimes}}) { + + print $data->{runtimes}[$tstamp]->{ts} . " -> "; + print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); + main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); + main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); + main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; + main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; + main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; + main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; + main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; + + } +} + +sub get_station { + my ($self,$number) = @_; + + return ($self->{data}->{stations}->[$number]->{state}); + +} + +sub set_station { + + my ($self,$station,$state,$time) = @_; + + return if (lc $state eq $self->{state}); + + #print "db: set_station state=$state, station=$station time=$time\n"; + my $cmd = "&sid=" . $station; + if (lc $state eq "on") { + $cmd .= "&en=1&t=" . $time; + } else { + $cmd .= "&en=0"; + } + my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set station to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + +} + +sub get_sunrise { + my ($self) = @_; +# add in nice calc, minutes since midnight + my $AMPM = "AM"; + my $hour = int ($self->{data}->{vars}->{sunrise} / 60); + my $minute = $self->{data}->{vars}->{sunrise} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + +sub get_sunset { + my ($self) = @_; + my $AMPM = "AM"; + my $hour = int($self->{data}->{vars}->{sunset} / 60); + my $minute = $self->{data}->{vars}->{sunset} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + + +sub get_tz { + my ($self) = @_; + my $tz = ($self->{data}->{options}->{tz} - 48) / 4; + if ($tz >= 0 ) { + $tz = "GMT+$tz"; + } else { + $tz = "GMT$tz"; + } + return ($tz); +} + +sub reboot { + my ($self) = @_; + + my $cmd = "&rbt=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub reset { + my ($self) = @_; + + my $cmd = "&rsn=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub get_waterlevel { + my ($self) = @_; + + return ($self->{data}->{info}->{waterlevel}); +} + +sub get_rainstatus { + my ($self) = @_; + + return ($self->{data}->{info}->{rain_sensor_status}); +} + +sub set_rain_delay { + my ($self,$hours) = @_; + + my $cmd = "&rsn=$hours"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } else { + + return if (lc $p_state eq $self->{state}); + my $en; + if ((lc $p_state eq "enabled") || (lc $p_state eq "on")) { + $en = 1; + } elsif ((lc $p_state eq "disabled") || (lc $p_state eq "off")) { + $en = 0; + } else { + main::print_log("[OpenSprinkler] Error. Unknown state $p_state"); + return (0); + } + + my $cmd = "&en=" . $en; + + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + if ($isSuccessResponse) { + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + } +} + + + +package OpenSprinkler_Station; + +@OpenSprinkler_Station::ISA = ('Generic_Item'); + +sub new +{ + my ($class,$object, $number, $on_timeout) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $$self{station} = $number; + push(@{$$self{states}}, 'on','off'); + $$self{on_timeout} = 3600; #default to an hour for 'on' + $$self{on_timeout} = $on_timeout * 60 if $on_timeout; + $object->register($self,'station',$number); + $self->set($object->get_station($number),'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby,$time_override) = @_; + + if ($p_setby eq 'poll') { + #print "db: setting by poll to $p_state\n"; + $self->SUPER::set($p_state); + } else { +#bounds check, add in time_override + my $time = $$self{on_timeout}; + $time = $time_override if ($time_override); + $$self{master_object}->set_station($$self{station},$p_state,$time); + } +} + + +package OpenSprinkler_Comm; + +@OpenSprinkler_Comm::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + push(@{$$self{states}}, 'online','offline'); + $object->register($self,'comm'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Waterlevel; + +@OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'waterlevel'); + $self->set($object->get_waterlevel,'poll'); + + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Rainstatus; + +@OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'rain_sensor_status'); + $self->set($object->get_rainstatus,'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +1; From 66e7b74534fcd0f38c150057496d464b5bbbb5de Mon Sep 17 00:00:00 2001 From: H Plato Date: Tue, 12 May 2015 21:46:59 -0600 Subject: [PATCH 18/34] Added in enabled/disabled stations --- lib/Opensprinkler.pm | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/Opensprinkler.pm b/lib/Opensprinkler.pm index 8fd9f70e4..2cae44dc5 100644 --- a/lib/Opensprinkler.pm +++ b/lib/Opensprinkler.pm @@ -85,7 +85,7 @@ sub new { $self->{password} = $pwd; $self->{debug} = 0; $self->{loglevel} = 1; - $self->{timeout} = 15; #300; + $self->{timeout} = 4; #300; push(@{$$self{states}}, 'enabled', 'disabled'); $self->_init; @@ -117,12 +117,22 @@ sub _init { if ($osp) { #->{fwv} > 213) { - main::print_log("[OpenSprinkler] OpenSprinkler hardware $osp->{hwv} found with firmware $osp->{fmv}"); + main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); for my $index (0 .. $#{$stations->{snames}}) { #print "$index: $stations->{snames}[$index]\n"; $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; } + # Check to see if station is disabled, Bitwise operation + for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { + my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; + for my $bit ( 0 .. 7) { + my $station_id = (($stn_dis * 8) + $bit); + my $disabled = substr $bin,(7-$bit),1; + $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; + } + } +#print Dumper $self; $self->{previous}->{info}->{waterlevel} = $osp->{wl}; $self->{previous}->{info}->{rs} = "init"; $self->{previous}->{info}->{state} = "disabled"; @@ -356,6 +366,7 @@ sub process_data { for my $index (0 .. $#{$self->{data}->{stations}}) { + next if ($self->{data}->{stations}->[$index]->{status} eq "disabled"); my $previous = "init"; $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); if ($previous ne $self->{data}->{stations}->[$index]->{state}) { From f7cc9b3d5c13532c21da82d0de77f521745702ff Mon Sep 17 00:00:00 2001 From: H Plato Date: Fri, 15 May 2015 15:46:17 -0600 Subject: [PATCH 19/34] A few fixes as per Brian's suggestions Renamed to OpenSprinkler from Opensprinkler new file: OpenSprinkler.pm deleted: Opensprinkler.pm --- lib/OpenSprinkler.pm | 710 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 710 insertions(+) create mode 100644 lib/OpenSprinkler.pm diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm new file mode 100644 index 000000000..9a69ec5c5 --- /dev/null +++ b/lib/OpenSprinkler.pm @@ -0,0 +1,710 @@ +package OpenSprinkler; + +use strict; +use warnings; + +use LWP::UserAgent; +use HTTP::Request::Common qw(POST); +use JSON::XS; +use Data::Dumper; + + + + +# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); +# +# $os_wl = new OpenSprinkler_Waterlevel($os1); +# $os_rs = new OpenSprinkler_Rainstatus($os1); +# $front_garden = new OpenSprinkler_Station($os1,0,60); +# +# $os1_comm = new OpenSprinkler_Comm($os1); +# methods +# -set disable +# -reboot +# -reset +# - get_waterlevel + + +#todo +# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. +#?? disabling the opensprinkler doesn't turn off the stations? +#?? parse return codes better, +#?? print logs +# # make the data poll non-blocking, turn off timer +# +# State can only be set by stat. Set mode will change the mode. + + +@OpenSprinkler::ISA = ('Generic_Item'); + + +# -------------------- START OF SUBROUTINES -------------------- +# -------------------------------------------------------------- + +our %rest; + +$rest{get_vars} = "jc"; +$rest{set_vars} = "cv"; + +$rest{get_options} = "jo"; +$rest{set_options} = "co"; +$rest{station_info} = "jn"; +$rest{get_stations} = "js"; +$rest{set_stations} = "cs"; +$rest{test_station} = "cm"; + +$rest{get_log} = "jl"; + + +our %result; +$result{1} = "Success"; +$result{2} = "Unauthorized"; +$result{3} = "Mismatch"; +$result{4} = "Data Missing"; +$result{5} = "Out of Range"; +$result{6} = "Data Format"; +$result{7} = "Error Page Not Found"; +$result{8} = "Not Permitted"; + +sub new { + my ($class, $host,$pwd,$poll) = @_; + my $self = {}; + bless $self, $class; + $self->{data} = undef; + $self->{child_object} = undef; + $self->{config}->{cache_time} = 5; #TODO fix cache timeouts + $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; + $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes + $self->{config}->{poll_seconds} = 10; + $self->{config}->{poll_seconds} = $poll if ($poll); + $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); + $self->{updating} = 0; + $self->{data}->{retry} = 0; + $self->{data}->{stations} = (); + $self->{host} = $host; + $self->{password} = $pwd; + $self->{debug} = 0; + $self->{loglevel} = 1; + $self->{timeout} = 4; #300; + push(@{$$self{states}}, 'enabled', 'disabled'); + + $self->_init; + $self->{timer} = new Timer; + $self->start_timer; + return $self; +} + +sub _poll_check { + my ($self) = @_; + #main::print_log("[OpenSprinkler] _poll_check initiated"); + #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background + $self->get_data(); +} + +sub get_data { + my ($self) = @_; + #main::print_log("[OpenSprinkler] get_data initiated"); + $self->poll; + $self->process_data; +} + +sub _init { + my ($self) = @_; + + my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); + + if ($isSuccessResponse1) { + + if ($osp) { #->{fwv} > 213) { + + main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); + my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); + for my $index (0 .. $#{$stations->{snames}}) { + #print "$index: $stations->{snames}[$index]\n"; + $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; + } + # Check to see if station is disabled, Bitwise operation + for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { + my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; + for my $bit ( 0 .. 7) { + my $station_id = (($stn_dis * 8) + $bit); + my $disabled = substr $bin,(7-$bit),1; + $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; + } + } +#print Dumper $self; + $self->{previous}->{info}->{waterlevel} = $osp->{wl}; + $self->{previous}->{info}->{rs} = "init"; + $self->{previous}->{info}->{state} = "disabled"; + $self->{previous}->{info}->{adjustment_method} = "init"; + $self->{previous}->{info}->{rain_sensor_status} = "init"; + $self->{previous}->{info}->{sunrise} = 0; + $self->{previous}->{info}->{sunset} = 0; + if ($self->poll()) { + main::print_log("[OpenSprinkler] Data Successfully Retrieved"); + $self->{active} = 1; + $self->print_info(); + $self->set($self->{data}->{info}->{state},'poll'); + + } else { + main::print_log("[OpenSprinkler] Problem retrieving initial data"); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); + $self->{active} = 0; + return ('1'); + } +} + +sub poll { + my ($self) = @_; + + main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); + + my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); + my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); + my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); + + + if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { + $self->{data}->{name} = $vars->{loc}; + $self->{data}->{loc} = $vars->{loc}; + $self->{data}->{options} = $options; + $self->{data}->{vars} = $vars; + $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; + $self->{data}->{info}->{waterlevel} = $options->{wl}; + $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; + $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; + $self->{data}->{info}->{sunrise} = $vars->{sunrise}; + $self->{data}->{info}->{sunset} = $vars->{sunset}; + + for my $index (0 .. $#{$stations->{sn}}) { + print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); + $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; + } + $self->{data}->{nstations} = $stations->{nstations}; + $self->{data}->{timestamp} = time; + $self->{data}->{retry} = 0; +#print Dumper $self; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + return ('1'); + } else { + main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); + $self->{data}->{retry}++; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } + +} + +#------------------------------------------------------------------------------------ +sub _get_JSON_data { + my ($self, $mode, $cmd) = @_; + + unless ($self->{updating}) { + $cmd = "" unless ($cmd); + $self->{updating} = 1; + my $ua = new LWP::UserAgent(keep_alive=>1); + $ua->timeout($self->{timeout}); + + my $host = $self->{host}; + my $password = $self->{password}; + print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); + my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); + #$request->content_type("application/x-www-form-urlencoded"); + + my $responseObj = $ua->request($request); + print $responseObj->content."\n--------------------\n" if $self->{debug}; + + my $responseCode = $responseObj->code; + print 'Response code: ' . $responseCode . "\n" if $self->{debug}; + my $isSuccessResponse = $responseCode < 400; + $self->{updating} = 0; + if (! $isSuccessResponse ) { + main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } else { + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + } + my $response; + eval { + $response = JSON::XS->new->decode ($responseObj->content); + }; + # catch crashes: + if($@){ + print "[OpenSprinkler:" . $self->{data}->{name} ."] ERROR! JSON parser crashed! $@\n"; + return ('0'); + } else { + return ($isSuccessResponse, $response) + } + } else { + main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); + return ('0'); + } +} + +sub register { + my ($self, $object, $type, $number ) = @_; + #my $name; + #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? + if (lc $type eq "station") { + &main::print_log("[OpenSprinkler] Registering station $number child object"); + $self->{child_object}->{station}->{$number} = $object; + $object->set_label($self->{data}->{stations}->[$number]->{name}); + } else { + &main::print_log("[OpenSprinkler] Registering $type child object"); + + $self->{child_object}->{$type} = $object; + } + + } + +sub stop_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->stop() if ($self->{timer}->active()); + } else { + main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); + } +} + +sub start_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); + } else { + main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); + } +} + +sub print_info { + my ($self) = @_; + + my (@state,@enabled,@rd,@rs,@pwenabled); + $state[0] = "off"; + $state[1] = "on"; + $enabled[1] = "ENABLED"; + $enabled[0] = "DISABLED"; + $pwenabled[0] = "ENABLED"; + $pwenabled[1] = "DISABLED"; + $rd[0] = "rain delay is currently in effect"; + $rd[1] = "no rain delay"; + $rs[0] = "rain is detected from rain sensor"; + $rs[1] = "no rain detected"; + + main::print_log("[OpenSprinkler] Device Hardware v" . $self->{data}->{options}->{hwv} . " with firmware " . $self->{data}->{options}->{fwv}); + main::print_log("[OpenSprinkler] *Mode is " . $self->{data}->{info}->{state}); + main::print_log("[OpenSprinkler] Time Zone is " . $self->get_tz()); + main::print_log("[OpenSprinkler] NTP Sync " . $state[$self->{data}->{options}->{ntp}]); + main::print_log("[OpenSprinkler] Use DHCP " . $state[$self->{data}->{options}->{dhcp}]); + main::print_log("[OpenSprinkler] Number of expansion boards " . $self->{data}->{options}->{ext}); + main::print_log("[OpenSprinkler] Station delay time " . $self->{data}->{options}->{sdt}); + main::print_log("[OpenSprinkler] Master station " . $self->{data}->{options}->{mas}); + main::print_log("[OpenSprinkler] master on time " . $self->{data}->{options}->{mton}); + main::print_log("[OpenSprinkler] master off time " . $self->{data}->{options}->{mtof}); + main::print_log("[OpenSprinkler] Rain Sensor " . $state[$self->{data}->{options}->{urs}]); + main::print_log("[OpenSprinkler] *Water Level " . $self->{data}->{info}->{waterlevel}); + main::print_log("[OpenSprinkler] Password is " . $pwenabled[$self->{data}->{options}->{ipas}]); + main::print_log("[OpenSprinkler] Device ID " . $self->{data}->{options}->{devid}) if defined ($self->{data}->{options}->{devid}); + main::print_log("[OpenSprinkler] LCD Contrast " . $self->{data}->{options}->{con}); + main::print_log("[OpenSprinkler] LCD Backlight " . $self->{data}->{options}->{lit}); + main::print_log("[OpenSprinkler] LCD Dimming " . $self->{data}->{options}->{dim}); + main::print_log("[OpenSprinkler] Relay Pulse Time " . $self->{data}->{options}->{rlp}) if defined ($self->{data}->{options}->{rlp}); + main::print_log("[OpenSprinkler] *Weather adjustment Method " . $self->{data}->{info}->{adjustment_method}); + main::print_log("[OpenSprinkler] Logging " . $enabled[$self->{data}->{options}->{lg}]); + main::print_log("[OpenSprinkler] Zone expansion boards " . $self->{data}->{options}->{dexp}); + main::print_log("[OpenSprinkler] Max zone expansion boards " . $self->{data}->{options}->{mexp}); + + main::print_log("[OpenSprinkler] Device Time " . localtime($self->{data}->{vars}->{devt})); + main::print_log("[OpenSprinkler] Number of 8 station boards " . $self->{data}->{vars}->{nbrd}); + main::print_log("[OpenSprinkler] Rain delay " . $self->{data}->{vars}->{rd}); + main::print_log("[OpenSprinkler] *Rain sensor status " . $self->{data}->{info}->{rain_sensor_status}); + main::print_log("[OpenSprinkler] Location " . $self->{data}->{vars}->{loc}); + main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); + main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); + main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); + +} + +sub process_data { + my ($self) = @_; + # Main core of processing + # set state of self for state + # for any registered child selfs, update their state if + + + for my $index (0 .. $#{$self->{data}->{stations}}) { + next if ($self->{data}->{stations}->[$index]->{status} eq "disabled"); + my $previous = "init"; + $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); + if ($previous ne $self->{data}->{stations}->[$index]->{state}) { + main::print_log("[OpenSprinkler] Station $index $self->{data}->{stations}->[$index]->{name} changed from $previous to $self->{data}->{stations}->[$index]->{state}") if ($self->{loglevel}); + $self->{previous}->{data}->{stations}->[$index]->{state} = $self->{data}->{stations}->[$index]->{state}; + if (defined $self->{child_object}->{station}->{$index}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{station}->{$index}->set($self->{data}->{stations}->[$index]->{state},'poll'); + } + } + } + + if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { + main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); + $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; + $self->set($self->{data}->{info}->{state},'poll'); + } + + if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { + main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); + $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; + if (defined $self->{child_object}->{waterlevel}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); + } + } + + if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { + main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); + $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; + if (defined $self->{child_object}->{rain_sensor_status}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); + } + } + + if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { + main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; + } + + if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { + main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; + } + + if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { + main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); + $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; + } + +} + + +sub print_logs { + my ($self) = @_; + my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); + + for my $tstamp (0..$#{$data->{runtimes}}) { + + print $data->{runtimes}[$tstamp]->{ts} . " -> "; + print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); + main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); + main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); + main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; + main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; + main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; + main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; + main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; + + } +} + +sub get_station { + my ($self,$number) = @_; + + return ($self->{data}->{stations}->[$number]->{state}); + +} + +sub set_station { + + my ($self,$station,$state,$time) = @_; + + return if (lc $state eq $self->{state}); + + #print "db: set_station state=$state, station=$station time=$time\n"; + my $cmd = "&sid=" . $station; + if (lc $state eq "on") { + $cmd .= "&en=1&t=" . $time; + } else { + $cmd .= "&en=0"; + } + my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set station to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + +} + +sub get_sunrise { + my ($self) = @_; +# add in nice calc, minutes since midnight + my $AMPM = "AM"; + my $hour = int ($self->{data}->{vars}->{sunrise} / 60); + my $minute = $self->{data}->{vars}->{sunrise} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + +sub get_sunset { + my ($self) = @_; + my $AMPM = "AM"; + my $hour = int($self->{data}->{vars}->{sunset} / 60); + my $minute = $self->{data}->{vars}->{sunset} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + + +sub get_tz { + my ($self) = @_; + my $tz = ($self->{data}->{options}->{tz} - 48) / 4; + if ($tz >= 0 ) { + $tz = "GMT+$tz"; + } else { + $tz = "GMT$tz"; + } + return ($tz); +} + +sub reboot { + my ($self) = @_; + + my $cmd = "&rbt=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub reset { + my ($self) = @_; + + my $cmd = "&rsn=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub get_waterlevel { + my ($self) = @_; + + return ($self->{data}->{info}->{waterlevel}); +} + +sub get_rainstatus { + my ($self) = @_; + + return ($self->{data}->{info}->{rain_sensor_status}); +} + +sub set_rain_delay { + my ($self,$hours) = @_; + + my $cmd = "&rsn=$hours"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } else { + + return if (lc $p_state eq $self->{state}); + my $en; + if ((lc $p_state eq "enabled") || (lc $p_state eq "on")) { + $en = 1; + } elsif ((lc $p_state eq "disabled") || (lc $p_state eq "off")) { + $en = 0; + } else { + main::print_log("[OpenSprinkler] Error. Unknown state $p_state"); + return (0); + } + + my $cmd = "&en=" . $en; + + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + if ($isSuccessResponse) { + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + } +} + + + +package OpenSprinkler_Station; + +@OpenSprinkler_Station::ISA = ('Generic_Item'); + +sub new +{ + my ($class,$object, $number, $on_timeout) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $$self{station} = $number; + push(@{$$self{states}}, 'on','off'); + $$self{on_timeout} = 3600; #default to an hour for 'on' + $$self{on_timeout} = $on_timeout * 60 if $on_timeout; + $object->register($self,'station',$number); + $self->set($object->get_station($number),'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby,$time_override) = @_; + + if ($p_setby eq 'poll') { + #print "db: setting by poll to $p_state\n"; + $self->SUPER::set($p_state); + } else { +#bounds check, add in time_override + my $time = $$self{on_timeout}; + $time = $time_override if ($time_override); + $$self{master_object}->set_station($$self{station},$p_state,$time); + } +} + + +package OpenSprinkler_Comm; + +@OpenSprinkler_Comm::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + push(@{$$self{states}}, 'online','offline'); + SUPER::set('offline'); + $object->register($self,'comm'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Waterlevel; + +@OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'waterlevel'); + $self->set($object->get_waterlevel,'poll'); + + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Rainstatus; + +@OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'rain_sensor_status'); + $self->set($object->get_rainstatus,'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +1; From 39dce1dcc09991eeaf88ddacdaf7b8f75fe81444 Mon Sep 17 00:00:00 2001 From: H Plato Date: Sun, 17 May 2015 10:11:09 -0600 Subject: [PATCH 20/34] Fixed SUPER set method --- lib/OpenSprinkler.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 9a69ec5c5..3cfbb5495 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -642,7 +642,7 @@ sub new { $$self{master_object} = $object; push(@{$$self{states}}, 'online','offline'); - SUPER::set('offline'); + $self->SUPER::set('offline'); $object->register($self,'comm'); return $self; From 084413050b2bd3ccdb9ad0d07617a9b8abd7b20c Mon Sep 17 00:00:00 2001 From: H Plato Date: Tue, 19 May 2015 21:51:29 -0600 Subject: [PATCH 21/34] fixed JSON return values, should now show true messages in print log and successful changes should be faster and not trigger a warning --- OpenSprinkler.pm | 714 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 OpenSprinkler.pm diff --git a/OpenSprinkler.pm b/OpenSprinkler.pm new file mode 100644 index 000000000..6ff6e4a9d --- /dev/null +++ b/OpenSprinkler.pm @@ -0,0 +1,714 @@ +package OpenSprinkler; + +use strict; +use warnings; + +use LWP::UserAgent; +use HTTP::Request::Common qw(POST); +use JSON::XS; +use Data::Dumper; + + + + +# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); +# +# $os_wl = new OpenSprinkler_Waterlevel($os1); +# $os_rs = new OpenSprinkler_Rainstatus($os1); +# $front_garden = new OpenSprinkler_Station($os1,0,60); +# +# $os1_comm = new OpenSprinkler_Comm($os1); +# methods +# -set disable +# -reboot +# -reset +# - get_waterlevel + + +#todo +# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. +#?? disabling the opensprinkler doesn't turn off the stations? +#?? parse return codes better, +#?? print logs +# # make the data poll non-blocking, turn off timer +# +# State can only be set by stat. Set mode will change the mode. + + +@OpenSprinkler::ISA = ('Generic_Item'); + + +# -------------------- START OF SUBROUTINES -------------------- +# -------------------------------------------------------------- + +our %rest; + +$rest{get_vars} = "jc"; +$rest{set_vars} = "cv"; + +$rest{get_options} = "jo"; +$rest{set_options} = "co"; +$rest{station_info} = "jn"; +$rest{get_stations} = "js"; +$rest{set_stations} = "cs"; +$rest{test_station} = "cm"; + +$rest{get_log} = "jl"; + + +our %result; +$result{1} = "success"; +$result{2} = "unauthorized"; +$result{3} = "mismatch"; +$result{4} = "data missing"; +$result{5} = "out of Range"; +$result{6} = "data format"; +$result{7} = "error page not found"; +$result{8} = "not permitted"; +$result{9} = "unknown error"; + +sub new { + my ($class, $host,$pwd,$poll) = @_; + my $self = {}; + bless $self, $class; + $self->{data} = undef; + $self->{child_object} = undef; + $self->{config}->{cache_time} = 5; #TODO fix cache timeouts + $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; + $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes + $self->{config}->{poll_seconds} = 10; + $self->{config}->{poll_seconds} = $poll if ($poll); + $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); + $self->{updating} = 0; + $self->{data}->{retry} = 0; + $self->{data}->{stations} = (); + $self->{host} = $host; + $self->{password} = $pwd; + $self->{debug} = 0; + $self->{loglevel} = 1; + $self->{timeout} = 4; #300; + push(@{$$self{states}}, 'enabled', 'disabled'); + + $self->_init; + $self->{timer} = new Timer; + $self->start_timer; + return $self; +} + +sub _poll_check { + my ($self) = @_; + #main::print_log("[OpenSprinkler] _poll_check initiated"); + #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background + $self->get_data(); +} + +sub get_data { + my ($self) = @_; + #main::print_log("[OpenSprinkler] get_data initiated"); + $self->poll; + $self->process_data; +} + +sub _init { + my ($self) = @_; + + my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); + + if ($isSuccessResponse1) { + + if ($osp) { #->{fwv} > 213) { + + main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); + my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); + for my $index (0 .. $#{$stations->{snames}}) { + #print "$index: $stations->{snames}[$index]\n"; + $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; + } + # Check to see if station is disabled, Bitwise operation + for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { + my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; + for my $bit ( 0 .. 7) { + my $station_id = (($stn_dis * 8) + $bit); + my $disabled = substr $bin,(7-$bit),1; + $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; + } + } +#print Dumper $self; + $self->{previous}->{info}->{waterlevel} = $osp->{wl}; + $self->{previous}->{info}->{rs} = "init"; + $self->{previous}->{info}->{state} = "disabled"; + $self->{previous}->{info}->{adjustment_method} = "init"; + $self->{previous}->{info}->{rain_sensor_status} = "init"; + $self->{previous}->{info}->{sunrise} = 0; + $self->{previous}->{info}->{sunset} = 0; + if ($self->poll()) { + main::print_log("[OpenSprinkler] Data Successfully Retrieved"); + $self->{active} = 1; + $self->print_info(); + $self->set($self->{data}->{info}->{state},'poll'); + + } else { + main::print_log("[OpenSprinkler] Problem retrieving initial data"); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); + $self->{active} = 0; + return ('1'); + } + + } else { + main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); + $self->{active} = 0; + return ('1'); + } +} + +sub poll { + my ($self) = @_; + + main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); + + my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); + my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); + my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); + + + if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { + $self->{data}->{name} = $vars->{loc}; + $self->{data}->{loc} = $vars->{loc}; + $self->{data}->{options} = $options; + $self->{data}->{vars} = $vars; + $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; + $self->{data}->{info}->{waterlevel} = $options->{wl}; + $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; + $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; + $self->{data}->{info}->{sunrise} = $vars->{sunrise}; + $self->{data}->{info}->{sunset} = $vars->{sunset}; + + for my $index (0 .. $#{$stations->{sn}}) { + print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); + $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; + } + $self->{data}->{nstations} = $stations->{nstations}; + $self->{data}->{timestamp} = time; + $self->{data}->{retry} = 0; +#print Dumper $self; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + return ('1'); + } else { + main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); + $self->{data}->{retry}++; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } + +} + +#------------------------------------------------------------------------------------ +sub _get_JSON_data { + my ($self, $mode, $cmd) = @_; + + unless ($self->{updating}) { + $cmd = "" unless ($cmd); + $self->{updating} = 1; + my $ua = new LWP::UserAgent(keep_alive=>1); + $ua->timeout($self->{timeout}); + + my $host = $self->{host}; + my $password = $self->{password}; + print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); + my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); + #$request->content_type("application/x-www-form-urlencoded"); + + my $responseObj = $ua->request($request); + print $responseObj->content."\n--------------------\n" if $self->{debug}; + + my $responseCode = $responseObj->code; + print 'Response code: ' . $responseCode . "\n" if $self->{debug}; + my $isSuccessResponse = $responseCode < 400; + $self->{updating} = 0; + if (! $isSuccessResponse ) { + main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } else { + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + } + my $response; + eval { + $response = JSON::XS->new->decode ($responseObj->content); + }; + # catch crashes: + if($@){ + print "[OpenSprinkler] ERROR! JSON parser crashed! $@\n"; + return ('0'); + } else { + my $result_code = 9; + $result_code = $response->{"result"} if (defined $response->{"result"}); + print "[OpenSpinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); + return ($isSuccessResponse, $result{$result_code}) + } + } else { + main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); + return ('0'); + } +} + +sub register { + my ($self, $object, $type, $number ) = @_; + #my $name; + #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? + if (lc $type eq "station") { + &main::print_log("[OpenSprinkler] Registering station $number child object"); + $self->{child_object}->{station}->{$number} = $object; + $object->set_label($self->{data}->{stations}->[$number]->{name}); + } else { + &main::print_log("[OpenSprinkler] Registering $type child object"); + + $self->{child_object}->{$type} = $object; + } + + } + +sub stop_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->stop() if ($self->{timer}->active()); + } else { + main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); + } +} + +sub start_timer { + my ($self) = @_; + + if (defined $self->{timer}) { + $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); + } else { + main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); + } +} + +sub print_info { + my ($self) = @_; + + my (@state,@enabled,@rd,@rs,@pwenabled); + $state[0] = "off"; + $state[1] = "on"; + $enabled[1] = "ENABLED"; + $enabled[0] = "DISABLED"; + $pwenabled[0] = "ENABLED"; + $pwenabled[1] = "DISABLED"; + $rd[0] = "rain delay is currently in effect"; + $rd[1] = "no rain delay"; + $rs[0] = "rain is detected from rain sensor"; + $rs[1] = "no rain detected"; + + main::print_log("[OpenSprinkler] Device Hardware v" . $self->{data}->{options}->{hwv} . " with firmware " . $self->{data}->{options}->{fwv}); + main::print_log("[OpenSprinkler] *Mode is " . $self->{data}->{info}->{state}); + main::print_log("[OpenSprinkler] Time Zone is " . $self->get_tz()); + main::print_log("[OpenSprinkler] NTP Sync " . $state[$self->{data}->{options}->{ntp}]); + main::print_log("[OpenSprinkler] Use DHCP " . $state[$self->{data}->{options}->{dhcp}]); + main::print_log("[OpenSprinkler] Number of expansion boards " . $self->{data}->{options}->{ext}); + main::print_log("[OpenSprinkler] Station delay time " . $self->{data}->{options}->{sdt}); + main::print_log("[OpenSprinkler] Master station " . $self->{data}->{options}->{mas}); + main::print_log("[OpenSprinkler] master on time " . $self->{data}->{options}->{mton}); + main::print_log("[OpenSprinkler] master off time " . $self->{data}->{options}->{mtof}); + main::print_log("[OpenSprinkler] Rain Sensor " . $state[$self->{data}->{options}->{urs}]); + main::print_log("[OpenSprinkler] *Water Level " . $self->{data}->{info}->{waterlevel}); + main::print_log("[OpenSprinkler] Password is " . $pwenabled[$self->{data}->{options}->{ipas}]); + main::print_log("[OpenSprinkler] Device ID " . $self->{data}->{options}->{devid}) if defined ($self->{data}->{options}->{devid}); + main::print_log("[OpenSprinkler] LCD Contrast " . $self->{data}->{options}->{con}); + main::print_log("[OpenSprinkler] LCD Backlight " . $self->{data}->{options}->{lit}); + main::print_log("[OpenSprinkler] LCD Dimming " . $self->{data}->{options}->{dim}); + main::print_log("[OpenSprinkler] Relay Pulse Time " . $self->{data}->{options}->{rlp}) if defined ($self->{data}->{options}->{rlp}); + main::print_log("[OpenSprinkler] *Weather adjustment Method " . $self->{data}->{info}->{adjustment_method}); + main::print_log("[OpenSprinkler] Logging " . $enabled[$self->{data}->{options}->{lg}]); + main::print_log("[OpenSprinkler] Zone expansion boards " . $self->{data}->{options}->{dexp}); + main::print_log("[OpenSprinkler] Max zone expansion boards " . $self->{data}->{options}->{mexp}); + + main::print_log("[OpenSprinkler] Device Time " . localtime($self->{data}->{vars}->{devt})); + main::print_log("[OpenSprinkler] Number of 8 station boards " . $self->{data}->{vars}->{nbrd}); + main::print_log("[OpenSprinkler] Rain delay " . $self->{data}->{vars}->{rd}); + main::print_log("[OpenSprinkler] *Rain sensor status " . $self->{data}->{info}->{rain_sensor_status}); + main::print_log("[OpenSprinkler] Location " . $self->{data}->{vars}->{loc}); + main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); + main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); + main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); + +} + +sub process_data { + my ($self) = @_; + # Main core of processing + # set state of self for state + # for any registered child selfs, update their state if + + + for my $index (0 .. $#{$self->{data}->{stations}}) { + next if ($self->{data}->{stations}->[$index]->{status} eq "disabled"); + my $previous = "init"; + $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); + if ($previous ne $self->{data}->{stations}->[$index]->{state}) { + main::print_log("[OpenSprinkler] Station $index $self->{data}->{stations}->[$index]->{name} changed from $previous to $self->{data}->{stations}->[$index]->{state}") if ($self->{loglevel}); + $self->{previous}->{data}->{stations}->[$index]->{state} = $self->{data}->{stations}->[$index]->{state}; + if (defined $self->{child_object}->{station}->{$index}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{station}->{$index}->set($self->{data}->{stations}->[$index]->{state},'poll'); + } + } + } + + if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { + main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); + $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; + $self->set($self->{data}->{info}->{state},'poll'); + } + + if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { + main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); + $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; + if (defined $self->{child_object}->{waterlevel}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); + } + } + + if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { + main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); + $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; + if (defined $self->{child_object}->{rain_sensor_status}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); + } + } + + if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { + main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; + } + + if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { + main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; + } + + if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { + main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); + $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; + } + +} + + +sub print_logs { + my ($self) = @_; + my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); + + for my $tstamp (0..$#{$data->{runtimes}}) { + + print $data->{runtimes}[$tstamp]->{ts} . " -> "; + print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); + main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); + main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); + main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; + main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; + main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; + main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; + main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; + + } +} + +sub get_station { + my ($self,$number) = @_; + + return ($self->{data}->{stations}->[$number]->{state}); + +} + +sub set_station { + + my ($self,$station,$state,$time) = @_; + + return if (lc $state eq $self->{state}); + + #print "db: set_station state=$state, station=$station time=$time\n"; + my $cmd = "&sid=" . $station; + if (lc $state eq "on") { + $cmd .= "&en=1&t=" . $time; + } else { + $cmd .= "&en=0"; + } + my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set station to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + +} + +sub get_sunrise { + my ($self) = @_; +# add in nice calc, minutes since midnight + my $AMPM = "AM"; + my $hour = int ($self->{data}->{vars}->{sunrise} / 60); + my $minute = $self->{data}->{vars}->{sunrise} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + +sub get_sunset { + my ($self) = @_; + my $AMPM = "AM"; + my $hour = int($self->{data}->{vars}->{sunset} / 60); + my $minute = $self->{data}->{vars}->{sunset} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } + + return ("$hour:$minute $AMPM"); +} + + +sub get_tz { + my ($self) = @_; + my $tz = ($self->{data}->{options}->{tz} - 48) / 4; + if ($tz >= 0 ) { + $tz = "GMT+$tz"; + } else { + $tz = "GMT$tz"; + } + return ($tz); +} + +sub reboot { + my ($self) = @_; + + my $cmd = "&rbt=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub reset { + my ($self) = @_; + + my $cmd = "&rsn=1"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub get_waterlevel { + my ($self) = @_; + + return ($self->{data}->{info}->{waterlevel}); +} + +sub get_rainstatus { + my ($self) = @_; + + return ($self->{data}->{info}->{rain_sensor_status}); +} + +sub set_rain_delay { + my ($self,$hours) = @_; + + my $cmd = "&rsn=$hours"; + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + + return ($status); +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } else { + + return if (lc $p_state eq $self->{state}); + my $en; + if ((lc $p_state eq "enabled") || (lc $p_state eq "on")) { + $en = 1; + } elsif ((lc $p_state eq "disabled") || (lc $p_state eq "off")) { + $en = 0; + } else { + main::print_log("[OpenSprinkler] Error. Unknown state $p_state"); + return (0); + } + + my $cmd = "&en=" . $en; + + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + if ($isSuccessResponse) { + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + } +} + + + +package OpenSprinkler_Station; + +@OpenSprinkler_Station::ISA = ('Generic_Item'); + +sub new +{ + my ($class,$object, $number, $on_timeout) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $$self{station} = $number; + push(@{$$self{states}}, 'on','off'); + $$self{on_timeout} = 3600; #default to an hour for 'on' + $$self{on_timeout} = $on_timeout * 60 if $on_timeout; + $object->register($self,'station',$number); + $self->set($object->get_station($number),'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby,$time_override) = @_; + + if ($p_setby eq 'poll') { + #print "db: setting by poll to $p_state\n"; + $self->SUPER::set($p_state); + } else { +#bounds check, add in time_override + my $time = $$self{on_timeout}; + $time = $time_override if ($time_override); + $$self{master_object}->set_station($$self{station},$p_state,$time); + } +} + + +package OpenSprinkler_Comm; + +@OpenSprinkler_Comm::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + push(@{$$self{states}}, 'online','offline'); + SUPER::set('offline'); + $object->register($self,'comm'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Waterlevel; + +@OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'waterlevel'); + $self->set($object->get_waterlevel,'poll'); + + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +package OpenSprinkler_Rainstatus; + +@OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); + +sub new { + my ($class,$object) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $object->register($self,'rain_sensor_status'); + $self->set($object->get_rainstatus,'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby) = @_; + + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } +} + +1; From 4be1bcafacbff36c6c8dbb206f38087ad219b85a Mon Sep 17 00:00:00 2001 From: H Plato Date: Thu, 21 May 2015 20:54:30 -0600 Subject: [PATCH 22/34] Separated out the data fetching subroutine from the data pushing. modified: OpenSprinkler.pm deleted: Opensprinkler.pm --- lib/OpenSprinkler.pm | 68 +++-- lib/Opensprinkler.pm | 709 ------------------------------------------- 2 files changed, 42 insertions(+), 735 deletions(-) delete mode 100644 lib/Opensprinkler.pm diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 3cfbb5495..38a24b437 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -57,14 +57,15 @@ $rest{get_log} = "jl"; our %result; -$result{1} = "Success"; -$result{2} = "Unauthorized"; -$result{3} = "Mismatch"; -$result{4} = "Data Missing"; -$result{5} = "Out of Range"; -$result{6} = "Data Format"; -$result{7} = "Error Page Not Found"; -$result{8} = "Not Permitted"; +$result{1} = "success"; +$result{2} = "unauthorized"; +$result{3} = "mismatch"; +$result{4} = "data missing"; +$result{5} = "out of Range"; +$result{6} = "data format"; +$result{7} = "error page not found"; +$result{8} = "not permitted"; +$result{9} = "unknown error"; sub new { my ($class, $host,$pwd,$poll) = @_; @@ -220,14 +221,12 @@ sub poll { sub _get_JSON_data { my ($self, $mode, $cmd) = @_; - unless ($self->{updating}) { - $cmd = "" unless ($cmd); - $self->{updating} = 1; my $ua = new LWP::UserAgent(keep_alive=>1); $ua->timeout($self->{timeout}); my $host = $self->{host}; my $password = $self->{password}; + $cmd = "" unless ($cmd); print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); #$request->content_type("application/x-www-form-urlencoded"); @@ -238,7 +237,6 @@ sub _get_JSON_data { my $responseCode = $responseObj->code; print 'Response code: ' . $responseCode . "\n" if $self->{debug}; my $isSuccessResponse = $responseCode < 400; - $self->{updating} = 0; if (! $isSuccessResponse ) { main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); if (defined $self->{child_object}->{comm}) { @@ -262,17 +260,35 @@ sub _get_JSON_data { }; # catch crashes: if($@){ - print "[OpenSprinkler:" . $self->{data}->{name} ."] ERROR! JSON parser crashed! $@\n"; + print "[OpenSprinkler] ERROR! JSON parser crashed! $@\n"; return ('0'); } else { - return ($isSuccessResponse, $response) - } - } else { - main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); - return ('0'); - } + return ($isSuccessResponse, $response); + } } +sub _push_JSON_data { + my ($self, $mode, $cmd) = @_; + + unless ($self->{updating}) { + $self->{updating} = 1; + my ($isSuccessResponse,$response) = $self->_get_JSON_data($mode,$cmd); + $self->{updating} = 0; + if (defined $response->{"result"}) { + my $result_code = 9; + $result_code = $response->{"result"} if (defined $response->{"result"}); + print "[OpenSpinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); + return ($isSuccessResponse, $result{$result_code}); + } else { + main::print_log("[OpenSprinkler] Warning, unknown response from data push"); + return ('0'); + } + } else { + main::print_log("[OpenSprinkler] Warning, not pushing data due to operation in progress"); + return ('0'); + } +} + sub register { my ($self, $object, $type, $number ) = @_; #my $name; @@ -460,7 +476,7 @@ sub set_station { } else { $cmd .= "&en=0"; } - my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); + my ($isSuccessResponse,$status) = $self->_push_JSON_data('test_station',$cmd); if ($isSuccessResponse) { #print "DB status=$status\n"; if ($status eq "success") { #todo parse return value @@ -520,7 +536,7 @@ sub reboot { my ($self) = @_; my $cmd = "&rbt=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_vars',$cmd); return ($status); } @@ -529,7 +545,7 @@ sub reset { my ($self) = @_; my $cmd = "&rsn=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_vars',$cmd); return ($status); } @@ -550,7 +566,7 @@ sub set_rain_delay { my ($self,$hours) = @_; my $cmd = "&rsn=$hours"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); + my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_vars',$cmd); return ($status); } @@ -577,11 +593,11 @@ sub set { my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); if ($isSuccessResponse) { - if ($status eq "success") { #todo parse return value + if ($status eq "success") { $self->poll; return (1); } else { - main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); + main::print_log("[OpenSprinkler] Error. Could not set state to $p_state. Status is $status"); return (0); } } else { @@ -642,7 +658,7 @@ sub new { $$self{master_object} = $object; push(@{$$self{states}}, 'online','offline'); - $self->SUPER::set('offline'); + $self->set('offline'); $object->register($self,'comm'); return $self; diff --git a/lib/Opensprinkler.pm b/lib/Opensprinkler.pm deleted file mode 100644 index 2cae44dc5..000000000 --- a/lib/Opensprinkler.pm +++ /dev/null @@ -1,709 +0,0 @@ -package OpenSprinkler; - -use strict; -use warnings; - -use LWP::UserAgent; -use HTTP::Request::Common qw(POST); -use JSON::XS; -use Data::Dumper; - - - - -# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); -# -# $os_wl = new OpenSprinkler_Waterlevel($os1); -# $os_rs = new OpenSprinkler_Rainstatus($os1); -# $front_garden = new OpenSprinkler_Station($os1,0,60); -# -# $os1_comm = new OpenSprinkler_Comm($os1); -# methods -# -set disable -# -reboot -# -reset -# - get_waterlevel - - -#todo -# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. -#?? disabling the opensprinkler doesn't turn off the stations? -#?? parse return codes better, -#?? print logs -# # make the data poll non-blocking, turn off timer -# -# State can only be set by stat. Set mode will change the mode. - - -@OpenSprinkler::ISA = ('Generic_Item'); - - -# -------------------- START OF SUBROUTINES -------------------- -# -------------------------------------------------------------- - -our %rest; - -$rest{get_vars} = "jc"; -$rest{set_vars} = "cv"; - -$rest{get_options} = "jo"; -$rest{set_options} = "co"; -$rest{station_info} = "jn"; -$rest{get_stations} = "js"; -$rest{set_stations} = "cs"; -$rest{test_station} = "cm"; - -$rest{get_log} = "jl"; - - -our %result; -$result{1} = "Success"; -$result{2} = "Unauthorized"; -$result{3} = "Mismatch"; -$result{4} = "Data Missing"; -$result{5} = "Out of Range"; -$result{6} = "Data Format"; -$result{7} = "Error Page Not Found"; -$result{8} = "Not Permitted"; - -sub new { - my ($class, $host,$pwd,$poll) = @_; - my $self = {}; - bless $self, $class; - $self->{data} = undef; - $self->{child_object} = undef; - $self->{config}->{cache_time} = 5; #TODO fix cache timeouts - $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; - $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes - $self->{config}->{poll_seconds} = 10; - $self->{config}->{poll_seconds} = $poll if ($poll); - $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); - $self->{updating} = 0; - $self->{data}->{retry} = 0; - $self->{data}->{stations} = (); - $self->{host} = $host; - $self->{password} = $pwd; - $self->{debug} = 0; - $self->{loglevel} = 1; - $self->{timeout} = 4; #300; - push(@{$$self{states}}, 'enabled', 'disabled'); - - $self->_init; - $self->{timer} = new Timer; - $self->start_timer; - return $self; -} - -sub _poll_check { - my ($self) = @_; - #main::print_log("[OpenSprinkler] _poll_check initiated"); - #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background - $self->get_data(); -} - -sub get_data { - my ($self) = @_; - #main::print_log("[OpenSprinkler] get_data initiated"); - $self->poll; - $self->process_data; -} - -sub _init { - my ($self) = @_; - - my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); - - if ($isSuccessResponse1) { - - if ($osp) { #->{fwv} > 213) { - - main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); - my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); - for my $index (0 .. $#{$stations->{snames}}) { - #print "$index: $stations->{snames}[$index]\n"; - $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; - } - # Check to see if station is disabled, Bitwise operation - for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { - my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; - for my $bit ( 0 .. 7) { - my $station_id = (($stn_dis * 8) + $bit); - my $disabled = substr $bin,(7-$bit),1; - $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; - } - } -#print Dumper $self; - $self->{previous}->{info}->{waterlevel} = $osp->{wl}; - $self->{previous}->{info}->{rs} = "init"; - $self->{previous}->{info}->{state} = "disabled"; - $self->{previous}->{info}->{adjustment_method} = "init"; - $self->{previous}->{info}->{rain_sensor_status} = "init"; - $self->{previous}->{info}->{sunrise} = 0; - $self->{previous}->{info}->{sunset} = 0; - if ($self->poll()) { - main::print_log("[OpenSprinkler] Data Successfully Retrieved"); - $self->{active} = 1; - $self->print_info(); - $self->set($self->{data}->{info}->{state},'poll'); - - } else { - main::print_log("[OpenSprinkler] Problem retrieving initial data"); - $self->{active} = 0; - return ('1'); - } - - } else { - main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); - $self->{active} = 0; - return ('1'); - } - - } else { - main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); - $self->{active} = 0; - return ('1'); - } -} - -sub poll { - my ($self) = @_; - - main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); - - my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); - my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); - my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); - - - if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { - $self->{data}->{name} = $vars->{loc}; - $self->{data}->{loc} = $vars->{loc}; - $self->{data}->{options} = $options; - $self->{data}->{vars} = $vars; - $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; - $self->{data}->{info}->{waterlevel} = $options->{wl}; - $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; - $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; - $self->{data}->{info}->{sunrise} = $vars->{sunrise}; - $self->{data}->{info}->{sunset} = $vars->{sunset}; - - for my $index (0 .. $#{$stations->{sn}}) { - print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); - $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; - } - $self->{data}->{nstations} = $stations->{nstations}; - $self->{data}->{timestamp} = time; - $self->{data}->{retry} = 0; -#print Dumper $self; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } - return ('1'); - } else { - main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); - $self->{data}->{retry}++; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } - return ('0'); - } - -} - -#------------------------------------------------------------------------------------ -sub _get_JSON_data { - my ($self, $mode, $cmd) = @_; - - unless ($self->{updating}) { - $cmd = "" unless ($cmd); - $self->{updating} = 1; - my $ua = new LWP::UserAgent(keep_alive=>1); - $ua->timeout($self->{timeout}); - - my $host = $self->{host}; - my $password = $self->{password}; - print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); - my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); - #$request->content_type("application/x-www-form-urlencoded"); - - my $responseObj = $ua->request($request); - print $responseObj->content."\n--------------------\n" if $self->{debug}; - - my $responseCode = $responseObj->code; - print 'Response code: ' . $responseCode . "\n" if $self->{debug}; - my $isSuccessResponse = $responseCode < 400; - $self->{updating} = 0; - if (! $isSuccessResponse ) { - main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } - return ('0'); - } else { - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } - } - my $response; - eval { - $response = JSON::XS->new->decode ($responseObj->content); - }; - # catch crashes: - if($@){ - print "[OpenSprinkler:" . $self->{data}->{name} ."] ERROR! JSON parser crashed! $@\n"; - return ('0'); - } else { - return ($isSuccessResponse, $response) - } - } else { - main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); - return ('0'); - } -} - -sub register { - my ($self, $object, $type, $number ) = @_; - #my $name; - #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? - if (lc $type eq "station") { - &main::print_log("[OpenSprinkler] Registering station $number child object"); - $self->{child_object}->{station}->{$number} = $object; - $object->set_label($self->{data}->{stations}->[$number]->{name}); - } else { - &main::print_log("[OpenSprinkler] Registering $type child object"); - - $self->{child_object}->{$type} = $object; - } - - } - -sub stop_timer { - my ($self) = @_; - - if (defined $self->{timer}) { - $self->{timer}->stop() if ($self->{timer}->active()); - } else { - main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); - } -} - -sub start_timer { - my ($self) = @_; - - if (defined $self->{timer}) { - $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); - } else { - main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); - } -} - -sub print_info { - my ($self) = @_; - - my (@state,@enabled,@rd,@rs,@pwenabled); - $state[0] = "off"; - $state[1] = "on"; - $enabled[1] = "ENABLED"; - $enabled[0] = "DISABLED"; - $pwenabled[0] = "ENABLED"; - $pwenabled[1] = "DISABLED"; - $rd[0] = "rain delay is currently in effect"; - $rd[1] = "no rain delay"; - $rs[0] = "rain is detected from rain sensor"; - $rs[1] = "no rain detected"; - - main::print_log("[OpenSprinkler] Device Hardware v" . $self->{data}->{options}->{hwv} . " with firmware " . $self->{data}->{options}->{fwv}); - main::print_log("[OpenSprinkler] *Mode is " . $self->{data}->{info}->{state}); - main::print_log("[OpenSprinkler] Time Zone is " . $self->get_tz()); - main::print_log("[OpenSprinkler] NTP Sync " . $state[$self->{data}->{options}->{ntp}]); - main::print_log("[OpenSprinkler] Use DHCP " . $state[$self->{data}->{options}->{dhcp}]); - main::print_log("[OpenSprinkler] Number of expansion boards " . $self->{data}->{options}->{ext}); - main::print_log("[OpenSprinkler] Station delay time " . $self->{data}->{options}->{sdt}); - main::print_log("[OpenSprinkler] Master station " . $self->{data}->{options}->{mas}); - main::print_log("[OpenSprinkler] master on time " . $self->{data}->{options}->{mton}); - main::print_log("[OpenSprinkler] master off time " . $self->{data}->{options}->{mtof}); - main::print_log("[OpenSprinkler] Rain Sensor " . $state[$self->{data}->{options}->{urs}]); - main::print_log("[OpenSprinkler] *Water Level " . $self->{data}->{info}->{waterlevel}); - main::print_log("[OpenSprinkler] Password is " . $pwenabled[$self->{data}->{options}->{ipas}]); - main::print_log("[OpenSprinkler] Device ID " . $self->{data}->{options}->{devid}) if defined ($self->{data}->{options}->{devid}); - main::print_log("[OpenSprinkler] LCD Contrast " . $self->{data}->{options}->{con}); - main::print_log("[OpenSprinkler] LCD Backlight " . $self->{data}->{options}->{lit}); - main::print_log("[OpenSprinkler] LCD Dimming " . $self->{data}->{options}->{dim}); - main::print_log("[OpenSprinkler] Relay Pulse Time " . $self->{data}->{options}->{rlp}) if defined ($self->{data}->{options}->{rlp}); - main::print_log("[OpenSprinkler] *Weather adjustment Method " . $self->{data}->{info}->{adjustment_method}); - main::print_log("[OpenSprinkler] Logging " . $enabled[$self->{data}->{options}->{lg}]); - main::print_log("[OpenSprinkler] Zone exapnsion boards " . $self->{data}->{options}->{dexp}); - main::print_log("[OpenSprinkler] Max zone expansion boards " . $self->{data}->{options}->{mexp}); - - main::print_log("[OpenSprinkler] Device Time " . localtime($self->{data}->{vars}->{devt})); - main::print_log("[OpenSprinkler] Number of 8 station boards " . $self->{data}->{vars}->{nbrd}); - main::print_log("[OpenSprinkler] Rain delay " . $self->{data}->{vars}->{rd}); - main::print_log("[OpenSprinkler] *Rain sensor status " . $self->{data}->{info}->{rain_sensor_status}); - main::print_log("[OpenSprinkler] Location " . $self->{data}->{vars}->{loc}); - main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); - main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); - main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); - -} - -sub process_data { - my ($self) = @_; - # Main core of processing - # set state of self for state - # for any registered child selfs, update their state if - - - for my $index (0 .. $#{$self->{data}->{stations}}) { - next if ($self->{data}->{stations}->[$index]->{status} eq "disabled"); - my $previous = "init"; - $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); - if ($previous ne $self->{data}->{stations}->[$index]->{state}) { - main::print_log("[OpenSprinkler] Station $index $self->{data}->{stations}->[$index]->{name} changed from $previous to $self->{data}->{stations}->[$index]->{state}") if ($self->{loglevel}); - $self->{previous}->{data}->{stations}->[$index]->{state} = $self->{data}->{stations}->[$index]->{state}; - if (defined $self->{child_object}->{station}->{$index}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{station}->{$index}->set($self->{data}->{stations}->[$index]->{state},'poll'); - } - } - } - - if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { - main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); - $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; - $self->set($self->{data}->{info}->{state},'poll'); - } - - if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { - main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); - $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; - if (defined $self->{child_object}->{waterlevel}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); - } - } - - if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { - main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); - $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; - if (defined $self->{child_object}->{rain_sensor_status}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); - } - } - - if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { - main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; - } - - if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { - main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; - } - - if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { - main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); - $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; - } - -} - - -sub print_logs { - my ($self) = @_; - my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); - - for my $tstamp (0..$#{$data->{runtimes}}) { - - print $data->{runtimes}[$tstamp]->{ts} . " -> "; - print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); - main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); - main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); - main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; - main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; - main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; - main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; - main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; - - } -} - -sub get_station { - my ($self,$number) = @_; - - return ($self->{data}->{stations}->[$number]->{state}); - -} - -sub set_station { - - my ($self,$station,$state,$time) = @_; - - return if (lc $state eq $self->{state}); - - #print "db: set_station state=$state, station=$station time=$time\n"; - my $cmd = "&sid=" . $station; - if (lc $state eq "on") { - $cmd .= "&en=1&t=" . $time; - } else { - $cmd .= "&en=0"; - } - my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); - if ($isSuccessResponse) { - #print "DB status=$status\n"; - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set station to $state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - -} - -sub get_sunrise { - my ($self) = @_; -# add in nice calc, minutes since midnight - my $AMPM = "AM"; - my $hour = int ($self->{data}->{vars}->{sunrise} / 60); - my $minute = $self->{data}->{vars}->{sunrise} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } - - return ("$hour:$minute $AMPM"); -} - -sub get_sunset { - my ($self) = @_; - my $AMPM = "AM"; - my $hour = int($self->{data}->{vars}->{sunset} / 60); - my $minute = $self->{data}->{vars}->{sunset} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } - - return ("$hour:$minute $AMPM"); -} - - -sub get_tz { - my ($self) = @_; - my $tz = ($self->{data}->{options}->{tz} - 48) / 4; - if ($tz >= 0 ) { - $tz = "GMT+$tz"; - } else { - $tz = "GMT$tz"; - } - return ($tz); -} - -sub reboot { - my ($self) = @_; - - my $cmd = "&rbt=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub reset { - my ($self) = @_; - - my $cmd = "&rsn=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub get_waterlevel { - my ($self) = @_; - - return ($self->{data}->{info}->{waterlevel}); -} - -sub get_rainstatus { - my ($self) = @_; - - return ($self->{data}->{info}->{rain_sensor_status}); -} - -sub set_rain_delay { - my ($self,$hours) = @_; - - my $cmd = "&rsn=$hours"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } else { - - return if (lc $p_state eq $self->{state}); - my $en; - if ((lc $p_state eq "enabled") || (lc $p_state eq "on")) { - $en = 1; - } elsif ((lc $p_state eq "disabled") || (lc $p_state eq "off")) { - $en = 0; - } else { - main::print_log("[OpenSprinkler] Error. Unknown state $p_state"); - return (0); - } - - my $cmd = "&en=" . $en; - - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - if ($isSuccessResponse) { - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - } -} - - - -package OpenSprinkler_Station; - -@OpenSprinkler_Station::ISA = ('Generic_Item'); - -sub new -{ - my ($class,$object, $number, $on_timeout) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $$self{station} = $number; - push(@{$$self{states}}, 'on','off'); - $$self{on_timeout} = 3600; #default to an hour for 'on' - $$self{on_timeout} = $on_timeout * 60 if $on_timeout; - $object->register($self,'station',$number); - $self->set($object->get_station($number),'poll'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby,$time_override) = @_; - - if ($p_setby eq 'poll') { - #print "db: setting by poll to $p_state\n"; - $self->SUPER::set($p_state); - } else { -#bounds check, add in time_override - my $time = $$self{on_timeout}; - $time = $time_override if ($time_override); - $$self{master_object}->set_station($$self{station},$p_state,$time); - } -} - - -package OpenSprinkler_Comm; - -@OpenSprinkler_Comm::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - push(@{$$self{states}}, 'online','offline'); - $object->register($self,'comm'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -package OpenSprinkler_Waterlevel; - -@OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $object->register($self,'waterlevel'); - $self->set($object->get_waterlevel,'poll'); - - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -package OpenSprinkler_Rainstatus; - -@OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $object->register($self,'rain_sensor_status'); - $self->set($object->get_rainstatus,'poll'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -1; From 8b636488e12e3921efd08f9bb2578a5af2366236 Mon Sep 17 00:00:00 2001 From: H Plato Date: Sat, 23 May 2015 10:31:18 -0600 Subject: [PATCH 23/34] Added program child object. Only able to enable/disable existing programs at this point --- lib/OpenSprinkler.pm | 173 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 153 insertions(+), 20 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 38a24b437..14e1ee3be 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -9,30 +9,28 @@ use JSON::XS; use Data::Dumper; - - # $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); # # $os_wl = new OpenSprinkler_Waterlevel($os1); # $os_rs = new OpenSprinkler_Rainstatus($os1); # $front_garden = new OpenSprinkler_Station($os1,0,60); +# $os_program = new OpenSprinkler_Program($os1,"Current"); # # $os1_comm = new OpenSprinkler_Comm($os1); # methods # -set disable # -reboot # -reset -# - get_waterlevel +# -get_waterlevel #todo +# - program control, haven't tested with spaces & special characters # - log runtimes. Maybe into a dbm file? log_runtimes method with destination. #?? disabling the opensprinkler doesn't turn off the stations? -#?? parse return codes better, #?? print logs # # make the data poll non-blocking, turn off timer # -# State can only be set by stat. Set mode will change the mode. @OpenSprinkler::ISA = ('Generic_Item'); @@ -52,6 +50,8 @@ $rest{station_info} = "jn"; $rest{get_stations} = "js"; $rest{set_stations} = "cs"; $rest{test_station} = "cm"; +$rest{get_programs} = "jp"; +$rest{set_program} = "cp"; $rest{get_log} = "jl"; @@ -174,9 +174,9 @@ sub poll { my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); - + my ($isSuccessResponse4,$programs) = $self->_get_JSON_data('get_programs'); - if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { + if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3 and $isSuccessResponse4) { $self->{data}->{name} = $vars->{loc}; $self->{data}->{loc} = $vars->{loc}; $self->{data}->{options} = $options; @@ -192,6 +192,14 @@ sub poll { print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; } + for my $index (0 .. $#{$programs->{pd}}) { + print "$index [flag=$programs->{pd}[$index][0]] [name=$programs->{pd}[$index][5]]\n" if ($self->{debug}); + $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled + $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{flag} = $programs->{pd}[$index][0]; + $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{pid} = $index; + $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{data} = "$programs->{pd}[$index][1],$programs->{pd}[$index][2],[" . join(",",@{$programs->{pd}[$index][3]}) . "],[" . join(",",@{$programs->{pd}[$index][4]}) . "]"; + } + $self->{data}->{nprograms} = $programs->{nprogs}; $self->{data}->{nstations} = $stations->{nstations}; $self->{data}->{timestamp} = time; $self->{data}->{retry} = 0; @@ -229,6 +237,11 @@ sub _get_JSON_data { $cmd = "" unless ($cmd); print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); + + # Violate RFC 2396 by forcing broken query string. Opensprinkler expectes [ and ] in URL + #${$request->uri} =~ s/%5B/[/; + #${$request->uri} =~ s/%5D/]/; + #$request->content_type("application/x-www-form-urlencoded"); my $responseObj = $ua->request($request); @@ -277,7 +290,7 @@ sub _push_JSON_data { if (defined $response->{"result"}) { my $result_code = 9; $result_code = $response->{"result"} if (defined $response->{"result"}); - print "[OpenSpinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); + main::print_log "[OpenSprinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); return ($isSuccessResponse, $result{$result_code}); } else { main::print_log("[OpenSprinkler] Warning, unknown response from data push"); @@ -288,15 +301,44 @@ sub _push_JSON_data { return ('0'); } } + +sub _url_encode { + my ($s) = @_; + #print "url [$s]\n"; + $s =~ s/ /+/g; + #$s =~ s/([^A-Za-z0-9\+-])/sprintf("%%%02X", ord($1))/seg; + $s =~ s/([^^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%0x", ord $1 /eg; + return $s; +} + +sub _setflag { + my ($flag,$state) = @_; + + my $bin = sprintf "%08b",$flag; + my $enable = substr $bin,-1,1; + my $bit = "0"; + $bit = "1" if (lc $state eq "enabled"); + + my $newbin = $bin; + substr ($newbin,-1,1) = $bit; + #print "[state=$state] [flag=$flag] [bin=$bin] [enable=$enable] [bit=$bit] [newbin=$newbin]\n"; + + return (oct "0b$newbin"); +} sub register { - my ($self, $object, $type, $number ) = @_; + my ($self, $object, $type, $id ) = @_; #my $name; #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? if (lc $type eq "station") { - &main::print_log("[OpenSprinkler] Registering station $number child object"); - $self->{child_object}->{station}->{$number} = $object; - $object->set_label($self->{data}->{stations}->[$number]->{name}); + &main::print_log("[OpenSprinkler] Registering station $id child object"); + $self->{child_object}->{station}->{$id} = $object; + $object->set_label($self->{data}->{stations}->[$id]->{name}); + } elsif (lc $type eq "program") { + &main::print_log("[OpenSprinkler] Registering program $id child object"); + $self->{child_object}->{program}->{$id} = $object; + $object->set_label($id); + } else { &main::print_log("[OpenSprinkler] Registering $type child object"); @@ -371,6 +413,13 @@ sub print_info { main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); + if (defined $self->{data}->{programs}) { + main::print_log("[OpenSprinkler] Programs found:"); + for my $key (keys %{$self->{data}->{programs}}) { + main::print_log("[OpenSprinkler]\t" . $key . " is " . $self->{data}->{programs}->{$key}->{status}); + } + } + } @@ -395,6 +444,19 @@ sub process_data { } } + for my $key (keys $self->{data}->{programs}) { + my $previous = "init"; + $previous = $self->{previous}->{data}->{programs}->{$key}->{status} if (defined $self->{previous}->{data}->{programs}->{$key}->{status}); + if ($previous ne $self->{data}->{programs}->{$key}->{status}) { + main::print_log("[OpenSprinkler] Program $key changed from $previous to $self->{data}->{programs}->{$key}->{status}") if ($self->{loglevel}); + $self->{previous}->{data}->{programs}->{$key}->{status} = $self->{data}->{programs}->{$key}->{status}; + if (defined $self->{child_object}->{program}->{$key}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{program}->{$key}->set($self->{data}->{programs}->{$key}->{status},'poll'); + } + } + } + if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; @@ -467,7 +529,8 @@ sub set_station { my ($self,$station,$state,$time) = @_; - return if (lc $state eq $self->{state}); + return unless (defined $self->{data}->{stations}->[$station]->{state}); + return if (lc $state eq $self->{data}->{stations}->[$station]->{state}); #print "db: set_station state=$state, station=$station time=$time\n"; my $cmd = "&sid=" . $station; @@ -493,6 +556,43 @@ sub set_station { } +sub get_program { + my ($self,$name) = @_; + + return ($self->{data}->{programs}->{$name}->{state}); + +} + +sub set_program { + + my ($self,$name,$state) = @_; + + return unless (defined $self->{data}->{programs}->{$name}); + return if (lc $state eq $self->{data}->{programs}->{$name}->{status}); + my $cmd = "&pid=" . $self->{data}->{programs}->{$name}->{pid}; + $cmd .= "&v=[" . _setflag($self->{data}->{programs}->{$name}->{flag},$state) . ","; + $cmd .= $self->{data}->{programs}->{$name}->{data} . "]"; + $cmd .= "&name=" . _url_encode($name); + + #print "cmd=$cmd\n"; + + my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_program',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set program to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } + +} + sub get_sunrise { my ($self) = @_; # add in nice calc, minutes since midnight @@ -645,6 +745,36 @@ sub set { } } +package OpenSprinkler_Program; + +@OpenSprinkler_Program::ISA = ('Generic_Item'); + +sub new +{ + my ($class,$object, $name) = @_; + + my $self={}; + bless $self,$class; + + $$self{master_object} = $object; + $$self{program} = $name; + push(@{$$self{states}}, 'enabled','disabled'); + $object->register($self,'program',$name); + $self->set($object->get_program($name),'poll'); + return $self; + +} + +sub set { + my ($self,$p_state,$p_setby,$time_override) = @_; + + if ($p_setby eq 'poll') { + #print "db: setting by poll to $p_state\n"; + $self->SUPER::set($p_state); + } else { + $$self{master_object}->set_program($$self{program},$p_state); + } +} package OpenSprinkler_Comm; @@ -666,10 +796,11 @@ sub new { sub set { my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } + if (defined $p_setby) { + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } + } } package OpenSprinkler_Waterlevel; @@ -693,9 +824,11 @@ sub new { sub set { my ($self,$p_state,$p_setby) = @_; - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } + if (defined $p_setby) { + if ($p_setby eq 'poll') { + $self->SUPER::set($p_state); + } + } } package OpenSprinkler_Rainstatus; From 033ca7659b2af9a5c3b77cdb23d10d6cb43f37ed Mon Sep 17 00:00:00 2001 From: H Plato Date: Sat, 23 May 2015 14:07:27 -0600 Subject: [PATCH 24/34] Fixed issue when spaces are in the names of programs --- lib/OpenSprinkler.pm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 14e1ee3be..6a12683aa 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -193,11 +193,13 @@ sub poll { $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; } for my $index (0 .. $#{$programs->{pd}}) { - print "$index [flag=$programs->{pd}[$index][0]] [name=$programs->{pd}[$index][5]]\n" if ($self->{debug}); - $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled - $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{flag} = $programs->{pd}[$index][0]; - $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{pid} = $index; - $self->{data}->{programs}->{$programs->{pd}[$index][5]}->{data} = "$programs->{pd}[$index][1],$programs->{pd}[$index][2],[" . join(",",@{$programs->{pd}[$index][3]}) . "],[" . join(",",@{$programs->{pd}[$index][4]}) . "]"; + my $name = $programs->{pd}[$index][5]; + $name =~ s/\+/ /g; #replace any + with spaces + print "$index [flag=$programs->{pd}[$index][0]] [osname=$programs->{pd}[$index][5]] [name=$name]\n" if ($self->{debug}); + $self->{data}->{programs}->{$name}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled + $self->{data}->{programs}->{$name}->{flag} = $programs->{pd}[$index][0]; + $self->{data}->{programs}->{$name}->{pid} = $index; + $self->{data}->{programs}->{$name}->{data} = "$programs->{pd}[$index][1],$programs->{pd}[$index][2],[" . join(",",@{$programs->{pd}[$index][3]}) . "],[" . join(",",@{$programs->{pd}[$index][4]}) . "]"; } $self->{data}->{nprograms} = $programs->{nprogs}; $self->{data}->{nstations} = $stations->{nstations}; @@ -444,7 +446,7 @@ sub process_data { } } - for my $key (keys $self->{data}->{programs}) { + for my $key (keys %{$self->{data}->{programs}}) { my $previous = "init"; $previous = $self->{previous}->{data}->{programs}->{$key}->{status} if (defined $self->{previous}->{data}->{programs}->{$key}->{status}); if ($previous ne $self->{data}->{programs}->{$key}->{status}) { @@ -574,7 +576,7 @@ sub set_program { $cmd .= $self->{data}->{programs}->{$name}->{data} . "]"; $cmd .= "&name=" . _url_encode($name); - #print "cmd=$cmd\n"; + #print "XXXX cmd=$cmd\n"; my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_program',$cmd); if ($isSuccessResponse) { From 0dd431868441be90d63daccbf9e20bfa44881a6e Mon Sep 17 00:00:00 2001 From: H Plato Date: Sat, 23 May 2015 19:03:19 -0600 Subject: [PATCH 25/34] Fixed an issue when trying to url encode spaces --- lib/OpenSprinkler.pm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 6a12683aa..8232f4d01 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -194,7 +194,6 @@ sub poll { } for my $index (0 .. $#{$programs->{pd}}) { my $name = $programs->{pd}[$index][5]; - $name =~ s/\+/ /g; #replace any + with spaces print "$index [flag=$programs->{pd}[$index][0]] [osname=$programs->{pd}[$index][5]] [name=$name]\n" if ($self->{debug}); $self->{data}->{programs}->{$name}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled $self->{data}->{programs}->{$name}->{flag} = $programs->{pd}[$index][0]; @@ -307,9 +306,8 @@ sub _push_JSON_data { sub _url_encode { my ($s) = @_; #print "url [$s]\n"; - $s =~ s/ /+/g; #$s =~ s/([^A-Za-z0-9\+-])/sprintf("%%%02X", ord($1))/seg; - $s =~ s/([^^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%0x", ord $1 /eg; + $s =~ s/([^^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%0X", ord $1 /eg; return $s; } From ce957a0f4726b245f1d9741a2de76190c52837d7 Mon Sep 17 00:00:00 2001 From: H Plato Date: Sun, 24 May 2015 19:15:27 -0600 Subject: [PATCH 26/34] Added some documentation, also disabled any programs that don't exist on the opensprinkler and generate a warning at startup --- lib/OpenSprinkler.pm | 775 +++++++++++++++++++++---------------------- 1 file changed, 382 insertions(+), 393 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 8232f4d01..3d7a33741 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -8,30 +8,58 @@ use HTTP::Request::Common qw(POST); use JSON::XS; use Data::Dumper; +# OpenSprinkler - MH Module for the opensprinker irrigation system. www.opensprinkler.com -# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); +# Master object : OpenSprinkler('ip-address','md5-password',poll-time) +# - poll-time default is every 10 seconds +# - to generate your md5 password use the md5 program /> md5 -s password +# md5 is an external program, seems to be installed by default on linux, my mac has it as well +# $os1 = new OpenSprinkler('10.0.0.1','5f4dcc3b5aa765d61d8327deb882cf99',10); # -# $os_wl = new OpenSprinkler_Waterlevel($os1); -# $os_rs = new OpenSprinkler_Rainstatus($os1); -# $front_garden = new OpenSprinkler_Station($os1,0,60); -# $os_program = new OpenSprinkler_Program($os1,"Current"); +# Communication tracker object: OpenSprinkler_Comm(master-object); +# - reports online/offline +# $os1_comm = new OpenSprinkler_Comm($os1); # -# $os1_comm = new OpenSprinkler_Comm($os1); -# methods -# -set disable -# -reboot -# -reset -# -get_waterlevel - - -#todo -# - program control, haven't tested with spaces & special characters -# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. -#?? disabling the opensprinkler doesn't turn off the stations? -#?? print logs -# # make the data poll non-blocking, turn off timer +# Water Level tracker: OpenSprinkler_Waterlevel(master-object); +# - reports water level in % +# $os_wl = new OpenSprinkler_Waterlevel($os1); # +# Rain Sensor Status: OpenSprinkler_Rainstatus(master-object); +# $os_rs = new OpenSprinkler_Rainstatus($os1); +# +# Stations: OpenSprinkler_Station(master,station-id,minutes-for-on); +# - station-d starts at 0. So station 1 is id 0 +# - minutes-for-on. Defaults to 60. Length of time that an MH 'ON' command will turn the station on for +# $front_garden = new OpenSprinkler_Station($os1,0,30); +# +# Program: OpenSprinkler_Program(master,"program name"); +# - program-name MUST match the name of the program in the OS for this to work. +# - allows for enabling/disabling programs +# $os_program = new OpenSprinkler_Program($os1,"Current"); +# + +# Opensprinkler operations +# $os1->reboot() +# $os1->reset() +# $os1->get_waterlevel() +# $os1->get_rainstatus() + +# General Notes: +# -only tested with firmware 2.14 +# Child object Notes: +# Master +# - Disabling the opensprinkler itself doesn't turn off any running stations. +# Programs +# - Only enabling/disabling programs is supported at this time + +# TODO +# - be nice to pull runtimes and store it into a dbm file, or maybe RRD? +# - disabling the opensprinkler doesn't turn off any running stations. +# - the architecture can create pauses -- long term adopt Kevin's approach w/ Nests +# - no ability to print logs. The built-in web interface does this well already + +# v1.0 release @OpenSprinkler::ISA = ('Generic_Item'); @@ -68,167 +96,154 @@ $result{8} = "not permitted"; $result{9} = "unknown error"; sub new { - my ($class, $host,$pwd,$poll) = @_; - my $self = {}; - bless $self, $class; - $self->{data} = undef; - $self->{child_object} = undef; - $self->{config}->{cache_time} = 5; #TODO fix cache timeouts - $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; - $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes - $self->{config}->{poll_seconds} = 10; - $self->{config}->{poll_seconds} = $poll if ($poll); - $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); - $self->{updating} = 0; - $self->{data}->{retry} = 0; - $self->{data}->{stations} = (); - $self->{host} = $host; - $self->{password} = $pwd; - $self->{debug} = 0; - $self->{loglevel} = 1; - $self->{timeout} = 4; #300; - push(@{$$self{states}}, 'enabled', 'disabled'); - - $self->_init; - $self->{timer} = new Timer; - $self->start_timer; - return $self; + my ($class, $host,$pwd,$poll) = @_; + my $self = {}; + bless $self, $class; + $self->{data} = undef; + $self->{child_object} = undef; + $self->{config}->{cache_time} = 5; + $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; + $self->{config}->{tz} = $::config_params{time_zone}; + $self->{config}->{poll_seconds} = 10; + $self->{config}->{poll_seconds} = $poll if ($poll); + $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); + $self->{updating} = 0; + $self->{data}->{retry} = 0; + $self->{data}->{stations} = (); + $self->{host} = $host; + $self->{password} = $pwd; + $self->{debug} = 0; + $self->{loglevel} = 1; + $self->{timeout} = 4; #300; + push(@{$$self{states}}, 'enabled', 'disabled'); + + $self->_init; + $self->{timer} = new Timer; + $self->start_timer; + return $self; } sub _poll_check { - my ($self) = @_; - #main::print_log("[OpenSprinkler] _poll_check initiated"); - #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background + my ($self) = @_; $self->get_data(); } sub get_data { - my ($self) = @_; - #main::print_log("[OpenSprinkler] get_data initiated"); - $self->poll; - $self->process_data; + my ($self) = @_; + $self->poll; + $self->process_data; } sub _init { - my ($self) = @_; - - my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); - - if ($isSuccessResponse1) { + my ($self) = @_; - if ($osp) { #->{fwv} > 213) { - - main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); - my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); - for my $index (0 .. $#{$stations->{snames}}) { - #print "$index: $stations->{snames}[$index]\n"; - $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; - } - # Check to see if station is disabled, Bitwise operation - for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { - my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; - for my $bit ( 0 .. 7) { - my $station_id = (($stn_dis * 8) + $bit); - my $disabled = substr $bin,(7-$bit),1; - $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; - } - } + my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); + + if ($isSuccessResponse1) { + if ($osp) { + main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); + my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); + for my $index (0 .. $#{$stations->{snames}}) { + #print "$index: $stations->{snames}[$index]\n"; + $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; + } + # Check to see if station is disabled, Bitwise operation + for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { + my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; + for my $bit ( 0 .. 7) { + my $station_id = (($stn_dis * 8) + $bit); + my $disabled = substr $bin,(7-$bit),1; + $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; + } + } #print Dumper $self; - $self->{previous}->{info}->{waterlevel} = $osp->{wl}; - $self->{previous}->{info}->{rs} = "init"; - $self->{previous}->{info}->{state} = "disabled"; - $self->{previous}->{info}->{adjustment_method} = "init"; - $self->{previous}->{info}->{rain_sensor_status} = "init"; - $self->{previous}->{info}->{sunrise} = 0; - $self->{previous}->{info}->{sunset} = 0; - if ($self->poll()) { - main::print_log("[OpenSprinkler] Data Successfully Retrieved"); - $self->{active} = 1; - $self->print_info(); - $self->set($self->{data}->{info}->{state},'poll'); - - } else { - main::print_log("[OpenSprinkler] Problem retrieving initial data"); - $self->{active} = 0; - return ('1'); - } - - } else { - main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); - $self->{active} = 0; - return ('1'); - } - + $self->{previous}->{info}->{waterlevel} = $osp->{wl}; + $self->{previous}->{info}->{rs} = "init"; + $self->{previous}->{info}->{state} = "disabled"; + $self->{previous}->{info}->{adjustment_method} = "init"; + $self->{previous}->{info}->{rain_sensor_status} = "init"; + $self->{previous}->{info}->{sunrise} = 0; + $self->{previous}->{info}->{sunset} = 0; + if ($self->poll()) { + main::print_log("[OpenSprinkler] Data Successfully Retrieved"); + $self->{active} = 1; + $self->print_info(); + $self->set($self->{data}->{info}->{state},'poll'); + } else { + main::print_log("[OpenSprinkler] Problem retrieving initial data"); + $self->{active} = 0; + return ('1'); + } + } else { + main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); + $self->{active} = 0; + return ('1'); + } } else { - main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); - $self->{active} = 0; - return ('1'); + main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); + $self->{active} = 0; + return ('1'); } } sub poll { - my ($self) = @_; - - main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); - - my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); - my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); - my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); - my ($isSuccessResponse4,$programs) = $self->_get_JSON_data('get_programs'); - - if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3 and $isSuccessResponse4) { - $self->{data}->{name} = $vars->{loc}; - $self->{data}->{loc} = $vars->{loc}; - $self->{data}->{options} = $options; - $self->{data}->{vars} = $vars; - $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; - $self->{data}->{info}->{waterlevel} = $options->{wl}; - $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; - $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; - $self->{data}->{info}->{sunrise} = $vars->{sunrise}; - $self->{data}->{info}->{sunset} = $vars->{sunset}; - - for my $index (0 .. $#{$stations->{sn}}) { - print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); - $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; - } - for my $index (0 .. $#{$programs->{pd}}) { - my $name = $programs->{pd}[$index][5]; - print "$index [flag=$programs->{pd}[$index][0]] [osname=$programs->{pd}[$index][5]] [name=$name]\n" if ($self->{debug}); - $self->{data}->{programs}->{$name}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled - $self->{data}->{programs}->{$name}->{flag} = $programs->{pd}[$index][0]; - $self->{data}->{programs}->{$name}->{pid} = $index; - $self->{data}->{programs}->{$name}->{data} = "$programs->{pd}[$index][1],$programs->{pd}[$index][2],[" . join(",",@{$programs->{pd}[$index][3]}) . "],[" . join(",",@{$programs->{pd}[$index][4]}) . "]"; - } - $self->{data}->{nprograms} = $programs->{nprogs}; - $self->{data}->{nstations} = $stations->{nstations}; - $self->{data}->{timestamp} = time; - $self->{data}->{retry} = 0; + my ($self) = @_; + main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); + my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); + my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); + my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); + my ($isSuccessResponse4,$programs) = $self->_get_JSON_data('get_programs'); + if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3 and $isSuccessResponse4) { + $self->{data}->{name} = $vars->{loc}; + $self->{data}->{loc} = $vars->{loc}; + $self->{data}->{options} = $options; + $self->{data}->{vars} = $vars; + $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; + $self->{data}->{info}->{waterlevel} = $options->{wl}; + $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; + $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; + $self->{data}->{info}->{sunrise} = $vars->{sunrise}; + $self->{data}->{info}->{sunset} = $vars->{sunset}; + for my $index (0 .. $#{$stations->{sn}}) { + print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); + $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; + } + for my $index (0 .. $#{$programs->{pd}}) { + my $name = $programs->{pd}[$index][5]; + print "$index [flag=$programs->{pd}[$index][0]] [osname=$programs->{pd}[$index][5]] [name=$name]\n" if ($self->{debug}); + $self->{data}->{programs}->{$name}->{status} = ($programs->{pd}[$index][0] % 2 == 1 ) ? "enabled" : "disabled"; #if number is odd, then bit 0 set and disabled + $self->{data}->{programs}->{$name}->{flag} = $programs->{pd}[$index][0]; + $self->{data}->{programs}->{$name}->{pid} = $index; + $self->{data}->{programs}->{$name}->{data} = "$programs->{pd}[$index][1],$programs->{pd}[$index][2],[" . join(",",@{$programs->{pd}[$index][3]}) . "],[" . join(",",@{$programs->{pd}[$index][4]}) . "]"; + } + $self->{data}->{nprograms} = $programs->{nprogs}; + $self->{data}->{nstations} = $stations->{nstations}; + $self->{data}->{timestamp} = time; + $self->{data}->{retry} = 0; #print Dumper $self; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } - return ('1'); - } else { - main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); - $self->{data}->{retry}++; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } - return ('0'); - } - + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } + return ('1'); + } else { + main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); + $self->{data}->{retry}++; + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } + return ('0'); + } } #------------------------------------------------------------------------------------ sub _get_JSON_data { - my ($self, $mode, $cmd) = @_; + my ($self, $mode, $cmd) = @_; my $ua = new LWP::UserAgent(keep_alive=>1); $ua->timeout($self->{timeout}); @@ -252,52 +267,52 @@ sub _get_JSON_data { print 'Response code: ' . $responseCode . "\n" if $self->{debug}; my $isSuccessResponse = $responseCode < 400; if (! $isSuccessResponse ) { - main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } + main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "offline") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("offline",'poll'); + } + } return ('0'); } else { - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } + if (defined $self->{child_object}->{comm}) { + if ($self->{child_object}->{comm}->state() ne "online") { + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{comm}->set("online",'poll'); + } + } } my $response; eval { - $response = JSON::XS->new->decode ($responseObj->content); + $response = JSON::XS->new->decode ($responseObj->content); }; - # catch crashes: - if($@){ - print "[OpenSprinkler] ERROR! JSON parser crashed! $@\n"; - return ('0'); - } else { - return ($isSuccessResponse, $response); + # catch crashes: + if($@){ + print "[OpenSprinkler] ERROR! JSON parser crashed! $@\n"; + return ('0'); + } else { + return ($isSuccessResponse, $response); } } sub _push_JSON_data { - my ($self, $mode, $cmd) = @_; - - unless ($self->{updating}) { - $self->{updating} = 1; - my ($isSuccessResponse,$response) = $self->_get_JSON_data($mode,$cmd); - $self->{updating} = 0; - if (defined $response->{"result"}) { - my $result_code = 9; - $result_code = $response->{"result"} if (defined $response->{"result"}); - main::print_log "[OpenSprinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); - return ($isSuccessResponse, $result{$result_code}); - } else { - main::print_log("[OpenSprinkler] Warning, unknown response from data push"); - return ('0'); - } - } else { + my ($self, $mode, $cmd) = @_; + + unless ($self->{updating}) { + $self->{updating} = 1; + my ($isSuccessResponse,$response) = $self->_get_JSON_data($mode,$cmd); + $self->{updating} = 0; + if (defined $response->{"result"}) { + my $result_code = 9; + $result_code = $response->{"result"} if (defined $response->{"result"}); + main::print_log "[OpenSprinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); + return ($isSuccessResponse, $result{$result_code}); + } else { + main::print_log("[OpenSprinkler] Warning, unknown response from data push"); + return ('0'); + } + } else { main::print_log("[OpenSprinkler] Warning, not pushing data due to operation in progress"); return ('0'); } @@ -327,44 +342,44 @@ sub _setflag { } sub register { - my ($self, $object, $type, $id ) = @_; - #my $name; - #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? - if (lc $type eq "station") { - &main::print_log("[OpenSprinkler] Registering station $id child object"); - $self->{child_object}->{station}->{$id} = $object; - $object->set_label($self->{data}->{stations}->[$id]->{name}); + my ($self, $object, $type, $id ) = @_; + if (lc $type eq "station") { + &main::print_log("[OpenSprinkler] Registering station $id child object"); + $self->{child_object}->{station}->{$id} = $object; + $object->set_label($self->{data}->{stations}->[$id]->{name}); } elsif (lc $type eq "program") { - &main::print_log("[OpenSprinkler] Registering program $id child object"); - $self->{child_object}->{program}->{$id} = $object; - $object->set_label($id); - + if (defined $self->{data}->{programs}->{$id}) { + &main::print_log("[OpenSprinkler] Registering program $id child object"); + $self->{child_object}->{program}->{$id} = $object; + $object->set_label($id); + } else { + &main::print_log("[OpenSprinkler] WARNING: Program $id doesn't have a corresponding program on the Opensprinkler!"); + } } else { - &main::print_log("[OpenSprinkler] Registering $type child object"); - + &main::print_log("[OpenSprinkler] Registering $type child object"); $self->{child_object}->{$type} = $object; } - } +} sub stop_timer { - my ($self) = @_; + my ($self) = @_; - if (defined $self->{timer}) { - $self->{timer}->stop() if ($self->{timer}->active()); - } else { - main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); - } + if (defined $self->{timer}) { + $self->{timer}->stop() if ($self->{timer}->active()); + } else { + main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); + } } sub start_timer { - my ($self) = @_; + my ($self) = @_; - if (defined $self->{timer}) { - $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); + if (defined $self->{timer}) { + $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); } else { - main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); - } + main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); + } } sub print_info { @@ -419,8 +434,6 @@ sub print_info { main::print_log("[OpenSprinkler]\t" . $key . " is " . $self->{data}->{programs}->{$key}->{status}); } } - - } sub process_data { @@ -458,42 +471,42 @@ sub process_data { } if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { - main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); - $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; - $self->set($self->{data}->{info}->{state},'poll'); + main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); + $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; + $self->set($self->{data}->{info}->{state},'poll'); } if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { - main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); - $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; - if (defined $self->{child_object}->{waterlevel}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); - } + main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); + $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; + if (defined $self->{child_object}->{waterlevel}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); + } } if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { - main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); - $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; - if (defined $self->{child_object}->{rain_sensor_status}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); - } + main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); + $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; + if (defined $self->{child_object}->{rain_sensor_status}) { + main::print_log "Child object found. Updating..." if ($self->{loglevel}); + $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); + } } if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { - main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; + main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; } if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { - main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; + main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); + $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; } if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { - main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); - $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; + main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); + $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; } } @@ -501,134 +514,115 @@ sub process_data { sub print_logs { my ($self) = @_; - my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); - - for my $tstamp (0..$#{$data->{runtimes}}) { - - print $data->{runtimes}[$tstamp]->{ts} . " -> "; - print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); - main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); - main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); - main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; - main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; - main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; - main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; - main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; - - } + } sub get_station { - my ($self,$number) = @_; - - return ($self->{data}->{stations}->[$number]->{state}); + my ($self,$number) = @_; + return ($self->{data}->{stations}->[$number]->{state}); } sub set_station { - my ($self,$station,$state,$time) = @_; - - return unless (defined $self->{data}->{stations}->[$station]->{state}); - return if (lc $state eq $self->{data}->{stations}->[$station]->{state}); - - #print "db: set_station state=$state, station=$station time=$time\n"; - my $cmd = "&sid=" . $station; - if (lc $state eq "on") { - $cmd .= "&en=1&t=" . $time; - } else { - $cmd .= "&en=0"; - } - my ($isSuccessResponse,$status) = $self->_push_JSON_data('test_station',$cmd); - if ($isSuccessResponse) { - #print "DB status=$status\n"; - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set station to $state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - + my ($self,$station,$state,$time) = @_; + + return unless (defined $self->{data}->{stations}->[$station]->{state}); + return if (lc $state eq $self->{data}->{stations}->[$station]->{state}); + + #print "db: set_station state=$state, station=$station time=$time\n"; + my $cmd = "&sid=" . $station; + if (lc $state eq "on") { + $cmd .= "&en=1&t=" . $time; + } else { + $cmd .= "&en=0"; + } + my ($isSuccessResponse,$status) = $self->_push_JSON_data('test_station',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set station to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } } sub get_program { - my ($self,$name) = @_; - - return ($self->{data}->{programs}->{$name}->{state}); + my ($self,$name) = @_; + return ($self->{data}->{programs}->{$name}->{state}); } sub set_program { - my ($self,$name,$state) = @_; + my ($self,$name,$state) = @_; - return unless (defined $self->{data}->{programs}->{$name}); - return if (lc $state eq $self->{data}->{programs}->{$name}->{status}); - my $cmd = "&pid=" . $self->{data}->{programs}->{$name}->{pid}; - $cmd .= "&v=[" . _setflag($self->{data}->{programs}->{$name}->{flag},$state) . ","; - $cmd .= $self->{data}->{programs}->{$name}->{data} . "]"; - $cmd .= "&name=" . _url_encode($name); + return unless (defined $self->{data}->{programs}->{$name}); + return if (lc $state eq $self->{data}->{programs}->{$name}->{status}); + my $cmd = "&pid=" . $self->{data}->{programs}->{$name}->{pid}; + $cmd .= "&v=[" . _setflag($self->{data}->{programs}->{$name}->{flag},$state) . ","; + $cmd .= $self->{data}->{programs}->{$name}->{data} . "]"; + $cmd .= "&name=" . _url_encode($name); - #print "XXXX cmd=$cmd\n"; - - my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_program',$cmd); - if ($isSuccessResponse) { - #print "DB status=$status\n"; - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set program to $state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - + #print "XXXX cmd=$cmd\n"; + + my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_program',$cmd); + if ($isSuccessResponse) { + #print "DB status=$status\n"; + if ($status eq "success") { #todo parse return value + $self->poll; + return (1); + } else { + main::print_log("[OpenSprinkler] Error. Could not set program to $state"); + return (0); + } + } else { + main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); + return (0); + } } sub get_sunrise { - my ($self) = @_; -# add in nice calc, minutes since midnight - my $AMPM = "AM"; - my $hour = int ($self->{data}->{vars}->{sunrise} / 60); - my $minute = $self->{data}->{vars}->{sunrise} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } + my ($self) = @_; + my $AMPM = "AM"; + my $hour = int ($self->{data}->{vars}->{sunrise} / 60); + my $minute = $self->{data}->{vars}->{sunrise} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } - return ("$hour:$minute $AMPM"); + return ("$hour:$minute $AMPM"); } sub get_sunset { - my ($self) = @_; - my $AMPM = "AM"; - my $hour = int($self->{data}->{vars}->{sunset} / 60); - my $minute = $self->{data}->{vars}->{sunset} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } + my ($self) = @_; + my $AMPM = "AM"; + my $hour = int($self->{data}->{vars}->{sunset} / 60); + my $minute = $self->{data}->{vars}->{sunset} % 60; + if ($hour > 12) { + $hour = $hour - 12; + $AMPM= "PM"; + } return ("$hour:$minute $AMPM"); } sub get_tz { - my ($self) = @_; - my $tz = ($self->{data}->{options}->{tz} - 48) / 4; - if ($tz >= 0 ) { - $tz = "GMT+$tz"; - } else { - $tz = "GMT$tz"; - } + my ($self) = @_; + my $tz = ($self->{data}->{options}->{tz} - 48) / 4; + if ($tz >= 0 ) { + $tz = "GMT+$tz"; + } else { + $tz = "GMT$tz"; + } return ($tz); } @@ -715,30 +709,29 @@ package OpenSprinkler_Station; sub new { - my ($class,$object, $number, $on_timeout) = @_; - - my $self={}; - bless $self,$class; + my ($class,$object, $number, $on_timeout) = @_; - $$self{master_object} = $object; - $$self{station} = $number; - push(@{$$self{states}}, 'on','off'); - $$self{on_timeout} = 3600; #default to an hour for 'on' - $$self{on_timeout} = $on_timeout * 60 if $on_timeout; - $object->register($self,'station',$number); - $self->set($object->get_station($number),'poll'); - return $self; + my $self={}; + bless $self,$class; + $$self{master_object} = $object; + $$self{station} = $number; + push(@{$$self{states}}, 'on','off'); + $$self{on_timeout} = 3600; #default to an hour for 'on' + $$self{on_timeout} = $on_timeout * 60 if $on_timeout; + $object->register($self,'station',$number); + $self->set($object->get_station($number),'poll'); + return $self; } sub set { - my ($self,$p_state,$p_setby,$time_override) = @_; + my ($self,$p_state,$p_setby,$time_override) = @_; if ($p_setby eq 'poll') { #print "db: setting by poll to $p_state\n"; $self->SUPER::set($p_state); } else { -#bounds check, add in time_override + #bounds check, add in time_override my $time = $$self{on_timeout}; $time = $time_override if ($time_override); $$self{master_object}->set_station($$self{station},$p_state,$time); @@ -751,25 +744,24 @@ package OpenSprinkler_Program; sub new { - my ($class,$object, $name) = @_; + my ($class,$object, $name) = @_; - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $$self{program} = $name; - push(@{$$self{states}}, 'enabled','disabled'); - $object->register($self,'program',$name); - $self->set($object->get_program($name),'poll'); - return $self; + my $self={}; + bless $self,$class; + $$self{master_object} = $object; + $$self{program} = $name; + push(@{$$self{states}}, 'enabled','disabled'); + $object->register($self,'program',$name); + $self->set($object->get_program($name),'poll'); + return $self; } sub set { - my ($self,$p_state,$p_setby,$time_override) = @_; + my ($self,$p_state,$p_setby,$time_override) = @_; if ($p_setby eq 'poll') { - #print "db: setting by poll to $p_state\n"; + #print "db: setting by poll to $p_state\n"; $self->SUPER::set($p_state); } else { $$self{master_object}->set_program($$self{program},$p_state); @@ -781,21 +773,20 @@ package OpenSprinkler_Comm; @OpenSprinkler_Comm::ISA = ('Generic_Item'); sub new { - my ($class,$object) = @_; + my ($class,$object) = @_; - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - push(@{$$self{states}}, 'online','offline'); - $self->set('offline'); - $object->register($self,'comm'); - return $self; + my $self={}; + bless $self,$class; + $$self{master_object} = $object; + push(@{$$self{states}}, 'online','offline'); + $self->set('offline'); + $object->register($self,'comm'); + return $self; } sub set { - my ($self,$p_state,$p_setby) = @_; + my ($self,$p_state,$p_setby) = @_; if (defined $p_setby) { if ($p_setby eq 'poll') { $self->SUPER::set($p_state); @@ -808,21 +799,20 @@ package OpenSprinkler_Waterlevel; @OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); sub new { - my ($class,$object) = @_; + my ($class,$object) = @_; - my $self={}; - bless $self,$class; + my $self={}; + bless $self,$class; - $$self{master_object} = $object; - $object->register($self,'waterlevel'); - $self->set($object->get_waterlevel,'poll'); - - return $self; + $$self{master_object} = $object; + $object->register($self,'waterlevel'); + $self->set($object->get_waterlevel,'poll'); + return $self; } sub set { - my ($self,$p_state,$p_setby) = @_; + my ($self,$p_state,$p_setby) = @_; if (defined $p_setby) { if ($p_setby eq 'poll') { @@ -836,20 +826,19 @@ package OpenSprinkler_Rainstatus; @OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); sub new { - my ($class,$object) = @_; + my ($class,$object) = @_; - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $object->register($self,'rain_sensor_status'); - $self->set($object->get_rainstatus,'poll'); - return $self; + my $self={}; + bless $self,$class; + $$self{master_object} = $object; + $object->register($self,'rain_sensor_status'); + $self->set($object->get_rainstatus,'poll'); + return $self; } sub set { - my ($self,$p_state,$p_setby) = @_; + my ($self,$p_state,$p_setby) = @_; if ($p_setby eq 'poll') { $self->SUPER::set($p_state); From 0fc43c856e22aa332dbdda6487b63b13dcc4da41 Mon Sep 17 00:00:00 2001 From: H Plato Date: Thu, 28 May 2015 20:17:12 -0600 Subject: [PATCH 27/34] Fixed rain sensor, and modified data push for reboot command --- lib/OpenSprinkler.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 3d7a33741..2b58b1fa2 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -630,7 +630,7 @@ sub reboot { my ($self) = @_; my $cmd = "&rbt=1"; - my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_vars',$cmd); + my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); return ($status); } @@ -659,7 +659,7 @@ sub get_rainstatus { sub set_rain_delay { my ($self,$hours) = @_; - my $cmd = "&rsn=$hours"; + my $cmd = "&rd=$hours"; my ($isSuccessResponse,$status) = $self->_push_JSON_data('set_vars',$cmd); return ($status); From c78fed31ae406f6af6f3e8231a83ee61bdafa098 Mon Sep 17 00:00:00 2001 From: H Plato Date: Sun, 31 May 2015 11:37:23 -0600 Subject: [PATCH 28/34] Cosmetic changes to communication tracker log entries. Kludge to respond back for reboot commands --- lib/OpenSprinkler.pm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/OpenSprinkler.pm b/lib/OpenSprinkler.pm index 2b58b1fa2..b2f478a75 100644 --- a/lib/OpenSprinkler.pm +++ b/lib/OpenSprinkler.pm @@ -223,7 +223,7 @@ sub poll { #print Dumper $self; if (defined $self->{child_object}->{comm}) { if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating to online..." if ($self->{loglevel}); $self->{child_object}->{comm}->set("online",'poll'); } } @@ -233,7 +233,7 @@ sub poll { $self->{data}->{retry}++; if (defined $self->{child_object}->{comm}) { if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating to offline..." if ($self->{loglevel}); $self->{child_object}->{comm}->set("offline",'poll'); } } @@ -259,10 +259,10 @@ sub _get_JSON_data { #${$request->uri} =~ s/%5D/]/; #$request->content_type("application/x-www-form-urlencoded"); - + my $responseObj = $ua->request($request); print $responseObj->content."\n--------------------\n" if $self->{debug}; - + my $responseCode = $responseObj->code; print 'Response code: ' . $responseCode . "\n" if $self->{debug}; my $isSuccessResponse = $responseCode < 400; @@ -270,7 +270,7 @@ sub _get_JSON_data { main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); if (defined $self->{child_object}->{comm}) { if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating to offline..." if ($self->{loglevel}); $self->{child_object}->{comm}->set("offline",'poll'); } } @@ -278,11 +278,14 @@ sub _get_JSON_data { } else { if (defined $self->{child_object}->{comm}) { if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); + main::print_log "[OpenSprinkler] Communication Tracking object found. Updating to online..." if ($self->{loglevel}); $self->{child_object}->{comm}->set("online",'poll'); } } - } + } + + return ($isSuccessResponse,'1') if ($cmd eq "&rbt=1"); #kludge, reboot kills the OSP, so we don't get a return code, just always return success + my $response; eval { $response = JSON::XS->new->decode ($responseObj->content); From b3ba7153db033d62545fd5f53f236a2adf53fce2 Mon Sep 17 00:00:00 2001 From: "H.Plato" Date: Sat, 6 Jun 2015 12:04:18 -0600 Subject: [PATCH 29/34] Removed sort routine from javascript.js and sorted all group members that are returned via json --- lib/json_server.pl | 18 +++++++++++++----- web/ia7/include/javascript.js | 8 ++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/json_server.pl b/lib/json_server.pl index 67ce4f180..dd1a9b913 100644 --- a/lib/json_server.pl +++ b/lib/json_server.pl @@ -527,11 +527,16 @@ sub build_parent_table { for my $group_name (&list_objects_by_type('Group')) { my $group = &get_object_by_name($group_name); $group_name =~ s/\$|\%|\&|\@//g; - for my $object ($group->list(undef, undef,1)) { - my $obj_name = $object->get_object_name; + unless (defined $group) { + print_log "json: build_parent_table, group_name $group_name doesn't have an object?" if $Debug{json}; + next; + } else { + for my $object ($group->list(undef, undef,1)) { + my $obj_name = $object->get_object_name; push (@{$parent_table{$obj_name}}, $group_name); - } - } + } + } + } return \%parent_table; } @@ -624,10 +629,13 @@ sub json_object_detail { ## can add linked items too. if (ref($object) eq 'Group') { $value = []; + my @tmp; ## pull all the members into a temp array that can be sorted for my $obj_name (&list_objects_by_group($object->get_object_name, 1)) { $obj_name =~ s/\$|\%|\&|\@//g; - push (@{$value}, $obj_name); + #push (@{$value}, $obj_name); + push (@tmp, $obj_name); } + push (@{$value}, sort @tmp); } } elsif ($f eq 'parents'){ diff --git a/web/ia7/include/javascript.js b/web/ia7/include/javascript.js index e148a991b..6f440fa2d 100644 --- a/web/ia7/include/javascript.js +++ b/web/ia7/include/javascript.js @@ -224,7 +224,7 @@ function changePage (){ nav_name = collection_keys_arr[i].replace("$", ''); nav_link = '#path=/objects&parents='+nav_name; if (nav_name == "Group") nav_link = '#path=objects&type=Group'; //Hardcode this use case - if (json_store.objects[nav_name].label != undefined) nav_name = (json_store.objects[nav_name].label); + if (json_store.objects[nav_name].label !== undefined) nav_name = (json_store.objects[nav_name].label); } else { @@ -348,9 +348,9 @@ var loadList = function() { // Sort that list if a sort exists, probably exists a shorter way to // write the sort - if (sort_list !== undefined){ - entity_list = sortArrayByArray(entity_list, sort_list); - } +// if (sort_list !== undefined){ +// entity_list = sortArrayByArray(entity_list, sort_list); +// } for (var i = 0; i < entity_list.length; i++) { var entity = entity_list[i]; From c105904109e2f8a3325a47d40b270dcd92ee6495 Mon Sep 17 00:00:00 2001 From: H Plato Date: Wed, 10 Jun 2015 06:23:05 -0600 Subject: [PATCH 30/34] Removed accidental OpenSprinkler in root --- OpenSprinkler.pm | 714 ----------------------------------------------- 1 file changed, 714 deletions(-) delete mode 100644 OpenSprinkler.pm diff --git a/OpenSprinkler.pm b/OpenSprinkler.pm deleted file mode 100644 index 6ff6e4a9d..000000000 --- a/OpenSprinkler.pm +++ /dev/null @@ -1,714 +0,0 @@ -package OpenSprinkler; - -use strict; -use warnings; - -use LWP::UserAgent; -use HTTP::Request::Common qw(POST); -use JSON::XS; -use Data::Dumper; - - - - -# $os1 = new OpenSprinkler('192.168.0.100','md5-password',poll); -# -# $os_wl = new OpenSprinkler_Waterlevel($os1); -# $os_rs = new OpenSprinkler_Rainstatus($os1); -# $front_garden = new OpenSprinkler_Station($os1,0,60); -# -# $os1_comm = new OpenSprinkler_Comm($os1); -# methods -# -set disable -# -reboot -# -reset -# - get_waterlevel - - -#todo -# - log runtimes. Maybe into a dbm file? log_runtimes method with destination. -#?? disabling the opensprinkler doesn't turn off the stations? -#?? parse return codes better, -#?? print logs -# # make the data poll non-blocking, turn off timer -# -# State can only be set by stat. Set mode will change the mode. - - -@OpenSprinkler::ISA = ('Generic_Item'); - - -# -------------------- START OF SUBROUTINES -------------------- -# -------------------------------------------------------------- - -our %rest; - -$rest{get_vars} = "jc"; -$rest{set_vars} = "cv"; - -$rest{get_options} = "jo"; -$rest{set_options} = "co"; -$rest{station_info} = "jn"; -$rest{get_stations} = "js"; -$rest{set_stations} = "cs"; -$rest{test_station} = "cm"; - -$rest{get_log} = "jl"; - - -our %result; -$result{1} = "success"; -$result{2} = "unauthorized"; -$result{3} = "mismatch"; -$result{4} = "data missing"; -$result{5} = "out of Range"; -$result{6} = "data format"; -$result{7} = "error page not found"; -$result{8} = "not permitted"; -$result{9} = "unknown error"; - -sub new { - my ($class, $host,$pwd,$poll) = @_; - my $self = {}; - bless $self, $class; - $self->{data} = undef; - $self->{child_object} = undef; - $self->{config}->{cache_time} = 5; #TODO fix cache timeouts - $self->{config}->{cache_time} = $::config_params{OpenSprinkler_config_cache_time} if defined $::config_params{OpenSprinkler_config_cache_time}; - $self->{config}->{tz} = $::config_params{time_zone}; #TODO Need to figure out DST for print runtimes - $self->{config}->{poll_seconds} = 10; - $self->{config}->{poll_seconds} = $poll if ($poll); - $self->{config}->{poll_seconds} = 1 if ($self->{config}->{poll_seconds} < 1); - $self->{updating} = 0; - $self->{data}->{retry} = 0; - $self->{data}->{stations} = (); - $self->{host} = $host; - $self->{password} = $pwd; - $self->{debug} = 0; - $self->{loglevel} = 1; - $self->{timeout} = 4; #300; - push(@{$$self{states}}, 'enabled', 'disabled'); - - $self->_init; - $self->{timer} = new Timer; - $self->start_timer; - return $self; -} - -sub _poll_check { - my ($self) = @_; - #main::print_log("[OpenSprinkler] _poll_check initiated"); - #main::run (sub {&VOpenSprinkler::get_data($self)}); #spawn this off to run in the background - $self->get_data(); -} - -sub get_data { - my ($self) = @_; - #main::print_log("[OpenSprinkler] get_data initiated"); - $self->poll; - $self->process_data; -} - -sub _init { - my ($self) = @_; - - my ($isSuccessResponse1,$osp) = $self->_get_JSON_data('get_options'); - - if ($isSuccessResponse1) { - - if ($osp) { #->{fwv} > 213) { - - main::print_log("[OpenSprinkler] OpenSprinkler found (v$osp->{hwv} / $osp->{fwv})"); - my ($isSuccessResponse2,$stations) = $self->_get_JSON_data('station_info'); - for my $index (0 .. $#{$stations->{snames}}) { - #print "$index: $stations->{snames}[$index]\n"; - $self->{data}->{stations}->[$index]->{name} = $stations->{snames}[$index]; - } - # Check to see if station is disabled, Bitwise operation - for my $stn_dis ( 0 .. $#{$stations->{stn_dis}}) { - my $bin = sprintf "%08b", $stations->{stn_dis}[$stn_dis]; - for my $bit ( 0 .. 7) { - my $station_id = (($stn_dis * 8) + $bit); - my $disabled = substr $bin,(7-$bit),1; - $self->{data}->{stations}->[$station_id]->{status} = ($disabled == 0 ) ? "enabled" : "disabled"; - } - } -#print Dumper $self; - $self->{previous}->{info}->{waterlevel} = $osp->{wl}; - $self->{previous}->{info}->{rs} = "init"; - $self->{previous}->{info}->{state} = "disabled"; - $self->{previous}->{info}->{adjustment_method} = "init"; - $self->{previous}->{info}->{rain_sensor_status} = "init"; - $self->{previous}->{info}->{sunrise} = 0; - $self->{previous}->{info}->{sunset} = 0; - if ($self->poll()) { - main::print_log("[OpenSprinkler] Data Successfully Retrieved"); - $self->{active} = 1; - $self->print_info(); - $self->set($self->{data}->{info}->{state},'poll'); - - } else { - main::print_log("[OpenSprinkler] Problem retrieving initial data"); - $self->{active} = 0; - return ('1'); - } - - } else { - main::print_log("[OpenSprinkler] Unknown device " . $self->{host}); - $self->{active} = 0; - return ('1'); - } - - } else { - main::print_log("[OpenSprinkler] Error. Unable to connect to " . $self->{host}); - $self->{active} = 0; - return ('1'); - } -} - -sub poll { - my ($self) = @_; - - main::print_log("[OpenSprinkler] Polling initiated") if ($self->{debug}); - - my ($isSuccessResponse1,$vars) = $self->_get_JSON_data('get_vars'); - my ($isSuccessResponse2,$options) = $self->_get_JSON_data('get_options'); - my ($isSuccessResponse3,$stations) = $self->_get_JSON_data('get_stations'); - - - if ($isSuccessResponse1 and $isSuccessResponse2 and $isSuccessResponse3) { - $self->{data}->{name} = $vars->{loc}; - $self->{data}->{loc} = $vars->{loc}; - $self->{data}->{options} = $options; - $self->{data}->{vars} = $vars; - $self->{data}->{info}->{state} = ($vars->{en} == 0 ) ? "disabled" : "enabled"; - $self->{data}->{info}->{waterlevel} = $options->{wl}; - $self->{data}->{info}->{adjustment_method} = ($options->{uwt} == 0) ? "manual" : "zimmerman"; - $self->{data}->{info}->{rain_sensor_status} = ($vars->{rs} == 0) ? "off" : "on"; - $self->{data}->{info}->{sunrise} = $vars->{sunrise}; - $self->{data}->{info}->{sunset} = $vars->{sunset}; - - for my $index (0 .. $#{$stations->{sn}}) { - print "$index: $stations->{sn}[$index]\n" if ($self->{debug}); - $self->{data}->{stations}->[$index]->{state} = ($stations->{sn}[$index] == 0 ) ? "off" : "on"; - } - $self->{data}->{nstations} = $stations->{nstations}; - $self->{data}->{timestamp} = time; - $self->{data}->{retry} = 0; -#print Dumper $self; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } - return ('1'); - } else { - main::print_log("[OpenSprinkler] Problem retrieving poll data from " . $self->{host}); - $self->{data}->{retry}++; - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } - return ('0'); - } - -} - -#------------------------------------------------------------------------------------ -sub _get_JSON_data { - my ($self, $mode, $cmd) = @_; - - unless ($self->{updating}) { - $cmd = "" unless ($cmd); - $self->{updating} = 1; - my $ua = new LWP::UserAgent(keep_alive=>1); - $ua->timeout($self->{timeout}); - - my $host = $self->{host}; - my $password = $self->{password}; - print "Opening http://$host/$rest{$mode}?pw=$password$cmd...\n" if ($self->{debug}); - my $request = HTTP::Request->new(GET => "http://$host/$rest{$mode}?pw=$password$cmd"); - #$request->content_type("application/x-www-form-urlencoded"); - - my $responseObj = $ua->request($request); - print $responseObj->content."\n--------------------\n" if $self->{debug}; - - my $responseCode = $responseObj->code; - print 'Response code: ' . $responseCode . "\n" if $self->{debug}; - my $isSuccessResponse = $responseCode < 400; - $self->{updating} = 0; - if (! $isSuccessResponse ) { - main::print_log("[OpenSprinkler] Warning, failed to get data. Response code $responseCode"); - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "offline") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("offline",'poll'); - } - } - return ('0'); - } else { - if (defined $self->{child_object}->{comm}) { - if ($self->{child_object}->{comm}->state() ne "online") { - main::print_log "[OpenSprinkler] Communication Tracking object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{comm}->set("online",'poll'); - } - } - } - my $response; - eval { - $response = JSON::XS->new->decode ($responseObj->content); - }; - # catch crashes: - if($@){ - print "[OpenSprinkler] ERROR! JSON parser crashed! $@\n"; - return ('0'); - } else { - my $result_code = 9; - $result_code = $response->{"result"} if (defined $response->{"result"}); - print "[OpenSpinkler] JSON fetch operation result is " .$result{$result_code} . "\n" if (($self->{loglevel}) or ($result_code != 1)); - return ($isSuccessResponse, $result{$result_code}) - } - } else { - main::print_log("[OpenSprinkler] Warning, not fetching data due to operation in progress"); - return ('0'); - } -} - -sub register { - my ($self, $object, $type, $number ) = @_; - #my $name; - #$name = $$object{object_name}; #TODO: Why can't we get the name of the child object? - if (lc $type eq "station") { - &main::print_log("[OpenSprinkler] Registering station $number child object"); - $self->{child_object}->{station}->{$number} = $object; - $object->set_label($self->{data}->{stations}->[$number]->{name}); - } else { - &main::print_log("[OpenSprinkler] Registering $type child object"); - - $self->{child_object}->{$type} = $object; - } - - } - -sub stop_timer { - my ($self) = @_; - - if (defined $self->{timer}) { - $self->{timer}->stop() if ($self->{timer}->active()); - } else { - main::print_log("[OpenSprinkler] Warning, stop_timer called but timer undefined"); - } -} - -sub start_timer { - my ($self) = @_; - - if (defined $self->{timer}) { - $self->{timer}->set($self->{config}->{poll_seconds}, sub {&OpenSprinkler::_poll_check($self)}, -1); - } else { - main::print_log("[OpenSprinkler] Warning, start_timer called but timer undefined"); - } -} - -sub print_info { - my ($self) = @_; - - my (@state,@enabled,@rd,@rs,@pwenabled); - $state[0] = "off"; - $state[1] = "on"; - $enabled[1] = "ENABLED"; - $enabled[0] = "DISABLED"; - $pwenabled[0] = "ENABLED"; - $pwenabled[1] = "DISABLED"; - $rd[0] = "rain delay is currently in effect"; - $rd[1] = "no rain delay"; - $rs[0] = "rain is detected from rain sensor"; - $rs[1] = "no rain detected"; - - main::print_log("[OpenSprinkler] Device Hardware v" . $self->{data}->{options}->{hwv} . " with firmware " . $self->{data}->{options}->{fwv}); - main::print_log("[OpenSprinkler] *Mode is " . $self->{data}->{info}->{state}); - main::print_log("[OpenSprinkler] Time Zone is " . $self->get_tz()); - main::print_log("[OpenSprinkler] NTP Sync " . $state[$self->{data}->{options}->{ntp}]); - main::print_log("[OpenSprinkler] Use DHCP " . $state[$self->{data}->{options}->{dhcp}]); - main::print_log("[OpenSprinkler] Number of expansion boards " . $self->{data}->{options}->{ext}); - main::print_log("[OpenSprinkler] Station delay time " . $self->{data}->{options}->{sdt}); - main::print_log("[OpenSprinkler] Master station " . $self->{data}->{options}->{mas}); - main::print_log("[OpenSprinkler] master on time " . $self->{data}->{options}->{mton}); - main::print_log("[OpenSprinkler] master off time " . $self->{data}->{options}->{mtof}); - main::print_log("[OpenSprinkler] Rain Sensor " . $state[$self->{data}->{options}->{urs}]); - main::print_log("[OpenSprinkler] *Water Level " . $self->{data}->{info}->{waterlevel}); - main::print_log("[OpenSprinkler] Password is " . $pwenabled[$self->{data}->{options}->{ipas}]); - main::print_log("[OpenSprinkler] Device ID " . $self->{data}->{options}->{devid}) if defined ($self->{data}->{options}->{devid}); - main::print_log("[OpenSprinkler] LCD Contrast " . $self->{data}->{options}->{con}); - main::print_log("[OpenSprinkler] LCD Backlight " . $self->{data}->{options}->{lit}); - main::print_log("[OpenSprinkler] LCD Dimming " . $self->{data}->{options}->{dim}); - main::print_log("[OpenSprinkler] Relay Pulse Time " . $self->{data}->{options}->{rlp}) if defined ($self->{data}->{options}->{rlp}); - main::print_log("[OpenSprinkler] *Weather adjustment Method " . $self->{data}->{info}->{adjustment_method}); - main::print_log("[OpenSprinkler] Logging " . $enabled[$self->{data}->{options}->{lg}]); - main::print_log("[OpenSprinkler] Zone expansion boards " . $self->{data}->{options}->{dexp}); - main::print_log("[OpenSprinkler] Max zone expansion boards " . $self->{data}->{options}->{mexp}); - - main::print_log("[OpenSprinkler] Device Time " . localtime($self->{data}->{vars}->{devt})); - main::print_log("[OpenSprinkler] Number of 8 station boards " . $self->{data}->{vars}->{nbrd}); - main::print_log("[OpenSprinkler] Rain delay " . $self->{data}->{vars}->{rd}); - main::print_log("[OpenSprinkler] *Rain sensor status " . $self->{data}->{info}->{rain_sensor_status}); - main::print_log("[OpenSprinkler] Location " . $self->{data}->{vars}->{loc}); - main::print_log("[OpenSprinkler] Wunderground key " . $self->{data}->{vars}->{wtkey}); - main::print_log("[OpenSprinkler] *Sun Rises at " . $self->get_sunrise()); - main::print_log("[OpenSprinkler] *Sun Sets at " . $self->get_sunset()); - -} - -sub process_data { - my ($self) = @_; - # Main core of processing - # set state of self for state - # for any registered child selfs, update their state if - - - for my $index (0 .. $#{$self->{data}->{stations}}) { - next if ($self->{data}->{stations}->[$index]->{status} eq "disabled"); - my $previous = "init"; - $previous = $self->{previous}->{data}->{stations}->[$index]->{state} if (defined $self->{previous}->{data}->{stations}->[$index]->{state}); - if ($previous ne $self->{data}->{stations}->[$index]->{state}) { - main::print_log("[OpenSprinkler] Station $index $self->{data}->{stations}->[$index]->{name} changed from $previous to $self->{data}->{stations}->[$index]->{state}") if ($self->{loglevel}); - $self->{previous}->{data}->{stations}->[$index]->{state} = $self->{data}->{stations}->[$index]->{state}; - if (defined $self->{child_object}->{station}->{$index}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{station}->{$index}->set($self->{data}->{stations}->[$index]->{state},'poll'); - } - } - } - - if ($self->{previous}->{info}->{state} ne $self->{data}->{info}->{state}) { - main::print_log("[OpenSprinkler] State changed from $self->{previous}->{info}->{state} to $self->{data}->{info}->{state}") if ($self->{loglevel}); - $self->{previous}->{info}->{state} = $self->{data}->{info}->{state}; - $self->set($self->{data}->{info}->{state},'poll'); - } - - if ($self->{previous}->{info}->{waterlevel} != $self->{data}->{info}->{waterlevel}) { - main::print_log("[OpenSprinkler] Waterlevel changed from $self->{previous}->{info}->{waterlevel} to $self->{data}->{info}->{waterlevel}") if ($self->{loglevel}); - $self->{previous}->{info}->{waterlevel} = $self->{data}->{info}->{waterlevel}; - if (defined $self->{child_object}->{waterlevel}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{waterlevel}->set($self->{data}->{info}->{waterlevel},'poll'); - } - } - - if ($self->{previous}->{info}->{rain_sensor_status} ne $self->{data}->{info}->{rain_sensor_status}) { - main::print_log("[OpenSprinkler] Rain Sensor changed from $self->{previous}->{info}->{rain_sensor_status} to $self->{data}->{info}->{rain_sensor_status}") if ($self->{loglevel}); - $self->{previous}->{info}->{rain_sensor_status} = $self->{data}->{info}->{rain_sensor_status}; - if (defined $self->{child_object}->{rain_sensor_status}) { - main::print_log "Child object found. Updating..." if ($self->{loglevel}); - $self->{child_object}->{rain_sensor_status}->set($self->{data}->{info}->{rain_sensor_status},'poll'); - } - } - - if ($self->{previous}->{info}->{sunset} != $self->{data}->{info}->{sunset}) { - main::print_log("[OpenSprinkler] Sunset changed to " . $self->get_sunset()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunset} = $self->{data}->{info}->{sunset}; - } - - if ($self->{previous}->{info}->{sunrise} != $self->{data}->{info}->{sunrise}) { - main::print_log("[OpenSprinkler] Sunrise changed to " . $self->get_sunrise()) if ($self->{loglevel}); - $self->{previous}->{info}->{sunrise} = $self->{data}->{info}->{sunrise}; - } - - if ($self->{previous}->{info}->{adjustment_method} ne $self->{data}->{info}->{adjustment_method}) { - main::print_log("[OpenSprinkler] Adjustment Method changed from $self->{previous}->{info}->{adjustment_method} to $self->{data}->{info}->{adjustment_method}") if ($self->{loglevel}); - $self->{previous}->{info}->{adjustment_method} = $self->{data}->{info}->{adjustment_method}; - } - -} - - -sub print_logs { - my ($self) = @_; - my ($isSuccessResponse1,$data) = get_JSON_data($self->{host},'runtimes'); - - for my $tstamp (0..$#{$data->{runtimes}}) { - - print $data->{runtimes}[$tstamp]->{ts} . " -> "; - print scalar localtime (($data->{runtimes}[$tstamp]->{ts}) - ($self->{config}->{tz}*60*60+1)); - main::print_log("\tCooling: " . $data->{runtimes}[$tstamp]->{cool1}); - main::print_log("\tHeating: " . $data->{runtimes}[$tstamp]->{heat1}); - main::print_log("\tCooling 2: " . $data->{runtimes}[$tstamp]->{cool2}) if $data->{runtimes}[$tstamp]->{cool2}; - main::print_log("\tHeating 2: " . $data->{runtimes}[$tstamp]->{heat2}) if $data->{runtimes}[$tstamp]->{heat2}; - main::print_log("\tAux 1: " . $data->{runtimes}[$tstamp]->{aux1}) if $data->{runtimes}[$tstamp]->{aux1}; - main::print_log("\tAux 2: " . $data->{runtimes}[$tstamp]->{aux2}) if $data->{runtimes}[$tstamp]->{aux2}; - main::print_log("\tFree Cooling: " . $data->{runtimes}[$tstamp]->{fc}) if $data->{runtimes}[$tstamp]->{fc}; - - } -} - -sub get_station { - my ($self,$number) = @_; - - return ($self->{data}->{stations}->[$number]->{state}); - -} - -sub set_station { - - my ($self,$station,$state,$time) = @_; - - return if (lc $state eq $self->{state}); - - #print "db: set_station state=$state, station=$station time=$time\n"; - my $cmd = "&sid=" . $station; - if (lc $state eq "on") { - $cmd .= "&en=1&t=" . $time; - } else { - $cmd .= "&en=0"; - } - my ($isSuccessResponse,$status) = $self->_get_JSON_data('test_station',$cmd); - if ($isSuccessResponse) { - #print "DB status=$status\n"; - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set station to $state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - -} - -sub get_sunrise { - my ($self) = @_; -# add in nice calc, minutes since midnight - my $AMPM = "AM"; - my $hour = int ($self->{data}->{vars}->{sunrise} / 60); - my $minute = $self->{data}->{vars}->{sunrise} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } - - return ("$hour:$minute $AMPM"); -} - -sub get_sunset { - my ($self) = @_; - my $AMPM = "AM"; - my $hour = int($self->{data}->{vars}->{sunset} / 60); - my $minute = $self->{data}->{vars}->{sunset} % 60; - if ($hour > 12) { - $hour = $hour - 12; - $AMPM= "PM"; - } - - return ("$hour:$minute $AMPM"); -} - - -sub get_tz { - my ($self) = @_; - my $tz = ($self->{data}->{options}->{tz} - 48) / 4; - if ($tz >= 0 ) { - $tz = "GMT+$tz"; - } else { - $tz = "GMT$tz"; - } - return ($tz); -} - -sub reboot { - my ($self) = @_; - - my $cmd = "&rbt=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub reset { - my ($self) = @_; - - my $cmd = "&rsn=1"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub get_waterlevel { - my ($self) = @_; - - return ($self->{data}->{info}->{waterlevel}); -} - -sub get_rainstatus { - my ($self) = @_; - - return ($self->{data}->{info}->{rain_sensor_status}); -} - -sub set_rain_delay { - my ($self,$hours) = @_; - - my $cmd = "&rsn=$hours"; - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - - return ($status); -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } else { - - return if (lc $p_state eq $self->{state}); - my $en; - if ((lc $p_state eq "enabled") || (lc $p_state eq "on")) { - $en = 1; - } elsif ((lc $p_state eq "disabled") || (lc $p_state eq "off")) { - $en = 0; - } else { - main::print_log("[OpenSprinkler] Error. Unknown state $p_state"); - return (0); - } - - my $cmd = "&en=" . $en; - - my ($isSuccessResponse,$status) = $self->_get_JSON_data('set_vars',$cmd); - if ($isSuccessResponse) { - if ($status eq "success") { #todo parse return value - $self->poll; - return (1); - } else { - main::print_log("[OpenSprinkler] Error. Could not set state to $p_state"); - return (0); - } - } else { - main::print_log("[OpenSprinkler] Error. Could not send data to OpenSprinkler"); - return (0); - } - } -} - - - -package OpenSprinkler_Station; - -@OpenSprinkler_Station::ISA = ('Generic_Item'); - -sub new -{ - my ($class,$object, $number, $on_timeout) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $$self{station} = $number; - push(@{$$self{states}}, 'on','off'); - $$self{on_timeout} = 3600; #default to an hour for 'on' - $$self{on_timeout} = $on_timeout * 60 if $on_timeout; - $object->register($self,'station',$number); - $self->set($object->get_station($number),'poll'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby,$time_override) = @_; - - if ($p_setby eq 'poll') { - #print "db: setting by poll to $p_state\n"; - $self->SUPER::set($p_state); - } else { -#bounds check, add in time_override - my $time = $$self{on_timeout}; - $time = $time_override if ($time_override); - $$self{master_object}->set_station($$self{station},$p_state,$time); - } -} - - -package OpenSprinkler_Comm; - -@OpenSprinkler_Comm::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - push(@{$$self{states}}, 'online','offline'); - SUPER::set('offline'); - $object->register($self,'comm'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -package OpenSprinkler_Waterlevel; - -@OpenSprinkler_Waterlevel::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $object->register($self,'waterlevel'); - $self->set($object->get_waterlevel,'poll'); - - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -package OpenSprinkler_Rainstatus; - -@OpenSprinkler_Rainstatus::ISA = ('Generic_Item'); - -sub new { - my ($class,$object) = @_; - - my $self={}; - bless $self,$class; - - $$self{master_object} = $object; - $object->register($self,'rain_sensor_status'); - $self->set($object->get_rainstatus,'poll'); - return $self; - -} - -sub set { - my ($self,$p_state,$p_setby) = @_; - - if ($p_setby eq 'poll') { - $self->SUPER::set($p_state); - } -} - -1; From f33da96df59373d896b8f99c7f22e70b47f29f61 Mon Sep 17 00:00:00 2001 From: JaredF Date: Sun, 5 Jul 2015 20:34:35 -0700 Subject: [PATCH 31/34] Clean up pollen type text and fix bug seen with perl 5.20.2 --- code/common/weather_pollen.pl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/code/common/weather_pollen.pl b/code/common/weather_pollen.pl index ac96ae54a..bb31bf749 100644 --- a/code/common/weather_pollen.pl +++ b/code/common/weather_pollen.pl @@ -55,17 +55,19 @@ &parse_pollen_forecast if (($Reload) && (-e $pollen_file)); sub parse_pollen_forecast { - my @pollen_data = file_read($pollen_file) || warn "Unable to open pollen data file."; + my $pollen_data = file_read($pollen_file) || warn "Unable to open pollen data file."; # The JSON file that is retuned by the service is malformed; these substitutions clean it up so that the perl JSON module can parse it. - for (@pollen_data) { + for ($pollen_data) { s/\"\{/\{/; s/\\//g; s/\}\"/\}/; } - my $json = decode_json(@pollen_data) || warn "Error parsing pollen info from file."; + my $json = decode_json($pollen_data) || warn "Error parsing pollen info from file."; $main::Weather{TodayPollenCount} = $json->{pollenForecast}{forecast}[0]; $main::Weather{TomorrowPollenCount} = $json->{pollenForecast}{forecast}[1]; $main::Weather{TodayPollenType} = $json->{pollenForecast}{pp}; + # Format the pollen type text to remove any leading spaces or trailing periods. + $main::Weather{TodayPollenType} =~ s/^\s//; $main::Weather{TodayPollenType} =~ s/\.//; } From 688f878d1ec0a76df2eec6a5353851ecea210ec6 Mon Sep 17 00:00:00 2001 From: JaredF Date: Wed, 9 Jul 2014 16:43:44 -0700 Subject: [PATCH 32/34] Fixes an issue with the Insteon Startup Scan not occuring during code reloads --- lib/Insteon/BaseInterface.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index cb49b427d..ef88bd355 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -50,7 +50,7 @@ sub poll_all { my $scan_at_startup = $main::config_parms{Insteon_PLM_scan_at_startup}; $scan_at_startup = 1 unless defined $scan_at_startup; - if ($scan_at_startup && $main::Save{mh_exit} ne 'normal'){ + if (!$main::Reload && $scan_at_startup && $main::Save{mh_exit} ne 'normal'){ ::print_log("[Insteon] Skipping startup scan because MisterHouse did not " ."exit cleanly."); $scan_at_startup = 0; From 393b55fc67729ab830f649f98599b05cd3965358 Mon Sep 17 00:00:00 2001 From: JaredF Date: Wed, 16 Jul 2014 22:00:40 -0700 Subject: [PATCH 33/34] Prevents Insteon all-scan from occuring on reloads and updates the POD to reflect --- lib/Insteon.pm | 4 ++-- lib/Insteon/BaseInterface.pm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 9792636f7..61b7eff5e 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -1243,8 +1243,8 @@ sub _active_interface if (!($$self{active_interface}) and $interface) { &main::print_log("[Insteon] Setting up initialization hooks") if $main::Debug{insteon}; &main::MainLoop_pre_add_hook(\&Insteon::BaseInterface::check_for_data, 1); - &main::Reload_post_add_hook(\&Insteon::check_all_aldb_versions, 1); - &main::Reload_post_add_hook(\&Insteon::BaseInterface::poll_all, 1); + &main::Reload_post_add_hook(\&Insteon::check_all_aldb_versions); + &main::Reload_post_add_hook(\&Insteon::BaseInterface::poll_all); $init_complete = 0; &main::MainLoop_pre_add_hook(\&Insteon::init, 1); &main::Reload_post_add_hook(\&Insteon::check_thermo_versions, 1); diff --git a/lib/Insteon/BaseInterface.pm b/lib/Insteon/BaseInterface.pm index ef88bd355..6fd6174af 100644 --- a/lib/Insteon/BaseInterface.pm +++ b/lib/Insteon/BaseInterface.pm @@ -35,7 +35,7 @@ sub check_for_data =item C -Called on startup or reload. Will always request and print the plm_info, which +Called at startup. Will always request and print the plm_info, which contains the PLM revision number, to the log on startup. If Insteon_PLM_scan_at_startup is set to 1 in the ini file, this routine will poll @@ -50,7 +50,7 @@ sub poll_all { my $scan_at_startup = $main::config_parms{Insteon_PLM_scan_at_startup}; $scan_at_startup = 1 unless defined $scan_at_startup; - if (!$main::Reload && $scan_at_startup && $main::Save{mh_exit} ne 'normal'){ + if ($scan_at_startup && $main::Save{mh_exit} ne 'normal'){ ::print_log("[Insteon] Skipping startup scan because MisterHouse did not " ."exit cleanly."); $scan_at_startup = 0; From b0e121037172e7d667aae68d55a55ae6410ce7c6 Mon Sep 17 00:00:00 2001 From: JaredF Date: Sun, 26 Jul 2015 14:19:27 -0700 Subject: [PATCH 34/34] Revert changes to persistent flag on two Insteon post-reload hooks --- lib/Insteon.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Insteon.pm b/lib/Insteon.pm index 61b7eff5e..9792636f7 100755 --- a/lib/Insteon.pm +++ b/lib/Insteon.pm @@ -1243,8 +1243,8 @@ sub _active_interface if (!($$self{active_interface}) and $interface) { &main::print_log("[Insteon] Setting up initialization hooks") if $main::Debug{insteon}; &main::MainLoop_pre_add_hook(\&Insteon::BaseInterface::check_for_data, 1); - &main::Reload_post_add_hook(\&Insteon::check_all_aldb_versions); - &main::Reload_post_add_hook(\&Insteon::BaseInterface::poll_all); + &main::Reload_post_add_hook(\&Insteon::check_all_aldb_versions, 1); + &main::Reload_post_add_hook(\&Insteon::BaseInterface::poll_all, 1); $init_complete = 0; &main::MainLoop_pre_add_hook(\&Insteon::init, 1); &main::Reload_post_add_hook(\&Insteon::check_thermo_versions, 1);