#! /usr/bin/perl -w

use strict;
use warnings FATAL => qw( all );

use JSON;
use Data::Dumper;

sub FOVMSLogin($$$);
sub OVMSLogout($);
sub DoOVMSCommand($$$);

sub LocalDateString(;$);

# configure for your account and vehicle
my $username = 'your_ovms_username';
my $password = 'your_ovms_password';
my $vehicle = 'your_vehicle_identifier';

my $fDriveLog = 0;
my $fChargeLog = 0;
my $fServerLog = 0;

foreach (@ARGV)
{
	if ($_ eq 'drive' || $_ eq 'd')
	{
		$fDriveLog = 1;
	}
	elsif ($_ eq 'charge' || $_ eq 'c')
	{
		$fChargeLog = 1;
	}
	elsif ($_ eq 'server' || $_ eq 's')
	{
		$fServerLog = 1;
	}
	else
	{
		die "unknown argument: $_";
	}
}

my %mp_mode_name = 
(
	0 => 'Standard',
	1 => 'Storage',
	3 => 'Range',
	4 => 'Performance'
);

my $filenameCookieJar = '~/Downloads/ovms-cookie';

my $reply;
my $cmd;
my $blob;

if (!FOVMSLogin($filenameCookieJar, $username, $password))
{
	die 'ERROR: login failed';
}

print "LOGIN succeeded\n";
print "\n";

if (0) # not yet implemented
{
	# connect to the vehicle to get current data updates
	$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'vehicle/' . $vehicle);
	print "CONNECT:\n";
	print $reply . "\n";
}

# get the charge record
$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'charge/' . $vehicle);

# decode the reply
$blob = decode_json($reply);

print "Charge Status:\n";
print "\n";

# dump out everything in the reply
# print Dumper($blob);
# print "\n";

# dump out selected data fields
print 'Charge State: ' . $blob->{'chargestate'} . "\n";
print 'Charge Mode: ' . $blob->{'mode'} . "\n";
print 'Ambient Temperature: ' . $blob->{'temperature_ambient'} . 'C, ' . sprintf('%.0fF', 32 + $blob->{'temperature_ambient'} * 9 / 5) . "\n";
print 'Battery Temperature: ' . $blob->{'temperature_battery'} . 'C, ' . sprintf('%.0fF', 32 + $blob->{'temperature_battery'} * 9 / 5) . "\n";
print 'PEM Temperature: ' . $blob->{'temperature_pem'} . 'C, ' . sprintf('%.0fF', 32 + $blob->{'temperature_pem'} * 9 / 5) . "\n";
print 'Motor Temperature: ' . $blob->{'temperature_motor'} . 'C, ' . sprintf('%.0fF', 32 + $blob->{'temperature_motor'} * 9 / 5) . "\n";
print 'Line Voltage: ' . $blob->{'linevoltage'} . "V\n";
print 'Charge Current: ' . ($blob->{'chargecurrent'} == 127 ? '0' : $blob->{'chargecurrent'}) . ' of ' . $blob->{'chargelimit'} . "A\n";
print 'SOC: ' . $blob->{'soc'} . '%, ' . $blob->{'idealrange'} . ' I' . $blob->{'units'} . "\n";
print "\n";

if (0) # not yet implemented
{
	# disconnect from the vehicle
	print "\n";
	$reply = DoOVMSCommand($filenameCookieJar, 'DELETE', 'vehicle/' . $vehicle);
	print "DISCONNECT:\n";
	print $reply . "\n";
}

if (0)
{
	# get the historical data summary
	$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'historical/' . $vehicle);

	# decode the reply
	$blob = decode_json($reply);

	print "Historical Data Summary:\n";

	# dump out everything in the reply
	print Dumper($blob);
	print "\n";
}

my %mp_date_entry = ();

if ($fDriveLog)
{
	# get the historical drive records
	$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'historical/' . $vehicle . '/*-Log-Drive');

	# decode the reply
	$blob = decode_json($reply);

	# dump out everything in the reply
#	print Dumper($blob);
#	print "\n";
	
	foreach my $rec (@{$blob})
	{
		my $entry = ();

		# start time, duration, drive mode, latitudeStart, longitudeStart, latitudeEnd, longitudeEnd, distance, pctStart, ixEnd, pctEnd, ixEnd
		($entry->{'dateStart'}, $entry->{'secDuration'}, $entry->{'mode'}, $entry->{'latStart'}, $entry->{'longStart'}, $entry->{'latEnd'}, $entry->{'longEnd'}, $entry->{'miDist'}, $entry->{'pctStart'}, $entry->{'imStart'}, $entry->{'pctEnd'}, $entry->{'imEnd'})
		  = split(',', $rec->{'h_data'});
		
		$entry->{'type'} = 'D';
		$mp_date_entry{$entry->{'dateStart'}} = $entry;
	}
}

if ($fChargeLog)
{
	# get the historical charge records
	$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'historical/' . $vehicle . '/*-Log-Charge');

	# decode the reply
	$blob = decode_json($reply);

	# dump out everything in the reply
#	print Dumper($blob);
#	print "\n";
	
	foreach my $rec (@{$blob})
	{
		my $entry = ();

		($entry->{'dateStart'}, $entry->{'secDuration'}, $entry->{'mode'}, $entry->{'lat'}, $entry->{'long'}, $entry->{'volts'}, $entry->{'amps'}, $entry->{'result'}, $entry->{'pctStart'}, $entry->{'imStart'}, $entry->{'pctEnd'}, $entry->{'imEnd'})
		  = split(',', $rec->{'h_data'});
	
		$entry->{'type'} = 'C';
		$mp_date_entry{$entry->{'dateStart'}} = $entry;
	}
}

print "Vehicle History:\n";
print "\n";
foreach my $date (sort keys %mp_date_entry)
{
	my $entry = $mp_date_entry{$date};
	
	my $mode = $entry->{'mode'};
	if ($mp_mode_name{$mode})
	{
		$mode = $mp_mode_name{$mode};
	}

	if ($entry->{'type'} eq 'D')
	{
		my $duration = sprintf('%d:%02d:%02d', $entry->{'secDuration'} / 3600, ($entry->{'secDuration'} / 60) % 60, $entry->{'secDuration'} % 60);
		print 'D ' . LocalDateString($entry->{'dateStart'}) . ' ' . LocalDateString($entry->{'dateStart'} + $entry->{'secDuration'}) . " ($duration): $entry->{'miDist'} mi, $entry->{'pctStart'}% -> $entry->{'pctEnd'}%, $entry->{'imStart'} -> $entry->{'imEnd'} ideal miles, $mode\n";
	}
	elsif ($entry->{'type'} eq 'C')
	{
		my $duration = sprintf('%d:%02d:%02d', $entry->{'secDuration'} / 3600, ($entry->{'secDuration'} / 60) % 60, $entry->{'secDuration'} % 60);
		my $chargePower = $entry->{'volts'} . 'V/' . $entry->{'amps'} . 'A';

		print 'C ' . LocalDateString($entry->{'dateStart'}) . ' ' . LocalDateString($entry->{'dateStart'} + $entry->{'secDuration'}) . " ($duration): $chargePower, $entry->{'pctStart'}% -> $entry->{'pctEnd'}%, $entry->{'imStart'} -> $entry->{'imEnd'} ideal miles, $mode\n";
	}
}

my @afield_header =
(
	'number',
	'letterC',
	'rx',
	'msg',
	'record_type'
);

my @afield_S =
(
	'SOC',
	'mi/km',
	'volts',
	'amps',
	'charge_state',
	'charge_mode',
	'iRange',
	'eRange',
	'charge_limit',
	'charge_duration',
	'charge_b4',
	'kwh_charge',
	'charge_substate_n',
	'charge_state_n',
	'charge_mode_n',
	'timer_mode',
	'timer_start',
	'timer_1stale',
	'CAC',
	'min_ctp_to_full',
	'min_ctp_to_limit',
	'acc_range_limit',
	'acc_soc_limit',
	'cooling',
	'cooldown_tbattery',
	'cooldown_timelimit',
	'acc_charge_estimate'
);

my @afield_D =
(
	'doors1',
	'doors2',
	'lockstate',
	'temp_pem',
	'temp_motor',
	'temp_battery',
	'trip',
	'odometer',
	'speed',
	'park',
	'temp_ambient',
	'doors3',
	'state_temps',
	'state_ambient',
	'acc_battery',
	'doors4',
	'acc_battery_ref',
	'doors5'
);

my @afield_L =
(
	'latitude',
	'longitude',
	'direction',
	'elevation', # called altitude in the OVMS code
	'gps-lock',
	'gps-stale'
);

my @afield_W =
(
	'f1p',
	'f1t',
	'r1p',
	'r1t',
	'f2p',
	'f2t',
	'r2p',
	'r2t',
);

if ($fServerLog)
{
	print "Server Logs:\n";
	print "\n";

	# get the recent server log records
	$reply = DoOVMSCommand($filenameCookieJar, 'GET', 'historical/' . $vehicle . '/*-OVM-ServerLogs');

	# decode the reply
	$blob = decode_json($reply);

	# dump out everything in the reply
#	print Dumper($blob);
#	print "\n";
	
	foreach my $rec (@{$blob})
	{
		my $entry = ();
		
		$entry->{'timestamp'} = $rec->{'h_timestamp'};

		my $strData = $rec->{'h_data'};
		$strData =~ s/\s+/,/g;
		my @data = split(',', $strData);
		
		foreach my $field (@afield_header)
		{
			$entry->{$field} = shift @data;
		}

		next if ($entry->{'record_type'} eq 'a'); # skip the ping records
		next if ($entry->{'record_type'} eq 'h'); # skip the log records

		print join(' ',  $entry->{'timestamp'}, $entry->{'record_type'});
		if ($entry->{'record_type'} eq 'S')
		{
			foreach my $field (@afield_S)
			{
				$entry->{$field} = shift @data;
			}
			print ' Status ', join(', ',
				$entry->{'SOC'} . '%',
				$entry->{'iRange'} . ' i' . $entry->{'mi/km'},
				$entry->{'volts'} . 'V/' . $entry->{'amps'} . 'A',
				$entry->{'acc_charge_estimate'} . ' min CTP',
				$entry->{'CAC'} . ' Ah CAC'
				);
		}
		elsif ($entry->{'record_type'} eq 'D')
		{
			foreach my $field (@afield_D)
			{
				$entry->{$field} = shift @data;
			}
			print ' Drive ', join(', ',
				$entry->{'temp_pem'} . 'C PEM',
				$entry->{'temp_motor'} . 'C Motor',
				$entry->{'temp_battery'} . 'C Battery',
				$entry->{'temp_ambient'} . 'C Ambient',
				$entry->{'trip'} / 10.0 . ' trip',
				$entry->{'odometer'} / 10.0 . ' odo',
				$entry->{'speed'} . ' speed',
				$entry->{'acc_battery'} . 'V acc batt'
				);
		}
		elsif ($entry->{'record_type'} eq 'P')
		{
			my $msg = shift @data;
			if ($msg =~ /(.)(.*)/)
			{
				if ($1 eq 'E')
				{
					# error code
					print ' ERROR ', join(', ',
						'vehicle type ' . $2,
						'error code ' . $data[0],
						'error data ' . $data[1]
						);
				}
				else
				{
					# alert
					print ' ALERT ', join(', ',
						$2,
						@data
						);
				}
			}
		}
		elsif ($entry->{'record_type'} eq 'L')
		{
			foreach my $field (@afield_L)
			{
				$entry->{$field} = shift @data;
			}
			print ' Location ', join(', ',
				"latitude $entry->{'latitude'}",
				"longitude $entry->{'longitude'}",
				"direction $entry->{'direction'}",
				"elevation $entry->{'elevation'}m",
				"gps lock $entry->{'gps-lock'}",
				"gps stale $entry->{'gps-stale'}"
				);
		}
		elsif ($entry->{'record_type'} eq 'W')
		{
			foreach my $field (@afield_W)
			{
				$entry->{$field} = shift @data;
			}
			print ' TPMS ', join(', ',
				"front-1 $entry->{'f1p'}psi $entry->{'f1t'}C",
				"rear-1 $entry->{'r1p'}psi $entry->{'r1t'}C",
				"front-2 $entry->{'f2p'}psi $entry->{'f2t'}C",
				"rear-2 $entry->{'r2p'}psi $entry->{'r2t'}C",
				);
		}
		else
		{
			print ' ', join(' ', @data);
		}
		print "\n";
	}
}

$reply = OVMSLogout($filenameCookieJar);

print $reply, "\n";

sub FOVMSLogin($$$)
{
	my ($cookiejar, $username, $password) = @_;

	$cmd = "curl -s -X GET -c $cookiejar \"http://tmc.openvehicles.com:6868/api/cookie?username=$username\&password=$password\"";
#	print $cmd, "\n";
	$reply = `$cmd`;
	
	return $reply =~ /login ok/i;
}

sub OVMSLogout($)
{
	my ($cookiejar) = @_;

	return DoOVMSCommand($cookiejar, 'DELETE', 'cookie');
}

sub DoOVMSCommand($$$)
{
	my ($cookiejar, $verb, $command) = @_;
	
	$cmd = "curl -s -X $verb -b $cookiejar http://tmc.openvehicles.com:6868/api/$command";
#	print $cmd, "\n";
	$reply = `$cmd`;
	
	return $reply;
}

sub LocalDateString(;$)
{
	my ($time) = @_;
	
	if (! $time)
	{
		$time = time();
	}
	my ($sec, $min, $hour, $mday, $mon, $year) = localtime($time);
	return sprintf("%02d/%02d/%04d-%02d:%02d:%02d", $mon + 1, $mday, $year + 1900, $hour, $min, $sec);
}
