use strict;
use warnings;
use Win32::SerialPort;
use Term::ReadKey;
use Switch;

# Software fuer Messgeraet M9803R

# fuer Debugging auf 1 setzen
my $DEBUG = 1;

# Definition von Dimension und Teilfaktoren fuer die
# einzelnen Messbereiche
# Messbereiche (Byte 5)
my %bereich = ( 0 => 'V DC',  1 => 'V AC',
                2 => 'mA DC', 3 => 'mA AC',
                4 => 'kOhm',  5 => 'Buzzer',
                6 => 'Diode', 7 => 'ADP',
                8 => 'A DC',  9 => 'A AC',
               10 => 'kHz',   11 => '?',
               12 => 'nF',
              );

# Teilfaktoren (Byte 6)
# Teilfaktor Spannung (Bereich 0, 1)
my %u_range = ( 0 => 10000.0, 1 => 1000.0,
                2 =>   100.0, 3 =>   10.0,
                4 =>     1.0,
              );

# Teilfaktor Strom (Bereich 2, 3)
my %i_range = ( 0 => 1000.0,  1 =>  100.0,
                2 =>   10.0,  3 =>    1.0,
              );

# Teilfaktor Widerstand (Bereich 4, 5)
my %r_range = ( 0 => 10000.0, 1 => 1000.0,
                2 =>   100.0, 3 =>   10.0,
                4 =>     1.0, 5 =>    0.1,
              );

# Teilfaktor Frequenz kHz (Bereich 10)
my %f1_range = ( 0 => 1000.0,  1 =>  100.0,
                 2 =>   10.0,
               );

# Teilfaktor Frequenz Hz (Bereich 10)
my %f2_range = (5 =>   100.0, 6 =>    10.0,
               );

# Teilfaktor Kapazitaet (Bereich 12)
my %c_range = ( 0 => 1000.0, 1 => 100.0,
                2 =>  10.0,  3 =>   1.0,
                4 =>   0.1,
              );

# Teilfaktor Diode (Bereich 6)
my %d_range = ( 0 => 1000.0 );

# Teilfaktor Strom (Bereich 8, 9)
my %I_range = ( 0 => 100.0 );


# Signalhandler fuer Stg-C
$SIG{INT}  = \&finish;

my $port = Win32::SerialPort->new('COM1')
    or die "Oops!\n";

$| = 1;
# Fehlermeldungen einschalten
$port->error_msg(1);
$port->user_msg(1);

# defined, weil "0" ein legaler Rueckgabewert ist
defined $port->parity_enable(1)	|| die "parity_enable geht nicht\n";

# Parameter setzen
$port->baudrate(9600)    || die "baudrate geht nicht\n";
$port->parity('none')	   || die "parity geht nicht\n";
$port->databits(8)       || die "databits geht nicht\n";
$port->stopbits(1)       || die "stopbits geht nicht\n";
$port->handshake('none') || die "handshake geht nicht\n";

# Das Interface des Geraets wird vom Computer aus mit
# Strom versorgt. Empfang erfolgt normal ueber RXD,
# positive Spannung ueber DTR (muss daher auf 1 gesetzt werden)
# negative Spannung ueber TXD (muss daher auf 1 gesetzt werden)
$port->dtr_active(1) 	   || die "DTR geht nicht\n";
$port->break_active(1) 	 || die "TXD geht nicht\n";

# Wartezeit fuer Lesen eines Zeichens
$port->read_char_time(10);
# Wartezeit fuer Lese-Overhead
$port->read_const_time(100);

# Devicecontrolblock schreiben
$port->write_settings	    || die "write_setings geht nicht\n";

my @data = ();
my $n = 0;

while(1)
  {
  # Zeichen von der seriellen Schnittstelle lesen
  my $byte = $port->read(1);
  if ($byte)
    {
    $byte = (ord($byte) & 127);
    $data[$n] = $byte;
    # Datensatz (hoffentlich) komplett
    if ($n == 10)
      {
	    if ($DEBUG)
	      {
	      print "Debug: ";
	      printf "%02x ", $_ foreach (@data);
	      print "\n";
	      }
      # Datensatz scheint o.k. zu sein
	    if ($byte == 0x0a)
	      {
	      # Datensatz auswerten
	      my ($raw, $disp, $spec) = analyze(@data);
	      print "$disp $spec\n";
	      # hier koennte man die $raw-Daten in einem Array
	      # fuer eine automatische Zeitreihen-Auswertung
	      # (zusammen mit einem Zeitstempel) abspeichern
	      # oder grafisch darstellen, oder, oder ...
	      }
  	  else
	      { die "Out of Sync!\n"; }
	    }
    $n = ($n + 1) % 11;
    }
  }


sub finish
  {
  # Programm ordentlich beenden
  $port -> close();
  undef $port;
  print "\nBye\n";
  exit 0;
  }

sub analyze # (@data)
  {
  # Datensatz auswerten
  my @data = @_;
  my $display = '';
  my ($dimension, $faktor);
  # Fehleranzeigen zuerst
  return (0, 'Overflow','') if ($data[0] & 0x01);
  return (0, 'Battery low', '') if ($data[0] & 0x04);
  # Messwert-Anzeige

  # Vorzeichen
  $display = '-' if ($data[0] & 0x08);
  # 48 ist der Wert des ASCII-Zeichen "0"
  $display .= chr(48 + $data[4]);
  $display .= chr(48 + $data[3]);
  $display .= chr(48 + $data[2]);
  $display .= chr(48 + $data[1]);
  my $value = 1.0 * $display;
  print "Debug: $display - $value\n" if ($DEBUG);

  # Messbereich und Teifaktor bestimmen
  $dimension = $bereich{$data[5]};
  switch ($data[5])
    {
    case [0, 1] { $faktor = $u_range{$data[6]}; }
    case [2, 3] { $faktor = $i_range{$data[6]}; }
    case [4, 5] { $faktor = $r_range{$data[6]}; }
    case [6]    { $faktor = $d_range{$data[6]}; }
    case [8, 9] { $faktor = $I_range{$data[6]}; }
    case [10]   { if ($data[6] < 3)
                    {
                    $faktor = $f1_range{$data[6]};
                    }
                  else
                    {
                    $faktor = $f2_range{$data[6]};
                    $dimension = 'Hz';
                    }
                }
    case [12]   { $faktor = $c_range{$data[6]}; }
    }
  $value = $value/$faktor;
  $display = sprintf("%4.3f %s", $value, $dimension);

  # Special Functions
  my $special = '';
  $special .= 'Hold,' if ($data[7] & 0x01);
  $special .= 'Rel,'  if ($data[7] & 0x02);
  $special .= 'Min,'  if ($data[7] & 0x04);
  $special .= 'Max,'  if ($data[7] & 0x08);

  $special .= 'Apof,' if ($data[8] & 0x01);
  $special .= 'Manl,' if ($data[8] & 0x02);
  $special .= 'Auto,' if ($data[8] & 0x04);
  $special .= 'Mem,'  if ($data[8] & 0x08);
  $special =~ s/,$//;  # Komma am Ende weg

  printf("Debug: %4.3f %s / %s\n", $value, 
             $dimension, $special) if ($DEBUG);
  return ($value, $display, $special);
  }

__END__


BYTE 0:    0  0  0  VZ  BAT  0  OR
             VZ:  0 for positive, 1 for negative
             BAT: 0 for normal,   1 for low battery
             OR:  0 for normal,   1 for Over Range

BYTE 1:    Anzeigeziffer (Digit 4) binaer
BYTE 2:    Anzeigeziffer (Digit 3) binaer
BYTE 3:    Anzeigeziffer (Digit 2) binaer
BYTE 4:    Anzeigeziffer (Digit 1) binaer

BYTE 5:    Unit / Messbereich
             0000000 DC V
             0000001 AC V
             0000010 DC mA
             0000011 AC mA
             0000100 Ohm
             0000101 Buzzer
             0000110 Diode
             0000111 ADP
             0001000 DC A
             0001001 AC A
             0001010 Hz
             0001011 ?
             0001100 CAP

BYTE 6:    Range / Messbereich
             0000000   400 mV     4 mA     400 Ohm     10 kHz      4 nF
             0000001      4 V    40 mA      4 kOhm    100 kHz     40 nF
             0000010     40 V   400 mA     40 kOhm   1000 kHz    400 nF
             0000011    400 V     4  A    400 kOhm              4000 nF
             0000100   4000 V            4000 kOhm             40000 nF
             0000101                    40000 kOhm     100 Hz
             0000110                                  1000 Hz
             Diode:       4 V

           Buzzer:                        400 Ohm

           DC A and AC A:         40 A

BYTE 7:    Special Function 1
           0  0  0  max  min  rel  hold
           0 for normal, 1 for the function

BYTE 8:    Special Function 2
           0  0  0  mem  auto  manl  apof
           0 for normal, 1 for the function

BYTE 9:    CR (Carrage Return, 0x0d)
BYTE 10:   LF (Line Feed, 0x0a)