#!/usr/bin/perl -w

# script that Michael K. used for automatic extraction of 
# ignition map f(rpm, map, clt) of a Lucas ignition ecu

# signals generated:
#  - 2*(18-1) tooth wheel pattern (capacitor)
#  - analog output for map sensor (pwm + lowpass filter)
#  - analog output for coolant (thermistor) (pwm + lowpass filter)
# signals sampled:
#  - 2*(18-1) tooth wheel pattern
#  - time of coil fire from "other" ecu

use Device::SerialPort;

if (@ARGV < 1) {
    print "Usage: $0 logfile [serial port]\n";
    undef $ob if defined $ob;
    exit;
}

$LOGFILE = $ARGV[0];

if (defined($ARGV[1])) {
    $PORT = $ARGV[1];
} else {
    $PORT = "";
}

if ($PORT eq "") {
    $PORT = "/dev/ttyS0";
}

$SIG{INT} = \&leave_int;

$ob = Device::SerialPort->new ($PORT) || die "Can't open $PORT: $!";
$ob->baudrate(9600)   || die "failed setting baudrate";
$ob->parity("none")    || die "failed setting parity";
$ob->databits(8)       || die "failed setting databits";
$ob->handshake("none") || die "failed setting handshake";
$ob->binary(T);
$ob->read_char_time(1000);
$ob->read_const_time(10000);
$ob->write_settings    || die "no settings";
$tmout = 10000;

open(LOG,">${LOGFILE}") || die "Can't open $LOGFILE: $!\n";
#select(LOG), $| = 1; # set nonbuffered mode

# setup:
#    wheel:   mst51
#    sensors: mttg10h16h26

# speed:      mspXX msqYY
# MAP:        mttg12hVV (map value)
# CLT:        mttg13hWW (clt value)
# Batt:       I dont care about dwell

# enable logging: mde20 

$log_enable = "mde20";
$log_disable = "mde00";

$numsamples = 4;
$packet_len = 10;
#$packet_len = 8;

##############################################

# rpm = 60 / ( 2 * 36 * (XX * 256 + YY)4e-6 )
# map = f(VV)
# clt = f(WW)

# adv = f(rpm, map, clt)

#$ob->read_const_time(500);
$ob->lookclear();

comm_send('Man'); 
$b = srl_wait(8);
if ($b =~ /Hello>/) {
    print "AVR said hello\n";
} else {
    print "No hello from AVR\n";
}
comm_send($log_disable);

# use mpx4250 as reference and measure output voltage of 
# unknown map sensor to get data for transfer function
#print "Vmap\tkpa\n";
#while (1) {
#    comm_send('mxL');
#    my $data = get_packet();
#    my @buf = split //, $data;

#    my $kpa250 = ( ord($buf[4])*256 + ord($buf[5]) ) / 4;
#    my $map = ord($buf[6])*256 + ord($buf[7]);
#    my $vmap = sprintf("%.3f", $map * 5/2048);
    

##    print "kpa250=$kpa250\tV(alien_map)=$vmap\n";
#    print "$vmap\t$kpa250\n";
#}

# setup stim
$last_p = 1;
my $str = sprintf("mst51msp%02x", $last_p);
print "stim_setup: $str\n";
comm_send($str);

$clt=88;
configure_clt($clt);

# start logging
$ob->lookclear();
comm_send($log_enable);

print LOG "rpm_vhr\tmap\tadv\n";
for (my $map=20; $map<=100; $map+=1) {
    configure_map($map);

    # sweep rpm
    for (my $rpm=100; $rpm<=6000; $rpm+=25) {
	configure_rpm($rpm);

	print "rpm=$rpm...\n";
	# collect data
	for ($i=0; $i<$numsamples; $i++) {
	    $data = get_packet();
	}

	if (!defined($data)) {
	    print "couldn't get data for rpm=$rpm, map=$map\n";
	    next;
	}

	# evaluate and store
	my @buf = split //, $data;

	my $adv = ord($buf[4])/2;
	my $rpm_vhr = ord($buf[6])*256 + ord($buf[7]);

	if ($adv != 127.5) {
	    print "$rpm_vhr\t$map\t$adv\n";
	    print LOG "$rpm_vhr\t$map\t$adv\n";
	}
#	print "RPM=$rpm, MAP=$map, CLT=$clt, adv=$adv, RPM_VHR=$rpm_vhr\n";
    }
}

# end logging
comm_send($log_disable);

leave();

sub Rthermistor{
    my $clt = shift;
#;	 0 Celcius =  5300 ohm
#;	 25 Celcius =  2500 ohm
#;	 100 Celcius =   192 ohm

    # 0.6*x*x - 106*x + 5300
    # hmmm, very bad fit. well... it doesn't matter much

    my $r = 0.6 * $clt * $clt - 106 * $clt + 5300;
    return $r;
}

sub configure_clt{
    my $clt = shift;

    # TODO: transfer function
    # 1.796k pullup, thermistor pulldown
    # Vout = Rtherm / (1.796k + Rtherm)
    # Rtherm = f(temp)
    # 4.97V max
    # DC = 5.0/4.97 * f(temp) / 1.796k + f(temp)
    
    my $vclt = 5 * Rthermistor($clt) / (1796 + Rthermistor($clt));
    my $dc = int ($vclt/4.97 * 256 + 0.5);  # 0.5 for rounding

    my $str = sprintf("mttg13h%02x", $dc);
    print "clt: $str\n";
    comm_send($str);
}


sub configure_map{
    my $map = shift;

# calculate transfer function of map sensor    
#f(x) = a*x+b
#fit f(x) 'ma_sort' using 2:1 via a,b
# Vmap = a * $map + b 
    my $a = 0.0382824; #        +/- 3.639e-05    (0.09507%)
    my $b = 0.198849;  #        +/- 0.003341     (1.68%)

    # calculate MAP output voltage
    my $vmap = $a * $map + $b;

    # calculate DC
    my $dc = int ($vmap/4.55 * 256 + 0.5);  # 0.5 for rounding

    my $str = sprintf("mttg12h%02x", $dc);
    print "map: $str\n";
    comm_send($str);

}

sub configure_rpm{
    my $rpm = shift;

# rpm = 60 / ( 2 * 36 * (XX * 256 + YY)4e-6 )
# <=> (XX * 256 + YY) = 60 / ( 2 * 36 * 4e-6 * rpm)

    # avoid div-by-0
    if ($rpm == 0) {
	return (0,0);
    }

    my $k = 60 / ( 2 * 36 * 0.000004 * $rpm);

    my $p = int $k / 256;
    my $q = $k % 256;

# dangerous to switch from one $p to another $p without first transmitting $p=0
    my $str = "";
    if ($p != $last_p) {
	$last_p = $p;

	$str = "msq00msp00";
	$str .= sprintf("msq%02x", $q);
	if ($p == 0) {
	    $str .= "msp01"; # make sure that stim is started by transmitting $p=1
	}
	$str .= sprintf("msp%02x", $p);
    } else {
	$str .= sprintf("msq%02x", $q);
    }

    print "rpm: $str\n";
    comm_send($str);
    return ($p, $q);
}

sub packet_valid{
    my $data = shift;
    my @buf = split //, $data;

    if (ord($buf[0]) == 13 && ord($buf[1]) == 10 &&
#	ord($buf[2]) == 107 && ord($buf[3]) == 61 ) {  # k
	ord($buf[2]) == 97 && ord($buf[3]) == 61 ) {  # a
#	ord($buf[2]) == 109 && ord($buf[3]) == 61 ) {  # m
	return 1;
    } else {
	return 0;
    }
}

sub get_packet{
	my $data = srl_wait($packet_len);
#	return $data;

	while(!packet_valid($data)) {
	    # problems, try finding \r\n
	    my @buf = split //, $data;
	    print "syncing: ";
	    my $sum=0;
	    for ($i=0; $i<$packet_len; $i++) {
		printf("%02x ", ord($buf[$i]));
		$sum += ord($buf[$i]);
	    }
	    print "\n";

	    if ($sum == 0) {  # buffer is empty, strange
		return undef;
	    }

	    do {
		$data = srl_wait(1);
		print "searching for '\\r'\n";
	    } while (ord($data) != 13);

	    $data .= srl_wait($packet_len-1);

	}
	return $data;
}


sub leave{
    print "Cleaning up and terminating...\n";
    comm_send('msq00');
    comm_send('msp00');
    comm_send($log_disable);
    comm_send('mbye');
    sleep(1);
    $ob->purge_rx();
    undef $ob if defined $ob;
    exit;
}

sub leave_int {
    $SIG{INT} = \&leave_int;
    leave();
}

#######################
sub comm_send
{
    my $str = shift ;
    $count_out = $ob->write($str);
    warn "write failed\n" unless ($count_out);
    warn "write incomplete\n" if ($count_out != length($str));
    return $count_out;
}

#######################
sub comm_recv
{
    my $b = shift;
    ($count_in, $string_in) = $ob->read($b);
    warn "read unsuccessful\n" unless ($count_in == $b);
    return ($count_in, $string_in);
}

#################################################
# what's the reply???
sub srl_wait
{
	my $realcharcnt = shift;
	if (defined($realcharcnt)) {
	    $charcnt = $realcharcnt;
	} else {
	    $charcnt = 1;
	}
# TODO: if no reply comes within 2 sec, we should start saying 'v' -s or sg.
# finally report the problem (or let the user ctrl-c)
	# $ob->read($in_);
	$ob->read_const_time($tmout);
	my ($cnt, $in_) = comm_recv($charcnt);
	#sysread (SRL, $in_, 1, 0) or die "cannot readback from SRL $!";
	
	if(defined $in_){
		#print "got back $cnt:$in_ :".ord(substr($in_, 0, 1));
		#print ' '.ord(substr($in_, 1, 1)) if(length($in_) > 1);
		#print "\n";
	} else {
		print "nothing\n";
	}
	
	return $in_ if defined($realcharcnt) && ($realcharcnt > 0);
	return undef if $in_ eq "\r";	# success 
	return 1;					# error
}
