Skip to content
Snippets Groups Projects
vrpn_NationalInstruments.C 26.34 KiB
#include <stdio.h>                      // for sprintf, fprintf, stderr, etc
#include "vrpn_Shared.h"                // for vrpn_gettimeofday

#include "vrpn_BaseClass.h"             // for ::vrpn_TEXT_WARNING, etc
#include "vrpn_Connection.h"            // for vrpn_HANDLERPARAM, etc
#include "vrpn_NationalInstruments.h"

#ifdef	VRPN_USE_NATIONAL_INSTRUMENTS
#include "server_src/NIUtil.cpp"
#endif


vrpn_National_Instruments_Server::vrpn_National_Instruments_Server (const char* name, vrpn_Connection * c, 
                             const char *boardName,
						     int numInChannels, int numOutChannels,
                             double minInputReportDelaySecs,
                             bool inBipolar, int inputMode, int inputRange, bool driveAIS, int inputGain,
                             bool outBipolar, double minOutVoltage, double maxOutVoltage) :
        vrpn_Analog(name, c),
		vrpn_Analog_Output(name, c),
#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
		d_analog_task_handle(0),
		d_analog_out_task_handle(0),
#else
		d_device_number(-1),
#endif
		d_out_min_voltage(minOutVoltage),
		d_out_max_voltage(maxOutVoltage),
        d_in_min_delay(minInputReportDelaySecs),
        d_in_gain(inputGain)
{
  // Set the polarity.  0 is bipolar, 1 is unipolar.
  if (inBipolar) {
    d_in_polarity = 0;
  } else {
    d_in_polarity = 1;
  }
  if (outBipolar) {
    d_out_polarity = 0;
  } else {
    d_out_polarity = 1;
  }

  setNumInChannels( numInChannels );
  setNumOutChannels( numOutChannels );

#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
  char	portName[1024];
  int32 error=0;

  // Input mode that we are using
  int32 terminalConfig;
  switch(inputMode) {
	  case vrpn_NI_INPUT_MODE_DIFFERENTIAL:
		  terminalConfig = DAQmx_Val_Diff;
		  break;
	  case vrpn_NI_INPUT_MODE_REF_SINGLE_ENDED:
		  terminalConfig = DAQmx_Val_RSE;
		  break;
	  case vrpn_NI_INPUT_MODE_NON_REF_SINGLE_ENDED:
		  terminalConfig = DAQmx_Val_NRSE;
		  break;
	  default:
		  fprintf(stderr,"vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Invalid inputMode (%d)\n", inputMode);
		  return;
  }

  // Minimum and maximum values that we expect to read.
  // Set the high end of the voltage range.  If we're bipolar, then
  // we get half the range above and half below zero.
  double	min_val = 0.0, max_val = inputRange;
  if (inBipolar) {
	  min_val = -max_val/2;
	  max_val = max_val/2;
  }

  // If we are going analog input, then we specify the range of input
  // ports that we use from 0 through the number requested.  We make a
  // task to handle reading of these channels as a group.  We then
  // start this task.
  if (numInChannels > 0) {
	  sprintf(portName, "%s/ai0:%d", boardName, numInChannels-1);
	  error = DAQmxCreateTask("", &d_analog_task_handle);
	  if (error == 0) {
		  error = DAQmxCreateAIVoltageChan(
			  d_analog_task_handle			// The handle we'll use to read values
			  , portName					// Device name and port range
			  , ""							// Our nickname for the channel
			  , terminalConfig				// Single-ended vs. differentil
			  , min_val, max_val			// Range of values we expect to read
			  , DAQmx_Val_Volts, ""			// Units, no custom scale
			);
		  if (error == 0) {
			  fprintf(stderr,"vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Cannot create input voltage channel\n");
			  error = DAQmxStartTask(d_analog_task_handle);
		  }
	  }
	  if (error) {
		  fprintf(stderr,"vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Cannot create input voltage task\n");
		  reportError(error);
		  return;
	  }
  }

  // If we are going analog output, then we specify the range of output
  // ports that we use from 0 through the number requested.  We make a
  // task to handle writing of these channels as a group.  Start the
  // task.  Also, set the outputs to their minimum value at startup.
  if (numOutChannels > 0) {
	  sprintf(portName, "%s/ao0:%d", boardName, numOutChannels-1);
	  error = DAQmxCreateTask("", &d_analog_out_task_handle);
	  if (error == 0) {
		  error = DAQmxCreateAOVoltageChan(
			  d_analog_out_task_handle		// The handle we'll use to write values
			  , portName					// Device name and port range
			  , ""							// Our nickname for the channel
			  , minOutVoltage, maxOutVoltage// Range of values we expect to write
			  , DAQmx_Val_Volts, ""			// Units, no custom scale
			);
		  if (error) {
			  fprintf(stderr,"vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Cannot create output voltage channel\n");
			  reportError(error);
			  return;
		  }
	  }
	  if (error == 0) {
		  error = DAQmxStartTask(d_analog_out_task_handle);
	  }
	  if (error) {
		  fprintf(stderr,"vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Cannot create or start output voltage task\n");
		  reportError(error);
		  return;
	  }

	  // Set the output values to zero or to the minimum if the minimum
	  // is above 0.  Also send these values to the board.
	  float64 minval = 0.0;
	  if (minval < minOutVoltage) { minval = minOutVoltage; }
	  int i;
	  for (i = 0; i < vrpn_Analog_Output::o_num_channel; i++) {
		  vrpn_Analog_Output::o_channel[i] = minval;
	  }
	  if (!setValues()) {
	    fprintf(stderr, "vrpn_National_Instruments_Server::vrpn_National_Instruments_Server(): Could not set values\n");
	    return;
	  }
  }

#elif defined(VRPN_USE_NATIONAL_INSTRUMENTS)
  short i;
  short	  update_mode = 0;	//< Mode 0 is immediate
  short	  ref_source = 0;	//< Reference source, 0 = internal, 1 = external
  double  ref_voltage = 10.0;

  // Open the board
  d_device_number = NIUtil::findDevice(boardName);
  if (d_device_number == -1) {
      fprintf(stderr, "vrpn_National_Instruments_Server: Error opening the D/A board %s\n", boardName);
      return;
  }

  // Set the parameters for each input channel.
  if (vrpn_Analog::num_channel > 0) {
    int ret = AI_Clear(d_device_number);
    if (ret < 0) {
      fprintf(stderr,"vrpn_National_Instruments_Server: Cannot clear analog input (error %d)\n", ret);
      setNumInChannels(0);
    }
  }
  for (i = 0; i < vrpn_Analog::num_channel; i++) {
    int ret = AI_Configure(d_device_number, i, inputMode, inputRange, d_in_polarity, driveAIS);
    if (ret < 0) {
      fprintf(stderr,"vrpn_National_Instruments_Server: Cannot configure input channel %d (error %d)\n", i, ret);
      setNumInChannels(0);
      break;
    }
  }

  // Set the parameters for each output channel.  Set the voltage for each channel to the minimum.
  for (i = 0; i < o_num_channel; i++) {

    int ret = AO_Configure(d_device_number, i, d_out_polarity, ref_source, ref_voltage, update_mode);
    // Code -10403 shows up but did not cause problems for the NI server, so we ignore it (probably at our peril)
    if ( (ret < 0) && (ret != -10403) ) {
      fprintf(stderr,"vrpn_National_Instruments_Server: Cannot configure output channel %d (error %d)\n", i, ret);
      fprintf(stderr,"     polarity: %d, reference source: %d, reference_voltage: %lg, update_mode: %d\n",
          d_out_polarity, ref_source, ref_voltage, update_mode);
      setNumOutChannels(0);
      break;
    }

    if (AO_VWrite(d_device_number, i, d_out_min_voltage)) {
      fprintf(stderr,"vrpn_National_Instruments_Server: Cannot set output channel %d to %lg\n", i, d_out_min_voltage);
      setNumOutChannels(0);
    }
  }

#else
  fprintf(stderr,"vrpn_National_Instruments_Server: Support for NI not compiled in, edit vrpn_Configure.h and recompile VRPN\n");
#endif

  // Check if we have a connection
  if (d_connection == NULL) {
    fprintf(stderr, "vrpn_National_Instruments_Server: Can't get connection!\n");
  }

  // Register a handler for the request channel change message
  if (register_autodeleted_handler(request_m_id,
	  handle_request_message, this, d_sender_id)) {
	  fprintf(stderr,"vrpn_Analog_Output_Server_NI: can't register change channel request handler\n");
	  d_connection = NULL;
  }

  // Register a handler for the request channels change message
  if (register_autodeleted_handler(request_channels_m_id,
	  handle_request_channels_message, this, d_sender_id)) {
	  fprintf(stderr,"vrpn_Analog_Output_Server_NI: can't register change channels request handler\n");
	  d_connection = NULL;
  }

  // Register a handler for vrpn_got_connection, so we can tell the
  // client how many channels are active
  if( register_autodeleted_handler( got_connection_m_id, handle_got_connection, this ) )
  {
	  fprintf( stderr, "vrpn_Analog_Output_Server_NI: can't register new connection handler\n");
	  d_connection = NULL;
  }

  // No report yet sent.
  d_last_report_time.tv_sec = 0;
  d_last_report_time.tv_usec = 0;
}

// virtual
vrpn_National_Instruments_Server::~vrpn_National_Instruments_Server()
{
#ifdef VRPN_USE_NATIONAL_INSTRUMENTS_MX
    if( d_analog_task_handle != 0 ) 
    {
        /*********************************************/
        // DAQmx Stop Code
        /*********************************************/
        DAQmxStopTask(d_analog_task_handle);
        DAQmxClearTask(d_analog_task_handle);
        d_analog_task_handle = 0 ;
    }

    if( d_analog_out_task_handle != 0 ) 
    {
        /*********************************************/
        // DAQmx Stop Code
        /*********************************************/
        DAQmxStopTask(d_analog_out_task_handle);
        DAQmxClearTask(d_analog_out_task_handle);
        d_analog_out_task_handle = 0 ;
    }
#endif
}

// virtual
void vrpn_National_Instruments_Server::mainloop(void)
{
  // Let the server code do its thing (ping/pong messages)
  server_mainloop();

  // See if it has been long enough since we sent the last report.
  // If so, then read the channels and send a new report.
  struct timeval now;
  vrpn_gettimeofday(&now, NULL);
  if (vrpn_TimevalDurationSeconds(now, d_last_report_time) >= d_in_min_delay) {
    d_last_report_time = now;

#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
	// Read one complete set of channel values from the board and store
	// the results (which are already in volts) into the channels.
	if (vrpn_Analog::num_channel > 0) {
		int32 error;
		int32 channelsRead = 0;
		float64	data[vrpn_CHANNEL_MAX];
		error = DAQmxReadAnalogF64(d_analog_task_handle, 1, 1.0, DAQmx_Val_GroupByChannel,
			data, vrpn_CHANNEL_MAX, &channelsRead, NULL);
		if (channelsRead != vrpn_Analog::num_channel) {
			send_text_message("vrpn_National_Instruments_Server::mainloop(): Cannot read channel", now, vrpn_TEXT_ERROR);
			setNumInChannels(0);
			return;
		}
		if (error) {
			  reportError(error);
			  return;
		}
		int i;
		for (i = 0; i < channelsRead; i++) {
			channel[i] = data[i];
		}
	}

#elif defined(VRPN_USE_NATIONAL_INSTRUMENTS)
    // Read from the board, convert to volts, and store in the channel array
    i16 value;
    int i;
    for (i = 0; i < vrpn_Analog::num_channel; i++) {
      int ret = AI_Read(d_device_number, i, d_in_gain, &value);
      if (ret != 0) {
        send_text_message("vrpn_National_Instruments_Server::mainloop(): Cannot read channel", now, vrpn_TEXT_ERROR);
        setNumInChannels(0);
        return;
      }
      if(0 == d_in_polarity){ //Bipolar: We multiply by 10, but it will only go halfway (it is signed).
		channel[i] = (value / 65536.0) * (10.0 / d_in_gain);		    
      } else { //Unipolar (cast it to unsigned, so it will be from zero to almost 10 volts).
		channel[i] = ((vrpn_uint16)value / 65536.0) * (10.0 / d_in_gain);
      }
    }
#endif

    // Send a report.
    vrpn_Analog::report();
  }
}

int vrpn_National_Instruments_Server::setNumInChannels (int sizeRequested) {
  if (sizeRequested < 0) sizeRequested = 0;
  if (sizeRequested > vrpn_CHANNEL_MAX) sizeRequested = vrpn_CHANNEL_MAX;
  vrpn_Analog::num_channel = sizeRequested;

  return vrpn_Analog::num_channel;
}

int vrpn_National_Instruments_Server::setNumOutChannels (int sizeRequested) {
  if (sizeRequested < 0) sizeRequested = 0;
  if (sizeRequested > vrpn_CHANNEL_MAX) sizeRequested = vrpn_CHANNEL_MAX;

  o_num_channel = sizeRequested;
  return o_num_channel;
}

/* static */
int VRPN_CALLBACK vrpn_National_Instruments_Server::handle_request_message(void *userdata,
	vrpn_HANDLERPARAM p)
{
    const char* bufptr = p.buffer;
    vrpn_int32 chan_num;
    vrpn_int32 pad;
    vrpn_float64 value;
    vrpn_National_Instruments_Server* me = (vrpn_National_Instruments_Server*)userdata;

    // Read the parameters from the buffer
    vrpn_unbuffer(&bufptr, &chan_num);
    vrpn_unbuffer(&bufptr, &pad);
    vrpn_unbuffer(&bufptr, &value);

    // Set the appropriate value, if the channel number is in the
    // range of the ones we have.
    if ( (chan_num < 0) || (chan_num >= me->o_num_channel) ) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message):  channel %d is not active.  Squelching.", chan_num );
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
      return 0;
    }
    // Make sure the voltage value is within the allowed range.
    if (value < me->d_out_min_voltage) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message): voltage %g is too low.  Clamping to %g.", value, me->d_out_min_voltage);
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
      value = me->d_out_min_voltage;
    }
    if (value > me->d_out_max_voltage) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message): voltage %g is too high.  Clamping to %g.", value, me->d_out_max_voltage);
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
      value = me->d_out_max_voltage;
    }
    me->o_channel[chan_num] = value;

    // Send the new value to the D/A board
#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
	if (!me->setValues()) {
	  me->send_text_message( "vrpn_National_Instruments_Server::handle_request_message(): Could not set values", p.msg_time, vrpn_TEXT_ERROR );
	  return -1;
	}

#elif	defined(VRPN_USE_NATIONAL_INSTRUMENTS)
    if (me->d_device_number != -1) {
      AO_VWrite(me->d_device_number, (short)(chan_num), value);
    }
#endif
    return 0;
}

/* static */
int vrpn_National_Instruments_Server::handle_request_channels_message(void* userdata,
    vrpn_HANDLERPARAM p)
{
    const char* bufptr = p.buffer;
    vrpn_int32 num;
    vrpn_int32 pad;
    vrpn_National_Instruments_Server* me = (vrpn_National_Instruments_Server*)userdata;
    vrpn_int32 chan_num;
    vrpn_float64 value;

    // Read the values from the buffer
    vrpn_unbuffer(&bufptr, &num);
    vrpn_unbuffer(&bufptr, &pad);
    if (num > me->o_num_channel) 
    {
         char msg[1024];
         sprintf( msg, "Error:  (handle_request_channels_message):  channels above %d not active; "
              "bad request up to channel %d.  Squelching.", me->o_num_channel, num );
         me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
         num = me->o_num_channel;
    }
    if (num < 0) 
    {
         char msg[1024];
         sprintf( msg, "Error:  (handle_request_channels_message):  invalid channel %d.  Squelching.", num );
         me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
         return 0;
    }
    for (chan_num = 0; chan_num < num; chan_num++) {
		vrpn_unbuffer(&bufptr, &value);

		// Make sure the voltage value is within the allowed range.
		if (value < me->d_out_min_voltage) {
		  char msg[1024];
		  sprintf( msg, "Error:  (handle_request_messages): voltage %g is too low.  Clamping to %g.", value, me->d_out_min_voltage);
		  me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
		  value = me->d_out_min_voltage;
		}
		if (value > me->d_out_max_voltage) {
		  char msg[1024];
		  sprintf( msg, "Error:  (handle_request_messages): voltage %g is too high.  Clamping to %g.", value, me->d_out_max_voltage);
		  me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
		  value = me->d_out_max_voltage;
		}
		me->o_channel[chan_num] = value;

		// Send the new value to the D/A board
#ifdef	VRPN_USE_NATIONAL_INSTRUMENTS
		if (me->d_device_number != -1) {
		  AO_VWrite(me->d_device_number, (short)(chan_num), value);
		}
#endif
    }

#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
	if (!me->setValues()) {
	  me->send_text_message( "vrpn_National_Instruments_Server::handle_request_channels_message(): Could not set values", p.msg_time, vrpn_TEXT_ERROR );
	  return -1;
	}
#endif

    return 0;
}

#if defined(VRPN_USE_NATIONAL_INSTRUMENTS_MX)
bool vrpn_National_Instruments_Server::setValues() {
	// Send all current values to the board, including the
	// changed one.
	float64 outbuffer[vrpn_CHANNEL_MAX];
	int i;
	for (i = 0; i < vrpn_Analog_Output::o_num_channel ; i++) {
	  outbuffer[i] = vrpn_Analog_Output::o_channel[i];
	}
	int32 error;
	error = DAQmxWriteAnalogF64(d_analog_out_task_handle, 1, true, 1.0,
	  DAQmx_Val_GroupByChannel, outbuffer, NULL, NULL);
	if (error) {
	  reportError(error);
	  return false;
	}
	/*
	printf("Debug: Setting %d channels:", o_num_channel);
	for (i = 0; i < o_num_channel; i++) {
		printf(" %lg", outbuffer[i]);
	}
	printf("\n");
	*/
        return true;
}
#endif

/* static */
int VRPN_CALLBACK vrpn_National_Instruments_Server::handle_got_connection( void* userdata, vrpn_HANDLERPARAM )
{
  vrpn_National_Instruments_Server* me = (vrpn_National_Instruments_Server*) userdata;
  if( me->report_num_channels( ) == false ) {
    fprintf( stderr, "Error:  failed sending active channels to client.\n" );
  }
  return 0;
}

bool vrpn_National_Instruments_Server::report_num_channels( vrpn_uint32 class_of_service )
{
	char msgbuf[ sizeof( vrpn_int32) ];
	vrpn_int32 len = sizeof( vrpn_int32 );;
	
	encode_num_channels_to( msgbuf, o_num_channel );
	vrpn_gettimeofday( &o_timestamp, NULL );
	if( d_connection && 
	    d_connection->pack_message( len, o_timestamp,	report_num_channels_m_id, 
							  d_sender_id, msgbuf, class_of_service ) ) 
	{
		fprintf(stderr, "vrpn_Analog_Output_Server_NI (report_num_channels): cannot write message: tossing\n");
		return false;
	}
	return true;
}


vrpn_int32 vrpn_National_Instruments_Server::encode_num_channels_to( char* buf, vrpn_int32 num )
{
    // Message includes: int32 number of active channels
    int	buflen = sizeof(vrpn_int32);

    vrpn_buffer(&buf, &buflen, num);
    return sizeof(vrpn_int32);
}

vrpn_Analog_Output_Server_NI::vrpn_Analog_Output_Server_NI(const char* name, vrpn_Connection * c, 
                                                     const char *boardName,
						     vrpn_int16 numChannels, bool bipolar,
						     double minVoltage, double maxVoltage) :
	vrpn_Analog_Output(name, c),
	NI_device_number(-1),
	min_voltage(minVoltage),
	max_voltage(maxVoltage),
	NI_num_channels(numChannels)
{
#ifdef	VRPN_USE_NATIONAL_INSTRUMENTS
  short i;
  short	  update_mode = 0;	//< Mode 0 is immediate
  short	  ref_source = 0;	//< Reference source, 0 = internal, 1 = external
  double  ref_voltage = 0.0;

  // Set the polarity
  if (bipolar) {
    polarity = 0;
  } else {
    polarity = 1;
  }

  // Open the D/A board and set the parameters for each channel.  Set the voltage for each
  // channel to the minimum.
  NI_device_number = NIUtil::findDevice(boardName);
  if (NI_device_number == -1) {
      fprintf(stderr, "vrpn_Analog_Output_Server_NI: Error opening the D/A board %s\n", boardName);
      return;
  }
  for (i = 0; i < NI_num_channels; i++) {
      AO_Configure(NI_device_number, i, polarity, ref_source, ref_voltage,
	  update_mode);
/*
      fprintf(stderr,"Configuring channel %d, polarity: %d, reference source: %d, reference_voltage: %lg, update_mode: %d\n",
          i, polarity, ref_source, ref_voltage, update_mode);
*/
      AO_VWrite(NI_device_number, i, min_voltage);
  }


  setNumChannels( numChannels );

  // Check if we have a connection
  if (d_connection == NULL) {
	  fprintf(stderr, "vrpn_Analog_Output_Server_NI: Can't get connection!\n");
  }

  // Register a handler for the request channel change message
  if (register_autodeleted_handler(request_m_id,
	  handle_request_message, this, d_sender_id)) {
	  fprintf(stderr,"vrpn_Analog_Output_Server_NI: can't register change channel request handler\n");
	  d_connection = NULL;
  }

  // Register a handler for the request channels change message
  if (register_autodeleted_handler(request_channels_m_id,
	  handle_request_channels_message, this, d_sender_id)) {
	  fprintf(stderr,"vrpn_Analog_Output_Server_NI: can't register change channels request handler\n");
	  d_connection = NULL;
  }

  // Register a handler for vrpn_got_connection, so we can tell the
  // client how many channels are active
  if( register_autodeleted_handler( got_connection_m_id, handle_got_connection, this ) )
  {
	  fprintf( stderr, "vrpn_Analog_Output_Server_NI: can't register new connection handler\n");
	  d_connection = NULL;
  }
#else
  fprintf(stderr,"vrpn_Analog_Output_Server_NI: Support for NI not compiled in, edit vrpn_Configure.h and recompile\n");
#endif
}

// virtual
vrpn_Analog_Output_Server_NI::~vrpn_Analog_Output_Server_NI(void) {}

// virtual
void vrpn_Analog_Output_Server_NI::mainloop(void)
{
  // Let the server code do its thing (ping/pong messages)
  server_mainloop();
}

vrpn_int32 vrpn_Analog_Output_Server_NI::setNumChannels (vrpn_int32 sizeRequested) {
  if (sizeRequested < 0) sizeRequested = 0;
  if (sizeRequested > vrpn_CHANNEL_MAX) sizeRequested = vrpn_CHANNEL_MAX;

  o_num_channel = sizeRequested;

  return o_num_channel;
}

/* static */
int VRPN_CALLBACK vrpn_Analog_Output_Server_NI::handle_request_message(void *userdata,
	vrpn_HANDLERPARAM p)
{
    const char* bufptr = p.buffer;
    vrpn_int32 chan_num;
    vrpn_int32 pad;
    vrpn_float64 value;
    vrpn_Analog_Output_Server_NI* me = (vrpn_Analog_Output_Server_NI*)userdata;

    // Read the parameters from the buffer
    vrpn_unbuffer(&bufptr, &chan_num);
    vrpn_unbuffer(&bufptr, &pad);
    vrpn_unbuffer(&bufptr, &value);

    // Set the appropriate value, if the channel number is in the
    // range of the ones we have.
    if ( (chan_num < 0) || (chan_num >= me->o_num_channel) ) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message):  channel %d is not active.  Squelching.", chan_num );
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
      return 0;
    }
    // Make sure the voltage value is within the allowed range.
    if (value < me->min_voltage) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message): voltage %g is too low.  Clamping to %g.", value, me->min_voltage);
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
      value = me->min_voltage;
    }
    if (value > me->max_voltage) {
      char msg[1024];
      sprintf( msg, "Error:  (handle_request_message): voltage %g is too high.  Clamping to %g.", value, me->max_voltage);
      me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
      value = me->max_voltage;
    }
    me->o_channel[chan_num] = value;

    // Send the new value to the D/A board
#ifdef	VRPN_USE_NATIONAL_INSTRUMENTS
    if (me->NI_device_number != -1) {
      AO_VWrite(me->NI_device_number, (short)(chan_num), value);
    }
#endif
    return 0;
}

/* static */
int vrpn_Analog_Output_Server_NI::handle_request_channels_message(void* userdata,
    vrpn_HANDLERPARAM p)
{
    const char* bufptr = p.buffer;
    vrpn_int32 num;
    vrpn_int32 pad;
    vrpn_Analog_Output_Server_NI* me = (vrpn_Analog_Output_Server_NI*)userdata;
    vrpn_int32 chan_num;
    vrpn_float64 value;

    // Read the values from the buffer
    vrpn_unbuffer(&bufptr, &num);
    vrpn_unbuffer(&bufptr, &pad);
    if (num > me->o_num_channel) 
    {
         char msg[1024];
         sprintf( msg, "Error:  (handle_request_channels_message):  channels above %d not active; "
              "bad request up to channel %d.  Squelching.", me->o_num_channel, num );
         me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
         num = me->o_num_channel;
    }
    if (num < 0) 
    {
         char msg[1024];
         sprintf( msg, "Error:  (handle_request_channels_message):  invalid channel %d.  Squelching.", num );
         me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
         return 0;
    }
    for (chan_num = 0; chan_num < num; chan_num++) {
	vrpn_unbuffer(&bufptr, &value);

	// Make sure the voltage value is within the allowed range.
	if (value < me->min_voltage) {
	  char msg[1024];
	  sprintf( msg, "Error:  (handle_request_messages): voltage %g is too low.  Clamping to %g.", value, me->min_voltage);
	  me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
	  value = me->min_voltage;
	}
	if (value > me->max_voltage) {
	  char msg[1024];
	  sprintf( msg, "Error:  (handle_request_messages): voltage %g is too high.  Clamping to %g.", value, me->max_voltage);
	  me->send_text_message( msg, p.msg_time, vrpn_TEXT_WARNING );
	  value = me->max_voltage;
	}
	me->o_channel[chan_num] = value;

	// Send the new value to the D/A board
#ifdef	VRPN_USE_NATIONAL_INSTRUMENTS
	if (me->NI_device_number != -1) {
	  AO_VWrite(me->NI_device_number, (short)(chan_num), value);
	}
#endif
    }
    
    return 0;
}


/* static */
int VRPN_CALLBACK vrpn_Analog_Output_Server_NI::handle_got_connection( void* userdata, vrpn_HANDLERPARAM )
{
	vrpn_Analog_Output_Server_NI* me = (vrpn_Analog_Output_Server_NI*) userdata;
	if( me->report_num_channels( ) == false )
	{
		fprintf( stderr, "Error:  failed sending active channels to client.\n" );
	}
	return 0;
}


bool vrpn_Analog_Output_Server_NI::report_num_channels( vrpn_uint32 class_of_service )
{
	char msgbuf[ sizeof( vrpn_int32) ];
	vrpn_int32 len = sizeof( vrpn_int32 );;
	
	encode_num_channels_to( msgbuf, o_num_channel );
	vrpn_gettimeofday( &o_timestamp, NULL );
	if( d_connection && 
	    d_connection->pack_message( len, o_timestamp,	report_num_channels_m_id, 
							  d_sender_id, msgbuf, class_of_service ) ) 
	{
		fprintf(stderr, "vrpn_Analog_Output_Server_NI (report_num_channels): cannot write message: tossing\n");
		return false;
	}
	return true;
}


vrpn_int32 vrpn_Analog_Output_Server_NI::
encode_num_channels_to( char* buf, vrpn_int32 num )
{
    // Message includes: int32 number of active channels
    int	buflen = sizeof(vrpn_int32);

    vrpn_buffer(&buf, &buflen, num);
    return sizeof(vrpn_int32);
}

//  This handles error reporting
#ifdef VRPN_USE_NATIONAL_INSTRUMENTS_MX
void vrpn_National_Instruments_Server::reportError(int32 errnumber, vrpn_bool exitProgram)
{
    char    errBuff[2048]={'\0'};

    if( DAQmxFailed(errnumber) )
    {
        DAQmxGetExtendedErrorInfo(errBuff,2048);
        printf("DAQmx Error: %s\n",errBuff);
        if (exitProgram==vrpn_true) {
            printf("Exiting...\n") ;
            throw(errnumber) ;    //  this will quit, cause the destructor to be called
        } else {
            printf("Sleeping...\n") ;
            vrpn_SleepMsecs(1000.0*1) ;    //  so at least the log will slow down so someone can see the error
        }
    }
}    //  reportError
#endif