#!/usr/bin/perl 

# basic waveform data acquisition program for TDS2024B
# oscilloscope.  Uses the 'Connection::Trace' facility
# to record the conversation with the scope, so that
# the full information can be reconstructed in offline
# analysis.
#
# usage:
#   DAQ_2024B [options] outputfile
# options:
#       --tmc_address=X         connect to scope on /dev/usbtmcX
#       -u --usb_serial=SER  connect scope with usb serial SER (text)
#       --visa_name=VNAM        connect scope with VISA name VNAM
#                               USB:0xVVVV::0xPPPP::SER::INSTR
#                               VVVV = vendor# PPPP = product# SER = serial string
#       if the above 'scope selections options' are not used, the first
#       TMC2024B found on usbtmc devices is used.
#
#       -S --Setup=n           load scope setup #n before starting acquistion
#       -n --nevents=#         number of events to store [def: 0=>infinity]
#       -I --ID='str'          ID string, stored in trace file
#       -c --channels='chans'  channels to read [def: all visible]
#       -s --self=T            force trigger every T seconds
#       -f --force             force overwrite of output file
#       -d --debug             turn on debugging
#       -q --quiet             suppress info messages
#       -h -? --help           show usage
#
# Default usage uses the first TDS2024B scope found, records the visible
# waveforms whenever the scope has a trigger (however the scope is set
# to trigger) and continues until stopped by a "kill" signal or
# Control-C.
#
# See the Lab::Connection::Trace module for how data and
# run comments are recorded to the data file.
#
# See the Lab::Data::Analysis::TekTDS module for code that
# can read and analyze the resulting output data file
#
use Lab::Generic::CLOptions;    # reclaim --debug switch
use Lab::Instrument::TDS2024B;
use Lab::Connection::Trace;
use Carp;
use Getopt::Long qw(:config bundling auto_version no_ignore_case);
use Time::HiRes qw(sleep gettimeofday);
use Data::Dumper;
use strict;

our $DEBUG   = $Lab::Generic::CLOptions::DEBUG;
our $VERSION = '3.544';
our $VERBOSE = 1;
our $TSTART;

#
# handle forced stop/interrupt
#
our $SHUTDOWN         = 0;
our $SHUTDOWN_TIMEOUT = 30;

sub stopreq {
    $SHUTDOWN = 1;
    alarm($SHUTDOWN_TIMEOUT);
}

sub muststop {
    die("timeout after shutdown requested");
}

$SIG{TERM} = \&stopreq;
$SIG{INT}  = \&stopreq;
$SIG{ALRM} = \&muststop;

main();

sub main {

    my $nev = 0;
    my $id;
    my $chans;
    my (@acqch);

    my $selfdelay;

    my $force = 0;
    my $outfile;

    my $tmc_address;
    my $visa_name;
    my $usb_serial;
    my $help;
    my $quiet = 0;
    my $setup;

    Getopt::Long::GetOptions(
        "nevents|n=s"    => \$nev,
        "ID|Id|id|i|I=s" => \$id,
        "channels|c=s"   => \$chans,
        "self|s=s"       => \$selfdelay,
        "quiet|q"        => \$quiet,
        "Setup|S=s"      => \$setup,

        "force|f" => \$force,

        "tmc_address=s"  => \$tmc_address,
        "visa_name=s"    => \$visa_name,
        "usb_serial|u=s" => \$usb_serial,
        "debug|d+"       => \$DEBUG,
        "h|?|help"       => \$help,
    );

    if ( defined($help) ) {
        usage();
        exit(0);
    }

    $VERBOSE = !$quiet;

    $outfile = shift(@ARGV);
    if ( defined($outfile) ) {
        if ( -e $outfile && !$force ) {
            croak("output file exists! use --force to overwrite");
        }
    }
    else {
        croak("missing output file parameter");
    }
    print "Sending output to $outfile\n" if $VERBOSE;

    if ( $nev <= 0 && $VERBOSE ) {
        print "Infinite running, use Control-C or 'kill $$' to stop\n";
    }

    OpenTraceFile($outfile);

    my $args = {};
    $args->{connection_type} = 'USBtmc::Trace';
    $args->{tmc_address}     = $tmc_address if defined $tmc_address;
    $args->{visa_name}       = $visa_name if defined $visa_name;
    $args->{usb_serial}      = $usb_serial if defined $usb_serial;
    $args->{debug}           = $DEBUG;

    my $s = new Lab::Instrument::TDS2024B($args);
    croak("error opening TDS2024B") unless defined $s;

    print_errors( $s, "initial" ) if $DEBUG;
    $s->connection->Comment("ID:$id") if defined($id);
    $s->connection->Comment("FORCED_TRIGGER delay=$selfdelay")
        if defined($selfdelay);

    $s->recall($setup) if defined($setup);

    print "Setting up for acquisition..." if $VERBOSE;
    $s->set_locked(1);
    print_errors( $s, "in DAQ setup" ) if $DEBUG;

    my $save_setup = $s->get_setup();    # records setup, plus fills cache
    print_errors( $s, "in DAQ setup" ) if $DEBUG;

    my (@want) = (qw(CH1 CH2 CH3 CH4 MATH REFA REFB REFC REFD));
    if ( defined($chans) ) {
        my (@l) = split( /\s*,\s*/, $chans );    # list requested chans
        my (%hch);                               # hash of chans
        foreach (@l) {
            if (/^(ch)?([1-4])$/i) {
                $hch{"CH$2"} = 1;
            }
            elsif (/^MATH$/i) {
                $hch{"MATH"} = 1;
            }
            elsif (/^(REF)?([a-d])$/i) {
                $hch{"REF$2"} = 1;
            }
            else {
                carp("invalid channel '$_' requested");
            }
        }
        @want = ( sort( keys(%hch) ) );
    }

    $s->connection->MuteTrace(1) unless $DEBUG;
    foreach my $ch (@want) {
        push( @acqch, $ch ) if $s->get_visible($ch);
        print_errors( $s, "in DAQ setup" ) if $DEBUG;
    }

    # pre-DAQ setup to restore afterwards
    my $header        = $s->get_header();
    my $sverb         = $s->get_verbose();
    my $data_width    = $s->get_data_width();
    my $data_encoding = $s->get_data_encoding();
    my $acq_state     = $s->get_acquire_state();       # for post-acq restore
    my $acq_stopafter = $s->get_acquire_stopafter();
    $s->connection->MuteTrace(0) unless $DEBUG;

    $s->set_header(1);
    $s->set_verbose(0);
    $s->set_data_width(1);
    $s->set_data_encoding('RPBINARY');                 # max efficiency
    $s->set_acquire_state('STOP');
    $s->set_acquire_stopafter('SEQUENCE');

    print_errors( $s, "after DAQ setup" ) if $DEBUG;
    print "...ready to go!\n" if $VERBOSE;

    StartRun();

    my $event = 0;
    while ( ( $nev <= 0 || $event != $nev ) && !$SHUTDOWN ) {
        $s->set_acquire_state('RUN');
        print "\tRead... " if $VERBOSE;
        if ( defined($selfdelay) ) {
            sleep($selfdelay);
            $s->trigger();
        }

        MuteTrace(1) unless $DEBUG;    # don't need all the BUSY? checks.
        while ( !$SHUTDOWN ) {
            last if !$s->test_busy();
            sleep(0.05);
        }
        MuteTrace(0) unless $DEBUG;

        my $tev = gettimeofday();
        $event++;
        NextEvent();
        print "Event ", $event, " \@ t=$tev ", scalar( localtime($tev) ),
            " .. reading"
            if $VERBOSE;

        foreach my $ch (@acqch) {
            $s->get_waveform($ch);
        }

        print_errors( $s, "after event" ) if $DEBUG;
        print "\n" if $VERBOSE;
    }

    print_errors( $s, "after last event" ) if $DEBUG;

    #cleanup

    $s->connection->MuteTrace(1) unless $DEBUG;
    $s->set_data_width($data_width);
    $s->set_data_encoding($data_encoding);
    $s->set_acquire_stopafter($acq_stopafter);
    $s->set_acquire_state($acq_state);
    $s->set_locked(0);
    $s->set_header($header);
    $s->set_verbose($sverb);
    print_errors( $s, "after cleanup" ) if $DEBUG;
    $s->connection->MuteTrace(0) unless $DEBUG;
    StopRun();

    my $tend = gettimeofday();
    print "STOP $event events @ t=$tend ", scalar( localtime($tend) ), "\n"
        if $VERBOSE;

}

sub usage {
    print "$0 [options] outputfile\n";
    print "  OPTIONS:\n";
    print "  -n --nevents=#          number of events to take,0=infinity\n";
    print "  -I --ID='string'        store ID with run header\n";
    print "  -c --channels='chans'   channels to read (def: all visible)\n";
    print "  -s --self=T             self-trigger, every T seconds\n";
    print "  -S --Setup=N            load scope setup N (1..10)\n";
    print "  -f --force              force overwrite of output file\n";
    print "  --tmc_address=N         use /dev/usbtmcN device\n";
    print "  --visa_name=V           use VISA style device spec:\n";
    print "          USB:0xAAAA::0xBBBB::SERIAL::INSTR\n";
    print "          0xAAAA = vendor id, 0xBBBB = product id\n";
    print "  -u --usb_serial=SERIAL  select by scope SERIAL \n";
    print " if no 'scope selection'  options given, first scope is used\n";
    print "  -d --debug              turn on debug\n";
    print "  -q --quiet              quiet mode, supress messages\n";
    print "  -h -? --help            this text\n";
}

sub print_errors {
    my $s    = shift;
    my $info = shift;

    my $dirty = 0;
    while (1) {
        my ( $code, $msg ) = $s->get_error();
        last if $code == 0;
        print "$info:\n" if defined($info) && !$dirty;
        print "\t$code: $msg\n";
        Comment("$code: $msg");
        $dirty = 1;
    }
}
