#!/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 = '10.5.6.76';
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 . "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);
	return 0 unless ($a[4] == 0xFC && $a[5] == 0x3E && $a[6] == 0x24 && $a[7] == 0x01);

	my $tagesleistung = sprintf ("%u", $a[37] + ( $a[38] << 8 ));  #tagesleistung
	my $gesamt = sprintf ("%u", $a[41] + ( $a[42] << 8 ));  #gesamt
#	my $status = sprintf ("%u", $a[27] ); #status
	my $status = sprintf ("%u", $a[25] ); #status
	#   // 0 = Warten auf Sonne
        #   // 2 = Anschieben
        #   // 3 = Einschaltverzögerung
        #   // 4 = erwärmt speicher

	# @a gets destroyed here
	my @n = AtoInt(splice(@a, 8));

	my $tsa = sprintf ("%.1f", $n[0] / 10); #kollektortemperatur
	my $tw = sprintf ("%.1f", $n[1] / 10); #speichertemp
	my $tsv = sprintf ("%.1f", $n[2] / 10); #solarvorlauf
	my $tam = sprintf ("%.1f", $n[3] / 10); #aussentemp
	my $tse = sprintf ("%.1f", $n[5] / 10); #solarruecklauf
	my $flow = sprintf ("%.1f", $n[6] / 10); #durchfluss
	my $pso = sprintf ("%u", $n[7] ); #pso
	
	$log->debug("tsa:" . $tsa . " tw:" . $tw . " tsv:" . $tsv . " tam:" . $tam . 
	" tse:" . $tse . " flow:" . $flow . " pso:" . $pso . " status:" . $status . 
        " tagesleistung:" . $tagesleistung . " gesamtleistung:" . $gesamt) if $Verbose;

	my %vals;
	$vals{tsa} = $tsa;
	$vals{tw} = $tw;
	$vals{tsv} = $tsv;
	$vals{tam} = $tam;
	$vals{tse} = $tse;
	$vals{flow} = $flow;
	$vals{pso} = $pso;
	$vals{status} = $status;
	$vals{tagesleistung} = $tagesleistung;
	$vals{gesamt} = $gesamt;

	my $line = data2line(
                'solardaten',
                \%vals,
                {},
		,
                );

	$log->debug ("data sent to influx: " . $line);

        my $res = Hijk::request({
        method       => "POST",
        host         => "127.0.0.1",
        port         => "8086",
        path         => "/write",
        query_string => "db=solardaten&precision=ms&u=kwb&p=kwb",
        body         => $line
        });



	return 1;
}

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();
