From c72dd48590789344ba94b8e8ae4257b5d7081127 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 17:45:00 -0700 Subject: [PATCH 01/12] Insteon_Irrigation: Rename Poll_Valve_status to Request_Status It is really what the user would expect to recevie for a request_status Sadly, while the device will tell us if a program is running, it doesn't appear that it will tell us WHICH program is running --- lib/Insteon/Irrigation.pm | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index fe7ca00d5..4af7f22f9 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -95,21 +95,27 @@ sub new { return $self; } -=item C +=item C Sends a message to the device requesting the valve status. The response from the device is printed to the log and stores the result in memory. =cut -sub poll_valve_status { - my ($self) = @_; +sub request_status { + my ($self, $requestor) = @_; my $subcmd = '02'; my $message = new Insteon::InsteonMessage('insteon_send', $self, 'sprinkler_control', $subcmd); $self->_send_cmd($message); return; } +#Deprecated Routine Name +sub poll_valve_status { + my ($self) = @_; + $self->request_status(); +} + =item C Used to directly control valves. Valve_id is a two digit number 00-07, @@ -275,16 +281,6 @@ sub _is_info_request { } -=item C - -This does nothing and returns 0, it prevents a request_status message, which the -device does not support, from being sent to the device. - -=cut - -# Overload methods we don't use, but would otherwise cause Insteon traffic. -sub request_status { return 0 } - =back =head2 AUTHOR From ed902cafb9436888af89a4b19724471295b0187b Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 17:45:00 -0700 Subject: [PATCH 02/12] Insteon_Irrigation: Fix Documentation --- lib/Insteon/Irrigation.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 4af7f22f9..a3cdaf3e2 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -145,7 +145,7 @@ sub set_valve { =item C Used to directly control programs. Program_id is a two digit number 00-03, -valve_state may be on or off. +proggram_state may be on or off. =cut From 173b10c6ec2a3607f84e4d06c0f6be03aec3a358 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 17:55:00 -0700 Subject: [PATCH 03/12] Insteon_Irrigation: Add Support for Get Timers Request Partial support was there, but not really enabled. --- lib/Insteon/Irrigation.pm | 61 ++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index a3cdaf3e2..4990fe877 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -229,19 +229,18 @@ sub get_pump_enabled() { return $$self{'pump_enabled'}; } -=item C +=item C -Sends a request to the device asking for it to respond with the current timers. -It does not appear that there is code to interpret the response provided by the -device. +Sends a request to the device asking for it to respond with the times for the +specified program. Program 0 is the default/manual timer. =cut sub get_timers() { - my ($self) = @_; + my ($self, $program) = @_; my $cmd = 'sprinkler_timers_request'; - my $subcmd = 0x1; - my $message = new Insteon::InsteonMessage('insteon_ext_send', $self, $cmd, $subcmd); + my $subcmd = sprintf("%02X", $program); + my $message = new Insteon::InsteonMessage('insteon_send', $self, $cmd, $subcmd); $self->_send_cmd($message); return; } @@ -271,7 +270,7 @@ sub _is_info_request { $$self{'valve_is_running'} = ($val >> 7) & 1; &::print_log("[Insteon::Irrigation] active_valve_id: $$self{'active_valve_id'}," . " valve_is_running: $$self{'valve_is_running'}, active_program: $$self{'active_program_number'}," - . " program_is_running: $$self{'program_is_running'}, pump_enabled: $$self{'pump_enabled'}") if $self->debuglevel(1, 'insteon'); + . " program_is_running: $$self{'program_is_running'}, pump_enabled: $$self{'pump_enabled'}"); } else { #Check if this was a generic info_request @@ -281,6 +280,52 @@ sub _is_info_request { } +=item C<_process_message()> + +Handles incoming messages from the device which are unique to this device, +specifically this handles the C response for the device, +all other responses are handed off to the C. + +=cut + +sub _process_message { + my ($self,$p_setby,%msg) = @_; + my $clear_message = 0; + my $pending_cmd = ($$self{_prior_msg}) ? $$self{_prior_msg}->command : $msg{command}; + my $ack_setby = (ref $$self{m_status_request_pending}) ? $$self{m_status_request_pending} : $p_setby; + if ($msg{is_ack} && $self->_is_info_request($pending_cmd,$ack_setby,%msg)) { + $clear_message = 1; + $$self{m_status_request_pending} = 0; + $self->_process_command_stack(%msg); + } + # The device uses cmd 0x41 differently depending on STD or EXT Msgs + elsif ($msg{command} eq "sprinkler_valve_off" && $msg{is_extended}) { + my $program = substr($msg{extra},0,2); + my $timer_1 = hex(substr($msg{extra},2,2)); + my $timer_2 = hex(substr($msg{extra},4,2)); + my $timer_3 = hex(substr($msg{extra},6,2)); + my $timer_4 = hex(substr($msg{extra},8,2)); + my $timer_5 = hex(substr($msg{extra},10,2)); + my $timer_6 = hex(substr($msg{extra},12,2)); + my $timer_7 = hex(substr($msg{extra},14,2)); + my $timer_8 = hex(substr($msg{extra},16,2)); + + #Print Resulting Message + ::print_log("[Insteon::Irrigation] The Timers for Program $program are" + ." as follows:\n Valve 1 = $timer_1\n Valve 2 = $timer_2\n" + ." Valve 3 = $timer_3\n Valve 4 = $timer_4\n Valve 5 = $timer_5\n" + ." Valve 6 = $timer_6\n Valve 7 = $timer_7\n Valve 8 = $timer_8"); + + #Clear message from message queue + $clear_message = 1; + $self->_process_command_stack(%msg); + } + else { + $clear_message = $self->SUPER::_process_message($p_setby,%msg); + } + return $clear_message; +} + =back =head2 AUTHOR From ff357794322912628ca47a54a91e265101aec7b7 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 18:05:00 -0700 Subject: [PATCH 04/12] Insteon_Irrigation: Add Pump and Status Message Features --- lib/Insteon/Irrigation.pm | 51 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 4990fe877..58402d528 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -63,6 +63,7 @@ package Insteon::Irrigation; our %message_types = ( %Insteon::BaseDevice::message_types, + sprinkler_status => 0x27, sprinkler_control => 0x44, sprinkler_valve_on => 0x40, sprinkler_valve_off => 0x41, @@ -259,7 +260,8 @@ sub _is_info_request { or $cmd eq 'sprinkler_valve_on' or $cmd eq 'sprinkler_valve_off' or $cmd eq 'sprinkler_program_on' - or $cmd eq 'sprinkler_program_off') { + or $cmd eq 'sprinkler_program_off', + or $cmd eq 'sprinkler_status') { $is_info_request = 1; my $val = hex($msg{extra}); &::print_log("[Insteon::Irrigation] Processing data for $cmd with value: $val") if $self->debuglevel(1, 'insteon'); @@ -326,6 +328,53 @@ sub _process_message { return $clear_message; } +=item C + +If set to true, this will treat valve 8 as a water pump. This will make valve +8 turn on whenever any other valve is turned on. Setting to false, returns +valve 8 to a normal sprinkler valve + +=cut + +sub enable_pump { + my ($self, $enable) = @_; + my $subcmd = '08'; + if ($enable){ + $subcmd = '07'; + ::print_log("[Insteon::Irrigation] Enabling valve 8 pump feature."); + } + else { + ::print_log("[Insteon::Irrigation] Setting valve 8 to act as regular valve."); + } + my $message = new Insteon::InsteonMessage('insteon_send', $self, 'sprinkler_control', $subcmd); + $self->_send_cmd($message); + return; +} + +=item C + +If set to true, this will cause the device to send a status message whenever +a valve changes status during a program. If not set, MH will not be informed +of the status of each of the valves during a program. It is HIGHLY recommended +that you enable this feature. + +=cut + +sub enable_status { + my ($self, $enable) = @_; + my $subcmd = '0A'; + if ($enable){ + $subcmd = '09'; + ::print_log("[Insteon::Irrigation] Enabling valve status messages."); + } + else { + ::print_log("[Insteon::Irrigation] Disabling valve status messages."); + } + my $message = new Insteon::InsteonMessage('insteon_send', $self, 'sprinkler_control', $subcmd); + $self->_send_cmd($message); + return; +} + =back =head2 AUTHOR From 5528db38238bc79928bf2decebee11992d74ac49 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 18:15:00 -0700 Subject: [PATCH 05/12] Insteon_Irrigation: Remove Status Broadcast Code, Insteon PLM Does not Support Every now and then I am reminded what an AWFUL protocol Insteon is. This is one of them. The EZFlora can be set to send out broadcast messages, however the PLM will not decode them. As a result, MisterHouse never sees them, and they are useless. According to the manual the PLM could be put into monitor mode allowing it to see these messages, however Insteon disabled that feature on the PLM. It is beyond stupid, that the PLM will not display a broadcast message sent from a device in its own link table. There is no security concern here. --- lib/Insteon/Irrigation.pm | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 58402d528..880b517b5 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -63,7 +63,6 @@ package Insteon::Irrigation; our %message_types = ( %Insteon::BaseDevice::message_types, - sprinkler_status => 0x27, sprinkler_control => 0x44, sprinkler_valve_on => 0x40, sprinkler_valve_off => 0x41, @@ -260,8 +259,7 @@ sub _is_info_request { or $cmd eq 'sprinkler_valve_on' or $cmd eq 'sprinkler_valve_off' or $cmd eq 'sprinkler_program_on' - or $cmd eq 'sprinkler_program_off', - or $cmd eq 'sprinkler_status') { + or $cmd eq 'sprinkler_program_off') { $is_info_request = 1; my $val = hex($msg{extra}); &::print_log("[Insteon::Irrigation] Processing data for $cmd with value: $val") if $self->debuglevel(1, 'insteon'); @@ -351,30 +349,6 @@ sub enable_pump { return; } -=item C - -If set to true, this will cause the device to send a status message whenever -a valve changes status during a program. If not set, MH will not be informed -of the status of each of the valves during a program. It is HIGHLY recommended -that you enable this feature. - -=cut - -sub enable_status { - my ($self, $enable) = @_; - my $subcmd = '0A'; - if ($enable){ - $subcmd = '09'; - ::print_log("[Insteon::Irrigation] Enabling valve status messages."); - } - else { - ::print_log("[Insteon::Irrigation] Disabling valve status messages."); - } - my $message = new Insteon::InsteonMessage('insteon_send', $self, 'sprinkler_control', $subcmd); - $self->_send_cmd($message); - return; -} - =back =head2 AUTHOR From 50421cd2ce0a4c7996da0c730efa1f7b10ccd9ab Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 18:15:00 -0700 Subject: [PATCH 06/12] Insteon_Irrigation: Enable Setting of Valve Timers --- lib/Insteon/Irrigation.pm | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 880b517b5..9acc510fe 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -245,6 +245,40 @@ sub get_timers() { return; } +=item C + +Sets the timers for the program. Program 0 is the manual/default timers that +are used if you just turn on a single timer. It is HIGHLY recommented that you +set the manual/default timer to the most number of minutes that you would ever +need for that zone. This will prevent accidental overwatering or flooding +should something happen to MisterHouse. + +Each valve time is specified in minutes with 255 being the maximum. + +By default, each valve is set to 30 minutes for each program. + +=cut + +sub set_timers() { + my ($self, $program, $v1, $v2, $v3, $v4, $v5, $v6, $v7, $v8) = @_; + #Command is reused in different format for EXT msgs + my $cmd = 'sprinkler_valve_on'; + my $extra = sprintf("%02X", $program); + $extra .= sprintf("%02X", $v1); + $extra .= sprintf("%02X", $v2); + $extra .= sprintf("%02X", $v3); + $extra .= sprintf("%02X", $v4); + $extra .= sprintf("%02X", $v5); + $extra .= sprintf("%02X", $v6); + $extra .= sprintf("%02X", $v7); + $extra .= sprintf("%02X", $v8); + $extra .= '0' x (30 - length $extra); + my $message = new Insteon::InsteonMessage('insteon_ext_send', $self, $cmd, $extra); + $self->_send_cmd($message); + return; +} + =item C<_is_info_request()> Used to intercept and handle unique EZFlora messages, all others are passed on From 5dfdf313a4fa3c2f132eaafedd8c74353445617c Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 22:00:50 -0700 Subject: [PATCH 07/12] Insteon_Irrigation: Add Status Check Timer --- lib/Insteon/Irrigation.pm | 82 +++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 9acc510fe..73b2aa860 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -90,8 +90,11 @@ sub new { $$self{program_is_running} = undef; $$self{pump_enabled} = undef; $$self{valve_is_running} = undef; - $self->restore_data('active_valve_id', 'active_program_number', 'program_is_running', 'pump_enabled', 'valve_is_running'); + $self->restore_data('active_valve_id', 'active_program_number', + 'program_is_running', 'pump_enabled', 'valve_is_running', 'timer_0', + 'timer_1', 'timer_2', 'timer_3', 'timer_4'); $$self{message_types} = \%message_types; + $$self{status_timer} = new Timer; return $self; } @@ -229,15 +232,21 @@ sub get_pump_enabled() { return $$self{'pump_enabled'}; } -=item C +=item C Sends a request to the device asking for it to respond with the times for the -specified program. Program 0 is the default/manual timer. +all programs. The times are then cached in MisterHouse. + +The EZFlora does not update MisterHouse when a timer has expired. As a result, +MisterHouse has to query the device to periodically determine what is going on. +If MisterHouse has an understanding of the timers, it can query the device at +the proper times. =cut sub get_timers() { my ($self, $program) = @_; + $program = 0 unless (defined $program); my $cmd = 'sprinkler_timers_request'; my $subcmd = sprintf("%02X", $program); my $message = new Insteon::InsteonMessage('insteon_send', $self, $cmd, $subcmd); @@ -296,7 +305,6 @@ sub _is_info_request { or $cmd eq 'sprinkler_program_off') { $is_info_request = 1; my $val = hex($msg{extra}); - &::print_log("[Insteon::Irrigation] Processing data for $cmd with value: $val") if $self->debuglevel(1, 'insteon'); $$self{'active_valve_id'} = ($val & 7) + 1; $$self{'active_program_number'} = (($val >> 3) & 3) + 1; $$self{'program_is_running'} = ($val >> 5) & 1; @@ -305,6 +313,19 @@ sub _is_info_request { &::print_log("[Insteon::Irrigation] active_valve_id: $$self{'active_valve_id'}," . " valve_is_running: $$self{'valve_is_running'}, active_program: $$self{'active_program_number'}," . " program_is_running: $$self{'program_is_running'}, pump_enabled: $$self{'pump_enabled'}"); + + # Set a timer to check the status of the device after we expect the timer + # for the current valve to run out. + if ($$self{'valve_is_running'} && $$self{status_timer}->inactive){ + my $action = $self->get_object_name . "->request_status()"; + my $program = 0; + $program = $$self{'active_program_number'} + if ($$self{'program_is_running'}); + my $time = $self->_valve_timer($program, + $$self{'active_valve_id'}); + $time = ($time * 60) + 5; #Add 5 seconds to allow things to happen. + $$self{status_timer}->set($time,$action); + } } else { #Check if this was a generic info_request @@ -334,22 +355,31 @@ sub _process_message { } # The device uses cmd 0x41 differently depending on STD or EXT Msgs elsif ($msg{command} eq "sprinkler_valve_off" && $msg{is_extended}) { - my $program = substr($msg{extra},0,2); - my $timer_1 = hex(substr($msg{extra},2,2)); - my $timer_2 = hex(substr($msg{extra},4,2)); - my $timer_3 = hex(substr($msg{extra},6,2)); - my $timer_4 = hex(substr($msg{extra},8,2)); - my $timer_5 = hex(substr($msg{extra},10,2)); - my $timer_6 = hex(substr($msg{extra},12,2)); - my $timer_7 = hex(substr($msg{extra},14,2)); - my $timer_8 = hex(substr($msg{extra},16,2)); - - #Print Resulting Message - ::print_log("[Insteon::Irrigation] The Timers for Program $program are" - ." as follows:\n Valve 1 = $timer_1\n Valve 2 = $timer_2\n" - ." Valve 3 = $timer_3\n Valve 4 = $timer_4\n Valve 5 = $timer_5\n" - ." Valve 6 = $timer_6\n Valve 7 = $timer_7\n Valve 8 = $timer_8"); - + my $program = hex(substr($msg{extra},0,2)); + for (my $i; $i<= 8; $i++){ + my $time = hex(substr($msg{extra},$i*2,2)); + $self->_valve_timer($program, $i, $time); + } + + if ($program < 4){ + $self->get_timers($program+1); + } + else { + my $output = "[Insteon::Irrigation] The timers for " + . $self->get_object_name . " are:\n"; + $output .= " Program 0: Program 1: Program 2: ". + "Program 3: Program 4:\n"; + for (my $i_v = 0; $i_v <= 8; $i_v++){ + $output .= ' '; + for (my $i_p = 0; $i_p <= 4; $i_p++){ + $output .= " Valve $i_v:" . + sprintf("% 3d", $self->_valve_timer($i_p, $i_v, )); + } + $output .= "\n"; + } + ::print_log($output); + } + #Clear message from message queue $clear_message = 1; $self->_process_command_stack(%msg); @@ -360,6 +390,18 @@ sub _process_message { return $clear_message; } + +# Used to store and retreive the valve times +sub _valve_timer { + my ($self, $program, $valve, $time) = @_; + if (defined $time){ + # Not the ideal way to store this, but restore_data can't handle hashes + # or arrays. So we store the times in a string similar to the msg payload. + substr($$self{'timer_' . $program},(($valve-1)*3),3) = sprintf("%03d", $time); + } + return int(substr($$self{'timer_' . $program},(($valve-1)*3),3)); +} + =item C If set to true, this will treat valve 8 as a water pump. This will make valve From 03e310cb052cbd1fc8667adc429eb0d7c3910c59 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Fri, 28 Mar 2014 22:01:39 -0700 Subject: [PATCH 08/12] Revert "Insteon_Irrigation: Remove Status Broadcast Code, Insteon PLM Does not Support" This reverts commit 5528db38238bc79928bf2decebee11992d74ac49. I was a little wrong in my prior assessment. It turns out that with PLM Monitor Mode enabled, I can actually see these messages. --- lib/Insteon/Irrigation.pm | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 73b2aa860..847a2f097 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -63,6 +63,7 @@ package Insteon::Irrigation; our %message_types = ( %Insteon::BaseDevice::message_types, + sprinkler_status => 0x27, sprinkler_control => 0x44, sprinkler_valve_on => 0x40, sprinkler_valve_off => 0x41, @@ -302,7 +303,8 @@ sub _is_info_request { or $cmd eq 'sprinkler_valve_on' or $cmd eq 'sprinkler_valve_off' or $cmd eq 'sprinkler_program_on' - or $cmd eq 'sprinkler_program_off') { + or $cmd eq 'sprinkler_program_off', + or $cmd eq 'sprinkler_status') { $is_info_request = 1; my $val = hex($msg{extra}); $$self{'active_valve_id'} = ($val & 7) + 1; @@ -425,6 +427,30 @@ sub enable_pump { return; } +=item C + +If set to true, this will cause the device to send a status message whenever +a valve changes status during a program. If not set, MH will not be informed +of the status of each of the valves during a program. It is HIGHLY recommended +that you enable this feature. + +=cut + +sub enable_status { + my ($self, $enable) = @_; + my $subcmd = '0A'; + if ($enable){ + $subcmd = '09'; + ::print_log("[Insteon::Irrigation] Enabling valve status messages."); + } + else { + ::print_log("[Insteon::Irrigation] Disabling valve status messages."); + } + my $message = new Insteon::InsteonMessage('insteon_send', $self, 'sprinkler_control', $subcmd); + $self->_send_cmd($message); + return; +} + =back =head2 AUTHOR From 5f460d128a3c2cb38d078dc02a46974655be5b64 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Mon, 31 Mar 2014 17:45:00 -0700 Subject: [PATCH 09/12] Insteon_Irrigation: Add Support for Broadcast Messages The irrigation broadcast messages are sent whenever the device changes a valve as a result of a timer. To use broadcast messages they must be enabled on the device and the PLM must be put in monitor mode. If a broadcast message is received, it will reset the status_timer and save MH from having to send a valve status request. This results in much more accurate status reports for the valves. --- lib/Insteon/Irrigation.pm | 72 +++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 847a2f097..0b670a27b 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -303,31 +303,9 @@ sub _is_info_request { or $cmd eq 'sprinkler_valve_on' or $cmd eq 'sprinkler_valve_off' or $cmd eq 'sprinkler_program_on' - or $cmd eq 'sprinkler_program_off', - or $cmd eq 'sprinkler_status') { + or $cmd eq 'sprinkler_program_off') { $is_info_request = 1; - my $val = hex($msg{extra}); - $$self{'active_valve_id'} = ($val & 7) + 1; - $$self{'active_program_number'} = (($val >> 3) & 3) + 1; - $$self{'program_is_running'} = ($val >> 5) & 1; - $$self{'pump_enabled'} = ($val >> 6) & 1; - $$self{'valve_is_running'} = ($val >> 7) & 1; - &::print_log("[Insteon::Irrigation] active_valve_id: $$self{'active_valve_id'}," - . " valve_is_running: $$self{'valve_is_running'}, active_program: $$self{'active_program_number'}," - . " program_is_running: $$self{'program_is_running'}, pump_enabled: $$self{'pump_enabled'}"); - - # Set a timer to check the status of the device after we expect the timer - # for the current valve to run out. - if ($$self{'valve_is_running'} && $$self{status_timer}->inactive){ - my $action = $self->get_object_name . "->request_status()"; - my $program = 0; - $program = $$self{'active_program_number'} - if ($$self{'program_is_running'}); - my $time = $self->_valve_timer($program, - $$self{'active_valve_id'}); - $time = ($time * 60) + 5; #Add 5 seconds to allow things to happen. - $$self{status_timer}->set($time,$action); - } + $self->_process_status($msg{extra}); } else { #Check if this was a generic info_request @@ -336,6 +314,38 @@ sub _is_info_request { return $is_info_request; } +sub _process_status { + my ($self, $val) = @_; + $val = hex($val); + $$self{'active_valve_id'} = ($val & 7) + 1; + $$self{'active_program_number'} = (($val >> 3) & 3) + 1; + $$self{'program_is_running'} = ($val >> 5) & 1; + $$self{'pump_enabled'} = ($val >> 6) & 1; + $$self{'valve_is_running'} = ($val >> 7) & 1; + &::print_log("[Insteon::Irrigation] active_valve_id: $$self{'active_valve_id'}," + . " valve_is_running: $$self{'valve_is_running'}, active_program: $$self{'active_program_number'}," + . " program_is_running: $$self{'program_is_running'}, pump_enabled: $$self{'pump_enabled'}"); + + # Set a timer to check the status of the device after we expect the timer + # for the current valve to run out. + if ($$self{'valve_is_running'}){ + my $action = $self->get_object_name . "->_timer_query()"; + my $program = 0; + $program = $$self{'active_program_number'} + if ($$self{'program_is_running'}); + my $time = $self->_valve_timer($program, + $$self{'active_valve_id'}); + $time = ($time * 60) + 5; #Add 5 seconds to allow things to happen. + $$self{status_timer}->set($time,$action); + } +} + +# Used by the timer to check the status of the device. Will only run if MH +# believes that a valve is still on +sub _timer_query { + my ($self) = @_; + $self->request_status() if ($$self{'valve_is_running'}); +} =item C<_process_message()> @@ -355,6 +365,13 @@ sub _process_message { $$self{m_status_request_pending} = 0; $self->_process_command_stack(%msg); } + elsif ($msg{type} eq 'broadcast' && $msg{cmd_code} eq '27') { + #These are the broadcast status messages from the device. + $self->_process_status($msg{dev_attribs}); + ::print_log("[Insteon::Irrigation] Received broadcast status update.") + if $self->debuglevel(2, 'insteon'); + $self->_process_command_stack(%msg); + } # The device uses cmd 0x41 differently depending on STD or EXT Msgs elsif ($msg{command} eq "sprinkler_valve_off" && $msg{is_extended}) { my $program = hex(substr($msg{extra},0,2)); @@ -431,8 +448,11 @@ sub enable_pump { If set to true, this will cause the device to send a status message whenever a valve changes status during a program. If not set, MH will not be informed -of the status of each of the valves during a program. It is HIGHLY recommended -that you enable this feature. +of the status of each of the valves during a program. + +These messages appear to only be available if you put your PLM in monitor mode. +At the moment, there does not appear to be any downside to this, but use at +your own risk. =cut From 28b163ec929ce7c3b0e59385695b077d845b681e Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 8 Apr 2014 17:45:00 -0700 Subject: [PATCH 10/12] Insteon_Irrigation: Standardize All Routines to Index from 1; Fix Get Timers Printout No Valve 0 - The underlying message structure is odd, sometimes things are indexed from 0 and sometimes from 1. This oddity should not be carried over into the user interface, it only causes confusion. --- lib/Insteon/Irrigation.pm | 50 ++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 0b670a27b..8bf030516 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -20,23 +20,21 @@ Turning on a valve: $v_valve_on = new Voice_Cmd "Turn on valve [1,2,3,4,5,6,7,8]"; if (my $valve = state_now $v_valve_on) { - $valve--; - set_valve $irrigation "0$valve", "on"; + set_valve $irrigation "$valve", "on"; } Turning off a valve: $v_valve_off = new Voice_Cmd "Turn off valve [1,2,3,4,5,6,7,8]"; if (my $valve = state_now $v_valve_off) { - $valve--; - set_valve $irrigation "0$valve", "off"; + set_valve $irrigation "$valve", "off"; } Requesting valve status: $v_valve_status = new Voice_Cmd "Request valve status"; if (state_now $v_valve_status) { - poll_valve_status $irrigation; + request_status $irrigation; } =head2 DESCRIPTION @@ -122,14 +120,14 @@ sub poll_valve_status { =item C -Used to directly control valves. Valve_id is a two digit number 00-07, -valve_state may be on or off. +Used to directly control valves. Valve_id may be 1-8, valve_state may be on +or off. =cut sub set_valve { my ($self, $valve_id, $state) = @_; - my $subcmd = $valve_id; + my $subcmd = sprintf("%02X", $valve_id-1); my $cmd = undef; if ($state eq 'on') { $cmd = 'sprinkler_valve_on'; @@ -148,14 +146,14 @@ sub set_valve { =item C -Used to directly control programs. Program_id is a two digit number 00-03, -proggram_state may be on or off. +Used to directly control programs. Program_id may be 1-4, program_state may be +on or off. =cut sub set_program { my ($self, $program_id, $state) = @_; - my $subcmd = $program_id; + my $subcmd = sprintf("%02X", $program_id-1); my $cmd = undef; if ($state eq 'on') { $cmd = 'sprinkler_program_on'; @@ -271,18 +269,16 @@ By default, each valve is set to 30 minutes for each program. =cut sub set_timers() { - my ($self, $program, $v1, $v2, $v3, $v4, $v5, $v6, $v7, $v8) = @_; + my ($self, $program, @time_array) = @_; #Command is reused in different format for EXT msgs my $cmd = 'sprinkler_valve_on'; my $extra = sprintf("%02X", $program); - $extra .= sprintf("%02X", $v1); - $extra .= sprintf("%02X", $v2); - $extra .= sprintf("%02X", $v3); - $extra .= sprintf("%02X", $v4); - $extra .= sprintf("%02X", $v5); - $extra .= sprintf("%02X", $v6); - $extra .= sprintf("%02X", $v7); - $extra .= sprintf("%02X", $v8); + foreach my $time (@time_array){ + #Store values in MH Cache + $self->_valve_timer($program, $time); + #compose message data + $extra .= sprintf("%02X", $time); + } $extra .= '0' x (30 - length $extra); my $message = new Insteon::InsteonMessage('insteon_ext_send', $self, $cmd, $extra); $self->_send_cmd($message); @@ -333,10 +329,10 @@ sub _process_status { my $program = 0; $program = $$self{'active_program_number'} if ($$self{'program_is_running'}); - my $time = $self->_valve_timer($program, - $$self{'active_valve_id'}); + my $time = $self->_valve_timer($program, $$self{'active_valve_id'}); $time = ($time * 60) + 5; #Add 5 seconds to allow things to happen. - $$self{status_timer}->set($time,$action); + #Only set the timer if it is something worthwhile ie actually set. + $$self{status_timer}->set($time,$action) if $time > 5; } } @@ -388,7 +384,7 @@ sub _process_message { . $self->get_object_name . " are:\n"; $output .= " Program 0: Program 1: Program 2: ". "Program 3: Program 4:\n"; - for (my $i_v = 0; $i_v <= 8; $i_v++){ + for (my $i_v = 1; $i_v <= 8; $i_v++){ $output .= ' '; for (my $i_p = 0; $i_p <= 4; $i_p++){ $output .= " Valve $i_v:" . @@ -410,7 +406,7 @@ sub _process_message { } -# Used to store and retreive the valve times +# Used to store and retreive the valve times from MH cache sub _valve_timer { my ($self, $program, $valve, $time) = @_; if (defined $time){ @@ -451,8 +447,8 @@ a valve changes status during a program. If not set, MH will not be informed of the status of each of the valves during a program. These messages appear to only be available if you put your PLM in monitor mode. -At the moment, there does not appear to be any downside to this, but use at -your own risk. +At the moment, there does not appear to be any downside to running MH with your +PLM in monitor mode, but do this at your own risk. =cut From 93cd62b02261420494a35024c07efb5d6509a01e Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Tue, 8 Apr 2014 19:30:42 -0700 Subject: [PATCH 11/12] Insteon_Irrigation: Add Child Objects for Valve and Programs Makes controlling valves and programs much easier from the web interface and other objects. Also allows the use of generic_items functions such as tie_event --- lib/Insteon/Irrigation.pm | 187 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 8bf030516..11ab2bed8 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -334,6 +334,22 @@ sub _process_status { #Only set the timer if it is something worthwhile ie actually set. $$self{status_timer}->set($time,$action) if $time > 5; } + + # Set child objects if they exist + my $valve = $$self{'active_valve_id'}; + my $program = $$self{'active_program_number'}; + my $valve_status = 'off'; + $valve_status = 'on' if ($$self{'valve_is_running'}); + my $program_status = 'off'; + $program_status = 'on' if ($$self{'program_is_running'}); + if (ref $$self{'child_valve_'.$valve} && + (lc($$self{'child_valve_'.$valve}->state) ne $valve_status)){ + $$self{'child_valve_'.$valve}->set_receive($valve_status); + } + if (ref $$self{'child_program_'.$program} && + (lc($$self{'child_program_'.$program}->state) ne $program_status)){ + $$self{'child_program_'.$program}->set_receive($program_status); + } } # Used by the timer to check the status of the device. Will only run if MH @@ -481,6 +497,177 @@ Kevin Robert Keegan L, L +=head1 Irrigation_valve + +=head1 DESCRIPTION + +A child object for an irrigation valve. + +=head1 SYNOPSIS + +When defining the children, you need to identify who their parent is. + + $valve_1 = new Insteon::Irrigation_valve($irrigation, 1); + $valve_1->set(ON); #Turn ON the valve for the default time + $valve_1->set('5 min'); #Turn ON the valve for 5 minutes only + $valve_1->set_states('off', '5 min', 'on'); #set the states to display + +=head1 AUTHOR + +Kevin Robert Keegan + +=head1 INHERITS + +B + +=head1 Methods + +=over + +=cut + +package Insteon::Irrigation_valve; +use strict; + +@Insteon::Irrigation_valve::ISA = ('Generic_Item'); + +sub new { + my ($class, $parent, $valve) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = $parent; + $$self{valve} = $valve; + @{$$self{states}} = ('Off', '5 min', '15 min', ' 30 min', 'On'); + $$self{parent}{'child_valve_'.$valve} = $self; + $$self{timer} = new Timer; + return $self; +} + +=item C + +Use just like the set function for any other descendant of a Generic_Item. + +Accepts on and off commands and will parse the number portion out of any command +into the number of minutes. So '5 min' will cause the valve to turn ON for 5 +minutes. + +NOTE: The maximum amount of time the valve can be turned on for is determined +by the default setting, contained in program 0. Turning on the child object +for longer than the default setting will result in the valve running for the +default length and then turning off. + +=cut + +sub set { + my ($self, $p_state, $p_setby, $p_response) = @_; + if ($p_state =~ /(on|off)/i){ + $p_state = $1; + ::print_log("[Insteon::Irrigation] Received request to set ". + $self->get_object_name . " $p_state."); + $$self{parent}->set_valve($$self{valve}, $p_state); + } + elsif ($p_state =~ /(\d*)/) { + $p_state = $1; + ::print_log("[Insteon::Irrigation] Received request to set ". + $self->get_object_name . " ON for $p_state minutes."); + $$self{parent}->set_valve($$self{valve}, 'on'); + #Set timer to turn off + my $action = $$self{parent}->get_object_name . "->set_valve(". + $$self{valve} .", 'off')"; + my $time = ($p_state * 60); + $$self{timer}->set($time,$action); + } + else { + ::print_log("[Insteon::Irrigation] Cannot set ". + $self->get_object_name . " to unknown state of $p_state."); + } +} + +sub set_receive { + my ($self, $p_state) = @_; + if ($p_state =~ /off/i){ + #Clear any off timers that are outstanding + $$self{timer}->unset; + } + $self->SUPER::set($p_state); +} + +=back + +=head1 Irrigation_program + +=head1 DESCRIPTION + +A child object for an irrigation program. + +=head1 SYNOPSIS + +When defining the children, you need to identify who their parent is. + + $program_1 = new Insteon::Irrigation_program($irrigation, 1); + $program_1->set(ON); #Turn ON the program + +=head1 AUTHOR + +Kevin Robert Keegan + +=head1 INHERITS + +B + +=head1 Methods + +=over + +=cut + +package Insteon::Irrigation_program; +use strict; + +@Insteon::Irrigation_program::ISA = ('Generic_Item'); + +sub new { + my ($class, $parent, $program) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = $parent; + $$self{program} = $program; + @{$$self{states}} = ('Off', 'On'); + $$self{parent}{'child_program_'.$program} = $self; + $$self{timer} = new Timer; + return $self; +} + +=item C + +Use just like the set function for any other descendant of a Generic_Item. + +Accepts on and off commands. + +=cut + +sub set { + my ($self, $p_state, $p_setby, $p_response) = @_; + if ($p_state =~ /(on|off)/i){ + $p_state = $1; + ::print_log("[Insteon::Irrigation] Received request to set ". + $self->get_object_name . " $p_state."); + $$self{parent}->set_valve($$self{valve}, $p_state); + } + else { + ::print_log("[Insteon::Irrigation] Cannot set ". + $self->get_object_name . " to unknown state of $p_state."); + } +} + +sub set_receive { + my ($self, $p_state) = @_; + $self->SUPER::set($p_state); +} + + +=back + =head2 LICENSE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. From 5e90dd3bc780fe016db56d1160790940d16e8da1 Mon Sep 17 00:00:00 2001 From: KRKeegan Date: Wed, 9 Apr 2014 21:08:43 -0700 Subject: [PATCH 12/12] Insteon_Irrigation: Fix Bugs, Update Status of All Valves and Programs on Status --- lib/Insteon/Irrigation.pm | 48 +++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/Insteon/Irrigation.pm b/lib/Insteon/Irrigation.pm index 11ab2bed8..a46133b45 100755 --- a/lib/Insteon/Irrigation.pm +++ b/lib/Insteon/Irrigation.pm @@ -129,9 +129,9 @@ sub set_valve { my ($self, $valve_id, $state) = @_; my $subcmd = sprintf("%02X", $valve_id-1); my $cmd = undef; - if ($state eq 'on') { + if (lc($state) eq 'on') { $cmd = 'sprinkler_valve_on'; - } elsif ($state eq 'off') { + } elsif (lc($state) eq 'off') { $cmd = 'sprinkler_valve_off'; } unless ($cmd and $subcmd) { @@ -155,9 +155,9 @@ sub set_program { my ($self, $program_id, $state) = @_; my $subcmd = sprintf("%02X", $program_id-1); my $cmd = undef; - if ($state eq 'on') { + if (lc($state) eq 'on') { $cmd = 'sprinkler_program_on'; - } elsif ($state eq 'off') { + } elsif (lc($state) eq 'off') { $cmd = 'sprinkler_program_off'; } unless ($cmd and $subcmd) { @@ -338,17 +338,25 @@ sub _process_status { # Set child objects if they exist my $valve = $$self{'active_valve_id'}; my $program = $$self{'active_program_number'}; - my $valve_status = 'off'; - $valve_status = 'on' if ($$self{'valve_is_running'}); - my $program_status = 'off'; - $program_status = 'on' if ($$self{'program_is_running'}); - if (ref $$self{'child_valve_'.$valve} && - (lc($$self{'child_valve_'.$valve}->state) ne $valve_status)){ - $$self{'child_valve_'.$valve}->set_receive($valve_status); + + # Loop valves, updating state of all that have changed + for (my $v = 1; $v <= 8; $v++){ + my $valve_status = 'off'; + $valve_status = 'on' if ($$self{'valve_is_running'} && $v == $valve); + if (ref $$self{'child_valve_'.$v} && + (lc($$self{'child_valve_'.$v}->state) ne $valve_status)){ + $$self{'child_valve_'.$v}->set_receive($valve_status); + } } - if (ref $$self{'child_program_'.$program} && - (lc($$self{'child_program_'.$program}->state) ne $program_status)){ - $$self{'child_program_'.$program}->set_receive($program_status); + + # Loop programs, updating state of all that have changed + for (my $p = 1; $p <= 4; $p++){ + my $program_status = 'off'; + $program_status = 'on' if ($$self{'program_is_running'} && $p == $program); + if (ref $$self{'child_program_'.$p} && + (lc($$self{'child_program_'.$p}->state) ne $program_status)){ + $$self{'child_program_'.$p}->set_receive($program_status); + } } } @@ -539,7 +547,7 @@ sub new { $$self{valve} = $valve; @{$$self{states}} = ('Off', '5 min', '15 min', ' 30 min', 'On'); $$self{parent}{'child_valve_'.$valve} = $self; - $$self{timer} = new Timer; + $$self{state_timer} = new Timer; return $self; } @@ -566,7 +574,7 @@ sub set { $self->get_object_name . " $p_state."); $$self{parent}->set_valve($$self{valve}, $p_state); } - elsif ($p_state =~ /(\d*)/) { + elsif ($p_state =~ /(\d+)/) { $p_state = $1; ::print_log("[Insteon::Irrigation] Received request to set ". $self->get_object_name . " ON for $p_state minutes."); @@ -575,7 +583,7 @@ sub set { my $action = $$self{parent}->get_object_name . "->set_valve(". $$self{valve} .", 'off')"; my $time = ($p_state * 60); - $$self{timer}->set($time,$action); + $$self{state_timer}->set($time,$action); } else { ::print_log("[Insteon::Irrigation] Cannot set ". @@ -587,7 +595,7 @@ sub set_receive { my ($self, $p_state) = @_; if ($p_state =~ /off/i){ #Clear any off timers that are outstanding - $$self{timer}->unset; + $$self{state_timer}->set(0); } $self->SUPER::set($p_state); } @@ -634,7 +642,7 @@ sub new { $$self{program} = $program; @{$$self{states}} = ('Off', 'On'); $$self{parent}{'child_program_'.$program} = $self; - $$self{timer} = new Timer; + $$self{state_timer} = new Timer; return $self; } @@ -652,7 +660,7 @@ sub set { $p_state = $1; ::print_log("[Insteon::Irrigation] Received request to set ". $self->get_object_name . " $p_state."); - $$self{parent}->set_valve($$self{valve}, $p_state); + $$self{parent}->set_program($$self{program}, $p_state); } else { ::print_log("[Insteon::Irrigation] Cannot set ".