#!/usr/bin/perl -w
#
# @#$mnm20101114

use strict;
use warnings;
use POSIX;
use RRDs;
use Time::HiRes;

##############################################################################
# Program setup
##############################################################################

# we need global variables
my $verbose = 1;
my $debug = 1;

my $path_to_log = "/home/srv/rrd/events_";
my $rrd = "/home/srv/rrd/heatingtemps12.rrd";
my $rrd_step = 30;   # 30 sec is the update intervall in the rrd 
my $log;
my $logtime;
my $logstring;
my $logepoch;
my @params;
my $numArgs;
my ($i, $j);
my $minus;
my $switch;
my $toggle;
my $out;
my ($start,$end);
my $path_to_owfs = "/mnt/1-wire";
my $id_out01 = "$path_to_owfs/29.646C08000000";
my $id_out02 = "$path_to_owfs/29.B46008000000";
my (@ds, @vals);
my $ds_name;
my ($temp, $temp_aussen_avg, $id_temp);
my $a_hc = -0.65;   # hc - heating curve linear term
my $b_hc = 42;     # 31 constant term of hc
my ($temp_hk_vl_set, $temp_hk_vl, $temp_puffer_oben);
my ($mix_signal, $mix_time, $mix_time_sign, $play_time, $last_mix_time_sign, $dt, $integral, $diff);
my ($mix_id_out, $last_fiddle, $last_temp, $last_mix_ratio, $last_mix_time, $temp_hk_vl_limit);
my $temp_hk_vl_max = 55; # no more than 55 degrees please
my $temp_hk_vl_min = 30; # but at least this
my $temp_boost = 2; # evening and morning boost, because people like to crank
my @tod; # time of day as an array of sec, min, hour ...
my $hod; # hour of day

##############################################################################
# Subroutines Here
##############################################################################

sub startup {
   # the purpose of this routine is to establish a stable condition of temp with the pump running
   # 
   # what is our condition
   # check if pump is already on
   # if so, check if fbh-vl is increasing over the last 2min or so
   # otherwise switch pump on
   # wait a while and check all 30 secs if fbh-vl is increasing
   # if it is increasing by quite a bit, the drive mixer all the way to cold
   # just to be safe, maybe the buffer gets heated (which we see with a delay only)
   # if stable leave the start-up routine
   
   print "pump up the jam\n\n";
   wr_out($id_out01, "6", "1", $path_to_log);
   
}


sub shutdown {
   print "this is the end...\n\n";
   wr_out($id_out01, "7", "0", $path_to_log);
   wr_out($id_out02, "0", "0", $path_to_log);
   sleep(0.1);
     
   exit;
}

sub lights_out {
   # switch everything off, controlled shutdown before shutdown of task
   print "cleaning up now...\n\n";

}

sub restart {
   lights_out();
   goto START;
}

sub waitaminute { #$fullsec
   my $fullsec = shift;
   my $wait;
   
   $wait = ($fullsec -time() % $fullsec);       # now we wait to the next full second
   #print "now sleeping for $wait seconds\n";
   sleep ($wait);	# so, for fullsec = 60 it would be every full minute
   
}

# Write log to a file
sub wr_log { #($file , $data)
  my ($file , $data) = @_;
  open HANDLE, ">>$file" or die "I could not open $file: $!";
  print HANDLE $data . "\n";
  close (HANDLE);
}

# establish todays logfilename based on today, so every we write and append to a new file
sub cr_logname {
    my $logname;
    
    my $sec, my $min, my $hour, my $mday, my $mon, my $year, my $wday, my $yday, my $isdst;
    
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    $year = $year + 1900; # of course the year has to start not at the believed christs birthday; personally I count years starting the day an ape through a bone
    $mon = $mon + 1; # for whatever reason january is month no. 0!!!
    $mon = substr("0$mon",-2);       # gimme two digits for month, so from 01 to 12
    $mday = substr("0$mday", -2);          # again two digits for day also from 01 to 31 
    #print "$sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst\n";
    $logname = "$year$mon$mday.log";
    #print localtime(time);
    return $logname;
}

# establish the time in hours and minute and seconds for writing the log
sub cr_logtime {
    my $logtime;
    
    my $sec, my $min, my $hour, my $mday, my $mon, my $year, my $wday, my $yday, my $isdst;
    
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    $hour = substr("0$hour",-2);       # gimme two digits for hour, so from 01 to 23
    $min = substr("0$min", -2);
    $sec = sprintf "%02.0f", $sec;
    $logtime = "$hour:$min:$sec";
    return $logtime;
}

# write a value to an address at an id on the 1-wire bus
sub wr_out { # ($id, $out, $value, $path_to_log)
  my ($id, $out_nr, $value, $path_to_log) = @_;
  my $temp;
  my $time;
  my ($log, $logtime, $logstring, $logepoch);
  my (@sensed,$sensed,$bit);

  #print "and the value is: $value\n";

  if (open PIO, ">>$id/PIO.$out_nr") {
     print PIO $value;
     $logepoch = sprintf "%.6f",Time::HiRes::time();
     close(PIO);
  } else {
     $logepoch = sprintf "%.6f",Time::HiRes::time();
     $logtime = cr_logtime(); #take time direct after write
     $log = cr_logname();     #gives the current year month day .log as logname
     $log = $path_to_log.$log;#adds the path to log 
     $logstring = "I could NOT open $id/PIO.$out_nr: $!";
     $logstring = $logepoch . " " . $logtime . " " . $logstring;
     wr_log($log,$logstring);
     return;
  }

  $logtime = cr_logtime(); #take time direct after write
  $log = cr_logname();     #gives the current year month day .log as logname
  $log = $path_to_log.$log;#adds the path to log 
  
  if (open SENSED, "<$id/sensed.ALL") {
     $sensed = <SENSED>;
     close(SENSED);
     if ( ! defined($sensed)) {
        $logstring = " *undefined value! did not receive a value for '$id/sensed.ALL' what is the matter with owfs";
        $logstring = $logepoch . " " . $logtime . " " . $logstring;
        wr_log($log,$logstring);
        return;
      }
  } else { #problem opening the file sensed.ALL
     $logstring = "I could NOT open $id/PIO.sensed.ALL: $!";
     $logstring = $logepoch . " " . $logtime . " " . $logstring;
     wr_log($log,$logstring);
     return;
  }
  
  # unfortunately sensed is MSB in the end and its comma-separated
  @sensed = split(/,/,$sensed);
  #print "@sensed[$out_nr], $value address: $out_nr\n";
  #print "sensed: ".((not(@sensed[$out_nr])) == $value)."\n";
  
  #print "$sensed\n";
  $sensed = "";
  foreach $bit (@sensed) {
     if ($bit) {$bit=0;} else {$bit=1;} #unfortunately sensed.ALL has inverse logic on top of it
     $sensed = ($bit.$sensed);
  }
  #print "$sensed\n\n";
  if (not($sensed[$out_nr] == $value)) {
     $logstring = "output $out_nr was not switched when sensed was read: $sensed";
     $logstring = $logepoch . " " . $logtime . " " . $logstring;
     wr_log($log,$logstring); return;
  }

  #print "$sensed\n";
  if ($value) {
     $logstring = "switched output $id $out_nr ON  $sensed";
  } else {
     $logstring = "switched output $id $out_nr OFF $sensed";
  }
  $logstring = $logepoch . " " . $logtime . " " . $logstring;

  wr_log($log, $logstring);
  #print "wrote $value to $id/PIO.$out_nr and logged it \n";
}

sub rrd_fetch { #($rrd, $start_time, $end_time, @ds, @vals)
# returns the list of available ds_names in @ds and
# the 2-dim array @vals [epoch][ds]
# where epoch starts at or after start_time and ends at or before end_time

   my ($rrd, $start_time, $end_time) = @_;

   my $ERR = 0;
   my $epoch;
   my $row = 0;
   my $col = 0;   
   my ($line, @vals, $val, @ds);   
 # fetch average values from the RRD database between start and end time
 my ($start,$step,$ds_names,$data) =
     RRDs::fetch($rrd, "AVERAGE","-s", "$start_time", "-e", "$end_time");
      $ERR=RRDs::error;
      die "ERROR while reading $rrd: $ERR\n" if $ERR;
   
   @ds = @$ds_names;
   #print "DS: @ds\n";
   
   #print "Number of values before is: ".($#vals+1)."\n";
   
 # save fetched values in a 2-dimensional array
   $epoch = $start;
   foreach $line (@$data) {
      
      #learn about defined. The array is defined, but not its elements
      #print "defined: ".defined(@$line)."\n";
    # see also: [from http://www.cs.mcgill.ca/~abatko/computers/programming/perl/howto/array/]
    #print "Value EXISTS, but may be undefined.\n"  if exists  $array[ $index ];
    #print "Value is DEFINED, but may be false.\n"  if defined $array[ $index ];
    #print "Value at array index $index is TRUE.\n" if         $array[ $index ];
      #if (defined($line)) {print "@$line\n";}

      foreach $val (@$line) {
         if (not (defined($val))) { # dont need no undefined values
            #print "$col : great! rrd gives us values that are not defined\n"; 
            $col++;
            next; # w can
         } else {
            #print "value is $val\n";
            $vals[$row][++$col] = $val;
            #print "Number of values is after value $val: ".($#vals+1)."\n";
         }
      }
      #print "Number of values is ".($#vals+1)."\n";
      $vals[$row][0] = $epoch;
      #print "DS: is ".$vals[$row][++$col]."\n";
      $row++;
      $col = 0;
      $epoch = $epoch + $step;
      
   }  
   END_FETCH:
   # [row] [col] is [epoch] [ds]
   
   # now throw away all undefined values, if a value is U its not undefined in our terms!
   
   
   return (\@ds, \@vals);

}


sub get_index { # @array, $search
   my ($ref_array,$search) = @_;
   my @array;
   my %index;
   
   @array = @$ref_array;
   #print "@array\n";
   #print "$search\n";
   @index{@array} = (0..$#array);
   my $index = $index{$search};
   
   return $index;
}


##############################################################################
# Program startup
##############################################################################
# everything stay where it is - for now

##############################################################################
# Trap interrupts here for shutdown
##############################################################################
$SIG{'INT' } = 'shutdown';
$SIG{'QUIT'} = 'lights_out';
$SIG{'HUP' } = 'restart';
$SIG{'TRAP'} = 'shutdown';
$SIG{'ABRT'} = 'shutdown';
$SIG{'STOP'} = 'lights_out';

##############################################################################
# Main Loop
##############################################################################
# Here's where we do whatever needs to be done in this non-interrupt
# driven sequence of events
#
#init


my $t_start = Time::HiRes::time();

START: startup();


$play_time = 2; # to compensate play in mixer
$last_mix_time_sign = 1; # 0 is positiv
$dt = 30;
$integral = 0;
$diff = 0;
$last_fiddle = time()-300;  # the last time we fiddled with the mixer
$last_temp = 30 ;       # the temperature when we fiddled the last time
$last_mix_ratio = 2; #1;#2; #in seconds per degree

waitaminute($dt);
sleep(17); #waiting for all the temps. beeing read all 30 seconds and processed into the rrd

while (1) {
   $logtime = cr_logtime;
   @tod = localtime(time);
   $hod = $tod[2];
   #print "hour of day is $hod\n";
   
   #if ($verbose) {print "----------$logtime------------\n";}
   
   # read in outside temp
   $end = time(); # - (60 * 60 * 4);
   $start = $end - (3600 * 6); #look back six hours for the outside temp
   my ($ds_ref, $vals_ref) = rrd_fetch($rrd, $start, $end); # making sure to get the read the last minute (which is 2 values)
   @ds = @$ds_ref;
   @vals = @$vals_ref;
   #print "  @ds\n";
   #print "Number of values is: ".($#ds+1)."\n";
   #print $vals[0][0]."\n";
   #print "Number of values is: ".($#vals+1)."\n";
   
   # calc average (last 1h) of outside temp
   $ds_name = "temp_aussen";
   $id_temp = get_index(\@ds, $ds_name);
   $id_temp = $id_temp + 1; # because index 0 is taken by the epoch-value
   
   #print "the index of $ds_name in \n@ds \nis: $id_temp\n";
   #$i = 0;
   #foreach  (@vals) {
   #   print "the temp. $ds_name at epoch ".$vals[$i][0]." was ".$vals[$i++][$id_temp]."\n";
   #}
   
   $i = 0;
   $j = 0;
   $temp = 0;
   foreach (@vals) {
      if (defined ($vals[$j][$id_temp])) {
         $temp = $temp + $vals[$j++][$id_temp];
         $i++;
      } else {
         #print "value not defined at $j \n";
         $j++;
      }
   }
   if ($i > 1) { 
      $temp_aussen_avg = $temp / $i;
      $temp_aussen_avg = sprintf "%.1f", $temp_aussen_avg;
   } else {
      die "need more than none or one value for outside temp to build an average\n";
   }
   
   # read in function of heating curve
   # calc set temp for fbh-vl
   $temp_hk_vl_set = $a_hc * $temp_aussen_avg + $b_hc;
   $temp_hk_vl_set = sprintf "%.1f", $temp_hk_vl_set;
   if ($temp_hk_vl_set < $temp_hk_vl_min) {
      $temp_hk_vl_set = $temp_hk_vl_min;
   }
   if ((($hod >= 7) and ($hod <= 6)) or (($hod >= 18) and ($hod <= 19))) {
      $temp_hk_vl_set = $temp_hk_vl_set * 1.05;  # boost by 5 percent
   }
   if ($temp_hk_vl_set < 34) {
      $temp_hk_vl_limit = sprintf "%.1f",($temp_hk_vl_set * 0.09);
   } else {
      $temp_hk_vl_limit = sprintf "%.1f",($temp_hk_vl_set * 0.05);
   }
   if ($temp_hk_vl_limit < 0.5) {$temp_hk_vl_limit = 0.5;}
   #if ($verbose) {print "current set-temp of VL for HK is $temp_hk_vl_set degC\n";}
   
   # read in current hk-vl
   $ds_name = "temp_hk_vl";
   $id_temp = get_index(\@ds, $ds_name);
   $id_temp = $id_temp + 1; # because index 0 is taken by the epoch-value
   $i = $#vals;
   $j = 0;
   $temp_hk_vl = 0;
   foreach (@vals) { # search backwards through the array to find the first valid value
      die if ($i < 0); # not existent in rrd?
      if (not (defined ($vals[$i][$id_temp]))) {
         $i--;
      } else {
         $temp_hk_vl = $temp_hk_vl + $vals[$i--][$id_temp];
         $j++;
         if ($j >= 6) {last;} 
      }
   }
   $temp_hk_vl = $temp_hk_vl / $j;
   $temp_hk_vl = sprintf "%.2f", $temp_hk_vl;
   
   # read in buffer top
   $ds_name = "temp_puffer_oben";
   $id_temp = get_index(\@ds, $ds_name);
   $id_temp = $id_temp + 1; # because index 0 is taken by the epoch-value
   $i = $#vals;
   $j = 0;
   $temp_puffer_oben = 0;
   foreach (@vals) { # search backwards through the array to find the first four valid values
      die if ($i < 0);
      if (not (defined ($vals[$i][$id_temp]))) {
         $i--;
      } else {
         $temp_puffer_oben = $temp_puffer_oben + $vals[$i--][$id_temp];
         $j++;
         if ($j >= 2) {last;}
      }
   }
   $temp_puffer_oben = $temp_puffer_oben / $j;
   $temp_puffer_oben = sprintf "%.2f", $temp_puffer_oben;
   
   
   if ($temp_puffer_oben < $temp_hk_vl_set) {
         # now there is s.th. going wrong
      #print "cooling the mixer\n";
      #wr_out($id_out01, "5", "1", $path_to_log);
      #sleep (210);
      #wr_out($id_out01, "5", "0", $path_to_log);
      #wr_out($id_out01, "3", "0", $path_to_log);
      
      print "mix it all from the buffer to stretch the warm water\n";
      wr_out($id_out01, "7", "1", $path_to_log);
      sleep (210);
      wr_out($id_out01, "7", "0", $path_to_log);
      die "water in buffer is not warm enough!\n";
   }

   # fill the hk-vl-graph with values from the rrd
   my @temp_hk_vl_graph = (); # empty array
      
   $ds_name = "temp_hk_vl";
   $id_temp = get_index(\@ds, $ds_name);
   $id_temp = $id_temp + 1; # because index 0 is taken by the epoch-value
   $i = $#vals;
   $j = 0;
   $temp_hk_vl = 0;
   foreach (@vals) { # go backwards through the array and fill the graph array with values
      die if ($i < 0); # not existent in rrd?
      if (not (defined ($vals[$i][$id_temp]))) {
         $i--;
      } else {
         $temp_hk_vl_graph[$j] = $vals[$i--][$id_temp];
         $j++;
         if ($j >= 6) {last;} # go back 6 times 30 sec. of steps, so look back 3 min
         if ($vals[$i][0] <= ($last_fiddle + $rrd_step)) {last;} # values before the last time we fiddled with the mixer have to be disregarded
      }
   } 
   
   # fill the differences array
   my @temp_hk_vl_diff = ();
   $i = 1;
   foreach (@temp_hk_vl_graph) { # subtract the values @ i and i-1 that is from recent back to last_fiddle or three minutes (6 * 30 seconds)
      if ($i > $#temp_hk_vl_graph) { # if the graph has only one element ($# is 0) then we can do not even one difference (of course)
         last;
      } else {
         $temp_hk_vl_diff[$i-1] = $temp_hk_vl_graph[$i-1] - $temp_hk_vl_graph[$i];
         #if ($debug) {print $temp_hk_vl_graph[$i-1], " ", $temp_hk_vl_graph[$i]," diff at $i-1 is ",$temp_hk_vl_diff[$i-1],"\n"; }
         $i++;
      }
   }
   
   # --- ANALYSIS ---
   # now analyse the hk-vl-graph to see what is going on
   $mix_time = 0; # reset any previous value to mix
   
   if ($temp_hk_vl_graph[0] > $temp_hk_vl_max) {   # we have to cool down a bit
      if ($verbose) {print "cooling the HK-VL temp\n";}
      wr_out($id_out02, "0", "1", $path_to_log);
      sleep (210); #must be more precise that a second
      wr_out($id_out02, "0", "0", $path_to_log);
      $last_fiddle = time();
      $last_temp = $temp_hk_vl_graph[0]; # the temperature value where we fiddled
      $last_mix_time = $mix_time * $mix_time_sign;
      if ($verbose) {print "sleeping for 10 minutes\n";}
      sleep(600);
   }
   
   # is the last fiddle already three minutes ore more ago?
   if ((time() - $last_fiddle) > 120) {
      if (($temp_hk_vl_graph[0] > ($temp_hk_vl_set + $temp_hk_vl_limit)) ) {  # temp is over set-temp + limit
         # if there is a significant difference that indicates an adjusting of the temperature in direction of the set-temp then do nothing
         #$diff = $temp_hk_vl_set + $temp_hk_vl_limit - $temp_hk_vl_graph[0];
         $diff = $temp_hk_vl_set - $temp_hk_vl_graph[0];
         $mix_time = $diff * $last_mix_ratio;

         if ($debug) { print "graph + last two differences ", ($temp_hk_vl_graph[0] + $temp_hk_vl_diff[0] + $temp_hk_vl_diff[1]), " set + limit ", ($temp_hk_vl_set + $temp_hk_vl_limit), "\n"}
         if (($temp_hk_vl_graph[0] + $temp_hk_vl_diff[0] + $temp_hk_vl_diff[1] )  < ($temp_hk_vl_set + $temp_hk_vl_limit)) {  #then in two more cycles we might be within the limits
            if ($debug) { print "setting mixtime to 0 because we might be in range withing two cycles\n"}
            $mix_time = 0;
         }         
         if ($temp_hk_vl_diff[0] < -1) {  
            if ($debug) {print "we are falling with more than 1 degrees per 30 seconds, better not change the mixer right now\n"}
            $mix_time = 0;
         }
      
      } elsif ($temp_hk_vl_graph[0] < ($temp_hk_vl_set - $temp_hk_vl_limit)) { # temp is under set-temp - limit
         $diff = $temp_hk_vl_set - $temp_hk_vl_graph[0];
         $mix_time = $diff * $last_mix_ratio;
         
         if ($debug) { print "graph + last two differences ", ($temp_hk_vl_graph[0] + $temp_hk_vl_diff[0] + $temp_hk_vl_diff[1]), " set - limit ", ($temp_hk_vl_set - $temp_hk_vl_limit), "\n"}
         if (($temp_hk_vl_graph[0] + $temp_hk_vl_diff[0] + $temp_hk_vl_diff[1] ) > ($temp_hk_vl_set - $temp_hk_vl_limit)) {  #then in two more cycles we might be within the limits
            if ($debug) { print "setting mixtime to 0 because we might be in range withing two cycles\n"}
            $mix_time = 0;
         }      
         if ($temp_hk_vl_diff[0] > 1) {  
            if ($debug) {print "we are rising with more than 1 degrees per 30 seconds, better not change the mixer right now\n"}
            $mix_time = 0;
         }
         
         if ($debug) {print "calculated a mix-time of $mix_time\n";}
      }
   } else { # there was fiddling going on, so see if the situation gets stable
      #nothing for now
      if ($debug ) { print "last_fiddle was ",time()-$last_fiddle, " seconds ago\n";}
   }
   
   # and now mix it
   if (not ($mix_time == 0)) {
      if ($mix_time < 0) {
         $mix_time_sign = -1;
         $mix_time = abs($mix_time);

         $mix_signal = 0;
         $mix_id_out = $id_out02;
      } else {
         # here we should check if its stable or not
         # right now we go slower on warming than cooling
         $mix_time = $mix_time * 0.5;
         $mix_time_sign = 1;
         $mix_signal = 7;
         $mix_id_out = $id_out01;
      }
       
      if ($mix_time > 15) {
         if ($verbose) {print "better not longer than 15 secs\n";}
         $mix_time = 15;
      }
      if (not ($mix_time_sign == $last_mix_time_sign)) {
         $mix_time = $mix_time + $play_time;
         $last_mix_time_sign = $mix_time_sign;
         if ($verbose) {print "play-time\n";}
      }
      
      
   # positive values are warmer (sig 4), negative colder (sig 5)
   # we don't move under half a second 
      if ($debug ) { print "last_fiddle was ",(sprintf("%.2f",(time()-$last_fiddle)/60)), " minutes ago\n";}
      if ($verbose) {print "average outside-temp of the last ".(($start-$end)/60)."min. is $temp_aussen_avg degC\n";}
      if ($verbose) {print "set-temp of HK is $temp_hk_vl_set with a limit of +/- $temp_hk_vl_limit and an actual VL-temp of ".(sprintf("%.2f",($temp_hk_vl_graph[0])))." degC\n";}
      if ($verbose) {print "the difference is $diff degC, calculated a mixing-time of ".(sprintf("%.2f",($mix_time * $mix_time_sign)))."!\n";}

   # now really do-ooo-ooo ittttt
      
      wr_out($mix_id_out, $mix_signal, "1", $path_to_log);
      Time::HiRes::sleep ($mix_time); #must be more precise that a second
      wr_out($mix_id_out, $mix_signal, "0", $path_to_log);
      $last_fiddle = time();
      $last_temp = $temp_hk_vl_graph[0]; # the temperature value where we fiddled
      $last_mix_time = $mix_time * $mix_time_sign;
      
      if ($verbose) {print "\n";}
      
   } else {
      #if ($verbose) {print "average outside-temp of the last ".(($start-$end)/60)."min. is $temp_aussen_avg degC\n";}
      if ($verbose) {print "outside-temp $temp_aussen_avg, set-temp of HK is $temp_hk_vl_set with a limit of +/- $temp_hk_vl_limit and an actual VL-temp of ".(sprintf("%.2f",($temp_hk_vl_graph[0])))." degC\n";}
      #if ($verbose) {print "nothing to mix\n";}
   }
   
   #print "wait to the next full 30 seconds and then another 10 seconds\n";
   
   waitaminute($dt);
   
   
   sleep(17); #waiting for all the temps. beeing read all 30 seconds and processed into the rrd
}

