#!/usr/bin/perl

########################################################################################
# Program to run on fusor-proximate machine to collect data into a Fusor database
# including direct aquisition from various uP boxes and eventual collection of video
# files internally and from other uP boxes.
# Large (video and audio) files will be placed in their own directory, with paths to them 
# placed in the database.  Yes, this sucks as a db design, but not for real use.  They're already files.
# We will just put their paths in the db. 

# At the start of a run, a log file is created (timestamp.log), and all devices are started.
# Polling locally connected devices will commence, and whatever data they have will be
# stuffed into the database, using the devices own timing info.  Other devices will record
# into files local to them that will be retrived at the end of a run, and stored, as will
# post-run commentary.

# To front run the obvious criticisms - don't do this at home.  Tons of hardcoded stuff that should not be
# were this going to be in wide (any?) use outside my lab.  
# Tons of "my" global variables for speed and control of garbage collection/re-allocation - I'm careful.  
# Great mind runs in the same track!  Perl gives you enough rope to shoot yourself in the foot.
# Crazy-ass calls from Rcam.pm back into this (main::) package - don't even try this in  your lab.
#######################################################################################

# Anything we thought worth paying special attention to (for changes or bugs) is marked with @@@
# in the comments for easy searching.  

####### modules and pragmata - generic
use warnings; # always use these
use strict;   # sanity-saving pragmas
use Gtk3 -init; # for our GUI
use Glib  qw(TRUE FALSE); # for periodic events etc
use Gtk3::Helper; # allows us to add "watch" callbacks to handles?
use DBI; # for database, in this case mysql
use Device::SerialPort; # the correct one for linux, talk to devices (including USB serial)

use Rcam; # my very own perl mod/class for handling a remote camera on a pi 
# @@@ the above makes a ton of assumptions about samba share names, paths, etc
use File::Basename 'dirname'; # for knowing where we are (ease for developer)

use Inline Python =>  <<'ENDPY';
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
CLAPBOARD_PIN = 24
GPIO.setup(CLAPBOARD_PIN,GPIO.OUT,initial=GPIO.LOW)

def clap_on():
 	GPIO.output (CLAPBOARD_PIN,True)
def clap_off():
	GPIO.output(CLAPBOARD_PIN,False)
def psleep(sec):
	time.sleep(sec)
def GPIO_cleanup():
	GPIO.cleanup

ENDPY

####### package global variables - generic
my $mainwin; # main window reference
my $builder = Gtk3::Builder->new(); # gtkbuilder object, creates a gui from glade's xml

####### package global variables - program-specific
my $statid; 	# for status bar messages
my $statusbar; 	# the widget reference
my $DSNr; 		# widget reference
my $DSN = "DBI:mysql:Fsr:localhost";		# data source name
my $PreRunr;	# widget reference
my $PreRunbr;	# widget text buffer reference
my $PreRun;	# pre-run comments - text
my $PostRunr;	# widget reference
my $PostRunbr;	# widget buffer reference
my $PostRun;	# post run text
my $setpoint;   # from edit box for inserting into arduino

# the r at the end ofthese names means "reference"
my $ardr;           # arduino combo reference
my $iongasr;        # gas combo reference
my $audior;         # audio combo reference
my $gridr;          # grid video combo reference
my $scoper;         # scope combo reference
my $roomr;          # room cam combo ref
my $runnor;         # run number textbox ref
my $logger;         # log text area ref
my $loggerbr;       # log area text buffer ref
my $debugr;         # ref to debug checkbox
my $startr;         # start button
my $stopr;          # stop button
my $commitr;        # commit button
my $cleanupr;       # delete run button
my $setpointr;      # edit box for setpoint numeric inputs
my $setintimer;     # button for setting gas in solenoid time (ms)
my $setouttimer;    # button to set outlet solendoid time
my $setionvaluer;    # ion voltage program duty cycle (legal 0-203 for 0-40kv)
my $gasinr;         # button to let gas in for programmed time
my $gasoutr;        # button to let out gas for programmed time
my $ionenabler;     # checkbox for ion voltage enable (HostEnable in arduino)



my $ardc;		# arduino schema	
my $iongasc;	# gas controller schema
my $audioc;		# audio schema
my $gridc;		# grid video schema
my $scopec;	# scope schema
my $roomc;		# room cam schema
my $debug = 1; 	   # set true for debugging/verbosity

my $running; 	# return value for polling
my $timestring;	# for various logging purposes
my @rawtime;	# from localtime(), will be converted to above

my $DBUser = 'fusor'; # @@@ could add gui elements, but this reduces clutter
my $DBPass = 'data';  # @@@ same here, hardcoded pword, eack! - we are all inside our firewall (we hope!)
my $dbh; # database handle
my $sth; # statement handle for general use
my $runno = 0; # current run number, set to zero for debug/error...danger name collisions possible

my $log;	# logfile handle
my $logstring;	# what to put there - built in this variable


my @lines; # data from some serial device (uno or teensy) as an array of lines
my $unoid = "usb-Adafruit_Adafruit_Metro_328_ADAOEMNCa-if00-port0"; # what's it called?
my $unoport; # serial port for above
my $gasionid ='usb-1a86_USB2.0-Serial-if00-port0';
my $gasionport; # thinks of itself as address 5
my $sthu; # sql insert statement handle for uno-only
my $sthfc; # same for controller uno
my @row; # a split up line for DB insertion

# at least %cams keys are now the same as the smb names of the remote cameras and also sql blobs table columns
my %cams = (fusorpi => '', tricam => '', gridcam => ''); # will contain references to Rcam objects for the above
#@@@ crappy scheme to map samba names to schema variable references >HARDCODED dreck<
my %camsch = (fusorpi=>\$roomc,  tricam=> \$scopec, gridcam=>\$gridc); # Tom Arnold - I'm goin' to hell for this. (true lies)

# audio support
my $soxpid;
my $soxfilebase = "/var/lib/FusorFiles/audio/";
my $soxrecname;
my $soxreccmd = "| rec -c2 -q "; # note trailing space

###########################################################
###### Data Aq subs #######################################
###########################################################

###########################################################
sub updatetime
{    # create global $timestring for "now" in YY_MM_DD_HH:MM:SS format
        # saves me from having to remember this shit all the time
    @rawtime    = localtime();    # get array of time values
    $timestring = sprintf(
        "%02d_%02d_%02d_%02d:%02d:%02d",
        $rawtime[5] % 100,
        $rawtime[4] + 1,
        $rawtime[3], $rawtime[2], $rawtime[1], $rawtime[0]
    );
}    # was that fiddly and opaque enough?
###########################################################
###########################################################
###########################################################


###########################################################
sub openlogfile
{ # create a file named "current time" for logging to - looks easy?
  #dirname($0) is path to program w/o program name on it or trailing slash
 updatetime(); # use current timestring as logfilename
 open($log, ">",dirname($0) ."/$timestring".'fm.log') or die "couldn't open logfile:$!\n";
} # about the only time "die" is a good idea in this program, debug goes to log/textarea
###########################################################
sub loggit
{ # write whatever we were called with, prepend time, suffix newline
  # put result in log window and file
 updatetime(); # get current time-string in $timestring
 $logstring = "$timestring" . "\t" . shift . "\n";
 print $log $logstring; # write to file
 $loggerbr->insert($loggerbr->get_end_iter(),$logstring);
 $logger->scroll_to_iter($loggerbr->get_end_iter(),0.0,TRUE,1.0,1.0); # scroll to end...duh
# nasty syntax described at: 
# https://developer.gnome.org/gtk3/stable/GtkTextView.html#gtk-text-view-scroll-to-iter
}
###########################################################
sub clap { # emit clapboard signal, using python above...yeech, but it works
 psleep .25;
 clap_on();
 psleep .25;
 clap_off();
 loggit "clap done"; # must be declared after loggit...
}
###########################################################


###########################################################
sub finddevice
{ # returns the filepath for the first match we find under /dev/serial/by-id
# pass usb-Arduino_www.arduino.cc to find an arduino
# our uno is:usb-Adafruit_Adafruit_Metro_328_ADAOEMNCa-if00-port0
# usb-Teensyduino_USB_Serial
# usb-Clab_StdDaq_001-if00

# @@@ need more detail in input if we have more than one of a type
 my $apath;
 my $devdesc = shift; # you passed me what to find, right?
 opendir(DIR,'/dev/serial/by-id') or die "No usb serial devices: $!\n";
 while (defined($apath = readdir(DIR)))
 {
  if ($apath =~ m/$devdesc/) # match a name-part?
   {
    $apath = '/dev/serial/by-id/' . $apath; #concatenate
    closedir(DIR); # be nice and clean
    return $apath; # here's the path to what you asked for
   } 
 }
 $apath = '';  # null means we couldn't find it
 loggit "No device like $devdesc found - we can't get data if there isn't any device connected!";
}
###########################################################
# setup serial port and return false if error, more involved than it should have to be, but...that's life.  
# call with full path to desired port to open, returns a reference to the port or null if failure
sub setupserial
{
 my $port;
 my $portpath = shift; # added this so caller can open multiple ports
#"/dev/serial/by-id/usb-Arduino__www.arduino.cc__0043_D1DDD1DDD1DD51DDD1DD-if00"; 
     $port = new Device::SerialPort ($portpath, 1); # the one means "quiet mode"
    # some of this not needed for USB, but do it anyway
      $port->databits(8);
      $port->baudrate(115200);
      $port->parity("none");
      $port->stopbits(1);
      $port->handshake("none");
      $port->stty_icanon(1);    # this is what gets us on line boundaries
      $port->are_match("\r\n"); # possible end strings (windows/arduino)
      $port->lookclear;         # empty the buffer
#   $port->read_const_time(10);       # const time for read (milliseconds)
#  $port->read_char_time(1);          # avg time between read char
      sleep 1; # let device boot
      $port->purge_all(); # kill any initial garbage
      return $port; # now caller has a pointer to a port object, or null on failure
}

########################################################################
# get lines from a given port, assumes a global variable @lines, call with ref to port
sub getlines
{ # get as many lines as we can without waiting any extra
 my $port = shift; # you passed in a ref to a port, right?
 my $gotit = "";
 @lines = (); # start clean
 do
 {
  $gotit = $port->streamline;      # see if data ready
  unless ($gotit eq "") {push (@lines,$gotit);} # if they're blank...
  $port->matchclear;# so we dont keep reading the same line over
 } until ($gotit eq "");
# return scalar(@lines); # if we need to know this
}


#######################################################
###### GUI subs #######################################
#######################################################
sub getwidgets # totally specific to this program, as it needs to match names with the GUI elements
{ # gets global references to the actual GUI objects for later use
 $statusbar = $builder->get_object('statusbar');
 $statid     = $statusbar->get_context_id( 'statusbar' );
 $statusbar->push ($statid, 'Ready'); # initial message

 $DSNr = $builder->get_object('DSN');
 $DSNr->set_text($DSN);

 $runnor = $builder->get_object('runno');
 $PreRunr = $builder->get_object('prerun');
 $PostRunr = $builder->get_object('postrun');
 $ardr = $builder->get_object('ARD');
 $iongasr = $builder->get_object('IONGAS');
 $audior = $builder->get_object('AUDIO');
 $audior->set_active_id(1); #default record audio
 $gridr = $builder->get_object('GRID');
 $gridr->set_active_id(1); # try a default value (on, in this case)
 $scoper = $builder->get_object('SCOPE');
 $scoper->set_active_id(1); # try a default value 
 $roomr = $builder->get_object('ROOM');
 $roomr->set_active_id(1); # try a default value 
 $logger = $builder->get_object('LOG');
 $loggerbr = $logger->get_buffer(); # text area widgets have a separate text buffer
 # get this one upfront as we use it a lot in time-critical situations.  The other two
 # we just get as needed
 $startr = $builder->get_object('START');
 $stopr = $builder->get_object('STOP');
 $commitr = $builder->get_object('COMMIT');
 $cleanupr = $builder->get_object('cleanup');
 $setpointr =  $builder->get_object('setpoint');    # numeric input edit for various uses
 $setintimer = $builder->get_object('setintime');    # button for setting gas in solenoid time (ms)
 $setouttimer = $builder->get_object('setouttime');    # button to set outlet solendoid time
 $setionvaluer = $builder->get_object('setionvalue');    # ion voltage program duty cycle (legal 0-203 for 0-40kv)
 $gasinr =  $builder->get_object('gasin');    # button to let gas in for programmed time
 $gasoutr = $builder->get_object('gasout');    # button to let out gas for programmed time
 $ionenabler = $builder->get_object('ionenable');    # checkbox for ion voltage enable (HostEnable in arduino)

 # some user mistakes now difficult to make
 $stopr->set_sensitive(FALSE);
 $commitr->set_sensitive(FALSE);
 $cleanupr->set_sensitive(FALSE); # better be nothing to clean up yet
 $debugr = $builder->get_object('debug');
 $debugr->set_active($debug); #@@@ set this one from programmed default - need for other init sometimes
 
}
############################
sub printstatus
{
 $_ = shift; # get first parameter
 $statusbar->remove_all ($statid); # don't accumulate entries
 $statusbar->push($statid,$_); # put in whatever we were passed
}
############################
sub getguidata
{ # fill in variables with the gui stuff
 $PreRunbr = $PreRunr->get_buffer();
# requires iters to know what text to get, and a bool to tell whether to show hidden chars.
 $PreRun = $PreRunbr->get_text($PreRunbr->get_start_iter(),$PreRunbr->get_end_iter(), 1); # finally have text
# post run has it's own routine, below
 $ardc = $ardr->get_active_id();			# arduino index	
 $iongasc = $iongasr->get_active_id();	# gas controller index
 $audioc = $audior->get_active_id();		# audio index
 $gridc = $gridr->get_active_id();			# grid video index
 $scopec = $scoper->get_active_id();		# scope schema index
 $roomc = $roomr->get_active_id();		# room cam schema from main pi
 $DSN = $DSNr->get_text();
 $debug = $debugr->get_active();
 $setpoint = $setpointr->get_text(); # get value from edit box
# loggit ("setpoint was:$setpoint");
}
############################
sub getpostrun
{
 $PostRunbr = $PostRunr->get_buffer(); # gets reference to gtk text buffer
# the following defines how much to get - you can get "slices" and selections etc, we just want it all.
 $PostRun = $PostRunbr->get_text($PostRunbr->get_start_iter(),$PostRunbr->get_end_iter(),1);
}
############################

############################
# call this instead after pasting finished gui XML into this file after __END__
sub createguilocal # create the gui from internal DATA filehandle
{# create a local scope
 my $guidata; # packed the xml internally, goes here
 local $/; # which makes this undefined for this sub
 $guidata = <main::DATA>; # which makes this slurp the entire "file"
 $builder->add_from_string($guidata); # create gui
 $mainwin = $builder->get_object('mainwin'); #@@@ assumes main window is called mainwin
 $builder->connect_signals(undef);
 $mainwin->signal_connect(destroy => sub {Gtk3->main_quit});
 $mainwin->show_all();
# not so program-specific you couldn't use it again
}
############################
sub createguifile # create the gui from xml in a .glade file 
{ # use this for developer convienience.  Final product should put glade xml
  # after the __END__ tag and use createguilocal instead
 $builder->add_from_file(dirname($0) . '/fm.glade'); #@@@ hardcoded filename
 $mainwin = $builder->get_object('mainwin'); #@@@ assumes main window is called mainwin
 $builder->connect_signals(undef);
 $mainwin->set_screen( $mainwin->get_screen() ); #??? from an example.  Seems redundant?
 $mainwin->signal_connect(destroy => sub {Gtk3->main_quit});
 $mainwin->show_all();
# not so program-specific you couldn't use it again
}
############################

############################ Events ####################################
sub on_quit_clicked {
#@@@ shutdown pi's here, maybe other stuff - make it safe!

 Gtk3->main_quit; # the sub name must match the glade event call
 } 
############################
sub on_START_clicked
{
 $startr->set_sensitive(FALSE);
 $stopr->set_sensitive(TRUE); # now that we've started, allow stopping
 my $camname;
 loggit "start clicked - starting";
 getguidata(); # get all the user input so far - we won't check during a run.
 
 $sth = $dbh->prepare("INSERT INTO runs(runno,date,prerun,unoschema,teensyschema,gridvid,scopevid,roomvid,audio) VALUES (NULL,NULL,?,?,?,?,?,?,?)") or die "bad prepare $!"; 
 $sth->execute($PreRun,$ardc,$iongasc,$gridc,$scopec,$roomc,$audioc) or die "bad SQL execute $!"; 
 $runno = $dbh->last_insert_id("NULL","NULL","runs","runno") or die "problem with last_insert $!"; # fetch run id number used as key in other tables
 $runnor->set_text("$runno");
 loggit "runno:$runno";  # updates time for us as a by-product
 while (Gtk3::events_pending) {Gtk3::main_iteration;} # @@@ works, weird syntax, no ->, which warns

#$audioc
 if($audioc) { # if we are to record audio
  $soxrecname = $soxfilebase . $timestring . ".mp3";
  loggit "audio path is:$soxrecname";
  $soxreccmd .= $soxrecname; # add filepath
  print "audio command:$soxreccmd\n";
  no warnings;
  $soxpid = open(SOX,$soxreccmd);
  print "sox opened, pid:$soxpid\n" if $debug;
  loggit "soxpid was $soxpid";
 }
while (Gtk3::events_pending) {Gtk3::main_iteration;} # @@@ works, weird syntax, no ->, which warns
 
# start cameras @@@ add only if selected in UI and  also remove any that fail to start
  foreach $camname (keys %cams) { # using hash order
#  print "camname:$camname\n";
  if(${$camsch{$camname}}) {  # de-reference funky syntax (%camsch is hash of refs to gui value variables)
   loggit "starting $camname";
   $cams{$camname}->start_cap() or loggit "cam:$camname didn't start"; # and get them going
  } else {
   loggit ("$camname camera not selected in gui, not starting it, and deleting it from operative list.");
   delete $cams{$camname}; # delete name from hash if fail (and report it)
  }
 } #foreach camname

# clapboard - starts arduino uno as well (falling edge of led signal I think)
 clap();  # wait .25 sec, blink led .25 sec, then return.  Falling edge starts arduino uno too

 Glib::Timeout::add($mainwin,250,\&poll); #!!!! Victory!  Timed polling with sleeps in between.
 $running = 1;  # we just tossed the ball out there, tell other parts of self about it
 printstatus "end of start click - running";
}
############################
sub on_STOP_clicked
{
 my $stmtb; # for handling blob runno insert, and audio, which we did already and locally (no copy required)
 my $camname;
 $running = 0;
 $stopr->set_sensitive(FALSE); # we're here already
   foreach $camname (keys %cams) { # using hash order
  $cams{$camname}->end_cap(); # stop them and start boxing video for later copy
 }
 $unoport->write('!'); # reset uno
 $unoport->purge_all();
 $unoport->lookclear();

#gasionport do here
 $gasionport->write("5!\n"); # reset uno
 $gasionport->purge_all();
 $gasionport->lookclear();

if ($soxpid) # one way to kill, we could send it ctrl-c via pipe?
 {
  loggit "trying to kill sox pid:$soxpid";
  kill 'INT',$soxpid or loggit "failed sox kill";
  $soxpid = 0; # hopefully this is a good thing to do

} else {
  loggit "no soxpid to kill - you'd better check it isn't recording forever";
}

 #moved from commit and added audio path
 if ($audioc) # we were recording, hopefully
 {
  $stmtb = "INSERT INTO blobs (runno,audio) VALUES ($runno,'$soxrecname');";
 
 } else {
 $stmtb = "INSERT INTO blobs (runno) VALUES ($runno);";
 }
 $dbh->do($stmtb) or loggit "couldn't add runno:$runno or maybe audio path:$soxrecname to blobs:$!";


 printstatus 'Stop clicked';
 $commitr->set_sensitive(TRUE);
 $cleanupr->set_sensitive(TRUE); # we/ve already made a mess, enable cleaning it up

# anything else we need to stop? @@@ 
}
############################
sub on_COMMIT_clicked
{ # copy the stuff that was just temp files, and post run comment into database and close it
 my $camname;
 my $destination;
 my $stmt; # for building SQL

 printstatus 'Commit clicked, working...';

# get post-run comments for insertion into runs table
 getpostrun(); # get any post run comments (hopefully typing them takes long enough for boxing to finish on slave cams)
 my $updstatement = "UPDATE runs SET postrun = '$PostRun' WHERE runno = $runno;"; # let perl get the quotes right 
# print "update sql:$updstatement\n"; 
 $dbh->do($updstatement);

 while (Gtk3::events_pending) {Gtk3::main_iteration;} # @@@ works sometimes (change to do loop?), weird syntax, no ->, which warns

 unless ($debug)
{ # note, may have to wait awhile for this for boxing to finish on the targets
  foreach $camname (keys %cams) { # using hash order
  $destination = $cams{$camname}->copy_vid(); # bring it on home to me (returns destination path)
  loggit "copied vid from $camname to $destination";
#@@@ add stuffing paths into db table "blobs" - copy returns destination path string. blobs columns named lowercase same as machine names
 $stmt = "UPDATE blobs set $camname='$destination' WHERE runno=$runno;" ;
 $dbh->do($stmt) or loggit "couldn't add cam blob:$destination for $camname";
 }
} else
{
 loggit "no vid copy if in debug mode";
}
#@@@ do audio here? No did it on stop
#@@@ possible issue of  bad names in runs table if audio failed

 $commitr->set_sensitive(FALSE); # no double commits
 $startr->set_sensitive(TRUE); # may want to do another run
 while (Gtk3::events_pending) {Gtk3::main_iteration;} # @@@ works, weird syntax, no ->, which warns
sleep 1;
 commitdone();
}
############################
sub commitdone
{
 #if ($dbh) {$dbh->disconnect();}
 printstatus('Commit finished!');
 return undef;
}

############################
sub on_TESTDSN_clicked
{ # make sure we can connect - no longer in real use, but you could type in a DSN before starting
 $DSN = $DSNr->get_text();
# see if we can connect to the desired database

#$dbh = DBI->connect($DSN,$DBUser,$DBPass) or loggit "couldn't connect to $DSN:$!";
my @tables;
@tables = $dbh->tables();

 printstatus "Test DSN clicked: $DSN \t tables: @tables";
}
############################
sub on_cleanup_clicked
{
 my $stmt; #sql statement to do
 my @blobpaths; #
 my $path;

  getguidata(); # get pre run words, and schema settings (if you hack, you can type a run number into the gui...)
  # connect to the desired database
  #$dbh = DBI->connect($DSN,$DBUser,$DBPass) or loggit "couldn't connect to $DSN:$!";
 
 if ($runno) {
   $stmt = "DELETE from uno WHERE runno=$runno;";
   $dbh->do($stmt) or loggit "err doing $stmt: $dbh->errstr"; # tested
  $stmt = "DELETE from servo WHERE runno=$runno;";
   $dbh->do($stmt) or loggit "err doing $stmt: $dbh->errstr"; # tested
  $stmt = "DELETE from teensy WHERE runno=$runno;";
   $dbh->do($stmt) or loggit "err doing $stmt: $dbh->errstr"; # tested
  $stmt = "DELETE from fusectl WHERE runno=$runno;";
   $dbh->do($stmt) or loggit "err doing $stmt: $dbh->errstr"; # tested

# @@@ add any other numeric data aq remove here (this was all of them for this rev of the code 5/11/16
# find any blobs for this runno and delete the files, then the row
   $stmt = "SELECT audio, gridcam, tricam, fusorpi FROM blobs WHERE runno=$runno;";
   @blobpaths = $dbh->selectrow_array($stmt) or loggit "statement:$stmt error: $dbh->errstr"; # fill arry with paths in order shown above
   foreach $path (@blobpaths) {
   if ($path) { #some might be blank
    unlink $path or loggit "couldn't unlink $path:$!";
    }  #if path
  } # for paths
  $stmt = "DELETE FROM blobs WHERE runno=$runno;"; # no need for this to stay and point to paths that aren't there anymore
  $dbh->do($stmt) or loggit "couldn't do $stmt:$dbh->errstr";

# then, finally, delete runs row of this run
 $stmt = "DELETE FROM runs WHERE runno=$runno;";
 $dbh->do($stmt) or loggit "couldn't do $stmt:$dbh->errstr";
  $runnor->set_text("none"); # show we've deleted the current run
 printstatus "Run $runno deleted."; 

############################@@@ MAKE $runno false and remove from gui (or something fancier, but valid)???
} else
{
 loggit "No run to remove";
}
 loggit "cleanup clicked - done, runno was:$runno";
 $cleanupr->set_sensitive(FALSE); # no double deletes
}
############################
sub on_TEST_clicked
{
    $gasionport->write("5s\n");
    loggit "test clicked - tuning saved";
}
############################
sub on_gasin_clicked  {
 loggit ("Gasin Clicked");
 $gasionport->write("5I\n"); # send command to let gas in for preprogrammed time
 }
############################
sub on_gasout_clicked {
 loggit ("Gas Out clicked"); 
 $gasionport->write("5O\n"); # send command to let gas out for preprogrammed time
 }
############################
sub on_ionenable_toggled {
 my $message = "ion enable turned ";
 # 5e for on, 5E for off
  my $enable = $ionenabler->get_active();
  if ($enable) {
   $gasionport->write("5e\n");
    $message .= "on.";
 } else {
   $gasionport->write("5E\n");
    $message .= "off.";
   }
   loggit ($message);
  }
############################
# v is ion duty, i is in time, 0 is out  time
#5s saves in eeprom
############################
sub on_setintime_clicked {
 my $value = $setpointr->get_text() + 0;
 if ($value > 255) {$value = 255;} # even that is past reasonable
 if ($value < 10) {$value = 10;} # @@@ adjust for minimum working time
 $gasionport->write("5i$value\n");
 loggit ("inlet time set to $value"); 
 }
############################
sub on_setouttime_clicked {
 my $value = $setpointr->get_text() + 0;
 if ($value > 255) {$value = 255;} # even that is past reasonable
 if ($value < 50) {$value = 50;} # @@@ adjust for minimum working time
 $gasionport->write("5o$value\n");
 loggit ("outlet time set to $value"); 
 }
############################
sub on_setionvalue_clicked {
 my $value = $setpointr->get_text() + 0;
 if ($value > 200) {$value = 200;} # even that is past reasonable
 if ($value < 0) {$value = 0;} # @@@ adjust for minimum useful duty cycle
 $gasionport->write("5v$value\n");
 loggit ("ion pwm output duty cycle set to $value"); 
 }
############################

############################ This is where the action is when we are running #######################

my $testcounter = 0;
sub poll
{
 return 0 unless $running; # stop clean on stop button event
 
 # check for and move data here
 printstatus "counter:$testcounter";
 $testcounter++;
# loggit("$testcounter");  #test loggit(), not a good idea for a lot of output
 
# do uno basic data aq here
 getlines($unoport); # see about any data from uno
 foreach my $line (@lines)
  {
   next if ($line =~ m/#/); # ignore comment lines
#   print "$line\n" if $debug;
   @row = split  /\s+/, $line,9; # uno data is supposed to be 9 fields.  ms, a0-a5, c0,c1
   if (9 == scalar(@row) ) { # catch simple errors which often make length wrong
    $sthu->execute($runno,@row); # almost too good to be true...
    } else {
    print " debug bad format from uno: $line\n" if $debug; # ok, what messed up?
   }
 } #end uno aq loop
# do acq from fusecontrol uno
getlines($gasionport); # see about any data from uno
 foreach my $line (@lines)
  {
   next if ($line =~ m/#/); # ignore comment lines
  # print "$line\n" if $debug;
   @row = split  /\s+/, $line,8; # uno data is supposed to be 8 fields.  ms, a0-a3, c0, da, flags
   if (8 == scalar(@row) ) { # catch simple errors which often make length wrong
    $sthfc->execute($runno,@row); # almost too good to be true...
    } else {
    loggit " debug bad format from fusecontrol: $line\n" if $debug; # ok, what messed up?
   }
 } #end fc aq loop



 return $running; # calls us again later if we return true, not if false
}

# note to self from C dox:
#while (gtk_events_pending ())
#  gtk_main_iteration ();
# gets events handled while doing something

#Glib::Timeout::add($mainwin,250, \&poll); # start the calls to the poll subroutine
# requires $mainwin (or other widget reference) in gtk3 - didn't in gtk2 (and gtk2 used -> instead of ::)

############################
############################
sub initialize
{ # do various init tasks
 my $infotext;
 my ($camref, $camname);
 my $callback = \&loggit;
 createguifile();
# createguilocal(); # show a gui for the program (createguifile expects a .glade file to read from, local from here)
 getwidgets(); # get widget references for exchanging the data with them
 openlogfile(); #open a logfile named "the current date and time.log" for loggit function

# connect to database, prepare statement handles to execute later on
 $dbh = DBI->connect($DSN,$DBUser,$DBPass) or die "couldn't connect to $DSN:$!\n"; # no point staying up if we can't do this
 $sthu = $dbh->prepare('INSERT INTO uno VALUES (?,?,?,?,?,?,?,?,?,?);') or die ("Uno insert statement prepare failed:" . DBI::errstr . "\n");  
$sthfc = $dbh->prepare('INSERT INTO fusectl VALUES(?,?,?,?,?,?,?,?,?);') or die ("FuseControl insert statement prepare failed" . DBI::errstr . "\n");
# create cam control objects from array of samba host names
 %cams = (fusorpi => '', tricam => '', gridcam => ''); # start fresh each time program runs (do time consuming stuff now instead of later)
### @@@ we'll have to check gui schema at start time and maybe remove a couple cams then
 foreach $camname (keys %cams) {
  loggit "creating $camname" if $debug; # debug
  $cams{$camname} = Rcam->new(host_name=>$camname,log_callback=>$callback); # fill hash with references to objects
  unless ($cams{$camname}->init()) {  # and get them connected, checking for error
  loggit ("$camname couldn't initialize in init, deleting from operative list: $!");
  delete $cams{$camname}; # delete name from hash if fail (and report it)
    } # check error
  } # create-init cams loop end
 

# connect to uno for data aq
  $unoport = setupserial(finddevice($unoid)); # get connection to data aq uno
  $infotext = $unoport ? "connected to $unoid" : "connect to $unoid failed";
 loggit $infotext;
 #same for gas-ion control
  $gasionport = setupserial(finddevice($gasionid)); # get connection to gasion uno
  $infotext = $gasionport ? "connected to $gasionid" : "connect to $gasionid failed";
 loggit $infotext;
 
}

#########################################################################################
####### main program ####################################################################
#########################################################################################


 initialize(); # set stuff up
 loggit "post-init test"; # test it

#my $testcam = Rcam->new(host_name=>'badname'); #  
#$testcam->init(); # should error if network issues only - doesn't care what name yet if there is one

 Gtk3->main; # GUI event Floop - when we quit, we fall out and exit

END { # clean house
  $dbh->disconnect() if ($dbh); # try for clean
# port objects auto-close on undef, so no need (and better than a close)

 print "We died clean-happy.\n";
}

__DATA__ # marker for end of program and start of internal data file section (GTK3 xml here)

