#!/usr/bin/perl

use Getopt::Std;
use strict;

# =====================================================================
# General information, to be moved out to library file once it is done

our %MODES = (
    0x01 => 'AC V',
    0x02 => 'AC mV',
    0x03 => 'DC V',
    0x04 => 'DC mV',
    0x05 => 'DC/AC V',
    0x06 => 'DC/AC mV',
    0x07 => 'unknown (0x07)',
    0x08 => 'unknown (0x08)',
    0x09 => 'Ohms',
    0x0A => 'nanoMhos',
    0x0b => 'Ohms Beeper',
    0x0c => 'Capacitance',
    0x0d => 'Diode',
    0x0e => 'AC A',
    0x0f => 'AC mA',
    0x10 => 'AC uA',
    0x11 => 'DC A',
    0x12 => 'DC mA',
    0x13 => 'DC uA',
    0x14 => 'DC/AC A',
    0x15 => 'DC/AC mA',
    0x16 => 'DC/AC uA',
    0x17 => 'unknown (0x17)',
    0x18 => 'unknown (0x18)',
    0x19 => 'unknown (0x19)',
    0x1a => 'Deg C',
    0x1b => 'Deg F',
    );

# This flag applies to byte 30
our %MODEFLAG30 = (
    0x01 => 'auto',		# 0x03 is autohold, ox 0x01 must be "auto"
    0x02 => 'hold',
    0x04 => 'logging',
    0x08 => 'average',
    0x10 => 'max',
    0x20 => 'min',
    0x40 => 'fast',		# 0x48, 0x50, 0x60 found for fast settings
    0x80 => 'unknown (0x80)',
    );

# This flag applies to byte 31
our %MODEFLAG31 = (
#    0x01 => 'inverted',
    0x02 => 'delta',
    0x04 => 'percent',
    0x08 => 'unknown (0x08)',
    0x10 => 'unknown (0x10)',
#    0x20 => '',			# Primary display active?
#    0x40 => '',			# Primary and secondary display active?
    0x80 => 'unknown (0x80)',
    );

# This is the upper 3 bits of byte 34, which also has the main mode in it.
our %SUPERMODE = (
    0x40 => 'Hz',
    0x80 => 'dutycycle',
    0xC0 => 'ms',
    );

our %UNITS = (
    -4 => 'pico',
    -3 => 'nano',
    -2 => 'micro',
    -1 => 'milli',
    0 => '',
    1 => 'kilo',
    2 => 'mega',
    3 => 'giga',
    );

our %ERROR =(
    0 => '',
    1 => '(blank)',
    0x16 => 'OPEN',
    0x1d => 'LEADS',
    0x1b => 'or FUSE',
    0x21 => '0L (off scale)',
    );

sub iserror($)
{
    return 0 if $_[0] <= 0x70000000;
    return $_[0]&0xFF;
}

# =====================================================================

our %opt;

$opt{p}="/dev/ttyUSB0";

getopts("mp:h", \%opt);
if ($opt{h})
{
    print <<EOF;
This program queries a Fluke 189 with the QD 0 command to fetch its
present reading.  Data is printed in a human-readable format by default.

Options are:

    -p port     Port to use (currently $opt{p})
    -m	        Print in machine-readable CSV format
EOF
    exit;
}

alarm(3);
$SIG{ALRM} = sub {die "No response!"};

system ("stty -F $opt{p} 9600 raw icrnl opost");

RETRY:

open SER, "+<$opt{p}" or die "Could not open $opt{p}";
select(SER);$|=1;select(STDOUT);
print SER "QD 0\r";

my $response;
read(SER, $response, 2);
if (substr($response,0,1) eq "1")
{
#    printf STDERR "Error 1, retrying\n";
    close SER;
    goto    RETRY;
}
die "Unknown error '$response'!" if (substr($response,0,1) ne "0");
read(SER, $response, 3);
die "Unexpected response $response" unless $response eq "QD,";

read(SER, $response, 42, 0);

my ($time,
    $primaryvalue,
    $primarydec,
    $primaryunits,
    $secondaryvalue,
    $secondarydec,
    $secondaryunits,
    $tertiaryvalue,
    $time2,
    $primaryvalue2,
    $primarydec2,
    $primaryunits2,
    $byte30, $byte31, $dbref, $mode, $unknown, $fixed2
    )=unpack("LlCclCclLlCcCCSCH8H6", $response);

my $junk;

print "Times differ!\n" if ($time != $time2);
print "There is a tertiary value!\n" if ($tertiaryvalue < 0x70000000);
print "Some unknown bytes are not 0x010100! ($fixed2)\n" if $fixed2 ne "010100";

# This seems to indicate percentage:
$primarydec = 2 if $primarydec == 0x81; 
$secondarydec = 2 if $secondarydec == 0x81; 

my $primaryformatted=iserror($primaryvalue) ? 9**999 : $primaryvalue/10**$primarydec;
my $secondaryformatted=iserror($secondaryvalue) ? 9**999 : $secondaryvalue/10**$secondarydec;

# Let us format the mode, which is a bit of an undertaking
my $modestring;
$modestring = $MODES{$mode&0x3F};
$modestring .= " / ".$SUPERMODE{$mode&0xC0} if $mode&0xC0; foreach my $key (keys %MODEFLAG30)
{
    $modestring .= " / ".$MODEFLAG30{$key} if $byte30 & $key;
}
foreach my $key (keys %MODEFLAG31)
{
    $modestring .= " / ".$MODEFLAG31{$key} if $byte31 & $key;
}

if ($opt{m})
{
    print "$time, $primaryformatted, $secondaryformatted, \"$modestring\"\n";
    exit;
}

printf "Timestamp: %.1f\n", $time/10;
if (iserror($primaryvalue)) {
    printf "Primary:   %s (x%x)\n", $ERROR{iserror($primaryvalue)}, iserror($primaryvalue);
} else {
    printf "Primary:   %g %s\n", $primaryformatted, $UNITS{$primaryunits};
}
if (iserror($secondaryvalue)) {
    printf "Secondary: %s (x%x)\n", $ERROR{iserror($secondaryvalue)}, iserror($secondaryvalue);
} else {
    printf "Secondary: %g %s\n", $secondaryformatted, $UNITS{$secondaryunits};
}

if ($tertiaryvalue < 0x70000000)
{
    printf "Tertiary:  %g\n", $tertiaryvalue;
}

printf "Mode:      %s\n", $modestring;
printf "DB Ref:    %d\n", $dbref;

printf "Settings:  %02X%02X %s %02X %s%s\n",
    $byte30, $byte31, unpack("H4", pack("S", $dbref)),
    $mode, $unknown, $fixed2;
