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

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

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

# we need global variables

my $path_to_log = "/home/srv/rrd/events_";
my $rrd = "/home/srv/rrd/heatingtemps12.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 (@ds, @vals);
my $ds_name;
my ($temp, $temp_aussen_avg, $id_temp);
my $a_hc = -0.4;   # hc - heating curve linear term
my $b_hc = 33;     # 31 constant term of hc
my ($temp_fbh_vl_set, $temp_fbh_vl, $temp_fbh_rl, $temp_puffer_oben);
my ($mix_signal, $mix_time, $mix_time_sign, $play_time, $last_mix_time_sign, $dt, $integral, $diff);

##############################################################################
# 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 "switching on the fbh-pump\n";
   wr_out($id_out01, "3", "1", $path_to_log);
}


sub shutdown {
   print "this is the end...\n\n";
   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;
}

sub fbh_pid { #($temp_fbh_vl_set, $temp_fbh_vl, $dt, $integral, $prev_diff)
   my ($temp_fbh_vl_set, $temp_fbh_vl, $dt, $integral, $prev_diff) = @_;
   my $KP = 1.75;
   my $KI = 0.0005 ;
   my $KD = 100;
   my ($P, $I, $D);
   my $diff;
   my $deriv;
   my $mix_time;
   
   $diff = $temp_fbh_vl_set - $temp_fbh_vl;
   $diff = sprintf "%.2f", $diff;
   #print "the difference is $diff degC\n";
   
   # delete integral if we cross the side of the error
   # integral should only pull us in
   if (substr($integral,0,1) eq "-") {
      if (not (substr($diff,0,1) eq "-")) {
         #print "diff: $diff and I: $integral, therefore setting I to 0 \n";
         $integral = 0;
      }
   } else {
      if (substr($diff,0,1) eq "-") {
         #print "diff: $diff and I: $integral, therefore setting I to 0 \n";
         $integral = 0;
      }
   }
   
   $integral = $integral + ($diff * $dt);
   $integral = sprintf "%.2f", $integral;
   $deriv = ($diff - $prev_diff) / $dt;
   $deriv = sprintf "%.2f", $deriv;
   $P = $KP * $diff;
   $I = $KI * $integral;
   $D = $KD * $deriv;
      
   if (abs($diff) > 0.5) { #we allow half a degree deviation
      
      if (abs($I) > 2) {
         if ($I > 0) { 
            $I = 2;
         } else { 
            $I = -2;
         }
      }
      $mix_time = $P + $I +$D;
      $mix_time = sprintf "%.2f", $mix_time;
      print "P: $P, I: $I, D: $D, mixing-time in sub PID: $mix_time\n";
   } else {

      $mix_time = $P + $I +$D;
      #print "P: $P, I: $I, D: $D, mixing-time in sub PID: $mix_time\n";
      
      #print "$diff degC is not enough difference to make an adjustment\n";
      $integral = 0;
      #$diff = 0;
      $mix_time = 0;
   }

   #if ($mix_time < 0) { # colder needs actually different PID values
   #   $mix_time = $mix_time * 2;
   #}
    
   return ($mix_time, $integral, $diff);
}

##############################################################################
# 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 = 4; # to compensate play in mixer
$last_mix_time_sign = 1; # 0 is positiv
$dt = 30 * 10;
$integral = 0;
$diff = 0;

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

while (1) {
   $logtime = cr_logtime;
   print "----------$logtime------------\n";
   
   # read in outside temp
   $end = time(); # - (60 * 60 * 4);
   $start = $end - (3600 * 3); #look back three 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_fbh_vl_set = $a_hc * $temp_aussen_avg + $b_hc;
   $temp_fbh_vl_set = sprintf "%.1f", $temp_fbh_vl_set;
   print "current set-temp of VL for FBH is $temp_fbh_vl_set degC\n";
   
   # read in current fbh-vl, fbh-rl, buffer-top
   $ds_name = "temp_fbh_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_fbh_vl = 0;
   foreach (@vals) { # search backwards through the array to find the first valid value
      die if ($i < 0);
      if (not (defined ($vals[$i][$id_temp]))) {
         $i--;
      } else {
         $temp_fbh_vl = $temp_fbh_vl + $vals[$i][$id_temp];
         $j++;
         if ($j >= 4) {last;} 
      }
   }
   $temp_fbh_vl = $temp_fbh_vl / $j;
   $temp_fbh_vl = sprintf "%.2f", $temp_fbh_vl;
   
   $ds_name = "temp_fbh_rl";
   $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_fbh_rl = 0;
   foreach (@vals) { # search backwards through the array to find the first valid value
      die if ($i < 0);
      if (not (defined ($vals[$i][$id_temp]))) {
         $i--;
      } else {
         $temp_fbh_rl = $temp_fbh_rl + $vals[$i][$id_temp];
         $j++;
         if ($j >= 4) {last;} 
      }
   }
   $temp_fbh_rl = $temp_fbh_rl / $j;
   $temp_fbh_rl = sprintf "%.2f", $temp_fbh_rl;   
   
   if (($temp_fbh_vl > 38) or ($temp_fbh_rl) > 38) {
      # 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);
      die "what happened?\n";
   }
   
   $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_fbh_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 "mixint it all from the buffer to stretch the warm water\n";
      wr_out($id_out01, "4", "1", $path_to_log);
      sleep (120);
      wr_out($id_out01, "4", "0", $path_to_log);
      die "water in buffer is not warm enough!\n";
   }

   # now calculate the new setting for the mixer
   
   #($mix_time, $integral, $diff) = fbh_pid($temp_fbh_vl_set, $temp_fbh_vl, $dt, $integral, $diff, $temp_puffer_oben);
   ($mix_time, $integral, $diff) = fbh_pid($temp_fbh_vl_set, $temp_fbh_vl, $dt, $integral, $diff);
   
   
   
   if (not ($mix_time == 0)) {
      
      print "average outside-temp of the last ".(($start-$end)/60)."min. is $temp_aussen_avg degC\n";
      print "current VL-temp of FBH is $temp_fbh_vl degC\n";
      print "current RL-temp of FBH is $temp_fbh_rl degC\n";
      print "current temp in buffer top is $temp_puffer_oben degC\n";
      
      # add something more for cooler buffer (needs to be refined)
      $mix_time = $mix_time * (100 / $temp_puffer_oben);
      
      if ($mix_time < 0) {
         $mix_time_sign = -1;
         $mix_time = abs($mix_time);
         $mix_signal = 5;
      } else {
         $mix_time_sign = 1;
         $mix_signal = 4;
      }
       
      if ($mix_time > 15) {
         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;
         print "play-time\n";
      }
      
      
   # positive values are warmer (sig 4), negative colder (sig 5)
   # we don't move under half a second 
      print "the difference is $diff degC, the integral-value is $integral. calculated a mixing-time of ".(sprintf("%.2f",($mix_time * $mix_time_sign)))."!\n";

   # now mix it
      
      wr_out($id_out01, $mix_signal, "1", $path_to_log);
      Time::HiRes::sleep ($mix_time); #must be more precise that a second
      wr_out($id_out01, $mix_signal, "0", $path_to_log);
   } else {
      print "average outside-temp of the last ".(($start-$end)/60)."min. is $temp_aussen_avg degC\n";
      print "current VL-temp of FBH is $temp_fbh_vl degC\n";

      print "the difference is $diff degC, nothing to mix\n";
   }
   
   #my $t_total = Time::HiRes::time() - $t_start;
   #$t_total = sprintf "%.3f",$t_total;
   #print "total $t_total\n";
   
   #print "wait to the next full 30 seconds and then another 10 seconds\n";
   waitaminute($dt);
   print "\n";
   
   sleep(15); #waiting for all the temps. beeing read all 30 seconds and processed into the rrd
}

