/****************************************************************************/
/* Copyright (c) 2014 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : Interface for the Modtronix SBCEC65 controller                */
/* Filename : Modtronix.cc                                                  */
/* Author   : Henthorn                                                      */
/* Project  : i2MAP                                                         */
/* Version  : 1.0                                                           */
/* Created  : 12/03/2014                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <sys/select.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <errno.h>
#include <unix.h>
#include <math.h>

#include "LayeredControlIF.h"
#include "EventService.h"
#include "TimeP.h"
#include "Syslog.h"
#include "Modtronix.h"
#include "ModtronixMsg.h"
#include "ModtronixOutput.h"
#include "ModtronixLog.h"
#include "EZ17.h"
#include "KiPro.h"

#define          ROUNDF(f)    ( (f) < 0.0 ? (f) - 0.5 : (f) + 0.5 )
#define  COUNTS_2_VOLTS(c)    ( ((c) * 5.0) / 1024.0 )


//#include "epiBattOutput.h"

#define MODX          0                // Modtronix  index into _mod_fds[]
#define CTDX          1                // CTD pack   index into _mod_fds[]
#define EZSX          2                // EZ Stepper index into _mod_fds[]
#define MOD_FD        _mod_fds[MODX]
#define CTD_FD        _mod_fds[CTDX]
#define EZS_FD        _mod_fds[EZSX]
#define MOD_BUF       _mod_bufs[MODX]
#define CTD_BUF       _mod_bufs[CTDX]
#define EZS_BUF       _mod_bufs[EZSX]
#define CTD_PORT      54125
#define EZS_PORT      54126
#define LT_BANK  'b'
#define LT_FIRST 2
#define LT_LAST  5
#define ON     1
#define OFF    0
#define SET   "1"
#define CLEAR "0"

#define BOOTTIME 50                // in seconds
#define MAX_POWER_INTERRUPTS  3    // before power cycling camera and recorder

#define FILE_DEBUG False

// Driver interface to the Modtronix for use by other components.
// Triggered by CARL_IF events and periodic timer.
//
// The events are things that the system wants done right away, like turning
// things on and off. The timer triggers data logging from the devices.
//
// Periodic callback (interval typically between 3 and 10 seconds) used
// to poll the Modtronix for analog environmental data.
//

Modtronix::Modtronix(const char* name, const char* server_ip, unsigned port,
   unsigned interval)
   : PeriodicTask(name), _mod_port(port), _mod_ip(NULL), _name(NULL),
   _aborted(False), _layeredControl(NULL), _interval_msec(interval),
   _ez17(NULL), _ezCmd(0), _ezMove(False), _ezHome(False), _ezPos(0),
   _alarmCounts(0), _aja(NULL), _bootTimer(0), _recordReq(False),
   _clipnameReq(False), _target_light_level(0), _irisClosed(False),
   _savedMsg(NULL), _fStop(0), _powerInterrupts(0)
{
   _ppid = getppid();

   _mod_ip = strdup(server_ip);
   _name = strdup(name);

   memset((void*)MOD_BUF, 0, sizeof(MOD_BUF));
   memset((void*)CTD_BUF, 0, sizeof(CTD_BUF));
   memset((void*)EZS_BUF, 0, sizeof(EZS_BUF));

   Syslog::write("Modtronix - Opening Interface to servers...");
   try {
     _carlIF = new CARL_IF(CARL_IFServerName, 4);
      Syslog::write("Modtronix - Interface opened to CARLServer OK");
   }
   catch(...) {
      Syslog::write("Modtronix - UNABLE to open interfaces to CARLServer");
      _carlIF = 0;
   }

   try {
     _layeredControl = new LayeredControlIF(LayeredControlIFServerName, 2);
      Syslog::write("Modtronix - Interface opened to LayeredControlServer OK");
   }
   catch(...) {
      Syslog::write("Modtronix - UNABLE to open interfaces to LayeredControlServer");
      _layeredControl = 0;
   }

   _msgQ     = new ModtronixMsg();
   _output   = new ModtronixOutput();
   _log      = new ModtronixLog(this, DataLog::BinaryFormat, "ModtronixLog");
//   _battery = new epiBattOutput(1, SharedData::Read);

}

Modtronix::~Modtronix()
{
   if (_mod_ip)         delete _mod_ip;
   if (_name)           delete _name;
   if (_output)         delete _output;
   if (_carlIF)         delete _carlIF;
   if (_layeredControl) delete _layeredControl;
   if (_msgQ)           delete _msgQ;
   if (_log)            delete _log;
   if (_ez17)           delete _ez17;
   if (_aja)            delete _aja;
}

// Standard UDP socket setup
//
int Modtronix::init_socket(int port, long recv_to)
{
   int fd;
   if ( (fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 ) {
      Syslog::write("%s can't open mod datagram socket\n", _name, errno);
      return fd;
   }

   memset((void*)&_mod_addr, 0, sizeof(_mod_addr));
   _mod_addr.sin_family      = AF_INET;
   _mod_addr.sin_addr.s_addr = inet_addr(_mod_ip);
   _mod_addr.sin_port        = htons(port);

   // Connect client socket
   if ( connect(fd, (struct sockaddr *)&_mod_addr, sizeof(_mod_addr)) < 0) {
      Syslog::write("Modtronix can't connect dgram socket on port %d: error %d",
         port, errno);
      return -1;
   }

   if (recv_to > 0)
   {
      // For socket option SO_RCVTIMEO
      //
      _mod_tv.tv_sec  = 0;
      _mod_tv.tv_usec = recv_to;

      if ( setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
               (const void **)&_mod_tv, sizeof(struct timeval)) < 0) {
        Syslog::write("Modtronix::initialize() setsockopt SO_RCVTIMEOUT failed: %d", errno);
        return -1;
      }
      else
         return fd;     // All set up
   }
   else {
      return -1;
   }
}

// Initialize the socket for communication with the Modtronix board
//
int Modtronix::initialize()
{
   Boolean debug = FILE_DEBUG || False;

   // Set-up periodic callback and event callback. These are the points
   // that trigger action in this driver.
   //
   // Peridoically this task will poll the device for environmental data
   // and log (temp, humidity, etc.)
   //
   dprintf("%s Modtronix: initializing callbacks\n");
   addPeriodicCallback(_interval_msec,
      (CallbackMethod)Modtronix::mod_callback);

   // Occasionally, the server will request outputs to be toggled.
   //
   if (_carlIF) _eventService->subscribe(_carlIF, CARL_IF::MessagesWaiting,
      (EventService::Callback)Modtronix::action );

   dprintf("%s Modtronix: initializing socket port %d...\n", _name, _mod_port);

   // Set up socket for Modtronix control
   // Modtronix will receive and send data from/to us
   //
   if ( (_mod_fds[MODX] = init_socket(_mod_port, 20000L)) < 0 ) {
      Syslog::write("%s can't open main mod datagram socket\n", _name, errno);
      return _mod_fds[MODX];
   }

   Syslog::write("%s We are ready to receive mod datagrams from %s on port %d sockfd %d...\n", _name, _mod_ip,
      _mod_port, _mod_fds[MODX]);

#if 0
   // Set up socket for comms to ctd (serial 1 on the Modtronix board)
   // Modtronix will receive and send data from/to us
   //
   if ( (_mod_fds[CTDX] = init_socket(CTD_PORT, 100000L)) < 0 ) {
      Syslog::write("%s can't open ctd datagram socket\n", _name, errno);
      return _mod_fds[CTDX];
   }

   Syslog::write("%s We are ready to receive ctd datagrams from %s on port %d sockfd %d...\n", _name, _mod_ip,
      54125, _mod_fds[CTDX]);
#endif

   // Set up socket for comms to em (serial 1 on the Modtronix board)
   // Modtronix will receive and send data from/to us
   //
   if ( (_mod_fds[EZSX] = init_socket(EZS_PORT, 250000L)) < 0 ) {
      Syslog::write("%s can't open ez servo datagram socket\n", _name, errno);
      return _mod_fds[EZSX];
   }

   Syslog::write("%s We are ready to send/receive ez datagrams from %s on port %d sockfd %d...\n",
      _name, _mod_ip, EZS_PORT, _mod_fds[EZSX]);

   _ez17 = new EZ17(_mod_fds[EZSX]);
   _aja  = new KiPro("mod-aja", "134.89.32.54");

   _output->data.ready = True;
   _output->data.goodComms  = True;

   _output->write();
   _log->write();
   return 0;;
}

// Read message from the UDP port. Resulting message is placed in member device buffer.
// Return the result of the recv call (< 0 indicates an error, >= 0 the number of bytes).
// The recv call blocks for the time specified in the setsockopt call in initialize().
//
int Modtronix::read_msg(int idx)
{
   Boolean debug = FILE_DEBUG || False;

   memset(_mod_bufs[idx], '\0', sizeof(_mod_bufs[idx]));

   int sl = 0, nbytes = 0;
   long ms = Time::milliseconds();
   while ((sl = recv(_mod_fds[idx], _mod_bufs[idx]+nbytes, sizeof(_mod_bufs[idx]) - nbytes, 0)) > 0)
      nbytes += sl;

   ms = Time::milliseconds() - ms;
   dprintf("Modtronix::read_msg() answer: %d bytes, \"%s\" in %ldms\n",
      nbytes, _mod_bufs[idx], ms);

   return nbytes;
}


// Send a UDP message to the Modtronix.
// Return the result of the recv call (< 0 indicates an error, >= 0 the number of bytes)
//
int Modtronix::send_msg(int idx, const char* cmd)
{
   Boolean debug = FILE_DEBUG || False;

   int cl = strlen(cmd) + 1;

   strncpy(_mod_bufs[idx], cmd, sizeof(_mod_bufs[idx]));
   dprintf("Modtronix::send_msg - sending %d bytes: \"%s\"\n", cl, _mod_bufs[idx]);

   int sl = send(_mod_fds[idx], _mod_bufs[idx], strlen(_mod_bufs[idx])+1, 0);
   if (sl < 0 || sl != cl)
   {
      dprintf("Modtronix::send_msg - issue sending cmd %s: %d", _mod_bufs[idx], sl);
   }
   return sl;
}

// Auto-iris function uses the lights and fstop controls to converge
// on a target mean light level
//
#define MARGIN 0.1
int Modtronix::auto_iris()
{
   Boolean debug = True;
   int delta = 0;    // Motor position delta

   // Return if iris motor already moving
   //
   if (_ezMove)
      return 0;

   // Open or close the iris depending on the light levels
   // Lowest level when camera is off or booting is 24.4
   //
   if ( _output->data.video_mean >= 1.25 && 
         MARGIN < fabs((_output->data.video_mean - (float)_target_light_level)) )
   {
      // Get the current motor position
      //
      EZ17Watch();

      if (_auto_iris_done)
      {
         // Required Iris adjustment just detected
         dprintf("Modtronix::auto_iris() triggered - measured:%f target:%d",
            _output->data.video_mean, _target_light_level);
      }

      _auto_iris_done    = False;
      if (_output->data.video_mean > _target_light_level)
      {
         if (_ezPos < 620) delta = 20;     // increment/decrement by 20 motor counts
         // else lower the lights?
      }
      else
      {
         if (_ezPos >  40) delta = -20;
         // else raise the lights?
      }
      dprintf("Modtronix::auto_iris() measured:%f target:%d, adjusting by %d",
         _output->data.video_mean, _target_light_level, delta);
   }
   else
   {
      if (!_auto_iris_done)
      {
         // Required Iris adjustment finished
         dprintf("Modtronix::auto_iris() satisfied - measured:%f target:%d",
            _output->data.video_mean, _target_light_level);
      }

      _auto_iris_done    = True;
   }

   // Adjust iris if necessary
   //
   if (delta != 0) _ez17->goto_steps(_ezPos + delta);

   return delta;
}

void Modtronix::abort()
{
   if (!_aborted)
   {
      try {
         if (!_layeredControl)
            _layeredControl = new LayeredControlIF(LayeredControlIFServerName);

         if (_layeredControl && !(_layeredControl->abortingMission()))
         {
            Syslog::write("\nModtronix::abort() - ABORTING ON WATER ALARM!\n");
            _layeredControl->abortMission();
            _aborted = True;
            delete _layeredControl;
         }
      }
      catch(...)
      {
         Syslog::write("Modtronix - UNABLE to get LayeredControlIF");
         _layeredControl = 0;
      }
   }
}

// Periodic callback function.
// Process messages in the queue, if any. Then request environmental and
// state data from Modtronix SBC
//
void Modtronix::mod_callback(void)
{
   Boolean debug = FILE_DEBUG || False;
   _output->read();

   // Exit if parent dies
   //
   pid_t pid = getppid();
   if (pid != _ppid)
   {
      Syslog::write("Modtronix::mod_callback() - parent exited, so will I");
      exit(0);
   }

   // Now, read the usual data from the device, log it, and perhaps even act on it
   //
   dprintf("Modtronix::mod_callback - polling for data and logging\n");

   // Faults and switches
   //
   send_msg(MODX, "%n25");   // Pin F0
   read_msg(MODX);
   _output->data.sw0 = atof(MOD_BUF);
   dprintf("Modtronix::mod_callback - \t\t\t\tSWO = %fV\n",
      _output->data.sw0);

   send_msg(MODX, "%n26");   // Pin F1
   read_msg(MODX);
   _output->data.sw1 = atof(MOD_BUF);
   dprintf("Modtronix::mod_callback - \t\t\t\tSW1 = %fV\n",
      _output->data.sw1);

   send_msg(MODX, "%n27");   // Pin F2
   read_msg(MODX);
   _output->data.sw2 = atof(MOD_BUF);
   dprintf("Modtronix::mod_callback - \t\t\t\tSW2 = %fV\n",
      _output->data.sw2);


   // Intermediate A/D values 
   // 
   float v, ft;
   int it;

   // Temperature and Humidity
   //
   send_msg(MODX, "%n12");   // Pin A2
   read_msg(MODX);
   // 0 to 100% in range 0.5v to 2.5v
   v = COUNTS_2_VOLTS(atoi(MOD_BUF));
   _output->data.humidity = (v - 0.5) * 50.0;
   dprintf("Modtronix::mod_callback - \t\tHumidity = %.2f\n", _output->data.humidity);

   send_msg(MODX, "%n13");   // Pin A3
   read_msg(MODX);
   // voltage = 0.537V + 0.01V * degrees C
   // temp = (voltage - 0,537)/0.01
   // 
   v = COUNTS_2_VOLTS(atoi(MOD_BUF));
   ft = (v - 0.537) / 0.01;
   //it = ROUNDF(ft); //ft < 0.0 ? (int)(ft - 0.5) : (int)(ft + 0.5);
   //_output->data.temp = it;
   ft = ROUNDF(ft); //ft < 0.0 ? (int)(ft - 0.5) : (int)(ft + 0.5);
   _output->data.temp = ft;
   dprintf("Modtronix::mod_callback - \t\t\tBuf = %s, V = %.3f, Temp = %.2f\n", 
               MOD_BUF, v, _output->data.temp);

   // Video light levels in percentage of the 5V max
   //
   send_msg(MODX, "%n10");   // Pin A0
   read_msg(MODX);
   // 0 to 100% in range 0v to 5v
   it = atoi(MOD_BUF);
   v = COUNTS_2_VOLTS(it);
   _output->data.video_max = (int)(v*100. / 5.);
   dprintf("Modtronix::mod_callback - \t\tPeak Video = %.2f (%d counts)\n",
              _output->data.video_max, it);

   send_msg(MODX, "%n11");   // Pin A1
   read_msg(MODX);
   // 0 to 100% in range 0v to 5v
   it = atoi(MOD_BUF);
   v = COUNTS_2_VOLTS(it);
   _output->data.video_mean = (int)(v*100. / 5.);
   dprintf("Modtronix::mod_callback - \t\tMean Video = %.2f (%d counts)\n",
              _output->data.video_mean, it);



   // Forward and Aft water alarms
   //
   send_msg(MODX, "%n29");   // Pin F4
   read_msg(MODX);
   _output->data.h2o_fwd = atof(MOD_BUF);
   // Alarm level is voltage > 0.5
   dprintf("Modtronix::mod_callback - \t\t\t\tFwd H2O = %fV of 0.5V\n",
      _output->data.h2o_fwd);

   send_msg(MODX, "%n24");   // Pin A5
   read_msg(MODX);
   _output->data.h2o_aft = atof(MOD_BUF);
   // Alarm level is voltage > 0.5
   dprintf("Modtronix::mod_callback - \t\t\t\tAft H2O = %fV of 0.5V\n",
      _output->data.h2o_aft);


   // Record water alarm and abort mission if water
   // sensors indicate water in the housing
   //
   if (_output->data.h2o_aft > 0.5 || _output->data.h2o_fwd > 0.5)
   {
      Syslog::write("Modtronix::mod_callback - AftH2O:%fV FwdH2O:%f",
         _output->data.h2o_aft, _output->data.h2o_fwd);
      if (_alarmCounts++ >= 4)
      {
         _output->data.h2o_alarm = 1;
         abort();
      }
   }
   else
   {
      _alarmCounts = 0;
      _output->data.h2o_alarm = 0;
   }

   if (_output->data.h2o_alarm == 1)
      Syslog::write("Modtronix - WATER ALARM ACTIVATED!");


   // Check and log power switches
   //
   ctd_state(1);
   _output->data.camera_power = power_state(1);
   for (int j = 0; j < 4; j++) light_state(j, 1);

   // Log the PWM settings
   send_msg(MODX, "%wf4");
   read_msg(MODX);
   dprintf("Modtronix::mod_callback - \t\tPWM mode = %s\n", MOD_BUF);
   _pwm_mode = atoi(MOD_BUF);

   send_msg(MODX, "%wf0");
   read_msg(MODX);
   dprintf("Modtronix::mod_callback - \t\tPWM freq = %s\n", MOD_BUF);
   _pwm_freq = atoi(MOD_BUF);

   send_msg(MODX, "%w12");
   read_msg(MODX);
   dprintf("Modtronix::mod_callback - \t\tPWM value = %s\n", MOD_BUF);
   _pwm_value = atoi(MOD_BUF);


   // Look in on iris motor
   //
   if (0 == EZ17Handler(NULL) && _savedMsg)
   {
      // Motor done with previous move. Execute saved move if there is one.
      // 
      Syslog::write("Modtronix - There is a saved msg for the EZ17");
      auto_iris_off();
      EZ17Handler(_savedMsg);
      _output->data.motor_action = 1;

      // Reset _savedMsg
      // 
      delete _savedMsg;
      _savedMsg = NULL;
   }

   if (_output->data.camera_power != 0 && _target_light_level > 0) auto_iris();
   _output->data.motor_position = _ezPos;
   _output->data.motor_action   = _ezMove;



   // Log AJA information
   //
   if (_output->data.recorder_ready = _aja->ready())
   {
       _output->data.recorder_power = 1;
       _powerInterrupts = 0;

       timecode();
       available_media();
       _output->data.recording = _aja->record_button();
   }
   else
   {
       // Oh-oh. Recorder power switch is on, but the recorder
       // is not responding. Flag for now.
       // Not a problem when the recorder is booting-up
       // 
       if (_bootTimer == 0 && _output->data.recorder_power == 1 &&
           _command.recorder_power == 1)
       {
          Syslog::write("Modtronix - AJA power yanked? (power:%d ready:%d)",
            _output->data.recorder_power, _output->data.recorder_ready);
          _powerInterrupts++;
       }
       else
       {
          _powerInterrupts = 0;
       }
   }

   // Recorder has likely powered-down unexpectedly.
   // Let's cycle power to camera and recorder.
   // 
   if (MAX_POWER_INTERRUPTS < _powerInterrupts)
   {
      _powerInterrupts = 0;
      Syslog::write("Modtronix - Power interruption! Cycle power...");
      Syslog::write("Modtronix - Recorder power off...");
      recorder_switch(OFF);
      Syslog::write("Modtronix - Camera power off...");
      power_switch(OFF);
      sleep(1);
      Syslog::write("Modtronix - Camera power on...");
      power_switch(ON);
      sleep(2);
      Syslog::write("Modtronix - Recorder power on...");
      recorder_switch(ON);
   }

   // If the recorder is booting, let's check it's progress
   //
   if (_bootTimer != 0)
   {
      if (time(NULL) > _bootTimer)
      {
         // Recorder should be ready by now
         //
         if (_output->data.recorder_ready)
         {
            Syslog::write("Modtronix - AJA is up");
            connect_aja();
            //sync();
            if (_recordReq) and_action();
            if (_clipnameReq) clipname("");
         }
         else
         {
            Syslog::write("Modtronix - AJA is overdue!");
         }
         _bootTimer = 0;     // reset
      }
   }
   // Check and handle alarm conditions (water probes)
   //

#if 0
   _battery->read();
   Syslog::write("Modtronix::mod_callback() - Bus voltage = %f", _battery->data[0].voltage);
#endif


   // Output and Log all data
   //
   _output->write();
   _log->write();

   // Check the output state against the commanded state
   //
   consistency_check();

   // Close the iris unless the recorder is on
   // Temporarily disabling this feature.
   //
   if ( False && (0 == _output->data.recorder_power) && (_output->data.camera_power == 1) )
   {
      // Iris is open and we're not already homing...
      // close the iris
      //
      if (!_irisClosed && !_ezHome)
      {
         Syslog::write("Modtronix - recorder off and iris open, so I am closing iris...");
         ModtronixMsg::Message msg;
         msg._msg = ModtronixMsg::EZHome;
         EZ17Handler(&msg);
      }
   }
}

// Check the output state against the commanded state
//
void Modtronix::consistency_check()
{
   int corrections = 0;
   if (_output->data.camera_power == OFF && _command.camera_power == ON)
   {
      Syslog::write("Modtronix::consistency_check() - camera_power: %d != desired state of %d",
         _output->data.camera_power, _command.camera_power);
      // Attempt to correct
      power_switch(_command.camera_power);
      corrections++;
   }

   if (_bootTimer == 0 &&
       _output->data.recorder_power == OFF &&  _command.recorder_power == ON)
   {
      Syslog::write("Modtronix::consistency_check() - recorder_power: %d != desired state of %d",
         _output->data.recorder_power, _command.recorder_power);
      // Attempt to correct
      recorder_switch(_command.recorder_power);
      corrections++;
   }

   if (_output->data.ctd_power == OFF &&  _command.ctd_power == ON)
   {
      Syslog::write("Modtronix::consistency_check() - ctd_power: %d != desired state of %d",
         _output->data.ctd_power, _command.ctd_power);
      // Attempt to correct
      ctd_switch(_command.ctd_power);
      corrections++;
   }

   // Check the light switches. Use the light_switch function to correct the states.
   // 
   int ls = 0;
   for (int i = 0; i < 4; i++)
      if ( !(ls = light_state(i, _command.light_power[i])) )
      {
         Syslog::write("Modtronix::consistency_check() - light_power[%d] is %d: state is %d",
            i, _command.light_power[i], _output->data.light_power[i]);
         // Attempt to correct. Any one mismatched is enough to trigger this
         //light_switch(_command.light_power[i]);
         corrections++;
         break;
      }

   // Check light level after possibly fixing the light switches since
   // the light_switch command automatically sets the light level to 50%
   // 
   if (_output->data.light_level < _command.light_level)
   {
      Syslog::write("Modtronix::consistency_check() - light_level: %d != desired state of %d",
         _output->data.light_level, _command.light_level);
      // Attempt to correct
      light_level((char)_command.light_level);
      corrections++;
   }

   if (_output->data.recording == 0  &&  _command.recording != 0)
   {
      Syslog::write("Modtronix::consistency_check() - recording: %d != desired state of %d",
         _output->data.recording, _command.recording);

      if (_command.recording)
         and_action();
      else
         cut();
   }

#if 0
   if (_output->data.mode != _command.mode)
   {
      Syslog::write("Modtronix::consistency_check() - mode: %d != %d",
         _output->data.mode, _command.mode);
   }
#endif

   if (corrections > 0) _output->write();

   return;
}


// Triggered callback function. Dispatch messages.
//
void Modtronix::action(TaskInterface *taskInterface, EventCode eventCode)
{
   Boolean debug = FILE_DEBUG || False;
   char postfix[32];

   _output->read();
   if (eventCode != CARL_IF::MessagesWaiting)
   {
      Syslog::write("Modtronix::action - unrecognized event (%d)", eventCode);
   }

   // Process messages in queue
   //
   int nmsgs = 0;
   ModtronixMsg::Message msg;
   while (_msgQ->read(&msg) > 0) {

      dprintf("Modtronix::action: Got msg: %d", msg._msg);
      nmsgs++;
      switch (msg._msg) {

      case ModtronixMsg::CameraOn:
         power_switch(ON);
         break;

      case ModtronixMsg::CameraOff:
         auto_iris_off();
         power_switch(OFF);
         break;

      case ModtronixMsg::RecorderOn:
         power_switch(ON);            // Camera must be on first
         recorder_switch(ON);
         Syslog::write("Modtronix::action() - recorder is booting...");
         break;

      case ModtronixMsg::RecorderOff:
         recorder_switch(OFF);
         break;

      case ModtronixMsg::Action:
         and_action();
         break;

      case ModtronixMsg::Cut:
         Syslog::write("Modtronix::action() - cut");
         cut();
         break;

      case ModtronixMsg::RecordMode:
         record_mode(1);
         break;

      case ModtronixMsg::DataMode:
         record_mode(0);
         break;

      case ModtronixMsg::Timecode:
         timecode();
         break;

      case ModtronixMsg::AvailableMedia:
         available_media();
         break;

      case ModtronixMsg::Sync:
         sync();
         break;

      case ModtronixMsg::Clipname:
         if (strlen(msg._str) > 0)
         {
            // Use the name provided as a postfix to a standard prefix
            strcpy(postfix, msg._str);
         }
         else
         {
            // Otherwise, use the provided depth and the current Fstop
            sprintf(postfix, "_%03dm_F%03d", msg._int, _fStop);
         }
         clipname(postfix);
         break;

      case ModtronixMsg::LightsOn:
         auto_iris_off();
         light_switch(ON);
         break;

      case ModtronixMsg::LightsOff:
         auto_iris_off();
         light_switch(OFF);
         break;

      case ModtronixMsg::LightLevel:
         // Track the commanded state
         _command.light_level = msg._int;

         light_level(msg._int);
         break;

      case ModtronixMsg::CtdOn:
         ctd_switch(ON);
         break;

      case ModtronixMsg::CtdOff:
         ctd_switch(OFF);
         break;

      case ModtronixMsg::EZAutoFS:
         Syslog::write("Modtronix::action() - auto-iris level:%.2f",
            msg._float);
         if (msg._float < 0.2)
         {
            auto_iris_off();
         }
         else if (msg._float <= 3.8)
         {
            _target_light_level = 3.8;
            _fStop = 777;
         }
         else
         {
            _target_light_level = msg._float;
            _fStop = 777;
         }

         break;

      case ModtronixMsg::EZFStop:
         _fStop = msg._int;  // save the most recent fstop setting
         if (False && !_aja->ready())
         {
            Syslog::write("Modtronix::action() - iris remains closed unless recorder is on");
            break;
         }
         // Fall through to next case
      case ModtronixMsg::EZHome:
         if (_ezMove)
         {
            // Save the command to use when the current move is finished
            // 
            Syslog::write("Modtronix::action() - saving EZ17 message");
            if (!_savedMsg) _savedMsg = new ModtronixMsg::Message();

            _savedMsg->_msg =   msg._msg;
            _savedMsg->_int =   msg._int;
            _savedMsg->_float = msg._float;
            break;
         }
         Syslog::write("Modtronix::action() - EZ17 message: %d %d %f",
            msg._msg, msg._int, msg._float);
         auto_iris_off();
         EZ17Handler(&msg);
         _output->data.motor_action = 1;
         break;

      default:
         nmsgs--;
         Syslog::write("Modtronix::action() - Invalid message: %d", msg._msg);
         break;
      }
   }
   _output->write();

   // Output and Log updated state and data
   //
   mod_callback();

}

// Set Modtronix pin to the state
// set_pin('f', 3, 1)  =>  sets Modtronix pin F3 to 1
// Returns True if successful
//
Boolean Modtronix::set_pin(char bank, char pin, char state)
{
   Boolean debug = FILE_DEBUG || False;

   char switch_buf[16];
   sprintf(switch_buf, "%c%d=%d", bank, pin, state);
   Syslog::write("Modtronix::set_pin() - %s", switch_buf);

   for (int i = 0; i < 3; i++) {
      send_msg(MODX, switch_buf);
      if (check_pin(bank, pin, state))
      {
         return True;                 // State of ctd == state parameter? That's good!
      }
      else
         dprintf("Modtronix::set_pin(%s) failed... - try again", switch_buf);
   }
   dprintf("Modtronix::set_pin(%d) - failed!", state);
   return False;
}

// Check Modtronix pin and compare it to the state
// check_pin('f', 3, 1)  =>  checks value of Modtronix pin F3 is 1
// Returns True if equal
//
Boolean Modtronix::check_pin(char bank, char pin, char state)
{
   Boolean debug = FILE_DEBUG || False;

   char *cmp = (state != OFF)? SET : CLEAR;

   char buf[16];
   sprintf(buf, "%%%c0%d", bank, pin, state);
   for (int i = 0; i < 3; i++) {
      int n;
      send_msg(MODX, buf);
      if ((n = read_msg(MODX)) < 1)
      {
         dprintf("Modtronix::check_pin() - No response from modtronix\n");
         continue;   // Next try
      }
      else
         break;

   }

   // Return 0 if the value from the modtronix does not match
   // the input parameter
   //
   dprintf("Modtronix::check_pin() %s ... %s", buf, MOD_BUF);
   if (strncmp(MOD_BUF, cmp, 1))
      return False;
   else
      return True;
}

void Modtronix::ctd_switch(int state)
{
   if (state != OFF)
      state = 1;

   // Track the commanded state
   _command.ctd_power = state;

   if (set_pin('b', 0, state))
   {
      _output->data.ctd_power = state;
   }
   else
      Syslog::write("Modtronix::ctd_switch(%d) - failed!", state);

   return;
}

// Return 1 if the state of the CTD switch is equal to state
//
int Modtronix::ctd_state(int state)
{
   if (state != OFF)
      state = 1;

   if (check_pin('b', 0, state))
   {
      _output->data.ctd_power = state;
   }
   else
   {
      _output->data.ctd_power = 1 - state;
   }
   return (_output->data.ctd_power == state);
}

// Switch on/off the recorder.
// The recorder is turned on by holding the pin low for less than 2 seconds.
// Off by holding it for 3 seconds.
// As one would expect...
// recorder_switch(1) => turn on  the recorder
// recorder_switch(0) => turn off the recorder
//
void Modtronix::recorder_switch(int state)
{
   // Track the commanded state
   _command.recorder_power = state;

   int on = state;
   int sleep_time;

   Boolean debug = FILE_DEBUG || False;

   dprintf("Modtronix::recorder_switch(%d)", state);

   // If command is ON and we're already on or are booting up, we can return
   //
   if ( on && (_bootTimer != 0 || _output->data.recorder_ready) )
   {
      Syslog::write("Modtronix::recorder_switch() - Recorder is either booting or is already on and ready");
      return;
   }

   // The timing is different depending on the request
   //
   if (ON == state)
      sleep_time = 2; //200;    // Turn recorder on with just a momentary
   else
      sleep_time = 4; //000;    // Turn recorder off with an extended push

   // The recorder switch is driven low, so to initiate a change, the state
   // that is passed to the modtronix board is 0.
   //
   state = 0;

   // Sometimes this action does not turn the recorder on,
   // so let's just do it a few times since there's not harm
   // in turning it on if is' already on.
   //
   int result;
   for (int i = 0; i < 1 ; i++)
   {
      set_pin('b', 7, state);
      //System::milliSleep(sleep_time*1000);

      long st = time(NULL) + sleep_time;
      while (time(NULL) < st) sleep(1);
      set_pin('b', 7, 1 - state);
      if (on == OFF) break;         // Just one time needed for OFF

      //System::milliSleep(200);      // Small delay between each toggle
      //sleep(1);
   }

   // Calculate the time at which the recorder should be ready to use.
   // It is checked and acted upon in mod_callback()
   //
   if (on)
      _bootTimer = time(NULL) + BOOTTIME;
   else
      (_bootTimer = 0) || (_recordReq = False) || (_clipnameReq = False);

   _output->data.recorder_power = state;

   return;
}

// Flip the camera power on or off.
// Do not turn on if the AUV bus voltage is below 25.
//
void Modtronix::power_switch(int state)
{
#if 0
   // Return if bus voltage < 25
   //
   _battery->read();
   if (_battery->data[0].voltage < 25.)
   {
      Syslog::write("Modtronix::power_switch(%d) - Bus voltage of %f < 25., not powering on",
         state, _battery->data[0].voltage);
      return;
   }
#endif

   if (state != OFF)
      state = ON;

   // Track the commanded state
   _command.camera_power = state;

   // The power switch on pin B1 is driven high to turn on
   //
   if (set_pin('b', 1, state))
   {
      _output->data.camera_power = state;
      if (state == ON) sleep(1);
      power_state(state);
   }
   else
      Syslog::write("Modtronix::power_switch(%d) - failed!", state);

   return;
}

// Returns 1 if the power state is the same
// as the queried state, otherwise 0.
//
// E.g., return 1 if power_state(OFF) is 0
// E.g., return 0 if power_state(ON) is 0
//
int Modtronix::power_state(int state)
{
   if (check_pin('b', 1, state))
   {
      _output->data.camera_power = state;
   }
   else
   {
      _output->data.camera_power = 1 - state;
   }

   return (_output->data.camera_power == state);
}

// Adjust the brightness of the lights. 0=>min, 100=>max
// Levels of all the lights controlled by single output C1
//
void Modtronix::light_level(char level)
{
   Boolean debug = FILE_DEBUG || True;

   if (level > 90) level = 90;
   char pwmval = 2.55*level;

   char buf[10];
   sprintf(buf, "w2=%d", pwmval);
   Syslog::write("Modtronix::light_level(%d) - %s", level, buf);
   send_msg(MODX, buf);

   _output->data.light_level = level;

   // Ensure that the PWM mode and frequency are set to
   // 8-bit mode (wmr=8) and 9.766 kHz (wfr=1) 
   // 
   send_msg(MODX, "wmr=8&wfr=1");   // 8-bit mode and 9.766 kHz
}

// TODO: Do not turn on if the AUV bus voltage is below 25.
//
// Flip all the lights on or off, with a small delay between each.
// The delay is a second for lights on, 0.1 second for lights off.
// Also, give each light 3 tries. Pins B2 to B5.
//
void Modtronix::light_switch(int state)
{
   Boolean debug = FILE_DEBUG || False;
   int on = state;

   // Return if bus voltage < 25
   //
   // The light switches are driven low, so invert the state
   //
   int st;         // milliseconds to sleep between each switch
   if (state != OFF)
   {
      st = 500;   // On: sleep 0.5 second between each switch
      state = OFF; // invert
   }
   else
   {
      st = 100;    // Off: sleep 0.1 seconds between each switch
      state = ON;  // invert
   }
   //if (on) light_level(on*50);   // Set level to 50% if turning on, else 0.

   // Track the commanded state
   _command.light_power[0] = _command.light_power[1] = _command.light_power[2] = _command.light_power[3] = on;

   // Turn on one light at a time, with a small delay between each
   //
   int result;
   for (int i = LT_FIRST; i <= LT_LAST; i++)
   {
      set_pin(LT_BANK, i, state);

      Syslog::write("Modtronix::light_power[%d] = %d",
         i-LT_FIRST, on);

      // Small delay between each
      //
      System::milliSleep(st);
   }

   return;
}


// Returns 1 if all the light state is the same
// as the queried state, otherwise 0.
//
// E.g., return 1 if light_state(0, OFF) is 0 (the state of the first light)
// E.g., return 0 if light_state(4, ON) is 0 (the state of the last light)
//
int Modtronix::light_state(int light, int state)
{
   Boolean debug = False || FILE_DEBUG;

   // The light switches are driven low. Asking if the state is '1', or on
   // means we return true if the value returned from the modtronix is 0.
   //
   int inverted = 1 - state;  // check against the inverted value

   if (check_pin(LT_BANK, light+LT_FIRST, inverted))
   {
      _output->data.light_power[light] = state;
      dprintf("Modtronix::light_state(%d, %d) == %d (%s)", light, state, 1, _mod_bufs[MODX]);
   }
   else
   {
      _output->data.light_power[light] = inverted;
      dprintf("Modtronix::light_state(%d, %d) == %d (%s)", light, state, 0, _mod_bufs[MODX]);
   }

   return (_output->data.light_power[light] == state);
}

void Modtronix::and_action()
{
   if (_bootTimer > 0)
   {
      // User often requests recording while recorder is booting
      // so we'll defer the action until ready.
      //
      Syslog::write("Modtronix - recorder is booting up");
      _recordReq = True;
   }
   else
   {
      Syslog::write("Modtronix::action() - ");
      _aja->record_button(1);

      // Track the commanded state
      _command.recording = 1;

      _recordReq = False;
   }
   if (!_aja->record_button())
   {
      Syslog::write("Modtronix::action() - Aja not recording! Press record again");
      _aja->record_button(1);
   }
}

void Modtronix::cut()
{
   if (_bootTimer > 0)
   {
      // User often requests recording while recorder is booting
      // so we'll cancel the deferred action.
      //
      Syslog::write("Modtronix - recorder is booting up");
      _recordReq = False;
   }
   else
   {
      Syslog::write("Modtronix::cut() - ");
      _aja->record_button(0);

      // Track the commanded state
      _command.recording = 0;
      System::milliSleep(1500);
   }
}

void Modtronix::record_mode(int mode)
{
   Syslog::write("Modtronix::record_mode(%d) - ", mode);
   if (mode != 0)
     _aja->record_mode();
   else
     _aja->data_mode();

    // Track the commanded state
    _command.mode = mode;
}

void Modtronix::connect_aja()
{
   _aja->connect();
}

void Modtronix::timecode()
{
   Boolean debug = False;
   _output->data.timecode = _aja->get_timecode();
   dprintf("Modtronix::timecode() - %f\n", _output->data.timecode);

   if (!_output->data.recorder_ready)
     dprintf("Modtronix::timecode() - recorder not ready\n");
}

void Modtronix::available_media()
{
   Boolean debug = False;
   _output->data.available_media = _aja->get_available_media();
   dprintf("Modtronix::available_media() - %d", _output->data.available_media);

   if (!_output->data.recorder_ready)
     dprintf("Modtronix::available_media() - recorder not ready\n");
}

void Modtronix::sync()
{
   if (_output->data.recorder_ready)
   {
     Syslog::write("Modtronix::sync() - ");
     _aja->time_sync();
   }
   else
     Syslog::write("Modtronix sync - recorder not ready");
}

void Modtronix::clipname(char *str)
{
   if (_bootTimer > 0)
   {
      // User often requests new clipname while recorder is booting
      // so we'll defer the action until ready.
      //
      Syslog::write("Modtronix - recorder is booting up");
      _clipnameReq = True;
   }
   else
   {
      // If we're recording, stop first
      //
      Boolean stop_start = _aja->record_button();
      if (stop_start) cut();

      Syslog::write("Modtronix::clipname(%s) - ", str);
      _aja->set_clip_name(str);
      System::milliSleep(500);
      _clipnameReq = False;

      // If we were recording, then continue
      //
      if (stop_start) and_action();
   }

   if (!_output->data.recorder_ready)
      Syslog::write("Modtronix clipname - recorder not ready");

}
