/****************************************************************************/
/* 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"

//#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   //seconds

#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)
{
   _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));

   _output = new ModtronixOutput();

   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;
}

// 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;
}

// check for CTD data on ctd port
//
int Modtronix::get_ctd_data()
{
   Boolean debug = FILE_DEBUG || True;

   int nbytes = read_msg(CTDX);
   if (nbytes > 0)
   {
      // Parse CTD data into output structure
      dprintf("Modtronix::mod_callback - %d bytes of CTD data: %s\n", nbytes, _mod_bufs[CTDX]);
   }
   else
      dprintf("Modtronix::mod_callback - no CTD data pending\n");

   send_msg(CTDX, "TS\r\n");

   return nbytes;
}

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");

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

   send_msg(MODX, "%n23");   // Pin A3
   read_msg(MODX);
   // Temp voltage = 0.5V + 0.01V * degrees C
   _output->data.temp = (atof(MOD_BUF) - 0.5) / 0.01;
   dprintf("Modtronix::mod_callback - \t\t\tTemp = %f\n", _output->data.temp);

   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);

#if 1
   //////////////////////// UDP test /////////////////////////
   // Alternate setting light 1 pin high and low. Speed is
   // determined by the timeout seting of the socket. Let's see
   // how many failed deliveries we encounter.
   //
   printf("Running UDP test...\n");
   int test_state = 1;
   for (unsigned long i = 0; i < 2000000L; i++)
   {
      // A call to set_pin() results in three UDP messages:
      // (1) set the indeicated pin to the indicates value
      // (2) request the current value of the pin to ensure it is set or not
      // (3) wait up to 1/50 of a second for the answer to (2)
      // set_pin() will squawk if any one of 3 UDP messages do not make it
      //
      set_pin(LT_BANK, LT_FIRST, test_state);
      test_state = 1 - test_state;    // flip the state next time
      if ((i % 500) == 0) {printf("%ld\r", i); fflush(stdout);}
   }
   exit(0);
   ///////////////////////////////////////////////////////////
#endif

   // 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 for CTD data on ctd port
   //
   //get_ctd_data();

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

   EZ17Handler(NULL);
   _output->data.motor_position = _ezPos;
   _output->data.motor_action = _ezMove;

   // Get AJA information
   //
   if (_output->data.recorder_ready = _aja->ready())
      _output->data.recorder_power = 1;
   timecode(); //_output->data.timecode = _aja->get_timecode();
   available_media(); //_output->data.available_media = _aja->get_available_media();

   // 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");
            _bootTimer = 0;     // reset
            if (_recordReq) and_action();
            if (_clipnameReq) clipname("");
         }
         else
         {
            Syslog::write("Modtronix - AJA is overdue!");
         }
      }
   }
   // Check and handle alarm conditions (water probes)
   //

#if 0
   _battery->read();
   Syslog::write("Modtronix::mod_callback() - Bus voltage = %f", _battery->data[0].voltage);
#endif
   // Log all data, even output pins and pwms
   //
   _output->write();
   _log->write();

}

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

   _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:
         power_switch(OFF);
         break;

      case ModtronixMsg::RecorderOn:
         recorder_switch(ON);
         break;

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

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

      case ModtronixMsg::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:
         clipname(msg._str);
         break;

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

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

      case ModtronixMsg::LightLevel:
         light_level(msg._int);
         break;

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

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

      case ModtronixMsg::EZHome:
      case ModtronixMsg::EZFStop:
      case ModtronixMsg::EZAutoFS:
         Syslog::write("Modtronix::action() - EZ17 message: %d", msg._msg);
         EZ17Handler(&msg);
         _output->data.motor_action = 1;
         break;

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

   // Log all 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 || True;

   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);
   dprintf("Modtronix::check_pin() %s ...", buf);
   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
   //
   if (strncmp(MOD_BUF, cmp, 1))
      return False;
   else
      return True;
}

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

   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 recroder
// recorder_switch(0) => turn off the recroder
//
void Modtronix::recorder_switch(int state)
{
   int on = state;
   int sleep_time;

   Boolean debug = FILE_DEBUG || True;

   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 = on;
   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;

   // 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)
{
   if (level > 100) level = 100;

   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;
}

// 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 || True;
   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 = 1000;   // On: sleep 1 second between each switch
      state = OFF; // invert
   }
   else
   {
      st = 100;    // Off: sleep 0.1 seconds between each switch
      state = ON;  // invert
   }
   light_level(on*50);   // Set level to 50% if turning on, else 0.

   // Turn on one light at a time, with a small delay between each
   //
   int result;
   for (int i = LT_FIRST; i <= LT_LAST; i++)
   {
      if (set_pin(LT_BANK, i, state))
         _output->data.light_power[i-LT_FIRST] = on;
      else
         _output->data.light_power[i-LT_FIRST] = 1 - on;

      Syslog::write("Modtronix::light_power[%d] = %d",
         i-LT_FIRST, _output->data.light_power[i-LT_FIRST]);

      // 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(2, OFF) is 0
// E.g., return 0 if light_state(3, ON) is 0
//
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.
   //
   if (state != OFF) state = OFF;  // inverted

   if (check_pin(LT_BANK, light, state))
   {
      _output->data.light_power[light] = 1 - state;
   }
   else
   {
      _output->data.light_power[light] = state;
   }

   int result = (_output->data.light_power[light] == state);
   dprintf("Modtronix::light_state(%d, %d) = %d\n", light, state, result);

   return result;
}

void Modtronix::and_action()
{
   if (_output->data.recorder_ready)
   {
      Syslog::write("Modtronix::action() - ");
      _aja->record_button(1);
      _recordReq = False;
   }
   else 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 - recorder not on");
}

void Modtronix::cut()
{
   if (_output->data.recorder_ready)
   {
      Syslog::write("Modtronix::cut() - ");
      _aja->record_button(0);
   }
   else 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 - recorder not ready");
}

void Modtronix::record_mode(int mode)
{
   if (_output->data.recorder_ready)
   {
     Syslog::write("Modtronix::record_mode(%d) - ", mode);
     if (mode != 0)
       _aja->record_mode();
     else
       _aja->data_mode();
   }
   else
     Syslog::write("Modtronix - recorder not ready");
}

void Modtronix::timecode()
{
   Boolean debug = False;
   if (_output->data.recorder_ready)
   {
     _output->data.timecode = _aja->get_timecode();
     dprintf("Modtronix::timecode() - %f\n", _output->data.timecode);
   }
   else
     dprintf("Modtronix::timecode() - recorder not ready\n");
}

void Modtronix::available_media()
{
   Boolean debug = False;
   if (_output->data.recorder_ready)
   {
     _output->data.available_media = _aja->get_available_media();
     dprintf("Modtronix::available_media() - %d", _output->data.available_media);
   }
   else
     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 - recorder not ready");
}

void Modtronix::clipname(char *str)
{
   if (_output->data.recorder_ready)
   {
      Syslog::write("Modtronix::clipname(%s) - ", str);
      _aja->set_clip_name(str);
      _clipnameReq = False;
   }
   else 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
      Syslog::write("Modtronix - recorder not ready");

}
