#! /usr/bin/perl
use Getopt::Long;
use Text::CSV;
use Time::Local;

my $VERSION = "0.2.7-perl";
my %CURRENCY = (
	"euro" => "€",
	"dollar" => "\$",
	"pound" => "£",
	"sFr" => "sFr"
);
my $YEAROFFSET = 2000;

my $verbose, $debug;
my $generateSetupFile;
my $decimalSeparator = '.', $valueSeparator = ',', $dateformat = "ymd";
my $outputWatt, $outputVa, $outputStart;
my $tar1 = 10, $tar2 = 10, $loggerId = 0, $future = 1, $currency = "€";
my $join = -1; # disabled
my $funcHeader = \&printHeader, $funcRow = \&printFloat;
GetOptions(
	"help|h|?" => \&help,
	"verbose|v:+" => \$verbose,
	"debug|d" => \$debug,
	"set" => \$generateSetupFile,
	"dpcomma|dpc" => sub { $decimalSeparator = ',' },
	"dpdot|dpd" => sub { $decimalSeparator = '.' },
	"dutch" => sub { $valueSeparator=";"; $decimalSeparator=","; $dateformat="dmy" },
	"int" => sub { $funcRow = \&printInt },
	"watt" => \$outputWatt,
	"va" => \$outputVa,
	"start" => \$outputStart,
	"values" => sub { $funcHeader = sub { } },
	"tab" => sub { $valueSeparator = "\t" },
	"sc" => sub { $valueSeparator = ";" },
	"loggerid|id=i" => \$loggerId,
	"currency|cur=s" => sub { $currency = $CURRENCY{$_[1]} or die "Unknown currency '".$_[1]."'\n" }, # euro dollar pound sFr
	"future|fut=i" => \$future,
	"tar=i" => sub { $tar1 = $tar2 = $_[1] },
	"tar1=i" => \$tar1,
	"tar2=i" => \$tar2,
	"join|j=i" => \$joinSessions,
	"version" => \&version,
);

if ($verbose) {
	print STDERR "Debug-mode: ".($debug ? "on" : "off")."\n";
	print STDERR "Value-separator        : $valueSeparator\n";
	print STDERR "Decimal-point separator: $decimalSeparator\n";
	print STDERR "Joining sessions which are max $joinSessions minutes apart\n" if $$joinSessions;
	print STDERR "Integer-mode: ".($outputInt ? "on" : "off")."\n";
}

# Generarte a setup-file for the Energy Logger 3500
if ($generateSetupFile) {
	my %TIMEFORMAT = ( 24=>2, 12=>1 );
	my %DATEFORMAT = ( YMD=>1, DMY=>2 );
	my %CURRENCY = ( "€"=>8, "\$"=>4, "sFr"=>2, "£"=>1 );
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time + 60*$future);
	my $tar = sprintf("%04d%04d", $tar1 % 10000, $tar2 % 10000);
	my @data = (
		184, 173, 242,
		$loggerId,
		$TIMEFORMAT{24},
		$DATEFORMAT{YMD},
		$hour, $min, $mon+1, $mday, 1900 + $year - $YEAROFFSET,
		$CURRENCY{$currency},
		split (//, $tar)
	);

	open OUT, ">setupel3.bin" or die "Error creating setup3.bin: $!\n";
	print OUT map(chr, @data);
	close OUT;

	if ($verbose) {
		print STDERR "Debug: Settingsfile, LoggerID: $loggerID\n";
		print STDERR "Debug: Settingsfile, #mins in future: $future\n";
		print STDERR "Debug: Settingsfile, Tarif1: $currency,$tar1\n";
		print STDERR "Debug: Settingsfile, Tarif2: $currency,$tar2\n";
	}

	exit(0);
}

my $csv = Text::CSV->new ({
		sep_char => $valueSeparator,
#		always_quote => 1
	});

# Output header
&$funcHeader;

my $dateTime = 0, $sample = 1;
for my $arg (sort {uc($a) cmp uc($b)} @ARGV) {
	my ($dir, $file) = $arg =~ /(?:(.*)\/)?(.+)/ or next;
	my ($loggerId, $dateInSec) = $file =~ /([[:xdigit:]])([[:xdigit:]]{7})\.bin/i or next;
	$loggerId = ord(uc($loggerId)) - ord 'A';
	$dateInSec = hex $dateInSec;
	print STDERR "$arg\t$dir\t$file\t$loggerId\t$dateInSec\n" if $debug;
	open DATA, "<", "$arg" or die "Can't open file '$arg': $!\n";
	my $data;
	my $status = read DATA,$data,16384;
	close DATA;

	if (substr($data, 0, 5) eq 'INFO:') {
		print STDERR "INFO: $arg\n" if $debug;
		# 02 55 3e
		# 05 3a 53
		# 05 32 38

		# 00 00 30
		# 00 02 3d
		# 00 04 23
		# 00 08 b1
		# 00 06 e9
		# 00 07 d5
		# 00 07 49
		# 00 08 15
		# 00 07 fe
		# 00 08 38

		# 04 b1
		# 09 60
		# 09 60
		# 09 60
		# 09 60
		# 09 60
		# 09 60
		# 09 60
		# 09 60
		# 09 60

		# 04 b0
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f
		# 09 5f

		# 00
		my $wh1 = unpack("N", "\0".substr $data,  5, 3);
		my $wh2 = unpack("N", "\0".substr $data, 14, 3);
		my $wh = $wh1 < 16777215 ? $wh1 > $wh2 ? $wh2 : $wh1 : 0;
		my $loggerId = unpack("C", substr $data, 84, 1);
		my $cost1 = bcd2perl(unpack("CCCC", substr $data, 85, 4)) / 1000.0;
		my $cost2 = bcd2perl(unpack("CCCC", substr $data, 89, 4)) / 1000.0;
		my ($hour, $min, $mday, $month, $year) = unpack("CCCCC", substr $data, 93, 5);
		$dateTime = timegm(0, $min, $hour, $mday, $month-1, $year+$YEAROFFSET-1900);
		if ($verbose) {
			print STDERR "Infofile-date : ".printDate($dateTime)."\n";
			printf STDERR "Infofile-cost1: %1.3f\n", $cost1;
			printf STDERR "Infofile-cost2: %1.3f\n", $cost2;
			print STDERR "Infofile-ID   : $loggerId\n";
			print STDERR "Infofile-Wh   : $wh\n";
		}
		next;
	}

	for (my $file=1; $data; $file=0,$sample++,$dateTime+=60) {
		if (substr($data, 0, 3) eq "\xe0\xc5\xea") {
			print STDERR "DATA: $arg\n" if $debug;
			my ($month, $mday, $year, $hour, $min) = unpack("CCCCC", substr $data, 3, 5);
			$dateTime = timegm(0, $min, $hour, $mday, $month-1, $year+$YEAROFFSET-1900);
			$sample = 1;
			substr $data, 0, 8, undef;
			if ($verbose) {
				print STDERR "Datafile-date : ".printDate($dateTime)."\n";
			}
		}

		my ($voltage, $current, $cosphi) = unpack("nnC", substr $data, 0, 5, undef);
		$voltage /= 10.0; # dV
		$current /= 1000.0; # mA
		$cosphi /= 100.0; # %
		&$funcRow($file, $sample, $dateTime, $voltage, $current, $cosphi);

		if (substr($data, 0, 4) eq "\xff\xff\xff\xff") {
			substr $data, 0, 4, undef;
			while (substr($data, 0, 1) eq "\xff") {
				substr $data, 0, 1, undef;
			}
			print STDERR "Debug: skipping FF\n" if $debug;
		}
	}
}

sub XD { return join(" ",map { sprintf "%02x", $_; } unpack("C*", $_[0])); }
sub bcd2perl { my $r=0; foreach (@_) { $r*=10; $r+=$_; } return $r; }

# Print floats using $decimalSeparator
sub fsprintf {
	my ($fmt, $val) = @_;
	$_ = sprintf($fmt, $val);
	eval "y/./$decimalSeparator/";
	return $_;
}

# Print row usi$decimalSeparatorng floats
sub printFloat {
	my ($file, $sample, $dateTime, $voltage, $current, $cosphi) = @_;
	my @columns;
	if ($outputStart) {
		push @columns, $file ? '*' : ' ';
		push @columns, sprintf ("%08d", $sample);
	}
	push @columns, printDate($dateTime);
	push @columns, fsprintf("%1.1f", $voltage);
	push @columns, fsprintf("%1.3f", $current);
	push @columns, fsprintf("%3.1f", $cosphi);
	push @columns, fsprintf("%1.1f", $voltage * $current) if $outputVa;
	push @columns, fsprintf("%1.1f", $voltage * $current * $cosphi) if $outputWatt;
	my $status = $csv->combine(@columns);
	my $line = $csv->string();
	print "$line\n";
}

# Print row using ints
sub printInt {
	my ($file, $sample, $dateTime, $voltage, $current, $cosphi) = @_;
	my $va = $voltage * 10.0 * $current * 1000.0;
	my $watt = $voltage * 10.0 * $current * 1000.0 * $cosphi * 100.0;
	my @columns;
	if ($outputStart) {
		push @columns, $file ? '*' : ' ';
		push @columns, sprintf ("%08d", $sample);
	}
	push @columns, printDate($dateTime);
	push @columns, sprintf("%d", $voltage * 10.0);
	push @columns, sprintf("%d", $current * 1000.0);
	push @columns, sprintf("%d", $cosphi * 100.0);
	push @columns, sprintf("%d", $va) if $outputVa;
	push @columns, sprintf("%d", $watt) if $outputWatt;
	my $status = $csv->combine(@columns);
	my $line = $csv->string();
	print "$line\n";
}

# Print header
sub printHeader {
	my @columns;
	if ($outputStart) {
		push @columns, "Set.Start";
		push @columns, "samplenr";
	}
	push @columns, "Date";
	push @columns, "Voltage";
	push @columns, "Current";
	push @columns, "Cos-phi";
	push @columns, "VA" if $outputVa;
	push @columns, "Watt" if $outputWatt;
	my $status = $csv->combine(@columns);
	my $line = $csv->string();
	print "$line\n";
}

# Print date in globaly specified format
sub printDate {
	my ($date) = @_;
	my ($dateSeparator, $dateTimeSeparator, $timeSeparator) = ('-',' ',':');
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($date);
	my $tmp;
	if ($dateformat eq 'ymd') { $tmp = sprintf("%04d%s%02d%s%02d", 1900+$year, $dateSeparator, 1+$mon, $dateSeparator, $mday); }
	elsif ($dateformat eq 'dmy') { $tmp = sprintf("%02d%s%02d%s%04d", $mday, $dateSeparator, 1+$mon, $dateSeparator, 1900+$year); }
	else { die "Unknown dateFormat '$dateformat'\n"; }
	$tmp .= $dateTimeSeparator;
	$tmp .= sprintf("%02d%s%02d", $hour, $timeSeparator, $min);
	return $tmp;
}

# Print version info
sub version {
	print <<"EOT";
EnergyLogger Version: $VERSION
(c) 2008, G.Noorlander  <gijs.noorlander at gmail.com>
(C) 2009-2010, Philipp Hahn  <pmhahn debian org>
Please feel free to contact, with suggestions or questions.

This program can decode the .bin log-files created by the Voltcraft
EnergyLogger 3500. The decoded data can be further processed with almost
any spreadsheet-program. e.g. Excel, Google Documents or Open Office

EOT
	die;
}

# Print help info
sub help {
	print <<"EOT";
Usage:
	EnergyLogger --set [--future=...] [--loggerid=...] [--tar[12]=...]
	EnergyLogger <options> <file.bin...>

Version: $VERSION
-int    : Output values in integers, to avoid regional issues in Excel
           Voltage is in 1/10th Volt
           Current is in mA (1/1000th Ampere
           Powerfactor is in percentage, devide by 100 before use
           Please note: accuracy of Watt and VA-column will decrease
-tab    : Use tab-character as separator between values
-sc     : Use ";"-character as separator between values
-dpc,dpd: Use "," resp. "." as decimal-separator
-va     : Display column with computed VA
-watt   : Display column with computed Watt
-values : Do not display legenda on 1st outputline
-d      : Debug-mode
-start  : Show when a new measurement was started
-dutch  : For use in Dutch Excel. Dateformat: D-M-Y. and options -sc -dpc 
-j [n]  : Join sessions which are less than n minutes apart. (default = 5)
-set    : Create a setup-file for the EnergyLogger 3500 (systemtime is used)
-id  [n]: Set logger-ID. Only used for setup-file
-cur [m]: Set currency in setupfile. [m]=euro,pound,dollar,sFr
-tar [n]: Set tarif1 & tarif2 to n. Only last 4 digits will be used.
-tar1[n]: Set Tarif1 in setupfile. (e.g. 1234 = 1,234)
-tar2[n]: Set Tarif2 in setupfile. (e.g.   56 = 0,056)
-fut [n]: Set time in setupfile n minutes in the future (default=1)
-v      : Verbose
-vv     : More verbose. Will also show decoded values
-version: Show program version
-? -h   : Show this help
EOT
	die;
}

# vim: set ts=2 sw=2 :
