/***************************************************************************************************/
/*                                                                                                 */
/* Copyright (C) 2003 Bauhaus University Weimar                                                    */
/* Released into the public domain on 6/23/2007 as part of the VRPN project                        */
/* by Jan P. Springer.                                                                             */
/*                                                                                                 */
/***************************************************************************************************/
/*                                                                                                 */
/*  module     :  vrpn_Atmel                                                                       */
/*  project    :  vrpn_Avango                                                                      */
/*  description:  server for microcontroller based on Atmel chip                                   */
/*                hardware developed by Albotronic: www.albotronic.de                              */
/*                                                                                                 */
/***************************************************************************************************/

#ifndef _WIN32

#include "vrpn_Atmel.h"
#include "vrpn_atmellib.h"
#include "vrpn_atmellib_helper.h"

#include <errno.h>
#include <stdio.h>

#if !defined(_WIN32)
#include <unistd.h>
#include <termios.h>
#endif

#include <string>
#include <string.h>

/***************************************************************************************************/
/***************************************************************************************************/
namespace {

struct termios init_params;
struct timeval wait;

/***************************************************************************************************/
// check if there if the Atmel is connected to the serial 
/***************************************************************************************************/
bool
check_serial(int fd)
{
  int ret=0;
	  
  // make a new fd set to watch
  fd_set  rfds;

  // clear the set
  FD_ZERO( &rfds );
  // ad the current fd to the set
  FD_SET( fd, &rfds );
    
  wait.tv_sec = 5;
  wait.tv_usec = 0;
    
  ret = select( fd+1 , &rfds , 0 , 0 , &wait);
    
  if (ret == 0) {

    printf("Atmel not connected to the specified port.\n");
    printf("Connect and try again.\n");

    return false;	  
  }
  else if (ret<0) {

    printf("Error while checking if Atmel is connected.\n");
    printf("Error: %s (%i)\n", strerror(errno), errno );

    return false;
  }

  printf("Atmel started.\n\n");
     
  return true; 
}

}  // end of namespace

/***************************************************************************************************/
/* factory */
/***************************************************************************************************/
/* static */ vrpn_Atmel *
vrpn_Atmel::Create(char* name, vrpn_Connection *c,
                    const char *port, long baud,
		    int channel_count ,
		    int * channel_mode)
{	
  int fd;

#ifdef VRPN_ATMEL_SERIAL_VRPN

  if ( (fd=vrpn_open_commport(port, baud)) == -1) {
    
    fprintf(stderr,"vrpn_Atmel: can't Open serial port\n");
  
    return NULL; 
  }
#else
  // Open the serial port
  if ( (fd=openPort(port, baud, &init_params)) == -1) {
    
    fprintf(stderr,"vrpn_Atmel: can't Open serial port\n");
  
    return NULL; 
  }
  
  // look if the atmel is connected
  if (! check_serial(fd) ) { 

    return NULL; 
  }
#endif 
 
  vrpn_Atmel * self = new vrpn_Atmel(name, c, fd);
      
  if ( (self->vrpn_Analog_Server::setNumChannels(channel_count) != channel_count) 
     || (self->vrpn_Analog_Output_Server::setNumChannels(channel_count) != channel_count) ) {
  
      fprintf(stderr,"vrpn_Atmel: the requested number of channels is not available\n");
      delete self;

      return NULL;
  }
  

  // init the channels based on the infos given from the config file
  self->init_channel_mode(channel_mode);
 
  self->_status = VRPN_ATMEL_STATUS_WAITING_FOR_CONNECTION; 

  return self;
}



/***************************************************************************************************/
/* constructor */
/***************************************************************************************************/
vrpn_Atmel::vrpn_Atmel(char* name, vrpn_Connection *c, int fd) 
  : vrpn_Analog_Server(name, c), vrpn_Analog_Output_Server(name, c),
    _status(VRPN_ATMEL_STATUS_ERROR), 
    serial_fd(fd)
{	
  // find out what time it is - needed?
  gettimeofday(&timestamp, 0);
  gettimeofday(&_time_alive, 0);
  vrpn_Analog::timestamp = timestamp;
}


/***************************************************************************************************/
/* destructor */
/***************************************************************************************************/
vrpn_Atmel::~vrpn_Atmel()
{
#ifdef VRPN_ATMEL_SERIAL_VRPN
  vrpn_close_commport(serial_fd);
#else
  closePort(serial_fd , &init_params);      
#endif
  
  
}

/***************************************************************************************************/
/* mainloop */
/***************************************************************************************************/
void 
vrpn_Atmel::mainloop()
{
  if (_status == VRPN_ATMEL_STATUS_ERROR) {

    return;
  }
	
  // o_num_channel is used as reference against num_channel
  if (o_num_channel != num_channel) {

    num_channel = o_num_channel;
  }

  server_mainloop();
  
  // get the pending messages so that the buffer to write down is updated
  d_connection->mainloop();
  
  if ( ! d_connection->connected()) {

    return;
  }
  
  // it's the first time we are in mainloop after 
  if ( _status == VRPN_ATMEL_STATUS_WAITING_FOR_CONNECTION) {
	  
    if (handle_new_connection()) {
  
      _status = VRPN_ATMEL_STATUS_RUNNING;
    
      fprintf(stderr,"vrpn_Atmel: mainloop()\n");
      fprintf(stderr,"  new connection. status set to RUNNING\n");
    }
    else {
   
      _status = VRPN_ATMEL_STATUS_ERROR;
    
      fprintf(stderr,"vrpn_Atmel: mainloop()\n");
      fprintf(stderr,"  error handling new connection. status set to ERROR\n");
   
      return; 
    }
    
  }

  // do the read/write operations on the serial port   
  if ( ! mainloop_serial_io() ) {
    
    fprintf(stderr,"vrpn_Atmel: mainloop()\n");
    fprintf(stderr,"  error while io operations ERROR\n");

  }
  
  vrpn_Analog::report();
  //vrpn_Analog::report_changes();

  d_connection->mainloop();
}
 
/***************************************************************************************************/
/* check if the connection to the Atmel is still reliable */
/***************************************************************************************************/
bool
vrpn_Atmel::Check_Serial_Alive()
{
  struct timeval look_time;

  // check serial status every second
  if ((timestamp.tv_sec - _time_alive.tv_sec) > VRPN_ATMEL_ALIVE_INTERVAL_SEC) {

    // reset time alive
    gettimeofday(&_time_alive,0);

    tcflush(serial_fd, TCIOFLUSH);

    // make a new fd set to watch
    fd_set  rfds;

    // clear the set
    FD_ZERO( &rfds );
    // ad the current fd to the set
    FD_SET( serial_fd, &rfds );

    look_time.tv_sec = VRPN_ATMEL_ALIVE_TIME_LOOK_SEC;
    look_time.tv_usec = VRPN_ATMEL_ALIVE_TIME_LOOK_USEC;

    if ((select( (serial_fd+1) , &rfds , 0 , 0 , &look_time)) <= 0) {

      fprintf(stderr, "\nExiting...\n");

      fprintf(stderr, "vrpn_Atmel::Check_Serial_Alive: connection timed out after (sec,msec):");
      fprintf(stderr, "%i,%i\n", VRPN_ATMEL_ALIVE_TIME_LOOK_SEC, VRPN_ATMEL_ALIVE_TIME_LOOK_USEC);
      fprintf(stderr, "Killing Program!!!\n");

      return false;
    }

#ifdef VRPN_ATMEL_VERBOSE 
    fprintf(stderr, "Connection still available...\n");
#endif
  }

  return true;
}
 
/***************************************************************************************************/
/* io operations on the serial port in mainloop */
/***************************************************************************************************/
bool
vrpn_Atmel::mainloop_serial_io() 
{
  gettimeofday(&timestamp, 0);
  vrpn_Analog::timestamp = timestamp;

  // check if there is still a valid connection to the Chip
  if (!Check_Serial_Alive()) {
      
    // kill the program
    // use e.g. a cron job to init the whole connection mechanism again
    exit(0);
  } 

  // do for all channels
  for(int i=0; i<o_num_channel; ++i) {
	  
    if (o_channel[i] == VRPN_ATMEL_CHANNEL_NOT_VALID) {

      // unused channel
      continue;
    }
	  
    // find out which channels have to been written down to the device
    if (o_channel[i] != channel[i]) {

      // test if channel is writable
      if ( (_channel_mode[i] != VRPN_ATMEL_MODE_RW) 
	    && (_channel_mode[i] != VRPN_ATMEL_MODE_WO) ) {
 
        fprintf(stderr,"vrpn_Atmel: mainloop_serial_io()\n");
        fprintf(stderr,"  channel not writable\n");
     	
        channel[i] = VRPN_ATMEL_ERROR_NOT_WRITABLE;
      
        continue;
      }

      // test if it is a valid value: atmel uses 8 bit
      if ((o_channel[i]<0) || (o_channel[i]>255)) {
 
        fprintf(stderr,"vrpn_Atmel: mainloop_serial_io()\n");
        fprintf(stderr,"  value out of range\n");
     	
        channel[i] = VRPN_ATMEL_ERROR_OUT_OF_RANGE;
      
        continue;
      }
      
       // try to write down
      if (setRegister(serial_fd, i, (unsigned char) o_channel[i])
           != ATMELLIB_NOERROR) {
        
        fprintf(stderr,"vrpn_Atmel: mainloop_serial_io()\n");
        fprintf(stderr,"  error writing down value, channel: %d\n",i);
     	
        channel[i] = VRPN_ATMEL_ERROR_WRITING_DOWN;
      
        continue;
      }
      else {

        fprintf(stderr,"vrpn_Atmel: mainloop_serial_io()\n");
        fprintf(stderr,"  writen down value, channel: %d\n",i);
      }
      
      // no error
      channel[i] = o_channel[i];
    
      continue;
    }

    // this values are not requested for change -> read them
   
    if ( (_channel_mode[i] == VRPN_ATMEL_MODE_RO)
	 || (_channel_mode[i] == VRPN_ATMEL_MODE_RW) ) {
	    
      if ( (channel[i] = getRegister(serial_fd, i)) < 0) {
 
#ifdef VRPN_ATMEL_VERBOSE 
        fprintf(stderr, "Channel read: i=%i val=%f\n",i,channel[i]);
        
        fprintf(stderr,"vrpn_Atmel: mainloop_serial_io()\n");
        fprintf(stderr,"  error reading out value. channel=%d\n",i);
#endif
        // reset to the old value - don't send the error
        // in some cases it's propably useful to know the error
        channel[i] = VRPN_ATMEL_ERROR_READING_IN;
        //channel[i] = last[i]; 
       }
    }
    
   } // end of for-loop

  for(int i=0; i<o_num_channel ;++i) {

    o_channel[i] = channel[i];
  }
  
  return true;
}


/***************************************************************************************************/
/* init the io mode of the channels: */
/***************************************************************************************************/
void
vrpn_Atmel::init_channel_mode(int * channel_mode)
{
  int val=0;
	
  for(int i=0; i<o_num_channel; ++i) {
    
    _channel_mode.push_back(channel_mode[i]);
 
    if ( (channel_mode[i] == VRPN_ATMEL_MODE_RO)
        || (_channel_mode[i] == VRPN_ATMEL_MODE_RW) ) {
	
      // read the current value of the register
      val=getRegister(serial_fd , i);
      if (val < 0) {
   
         // error while reading register value
         fprintf(stderr,"vrpn_Atmel: init_channel_mode()\n");
         fprintf(stderr,"  error reading out value: %i\n", val);
	
	 o_channel[i] = VRPN_ATMEL_CHANNEL_NOT_VALID;
	 channel[i] = VRPN_ATMEL_CHANNEL_NOT_VALID;
	}
	else {
          
          channel[i] = (float) val;
	  o_channel[i] = (float) val;
          
#ifdef VRPN_ATMEL_VERBOSE 
	  fprintf(stderr, "vrpn_Atmel: i=%d val=%d\n",i ,val);
#endif

	}
    }
    else {

      // channel is not for reading 	    
      o_channel[i] = VRPN_ATMEL_CHANNEL_NOT_VALID;
      channel[i] = VRPN_ATMEL_CHANNEL_NOT_VALID;
    }

  } // end of for loop
}


/***************************************************************************************************/
/* init the io mode of the channels: */
/***************************************************************************************************/
bool
vrpn_Atmel::handle_new_connection()
{
  // request to send the current status of all registers 	
  vrpn_Analog::report();

  printf("\nconnection received.\n\n");
  
  // really send the current status 
  d_connection->mainloop();

  return true;
}

#endif

