#!/usr/bin/perl

# pulsecounter.pl
#
# Daemon to for pulsecounter hardware
#
# (c) Daniel Vindev├ąg, daniel@vindevag.com 
# http://www.nattsjo.se/temp/power-hw/



## Dependencies
#
# apt-get install libdevice-serialport-perl libconfig-simple-perl libdbi-perl libdbd-mysql-perl libsys-syslog-perl libwww-perl  libio-socket-timeout-perl

## Device
#
# To use /dev/pulsecounter instead of /dev/ttyACM[n], 
# the following udev rule must be applied
#
#ACTION=="add", SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="9136", SYMLINK+="pulsecounter", MODE="0666"

## Make firmware update accessable to userland
#
# atmega32u2 DFU
#ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff0", MODE="0666"

## Install timzone support in MySQL
#
# mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql -p



## Database layout 
#
# CREATE TABLE IF NOT EXISTS power1 (id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, time TIMESTAMP NULL, counter INTEGER unsigned, power MEDIUMINT unsigned, PRIMARY KEY(id));
# INSERT INTO power1 values (null, now(), 1, 1);

# DB sql strings
my $sql_insert = "INSERT INTO %s VALUES (null, FROM_UNIXTIME(%d), %d, %d)";
my $sql_select = "SELECT time, UNIX_TIMESTAMP(time), counter, power FROM %s ORDER BY -time LIMIT 1;";
my $sql_create = "CREATE TABLE IF NOT EXISTS %s (id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, time TIMESTAMP NULL, counter INTEGER UNSIGNED, power MEDIUMINT UNSIGNED, PRIMARY KEY(id));";



use FindBin;            	# locate this script
use lib "$FindBin::Bin";    # include script directory
use Device::SerialPort;
use Sys::Syslog qw(:standard :macros);
use POSIX qw(setsid strftime setuid setgid floor);
use Time::HiRes qw(usleep nanosleep);
use DBI;
use Config::Simple;
use LWP::UserAgent;
use HTTP::Request::Common qw(GET POST);
use IO::Socket;
use IO::Socket::Timeout;
use strict;
use File::Basename;
use File::Spec;



# global variables
my $COUNTER;            	    # Serial port object
my $rtc_set;            	    # Timestamp for last RTC sync
my $rtc_drift;          	    # Drift correction, subtract 1s every #
my $rtc_check_hours = 3;        # Adjust hardware clock every x hours
my $sock;                       # TCP/IP Socket

# Default values for global variables set in config file
#
my $counter_report 	 = 100;      # Hardware reports every x pulses
my $counter_overflow = 10000;    # Hardware overflows fter x pulses
my $db_report_wh     = 500;      # Add to db every x Wh
my $db_report_delta  = 10;       # Add to db if power incr/decr more than x %
my $mcu = "atmega32u2";          # Hardware MCU
my $hw_tz = 0;                   # Hardware timezone
my $db_max_power_check = 30000;  # Filter out illegal power values

# Device port and seconds until program quits if device disappear
my $port_name = "/dev/pulsecounter";	# Device filename
my $port_ttl = 60*10;

# TCP port number for status info and password to calibrate
my $tcp_port = "";
my $daemon_password = "";

# setuid and run as daemon
my $user = 0;
my $group = 0;
my $daemonize = 1;

# Save pid to file, leave blank to ignore
my @n = split("/",$0);
my $n = pop @n;
my ($program_name,$ext) = split('\.',$n);
undef @n;
undef $n;
undef $ext; 
my $pidfile = "/run/$program_name.pid";

# Database config, 
my $DBHOST  = "";
my $DBNAME  = "";
my $DBUSER  = "";
my $DBPASS  = "";
my $DBTABLE = "";

# Use remote url for database
my $webreport = "";
my $web_password = "";

# HD44780 character LCD, size, backligth intensity 0 - 255
my $lcd_lines = 0;
my $lcd_columns = 0;
my $lcd_bl = 50;


# functions
sub open_port;
sub check_port;
sub db_add;
sub log_and_die;
sub daemonize;
sub main;
sub read_config_file;
sub rtc_get;
$SIG{INT} = \&sigint;
$SIG{TERM} = \&sigterm;
$SIG{HUP} = \&sighup;



### begin

read_config_file;

# Connect to deamon and print status
if ($ARGV[0] =~ "-status") {
	$sock = new IO::Socket::INET(
		PeerAddr => "localhost",
		PeerPort => $tcp_port,
		Proto    => 'tcp',
		Blocking => 0,
	);
	$sock or die "Can't connect to local daemon!\n";
	sleep 1;
	print <$sock>;
	$sock->close();
	exit;
}

if (-e $pidfile && !system("ps `cat $pidfile` >/dev/null")) {
	if ($ARGV[0] eq "-set") {
		$daemonize = 0;
		my $set_counter = int($ARGV[1]);
		$sock = new IO::Socket::INET(
			PeerAddr => "localhost",
			PeerPort => $tcp_port,
			Proto    => 'tcp',
			Blocking => 0,
		);
		$sock or die "Can't connect to local daemon!\r\n";
		if ($daemon_password) {
			print $sock "CALIBRATE $set_counter $daemon_password";
			#print "CALIBRATE $set_major$set_minor $daemon_password\n";
			sleep 5;
			print <$sock>;
		} else {
			print "No password in config file!\n";
		}
		$sock->close();
		exit;
	}

	die "Another instance of $program_name is currently running, exiting!";
}

# Command line parameters
#
for (my $p=0; $p<5; $p++) {
	if ($ARGV[$p]) {
		if ($ARGV[$p] =~ "-h") {
			print "Power Meter Pulse Counter\n\n";
			print "   -status       Connect to deamon and show current values\n";
			print "   -set [Wh]     Calibrate hardware to X Wh.\n";
			print "   -createdb     Create database table.\n";
			print "   -flash FILE   Update firmware with FILE.\n";
			print "   -halt         Save hardware data and halt hardware.\n";
			print "   -start        Resume hardware.\n\n";
			exit;
		}

		if ($ARGV[$p] eq "-set") {
			$daemonize = 0;
			my $set_major = floor(int($ARGV[$p+1]) / 1000);
			my $set_minor = (int($ARGV[$p+1]) % 1000) * $counter_overflow / 1000;
			open_port();

			my $result = $COUNTER->input;
			$COUNTER->write("ATCNTMNSET $set_minor\r\n");
			sleep 1;
			$result = $COUNTER->input;
			$COUNTER->write("ATCNTMJSET $set_major\r\n");
			sleep 1;
			$result = $COUNTER->input;

			my $tz = int(strftime("%z", localtime())/100);
			my $TZ = strftime("%Z", localtime());
			$COUNTER->write("ATRTCTZSET $tz\r\n");
			sleep 1;
			$result = $COUNTER->input;
			print "Timezone set to: $tz ($TZ)\n";

			$COUNTER->write("ATCNTGET\r\n");
			sleep 1;
			$result = $COUNTER->input;
			print "(major, minor, f, report, overflow, time): $result";
			my ($major, $minor, $f, $report, $overflow, $time) = split(" ", $result);
			my $error = db_add(time(), ($major*1000)+(1000 * $minor/$counter_overflow), 0); 
			undef $COUNTER;
			if ($error == -1) {
				die "Database error!\n";
			}
			syslog(LOG_INFO, "Set Major = $major, Minor = $minor");
			exit;	
		}

		if ($ARGV[$p] eq "-createdb") {
			my $sql = sprintf($sql_create, $DBTABLE);
			printf "Creating table '%s' in database '%s' on host '%s'\n", $DBTABLE, $DBNAME, $DBHOST;
			syslog(LOG_INFO, $sql);
			my $db = DBI->connect("DBI:mysql:$DBNAME:$DBHOST", $DBUSER, $DBPASS, { PrintError => 0, RaiseError => 0 });
			if (!$db) { 
				undef $db; 
				log_and_die("Database error, exiting");
			} else {
				$DBI::result = $db->prepare($sql);
				$DBI::result->execute();
				$DBI::result->finish();
				$db->disconnect;
				undef $db; 
				exit;
			}
		}

		if ( ($ARGV[$p] eq "-halt") || ($ARGV[$p] eq "-flash")) {
			if (! -e $ARGV[$p+1]) {
				print "Error, file not found (" . $ARGV[$p+1] . ")\n";
				exit;
			}
			$daemonize = 0;
			open_port();
		    $COUNTER->write("ATV\r\n");
		    sleep 1;
			my $result = $COUNTER->input;
			$COUNTER->write("ATF\r\n");
			if (-e $ARGV[$p+1]) {
				print "Firmware upgrade...\n";
				sleep 2;
				system("dfu-programmer $mcu erase");
				system("dfu-programmer $mcu flash $ARGV[$p+1]");
				system("dfu-programmer $mcu start");
			}
			exit;
		}

		if ($ARGV[$p] eq "-start") {
			system("dfu-programmer $mcu start");
			exit;
		}

	}
} # CLI parameters

openlog($program_name, "ndelay,pid", "local0");
if ( (!$webreport) && (!$DBHOST  || !$DBNAME  || !$DBUSER || !$DBPASS || !$DBTABLE) ) { log_and_die "Database config missing, exiting!"; }

if ($daemonize) { daemonize; }
check_port();
if (not defined $COUNTER) { open_port(); }
main;

### end



# Signal handlers
#
sub sigint {
	undef $COUNTER;
	undef $sock;
	syslog(LOG_INFO, "SIGINT exiting!");
	closelog();
	exit;
};

sub sigterm {
	undef $COUNTER;
	undef $sock;
    syslog(LOG_INFO, "SIGTERM exiting!");
	closelog();
	exit;
};

sub sighup {
	syslog(LOG_INFO, "SIGHUP reloading!");
	print "SIGHUP reloading!\n";
	undef $COUNTER;
	undef $sock;
	sleep 1;
	read_config_file;
	open_port();
};


sub log_and_die {
	syslog(LOG_INFO, $_[0]);
	closelog();
	print strftime("%Y%m%d %H:%M:%S", localtime()) . ": " . $_[0] . "\n";
	die;
}



sub daemonize {
	chdir "/" or log_and_die("Can't chdir to /: $!");
	defined(my $pid = fork) or log_and_die("Can't fork: $!");
	if ($pid) {
        syslog(LOG_INFO, "$program_name daemonized as $pid");
        if ($pidfile) {
            open PIDFILE, ">", $pidfile or log_and_die($!);
            print PIDFILE "$pid\n";
            close PIDFILE;
			chown($user, $group, $pidfile);
        }
        closelog();
        exit;
	}
	setsid() or log_and_die("Can't start a new session: $!");
	umask 0;
	open STDOUT, ">>/dev/null" or log_and_die("Can't write to /dev/null: $!");
	open STDERR, ">>/dev/null" or log_and_die("Can't write to /dev/null: $!");
	open STDIN,  ">>/dev/null" or log_and_die("Can't read /dev/null: $!");
	if ($group) { setgid($group); }
	if ($user)  { setuid($user);  }
	syslog(LOG_INFO, "Running as daemon, UID ". $< . ", GID " . $( );
}




# Main program
#
sub main {
	my $last_power = 0;				# Last power from hw
	my $last_hw_timestamp = 0;		# Last timestamp from hw
	my $last_count = 0;				# Last counter from hw
	my $prev_db = 0;				# Last count in db
	my $prev_db_timestamp = 0;		# Last timestamp in db
	my $db_count = 0;				# Current count in db
	my $db_power = 0;				# Current power in db
	my $hw_connected_counter = 0;    		# Counter for checking if hw is connected
	my $rtc_check_counter = 0;		# Counter for checking hw RTC

	# Get last value from db
	if ($DBHOST) {
		my $sql = sprintf($sql_select, $DBTABLE);
		my $db = DBI->connect("DBI:mysql:$DBNAME:$DBHOST", $DBUSER, $DBPASS, { PrintError => 0, RaiseError => 0 });
   		if (!$db) { 
			undef $db; 
			syslog(LOG_INFO, $sql);
			log_and_die("Database error, exiting");
		} else {
			$DBI::result = $db->prepare($sql);
			$DBI::result->execute();
			my ($db_old_date, $db_old_timestamp, $db_old_count, $db_old_power) = $DBI::result->fetchrow_array;
			if ($db_old_timestamp) {
				if (!$daemonize) {
					print "DB: $db_old_date: $db_old_timestamp, " . $db_old_count/1000 . " kWh, $db_old_power W\n";
				}
		        syslog(LOG_INFO, "DB: $db_old_date: " . $db_old_count/1000 . " KWh, $db_old_power W");
				$prev_db = $db_old_count;
				$db_power = $db_old_power;
				$prev_db_timestamp = $db_old_timestamp;
			}
			$DBI::result->finish();
			$db->disconnect;
			undef $db; 
		}
	} else { 
		if ($webreport) {
			my $ua = LWP::UserAgent->new;
			my $req = GET $webreport;
			my $res = $ua->request($req);
			if ($res->is_success) {
				my ($db_old_timestamp, $db_old_count, $db_old_power, $db_old_date, $db_old_time) = split(/ /, $res->content);
				$db_old_time  =~ s/\R//g;
				$db_old_date .= " " . $db_old_time;		
				if ($db_old_timestamp) {
					if (!$daemonize) {
						print "DB: $db_old_date: $db_old_timestamp, " . $db_old_count/1000 . " Wh, $db_old_power W\n";
					}
			        syslog(LOG_INFO, "DB: $db_old_date: " . $db_old_count/1000 . " KWh, $db_old_power W");
					$prev_db = $db_old_count;
					$db_power = $db_old_power;
					$prev_db_timestamp = $db_old_timestamp;
				}
			}
			undef $ua;
			undef $req;
			undef $res;
		} # if webreport
	} # if dbhost



	# Main program loop
	#
	while (1) {
		sleep 1;
		if ($hw_connected_counter++ == 5) {     # Check port every 5s
			check_port();
			$hw_connected_counter = 0;
		}		
		if ( $rtc_check_counter++ == ($rtc_check_hours*3600) ) {   # Check and adjust RTC every x hour
			rtc_get();
			$rtc_check_counter = 0;
		}

		# Read input                                                            
		my @lines;
		my $input = "";
		if (defined($COUNTER)) { $input = $COUNTER->input; } # else { undef $COUNTER; }
		if ($input) {
			while (!($input =~ m/\n$/)) {
				usleep(100000);
				$input .= $COUNTER->input;
			}
		}
		if ($input) { @lines = split /\r\n/, $input; }

		# Process input
		foreach my $result (@lines) {
		if ($result =~ "Counter") {
			my ($c, $major, $minor, $hertz, $hw_timestamp) = split(" ", $result);
			last if ($hw_timestamp == $last_hw_timestamp); 	# Two reports the same second, ignore to avoid div 0
			if (!$daemonize) { 
				print strftime("%y-%m-%d %H:%M:%S", localtime(int($hw_timestamp))) . ": ";
				print "major: $major, minor: $minor, f: " . $hertz . "Hz";	
			}
			# KWh -> Ws
			my $power =  ( (60*60*1000) / ( $counter_overflow/$counter_report) ) / ($hw_timestamp-$last_hw_timestamp);

			if ($last_hw_timestamp) { 
				my $p_delta = int(($power-$last_power)*100/$power);
				my $p_db_delta = 0;
				if ($db_power) {
					$p_db_delta = int(($power-$db_power)*100/$db_power);
				}
				if (!$last_power) { $p_delta = 0; }
				if (!$daemonize) { 
					print ", P: " . int($power) . "W, delta P: " . $p_delta . "% ($p_db_delta%)\n";
				}
				$db_count = int( ($major+($minor/$counter_overflow))*1000 );
				if ( (abs($p_db_delta) > $db_report_delta) || ( !($db_count % $db_report_wh) ) ) {
					if ( (abs($p_db_delta) > $db_report_delta) && $prev_db_timestamp) {
						if ($last_hw_timestamp != $prev_db_timestamp) {
							my $db_old_power = int ( ($last_count-$prev_db)*3600 / (($last_hw_timestamp-$prev_db_timestamp)) );
							if ($db_old_power>$db_max_power_check) { $db_old_power = 0; } 	# Filter out illegal power values
							db_add($last_hw_timestamp, $last_count, $db_old_power); 
						}
						$prev_db_timestamp = $last_hw_timestamp;
						$prev_db = $last_count;
					}

					$db_power = int ( ($db_count - $prev_db)*3600 / (($hw_timestamp - $prev_db_timestamp)) );
					if (!($prev_db_timestamp)) { $db_power = 0; } 			# first db entry
					if ($db_power>$db_max_power_check) { $db_power = 0; } 	# Filter out illegal power values
					if (!db_add($hw_timestamp, $db_count, $db_power)) { 	# check for db error
						$prev_db = $db_count;
						$prev_db_timestamp = $hw_timestamp;
					}
				}
			} else {
				if (!$daemonize) { print "\n"; }
			}
			$last_count = int( ($major+($minor/$counter_overflow))*1000 );
			$last_hw_timestamp = $hw_timestamp;
			$last_power = $power;
		} # if counter
		} # foreach

		# Print status over TCP/IP
		if ( defined $sock && (my $connection = $sock->accept)) {
			my $input = <$connection>;
			if ($input =~ m/CALIBRATE\s(\d+)\s(\w+)/g) {
				my $new_counter = $1;
				my $pw = $2;
				if ( ($new_counter >0) && ($pw eq $daemon_password) ) {
					my $new_major = int($new_counter / 1000);
					my $new_minor = $new_counter % 1000 * $counter_overflow / 1000;
					print $connection "Calibrate to: " . $new_counter/1000 . " kWh\n";

					$COUNTER->write("ATCNTMNSET $new_minor\r\n");
					sleep 1;
					my $result = $COUNTER->input;
					$COUNTER->write("ATCNTMJSET $new_major\r\n");
					sleep 1;

					$COUNTER->write("ATRTCTZSET " . int(strftime("%z", localtime())/100) . "\r\n");
					sleep 1;
					$result = $COUNTER->input;

					$COUNTER->write("ATCNTGET\r\n");
					sleep 1;
					$result = $COUNTER->input;
					print "(major, minor, f, report, overflow, time): $result";
					my ($major, $minor, $f, $report, $overflow, $time) = split(" ", $result);
					if ($time) {
						my $error = db_add($time, ($major*1000)+(1000 * $minor/$counter_overflow), 0); 
						if ($error == -1) {
					        syslog(LOG_INFO, "Database error");
						}
				        syslog(LOG_INFO, "Set Major = $major, Minor = $minor, (" . ($major*1000)+(1000 * $minor/$counter_overflow) . " kWh)" );
						$last_hw_timestamp = $time;
						$last_count = $new_counter;
						$last_power = 0;
						$prev_db_timestamp = $time;
						$prev_db = $new_counter; 
						$db_power = 0;
					} else {
						print $connection "Error no reply from hardware\n";
					}
				} else {
					print $connection "Wrong password or zero counter value.\n";
				}
			} else {
				if ($input) {
					print $connection "Error, input format: CALIBRATE [meter in Ws] [password]\n";
				}
			}
			printf $connection "%d %d %.0f \r\n", $last_hw_timestamp, $last_count, $last_power;
			printf $connection "%d %d %.0f \r\n", $prev_db_timestamp, $prev_db, $db_power;

			print $connection "HW Timestamp: $last_hw_timestamp (";
			print $connection strftime("%Y-%m-%d %H:%M:%S", localtime($last_hw_timestamp)); 
			print $connection ") " . (time() - $last_hw_timestamp) . " seconds ago\r\n";
			print $connection "HW Meter: " . ($last_count/1000) . " KWh\r\n";
			printf $connection "HW Power: %.0f W\r\n", $last_power;
			printf $connection "HW Overflow: %d, Report: %d, (%d Wh)\r\n", 
				$counter_overflow, $counter_report, $counter_report / ($counter_overflow / 1000 );

			print $connection "DB Timestamp: $prev_db_timestamp (";
			print $connection strftime("%Y-%m-%d %H:%M:%S", localtime($prev_db_timestamp)); 
			print $connection ") " . (time() - $prev_db_timestamp) . " seconds ago\r\n";
			print $connection "DB Meter: " . ($prev_db/1000) . " KWh\r\n";
			printf $connection "DB Power: %.0f W\r\n", $db_power;
			printf $connection "DB Report: %d Wh, %d%\r\n", $db_report_wh, $db_report_delta; 
			$connection->close();
		}
	} # while
} # main




# Add entry to database.
#
sub db_add {
	my $time = shift(@_);
	my $count = shift(@_);
	my $power = shift(@_);
	if ($webreport) {
	    my $ua = LWP::UserAgent->new;
	    my $req = POST $webreport, ["pw" => "$web_password", "time" => $time, "count" => $count, "power" => $power];
	    $ua->request($req);
		undef $ua;
		undef $req;
	}
	if ($DBHOST) {
		my $sql = sprintf($sql_insert, $DBTABLE, $time, $count, $power);
		if (!$daemonize) { print $sql . "\n"; }
		my $db = DBI->connect("DBI:mysql:$DBNAME:$DBHOST", $DBUSER, $DBPASS, { PrintError => 0, RaiseError => 0 });
		if (!$db) { 
			undef $db; 
			if (!$daemonize) { print "Database error\n"; }
			syslog(LOG_INFO, "Database error");
			syslog(LOG_INFO, $sql);
			return -1;
	    }
		$DBI::result = $db->prepare($sql);
		$DBI::result->execute();
		$DBI::result->finish();
		$db->disconnect;
		undef $db; 
	}
}



# Check if serial port exists, open or wait until it reappears
#
sub check_port {
	if ( ! (-e $port_name) ) {
		syslog(LOG_INFO, "Port disappeared");
		if (!$daemonize) { print strftime("%Y-%m-%d %H:%M:%S", localtime(time())) . ": Port disappeared\n"; }
		if (defined $COUNTER) { undef $COUNTER; }
		undef $sock;
		my $ttl_count = $port_ttl;
		while ($ttl_count && ! (-e $port_name) ) {
			sleep 1;
			if ( (-e $port_name) ) {
				syslog(LOG_INFO, "Port reappered");
				if (!$daemonize) { print strftime("%Y-%m-%d %H:%M:%S", localtime(time())) . ": Port reappered\n"; }
				sleep 1;
				open_port();
			}
			$ttl_count--;
		}
	}
	if ( ! (-e $port_name) ) {
		syslog(LOG_INFO, "Port gone to long exiting!");
		if (!$daemonize) { print strftime("%Y-%m-%d %H:%M:%S", localtime(time())) . ": Port gone to long exiting!\n"; }
		exit (-1);
	}
}



# Open serial port
#
sub open_port {
	my $result;
	my $str;
	$COUNTER = new Device::SerialPort($port_name) || log_and_die("Can't open $port_name: $!\n");
	$COUNTER->baudrate(115200);
	$COUNTER->parity("none");
	$COUNTER->databits(8);
	$COUNTER->stopbits(1);
	$COUNTER->handshake("none");

	my $full_portname = File::Spec->rel2abs(dirname($port_name)) . "/" . readlink($port_name);

	if (readlink($port_name)) {
		$str = "Port: " . $port_name . " ($full_portname) opened";
	} else {
		$str = "Port: $port_name opened";
	}
	if (!$daemonize) { print "$str\n"; }
	syslog(LOG_INFO, $str);
	sleep 1;
	$COUNTER->write("ATV\r\n");
	sleep 1;
	$result = $COUNTER->input;
	$COUNTER->write("ATV\r\n");
	sleep 1;
	$result = $COUNTER->input;
	my $ver = 0;
	($ver, $mcu) = split(" ", $result);
	if (!int($ver)) { log_and_die("Unexpected hardware version $result\n"); }
	if ($result) {
		$str = "Version: " . int($result) . " (" . strftime("%Y%m%d %H:%M:%S", localtime(int($result))) . ")";
		if (!$daemonize) { print "$str\n"; }
		syslog(LOG_INFO, $str);
	}

	$COUNTER->write("ATLCDSIZE\r\n");
	sleep 1;
	$result = $COUNTER->input;
	($lcd_lines, $lcd_columns) = split(" ", $result);
	if ($lcd_lines) {
		$str = "LCD: $lcd_lines x $lcd_columns, MCU: $mcu";
		$COUNTER->write("ATLCDPWMSET $lcd_bl\r\n");
		sleep 1;
		$result = $COUNTER->input;
	} else {
		$str = "LCD: no, MCU: $mcu";
	}
	if (!$daemonize) { print "$str\n"; }
	syslog(LOG_INFO, $str);

	$COUNTER->write("ATRTCOGET\r\n");
	sleep 1;
	$result = $COUNTER->input;
	$rtc_drift = int($result);

	$COUNTER->write("ATRTCGET\r\n");
	$rtc_set = time();
	sleep 1;
	$result = $COUNTER->input;
	my $rtc = int($result); 
	if ($rtc) {
		my $diff = $rtc-$rtc_set;
		$str = "RTC: " . strftime("%Y-%m-%d %H:%M:%S", localtime($rtc)) . " ($diff s), drift = $rtc_drift";
		if (!$daemonize) { print "$str\n"; }
		syslog(LOG_INFO, $str);
		if ($diff) {
			$rtc_set = time();
			$COUNTER->write("ATRTCSET $rtc_set\r\n");
			sleep 1;
			$result = $COUNTER->input;

			my $tz = int(strftime("%z", localtime())/100);
			my $TZ = strftime("%Z", localtime());
			$COUNTER->write("ATRTCTZSET $tz\r\n");
			sleep 1;
			$result = $COUNTER->input;
			$str = "Timezone set to: $tz ($TZ)";
			if (!$daemonize) { print "$str\n"; }
			syslog(LOG_INFO, $str);
			$hw_tz = $tz;
		}
	}

	$COUNTER->write("ATCNTGET\r\n");
	sleep 1;
	$result = $COUNTER->input;
	my ($major, $minor, $hertz, $report, $overflow, $hw_timestamp) = split(" ", $result);
	$str = "Hardware: " . ($major+($minor/$counter_overflow)) . " KWh, Overflow: $counter_overflow, Report: $counter_report (" .
		$counter_report / ($counter_overflow / 1000 ) . " Wh)";
	if (!$daemonize) {
		print $str . "\n";
	}
	syslog(LOG_INFO, $str);

	$COUNTER->write("ATCNTOVERFLOW " . $counter_overflow . "\r\n");
	sleep 1;
	$result = $COUNTER->input;
	$COUNTER->write("ATCNTREPORT " .$counter_report . "\r\n");
	sleep 1;
	$result = $COUNTER->input;

	$str = "DB Report: $db_report_wh Wh, $db_report_delta %";
	if (!$daemonize) {
		print $str . "\n";
	}
	syslog(LOG_INFO, $str);

	# TCP port
	if ($tcp_port) {
		$sock = new IO::Socket::INET ( 
			LocalHost => '', 
			LocalPort => $tcp_port, 
			Proto => 'tcp', 
			Listen => 1, 
#			Reuse => 1, 
#			Timeout  => 1,
#			Blocking => 0,
		); 
		log_and_die "Could not create socket for port $tcp_port: \n" unless $sock;
		IO::Socket::Timeout->enable_timeouts_on($sock);
		$sock->read_timeout(0.1);
	}

}



# Check and adjust hardware clock
#
sub rtc_get() {
    my $result;
    my $line = "";
	my $hour = strftime("%H", localtime());
	# set timezone
	my $tz = int(strftime("%z", localtime())/100);
	my $TZ = strftime("%Z", localtime());
	if ($hw_tz != $tz) {
		$COUNTER->write("ATRTCTZSET $tz\r\n");
		sleep 1;
		$result = $COUNTER->input;
		syslog(LOG_INFO, "Timezone set to: $tz ($TZ)");
		$hw_tz = $tz;
	}

    $COUNTER->write("ATRTCGET\r\n");
    my $rtc_local = time();
    sleep 1;
	$result = $COUNTER->input;
	my $rtc = int($result);
	if ($rtc) {
		my $diff = $rtc-$rtc_local;
		$line = "RTC: " . strftime("%H:%M:%S", localtime($rtc)) . " ($diff s)";
		# Adjust drift                                                      
		if ($diff != 0) {
			my $drift = int ( ($rtc_local-$rtc_set)/$diff );
			if ($rtc_drift) {
				$drift = int( abs (($rtc_drift*$drift) / ($rtc_drift+$drift)) );
			}
			if ( (abs($diff) >= 10) || ($rtc_drift && abs($diff) >= 5)) {
				if (abs($diff) < 30) {
					$COUNTER->write("ATRTCOSET $drift\r\n");
					sleep 1;
					$result = $COUNTER->input;
					$line .= ", set drift = $drift";
					$rtc_drift = $drift;
				} else {	# Only set clock
					$line .= ", set RTC";
				}
				$rtc_set = time();
				$COUNTER->write("ATRTCSET $rtc_set\r\n");
				sleep 1;
				$result = $COUNTER->input;
			} else {
				$line .= ", drift: $drift";
			}
		}
		syslog(LOG_INFO, $line);
	}
}



# Read config file
#
sub read_config_file {
	my $configfile;
		if (@ARGV && -e $ARGV[0]) {
			$configfile = $ARGV[0];
		} else {
			if (-e "$FindBin::Bin/$program_name.conf") {
				$configfile = "$FindBin::Bin/$program_name.conf";
			} else {
				if (-e "~/.$program_name") {
					$configfile = "~/.$program_name";
				} else {
					if (-e "/etc/$program_name.conf") {
					$configfile = "/etc/$program_name.conf";
				}
			}
		}
	}

	if (!$configfile) { die "Missing config file, supply config file as start parameter or use $program_name.conf in the same directory or ~/.$program_name or /etc/$program_name.conf!"; 
	}
	my $cfg = new Config::Simple();
	$cfg->read($configfile);
	if ($cfg->param('port_name') ne "") { $port_name = $cfg->param('port_name'); }
	if ($cfg->param('port_ttl') ne "")  { $port_ttl  = $cfg->param('port_ttl'); }
	if ($cfg->param('pidfile') ne "")   { $pidfile  = $cfg->param('pidfile'); }
	if ($cfg->param('daemonize') ne "") { $daemonize  = $cfg->param('daemonize'); }
	if ($cfg->param('user') ne "")      { $user  = $cfg->param('user'); }
	if ($cfg->param('group') ne "")     { $group = $cfg->param('group'); }
	if ($cfg->param('tcp_port') ne "")  { $tcp_port = $cfg->param('tcp_port'); }

	if ($cfg->param('DBHOST'))  { $DBHOST  = $cfg->param('DBHOST'); }
	if ($cfg->param('DBNAME'))  { $DBNAME  = $cfg->param('DBNAME'); }
	if ($cfg->param('DBUSER'))  { $DBUSER  = $cfg->param('DBUSER'); }
	if ($cfg->param('DBPASS'))  { $DBPASS  = $cfg->param('DBPASS'); }
	if ($cfg->param('DBTABLE')) { $DBTABLE = $cfg->param('DBTABLE'); }

	if ($cfg->param('password'))  { $web_password = $cfg->param('password'); }	
	if ($cfg->param('webreport')) { $webreport = $cfg->param('webreport'); }

	if ($cfg->param('password'))  { $daemon_password = $cfg->param('password'); }	

	if ($cfg->param('counter_report') ne "")     { $counter_report   = int($cfg->param('counter_report')); }
	if ($cfg->param('counter_overflow') ne "")   { $counter_overflow = int($cfg->param('counter_overflow')); }
	if ($cfg->param('db_report_wh') ne "")       { $db_report_wh     = int($cfg->param('db_report_wh')); }
	if ($cfg->param('db_report_delta') ne "")    { $db_report_delta =  int($cfg->param('db_report_delta')); }
	if ($cfg->param('db_max_power_check') ne "") { $db_max_power_check =  int($cfg->param('db_max_power_check')); }

	if ($cfg->param('lcd_bl') ne "") { $lcd_bl =  int($cfg->param('lcd_bl')); }

    undef $cfg;
}