#!/usr/bin/perl
use strict;
use warnings;

use Getopt::Std;
use Time::Local;
use IO::Socket::INET;
use InfluxDB::LineProtocol qw(data2line precision=ms);
use Switch;
use DateTime;
use DateTime::Duration;
use DateTime::Format::Strptime;
use Date::Format;
use Hijk;
use Data::Dumper;
use Log::Dispatch;
use Log::Dispatch::File;
use File::Fetch;
use URI::Escape qw(uri_escape);

use constant LOG_DIR    => '/var/log/paradigma';
use constant LOG_FILE   => 'paradigma.log';

my $systalan_ip = '192.168.178.48';
my $systalan_port = '7260';
my $systalan_pw = '31323334';
my $SocketTimeOut  = 120; # seconds, must be > 60
my $MaxDataLength  = 2048;


my $Usage = qq{
Usage: $0 [options]

Options: -d       dump data bytes
         -D       print debug output
         -v       be verbose
         -w COLS  print COLS columns when dumping (default is 8)
};
our ($opt_d, $opt_D, $opt_v, $opt_w);

getopts("dDvw:") || die $Usage;

our $Dump    = $opt_d;
our $Debug   = $opt_D;
our $Verbose = $opt_v;
our $Columns = $opt_w || 8;

#logging
my $log = new Log::Dispatch(
      callbacks => sub { my %h=@_; return Date::Format::time2str('%B %e %T', time)." $0\[$$]: ".$h{message}."\n"; }
);
$log->add( Log::Dispatch::File->new( name      => 'file1',
                                     min_level => 'debug',
                                     mode      => 'append',
                                     filename  => File::Spec->catfile( LOG_DIR, LOG_FILE),
                                   )
);

sub Main {
	$log->warning("Starting Processing:  ");

	my $keepgoing = 1;

	$SIG{HUP}  = sub { $log->warning("Caught SIGHUP:  exiting gracefully") ; $keepgoing = 0; };
	$SIG{INT}  = sub { $log->warning("Caught SIGINT:  exiting gracefully") ; $keepgoing = 0; };
	$SIG{QUIT} = sub { $log->warning("Caught SIGQUIT:  exiting gracefully" ); $keepgoing = 0; };

	my $Socket = new IO::Socket::INET(
		PeerHost => $systalan_ip,
		PeerPort => $systalan_port,
		Proto     => "udp",
		) or die "ERROR in socket creation: $!\n";

	my $lastmin = (localtime())[1];
	$lastmin--;
	
	my $LastPacket = 0;
	## MAIN loop
	while ($keepgoing) {
		my $now = (localtime())[1]; 
		if ($lastmin ne $now) {
			$lastmin = $now;
			SendMonitorCommand($Socket);
		};

	        my @Data = Receive($Socket);
		# only process each 5 secs
		if ( (time() - $LastPacket > 5) && @Data) {
          		$LastPacket = time() if Process(@Data);
           	}
        	Check($LastPacket && (time() - $LastPacket > $SocketTimeOut), "Kein Datenpaket empfangen!", "Datenpaket empfangen.", "MailNoPacket");
        }
	
	$Socket->close();

}

sub SendMonitorCommand {
	my $Socket = shift;

#	my $command = $systalan_pw . "0a031c0b"; 
	my $command = $systalan_pw . "0a0114e1";
	my @data = map { hex($_) } ($command =~ /(..)/g);
	Send ($Socket, @data);

}

sub Receive {
	my $Socket = shift;
	my $s = "";
	my $TimedOut = 0;
	eval {
	    local $SIG{ALRM} = sub { $TimedOut = 1; };
	    alarm($SocketTimeOut);
	    $Socket->recv($s, $MaxDataLength);
	    alarm(0);
	    1;
	    };
	return () unless Check($TimedOut, "Keine Verbindung!", "Verbindung OK.", "MailLostConnection");
	if ($Verbose) {
		my $logmsg = sprintf("received %d bytes from %s:%s", length($s), $Socket->peerhost(), $Socket->peerport());
		$log->info ($logmsg);
	}
	my @a = StringToArray($s);
	Dump(@a) if ($Dump);
	return @a;
}

sub Send {
	my ($Socket, @a) = @_;
	if ($Verbose) {
		my $logmsg = sprintf("sending %d bytes to %s:%s", $#a + 1, $Socket->peerhost(), $Socket->peerport());
		$log->info ($logmsg);
	}

	Dump(@a) if ($Dump);
	$Socket->send(ArrayToString(@a));
}

sub ArrayToString {
	my @bytes = (@_);

	return join '', map chr, @bytes;
}

sub StringToArray {
	my $s = shift;
	return unpack('C*',$s);
}


sub Dump {
  my @a = @_;
  my $logmsg="";
  for (my $i = 0; $i <= $#a; $i++) {
      $logmsg .= sprintf("\n") if ($i && ($i % $Columns) == 0);
      $logmsg .= sprintf(" %02X", $a[$i]);
      }
  $logmsg .= sprintf("\n");
  $log->info ($logmsg);
}

sub Check
{
  my ($ErrorCondition, $ErrorMsg, $OkMsg, $LastReported) = @_;
  return !$ErrorCondition;
}

sub Process {
	my @a = @_;
    return 0 if ((scalar @a) < 8);
	  #Solarwerte
	return 0 unless ($a[4] == 0xFC && $a[5] == 0x16 && $a[6] == 0x0b && $a[7] == 0x01)
	#Heizung
	  or             ($a[4] == 0xFC && $a[5] == 0x20 && $a[6] == 0x0c && $a[7] == 0x01)
      or             ($a[4] == 0xFC && $a[5] == 0x22 && $a[6] == 0x0c && $a[7] == 0x02);


if ($a[4] == 0xFC && $a[5] == 0x16 && $a[6] == 0x0b && $a[7] == 0x01) {
	my $tsa = sprintf ("%u", ($a[8] << 8) + $a[9]);  #tsa
	my $tse = sprintf ("%u", ($a[10] << 8) + $a[11]);  #tw
	my $twu = sprintf ("%u", ($a[12] << 8) + $a[13]);  #tsv
	my $pso = sprintf ("%u", $a[16] ); #PSO in %
	my $statussolar = sprintf ("%u", $a[18] ); #status-solar
        #   // 2 = Frostschutz
	    #   // 3 = Einspeisen
        #   // 5 = Einschaltverzögerung
	 
	my $ctr = sprintf ("%u", $a[21]);  #ctr
    my $froststatus = sprintf ("%u", $a[20] ); #frostschutz - status
    	 #   // 2 = Ausphase
         #   // 3 = Einphase
         #   // 4 = Einspeisen
	
	# Umrechnen auf Kommastelle
	$tsa = $tsa / 10;
	$tse = $tse / 10;
	$twu = $twu / 10;
	$ctr = $ctr / 10;
	
	
	$log->debug("TSA:" . $tsa . " TSE:" . $tse . " TWU:" . $twu  . " CTR" . $ctr ." tse:" . $tse . " pso:" . $pso . " Status-Solar:" . $statussolar . " Frostschutzstatus:" . $froststatus ) if $Verbose;
	
	  # Werte in IOBroker schreiben
 my $res = Hijk::request({
        method       => "POST",
        host         => "127.0.0.1",
        port         => "8087",
        path         => "/setBulk",
        query_string => "Solar.0.TSA=".$tsa."&Solar.0.TSE=".$tse."&Solar.0.TWU=".$twu."&Solar.0.CTR=".$ctr."&Solar.0.PSO=".$pso."&Solar.0.Status=".$statussolar."&Solar.0.Frostschutz=".$froststatus."&prettyPrint"
        });
    }
   # Heizung
if ($a[4] == 0xFC && $a[5] == 0x20 && $a[6] == 0x0c && $a[7] == 0x01){
   #Werte extrahieren
   my $TA = sprintf ("%u", ($a[12] << 8) + $a[13]);  #TA Außentemperatur
   my $TWO = sprintf ("%u", ($a[14] << 8) + $a[15]);  #TWO (Temp Speicher Oben)
   my $TI = sprintf ("%u", ($a[20] <<8) +$a[21]);  #TI HK1
   my $TV = sprintf ("%u", ($a[24] << 8) + $a[25]);  #TV Vorlauf-Temp. HK1
   my $TR = sprintf ("%u", ($a[28] << 8) + $a[29]);  #TR Rücklauf-Temp. HK1
   my $TPO = sprintf ("%u", ($a[32] << 8) + $a[33]);  #TPO
   my $TPU = sprintf ("%u", ($a[34] << 8) + $a[35]);  #TPU
   



   #Werte mit Komma verschieben
   $TA = $TA / 10;
   $TWO = $TWO / 10;
   $TI = $TI / 10;
   $TV = $TV /10;
   $TR = $TR /10;
   $TPO = $TPO / 10;
   $TPU = $TPU / 10;
   
    #Werte ausgeben im Log
   $log->debug("TA:" . $TA ." TWO:" . $TWO . " TI:" . $TI . " TV:" . $TV . " TR:" . $TR . "TPO:" . $TPO . "TPU:" . $TPU) if $Verbose;
       
   # Werte in IOBroker schreiben
 my $res = Hijk::request({
        method       => "POST",
        host         => "127.0.0.1",
        port         => "8087",
        path         => "/setBulk",
        query_string => "Heizung.0.TA=".$TA."&Heizung.0.TWO=".$TWO."&Heizung.0.TI=".$TI."&Heizung.0.TV=".$TV."&Heizung.0.TR=".$TR."&Heizung.0.TPO=".$TPO."&Heizung.0.TPU=".$TPU."&prettyPrint"
         });
         
        
}

if ($a[4] == 0xFC && $a[5] == 0x22 && $a[6] == 0x0c && $a[7] == 0x02){
   #Werte extrahieren
   my $TIsoll = sprintf ("%u", ($a[8] << 8) +$a[9]);  #TIsoll HK1
   my $TVsoll = sprintf ("%u", ($a[12] << 8) + $a[13]);  #Temp Vorlauf Soll HK1
   my $TWOsoll = sprintf ("%u", ($a[16] << 8) + $a[17]);  #TWO Soll (Temp Speicher Oben?)
   my $TPOsoll = sprintf ("%u", ($a[18] << 8) + $a[19]);  #TPO Soll (???)
   my $BK = sprintf ("%u", ($a[24] << 8) + $a[25]); # Betriebsstunden Kessel
   my $KS = sprintf ("%u", ($a[28] << 8) + $a[29]); # Kesselstarts
   my $PHK = sprintf ("%u", $a[37]); # Pumpe HK1 in %
  
   #Werte mit Komma verschieben
   $TIsoll = $TIsoll / 10;
   $TVsoll = $TVsoll / 10;
   $TWOsoll = $TWOsoll / 10;
   $TPOsoll = $TPOsoll / 10;
   
   
   
   #Werte ausgeben im Log
   $log->debug("TIsoll:" . $TIsoll . " TVsoll:" . $TVsoll . " TWOsoll:" . $TWOsoll . " TPOsoll:" . $TPOsoll . " PHK:" . $PHK ."%" . " Betriebsstunden Kessel: " . $BK . " Kesselstarts:" . $KS ) if $Verbose;
      
      # Werte in IOBroker schreiben
 my $res = Hijk::request({
       method       => "POST",
       host         => "127.0.0.1",       
       port         => "8087",
        path         => "/setBulk",
        query_string => "Heizung.0.TIsoll=".$TIsoll."&Heizung.0.TVsoll=".$TVsoll."&Heizung.0.TWOsoll=".$TWOsoll."&Heizung.0.TPOsoll=".$TPOsoll."&Heizung.0.PHK=".$PHK."&Heizung.0.BetriebsstundenKessel=".$BK."&Heizung.0.Kesselstarts=".$KS."&prettyPrint"
         });  
    
   }
  
}

sub AtoInt {
  my @a = @_;
  my @n = ();
  for (my $i = 0; $i < $#a; $i += 2) {
      my $t = $a[$i] + ($a[$i + 1] << 8);
      $t -= 0xFFFF if (($a[$i + 1] & 0x80) != 0);
      push(@n, $t);
      }
  return @n;
}

Main();
