//===========================================================================//
// File:	RIO.cc                                                              //
// Project: MUNGA           Brick: Platform-specific IO                      //
// Contents: Remote I/O interface                                            //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  -----------------------------------------------------------//
// 01/30/95  CPB  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All rights reserved //
//                      PROPRIETARY and CONFIDENTIAL                         //
//===========================================================================//

#include <mungal4.hpp>
#pragma hdrstop

#if !defined(L4RIO_HPP)
#	include <l4rio.hpp>
#endif

#if !defined(L4CTRL_HPP)
#	include <l4ctrl.hpp>
#endif

#if defined(TRACE_RIO_RECEIVE_PACKET)
	BitTrace RIO_Receive_Packet("RIO Receive Packet");
#endif

#if defined(TRACE_RIO_SEND_PACKET)
	BitTrace RIO_Send_Packet("RIO Send Packet);
#endif

#if defined(DEBUG)
#	define Test_Tell(n) DEBUG_STREAM << n
#else
#	define Test_Tell(n)
#endif

#define default_deadband_value	0.03

static Byte RIOLengths[] = {
	0,		// CheckRequest
	0,		// VersionRequest
	0,		// AnalogRequest
	1,		// ResetRequest
	2,		// LampRequest
	2,		// CheckReply
	2,		// VersionReply
	10,	// AnalogReply
	1,		// ButtonPressed
	1,		// ButtonReleased
	2,		// KeyPressed
	2,		// KeyReleased
	1		// TestModeChange
};

Byte	RIO::reply_check_string[]         = {RIO::CheckReply, 3, 2 };
Byte	RIO::reply_version_string[]       = {RIO::VersionReply, 1, 23};
Byte	RIO::reply_button_press_string[]  = {RIO::ButtonPressed, 22};
Byte	RIO::reply_button_release_string[]= {RIO::ButtonReleased, 22};
Byte	RIO::reply_key_press_string[]     = {RIO::KeyPressed, 0, 4};
Byte	RIO::reply_key_release_string[]   = {RIO::KeyReleased, 0, 4};
Byte	RIO::reply_test_enter_string[]    = {RIO::TestModeChange, 1};
Byte	RIO::reply_test_exit_string[]     = {RIO::TestModeChange, 0};
Byte	RIO::reply_analog_string[] =
{
	RIO::AnalogReply,	1, 0, 2, 0, 3, 0, 4, 0, 5, 0
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

int
	CombinePair(Byte low_value, Byte high_value)
{
	int result;

	result = ((int) (low_value & 0x7F)) | (((int) high_value) << 7);
	if (result & 0x2000)
	{
			result |= ~0x3FFF;
	}
	Check_Fpu();
	return result;
}


void
	BoardError(NotationFile *notation_file, char *type, int position)
{
	Check(notation_file);
	Check_Pointer(type);

	char
		where[40];

	sprintf(where, "%s@Slot=%d:Address=%d", type, position >> 3, position & 7);
	notation_file->AppendEntry("RIOBoardErrors", "error", where);
	Check_Fpu();
}

//###########################################################################
//############################ Joystick classes #############################
//###########################################################################
const char *FilterChannel::highName   = "high";
const char *FilterChannel::centerName = "center";
const char *FilterChannel::lowName    = "low";
const char *FilterChannel::joystickXName  = "JoystickX";
const char *FilterChannel::joystickYName  = "JoystickY";
const char *FilterChannel::throttleName   = "Throttle";
const char *FilterChannel::leftPedalName  = "LeftPedal";
const char *FilterChannel::rightPedalName = "RightPedal";

FilterChannel::FilterChannel():
	average(5,0)
{
	valuesFromFile = False;
	previousValue = 0;
	polarity = unipolar;
	mode = unaligned;
	deadbandScalar = default_deadband_value;

	lowerDeadband = 0;
	upperDeadband = 0;

	Check_Fpu();
}

FilterChannel::FilterChannel(
	NotationFile *init_file,
	const char *page_name,
	int default_min, int default_max
):
	average(5,0)
{
	valuesFromFile = True;
	previousValue = 0;
	polarity = unipolar;
	mode = normal;
	deadbandScalar = default_deadband_value;

	pageName = page_name;
	min = default_min;
	max = default_max;
	//-------------------------------------------
	// Attempt to read calibration values from
	// the notation file.  If the values don't
	// exist, then create them from the defaults.
	//-------------------------------------------
	if (init_file->GetEntry(pageName, highName, &max) == 0)
	{
		init_file->SetEntry(pageName, highName, default_max);
	}

	if (init_file->GetEntry(pageName, lowName, &min) == 0)
	{
		init_file->SetEntry(pageName, lowName, default_min);
	}

	center = (max-min)/2;

	Check_Fpu();
}

FilterChannel::FilterChannel(
	NotationFile *init_file,
	const char *page_name,
	int default_min, int default_center, int default_max
):
	average(5,0)
{
	valuesFromFile = True;
	previousValue = 0;
	polarity = bipolar;
	mode = normal;
	deadbandScalar = default_deadband_value;

	pageName = page_name;
	min = default_min;
	center = default_center;
	max = default_max;

	//-------------------------------------------
	// Attempt to read calibration values from
	// the notation file.  If the values don't
	// exist, then create them from the defaults.
	//-------------------------------------------

	if (init_file->GetEntry(pageName, highName, &max) == 0)
	{
		init_file->SetEntry(pageName, highName, default_max);
	}

	if (init_file->GetEntry(pageName, centerName, &center) == 0)
	{
		init_file->SetEntry(pageName, centerName, default_center);
	}

	if (init_file->GetEntry(pageName, lowName, &min) == 0)
	{
		init_file->SetEntry(pageName, lowName, default_min);
	}

	CalculateDeadBands();

	Check_Fpu();
}

FilterChannel::~FilterChannel()
{
	Check(this);
	Check_Fpu();
}

void
	FilterChannel::SetPolarity(PolarMode newPolarity)
{
	Check(this);
	polarity = newPolarity;

	// HACK - filterchannel must be realigned when changing to bipolar!

	Check_Fpu();
}

void
	FilterChannel::SetDeadBand(Scalar dead_band)
{
	Check(this);

	deadbandScalar = fabs(dead_band);
	//---------------------------------------------------------
	// Recalculate deadbands and ranges if needed
	//---------------------------------------------------------
	if (mode == normal)
	{
		CalculateDeadBands();
	}
	Check_Fpu();
}

void
	FilterChannel::BeginAlignment()
{
	Check(this);
	//---------------------------------------------------------
	// Set bogus min/max, force update
	//---------------------------------------------------------
	mode = unaligned;
	min = 10000;
	max = -10000;
	Check_Fpu();
}

void
	FilterChannel::EndAlignment(NotationFile *init_file)
{
	Check(this);
	//---------------------------------------------------------
	// Save the most recent value as the center
	//---------------------------------------------------------
	center = previousValue;
	//------------------------------------------------------------
	// If channel was never adjusted, synthesize reasonable values
	//------------------------------------------------------------
	// I don't trust abs(): saw weirdness.
	int
		delta = max-min;
	if (delta < 0)
	{
		delta = -delta;
	}

	if (delta < 10)
	{
		if (polarity == bipolar)
		{
			min = center-100;
			max = center+100;
		}
		else
		{
			min = 0;
			max = 100;
		}
	}
	else
	{
		int
			setback = delta/33;		// move the edges in by 3%

		max -= setback;
		min += setback;
	}
	//---------------------------------------------------------
	// Save the new values
	//---------------------------------------------------------
	if (valuesFromFile)
	{
		if (init_file != NULL)
		{
			init_file->SetEntry(pageName, highName, max);
			init_file->SetEntry(pageName, lowName, min);

			if (polarity == bipolar)
			{
				init_file->SetEntry(pageName, centerName, center);
			}
		}
	}
	//---------------------------------------------------------
	// Calculate real deadband values
	//---------------------------------------------------------
	CalculateDeadBands();
	//---------------------------------------------------------
	// Go to normal mode
	//---------------------------------------------------------
	mode = normal;

	Check_Fpu();
}

Scalar
	FilterChannel::Update(int value)
{
	Check(this);

	Scalar
		result = 0.0;

	previousValue = value;

	//---------------------------------------------------------
	// Generate average, use for min/max determination
	//---------------------------------------------------------
	average.Add(value);
	{
		int
			the_average = average.CalculateOlympicAverage();

		if (the_average < min) { min = the_average; }
		if (the_average > max) { max = the_average; }
	}
	//---------------------------------------------------------
	// Perform position calculations
	//---------------------------------------------------------
	switch (polarity)
	{
	case unipolar:
		{
			//---------------------------------------------------------
			// Subtract 'min' from current value
			//---------------------------------------------------------
			value -= min;
			//---------------------------------------------------------
			// Subtract deadband value
			//---------------------------------------------------------
			int
				deadband_adjustment = (int) ((max-min)*deadbandScalar);

			value -= deadband_adjustment;
			if (value > 0)
			{
				int
					adjusted_maximum = max-deadband_adjustment;

				if (adjusted_maximum > 0)
				{
					result = ((Scalar)value)/adjusted_maximum;
				}
			}
		}
		break;

	case bipolar:
		if (mode == unaligned)
		{
			//---------------------------------------------------------
			// Use 1/2 (max-min) as temporary center
			//---------------------------------------------------------
			center = (max-min) >> 1;
			//---------------------------------------------------------
			// Set temporary deadband values
			//---------------------------------------------------------
			upperDeadband = (int) ((max-min)*deadbandScalar);
			lowerDeadband= upperDeadband;
		}
		//---------------------------------------------------------
		// Adjust for center
		//---------------------------------------------------------
		value -= center;
		//---------------------------------------------------------
		// Generate scaled results
		//---------------------------------------------------------
		if (value < 0)
		{
			value += lowerDeadband;
			if (value < 0)
			{
				if (lowerRange > 0)
				{
					if (value <= -lowerRange)
					{
						result = -1.0;
					}
					else
					{
						result = ((Scalar) value)/lowerRange;
					}
				}
			}
		}
		else
		{
			value -= upperDeadband;
			if (value > 0)
			{
				if (upperRange > 0)
				{
					if (value >= upperRange)
					{
						result = 1.0;
					}
					else
					{
						result = ((Scalar) value)/upperRange;
					}
				}
			}
		}
		break;
	}
	//---------------------------------------------------------
	// Return result
	//---------------------------------------------------------
	Check_Fpu();

#	if defined(DEBUG)
		DEBUG_STREAM << pageName << ":" << value << "=" << result << "\n";
#	endif

	return result;
}

Logical
	FilterChannel::TestInstance() const
{
	return True;
}

void
	FilterChannel::CalculateDeadBands()
{
	Check(this);

	lowerDeadband = (int) ((center-min)*deadbandScalar + .5);
	lowerRange = center - (min+lowerDeadband);

	upperDeadband = (int) ((max-center)*deadbandScalar + .5);
	upperRange = (max-upperDeadband) - center;

#	if defined(DEBUG)
		DEBUG_STREAM << "pageName     =" << pageName <<"\n";
		DEBUG_STREAM << "min   =" << min <<"\n";
		DEBUG_STREAM << "center=" << center <<"\n";
		DEBUG_STREAM << "max=   " << max <<"\n";
		DEBUG_STREAM << "lowerDeadband=   " << lowerDeadband <<"\n";
		DEBUG_STREAM << "lowerRange   =   " << lowerRange <<"\n";
		DEBUG_STREAM << "upperDeadband=   " << upperDeadband <<"\n";
		DEBUG_STREAM << "upperRange   =   " << upperRange <<"\n";
#	endif

	Check_Fpu();
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ranger ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Ranger::Ranger(
	const char *page_name,
	int hardware_min,
	int hardware_max,
	Scalar deadband
){
	Check_Pointer(this);

	pageName = page_name;
	hardwareMinimum = hardware_min;
	hardwareMaximum = hardware_max;

	SetDeadBand(deadband);
	//------------------------------------------------------------
	// We ASSUME that the control is at rest (center for joystick)
	//------------------------------------------------------------
	ForceToZero();

	Check_Fpu();
}

Ranger::~Ranger()
{
	Check(this);
	Check_Fpu();
}

Logical
	Ranger::TestInstance() const
{
	Check_Fpu();
	return True;
}

void
	Ranger::SetDeadBand(Scalar dead_band)
{
	Check(this);

	deadbandInteger =
		(int)((hardwareMaximum - hardwareMinimum) * fabs(dead_band));

	Check_Fpu();
}

void
	Ranger::ForceToZero()
{
	Check(this);
	sampledInputFlag = False;
	Check_Fpu();
}

Scalar
	Ranger::Update(int input)
{
	Check(this);
	int
		range;
	Scalar
		result = ((Scalar) 0);

	Verify(hardwareMaximum > hardwareMinimum);
	//------------------------------------------------------------
	// Uncentered processing: slide offset to keep value
	// within the proscribed range
	//------------------------------------------------------------
	if (hardwareMinimum >= 0)	 	// this designates an uncentered Ranger
	{
		//---------------------------------------------------------
		// Sample current input for offset value
		//---------------------------------------------------------
		if (!sampledInputFlag)
		{
			sampledInputFlag = True;

			offset = -input;
			//------------------------------------------
			// Initialize to reasonable values
			//------------------------------------------
			highestInput = (hardwareMaximum*3)/4;
			lowestInput = hardwareMinimum;
		}
		//------------------------------------------------------------
		// Add offset
		//------------------------------------------------------------
		input += offset;
		//------------------------------------------------------------
		// Slide offset if needed
		//------------------------------------------------------------
		if (input > hardwareMaximum)
		{
			offset -= input-hardwareMaximum;
			input = hardwareMaximum;
		}
		else if (input < hardwareMinimum)
		{
			offset -= input-hardwareMinimum;
			input = hardwareMinimum;
		}
		//-----------------------------------------------------------------
		// Keep track of limit (lowestValue will always be hardwareMinimum)
		//-----------------------------------------------------------------
		if (input > highestInput)
		{
			highestInput = input;
		}
		//------------------------------------------------------------
		// Generate scaled result
		//------------------------------------------------------------
		input -= hardwareMinimum;		// normalize to zero

		if (input > deadbandInteger)
		{
			range = highestInput - lowestInput - deadbandInteger;

			if (range > 0)
			{
				result = ((Scalar)(input-deadbandInteger))/(Scalar)range;
			}
		}
	}
	//------------------------------------------------------------
	// Centered processing: keep max, min of input to
	// determine total range
	//------------------------------------------------------------
	else
	{
		//---------------------------------------------------------
		// Sample current input for offset value
		//---------------------------------------------------------
		if (!sampledInputFlag)
		{
			sampledInputFlag = True;
			//------------------------------------------
			// Set offset such that this is the new zero
			//------------------------------------------
			offset = -input;
			//------------------------------------------
			// Initialize to reasonable values...
			// ...add offset to reflect true limits
			// that are available with this offset
			//------------------------------------------
			// HardwareMinimum is known to be negative because of 'if' statement
			Verify(hardwareMaximum > 0);

			highestInput = (hardwareMaximum*3)/4 + offset;
			lowestInput = (hardwareMinimum*3)/4 + offset;
		}
		//------------------------------------------------------------
		// Adjust for offset
		//------------------------------------------------------------
		input += offset;

		if (input > highestInput)
		{
			highestInput = input;
		}
		if (input < lowestInput)
		{
			lowestInput = input;
		}
		//---------------------------------------
		// Let's make sure before we continue...
		//---------------------------------------
		Verify(highestInput >= lowestInput);
		Verify(input <= highestInput);
		Verify(input >= lowestInput);
		//------------------------------------------------------------
		// Generate scaled result
		//------------------------------------------------------------
		if (input < -deadbandInteger)
		{
			//---------------------------------------
			// Generate range
			//---------------------------------------
			range = abs(lowestInput) - deadbandInteger;
			//---------------------------------------
			// Generate scaled result
			//---------------------------------------
			if (range > 0)
			{
				result = ((Scalar)(input+deadbandInteger))/(Scalar)range;
			}
		}
		else if (input > deadbandInteger)
		{
			//---------------------------------------
			// Generate range
			//---------------------------------------
			range = abs(highestInput) - deadbandInteger;
			//---------------------------------------
			// Generate scaled result
			//---------------------------------------
			if (range > 0)
			{
				result = ((Scalar)(input-deadbandInteger))/(Scalar)range;
			}
		}
	}

	if (result > ((Scalar) 1.0))
	{
		DEBUG_STREAM <<
			"Too high!" <<
			" input=" << input << " result=" << result << "\n" <<

			pageName <<
			" offset=" << offset <<
			" lowestInput=" << lowestInput <<
			" highestInput=" << highestInput <<
			" deadbandInteger=" << deadbandInteger <<
			" range=" << range <<
			"\n";

		result = ((Scalar) 1.0);
//Fail("Ranger::Update above 1.0");
	}
	else if (result < ((Scalar)-1.0))
	{
		DEBUG_STREAM <<
			"Too low!" <<
			" input=" << input << " result=" << result << "\n" <<

			pageName <<
			" lowestInput=" << lowestInput <<
			" highestInput=" << highestInput <<
			" deadbandInteger=" << deadbandInteger <<
			" range=" << range <<
			"\n";

		result = ((Scalar)-1.0);
//Fail("Ranger::Update below -1.0");
	}

	Verify(result <= ((Scalar) 1.0));
	Verify(result >= ((Scalar) -1.0));

	Check_Fpu();
	return result;
}

void
	Ranger::Statistics(NotationFile *failure_file)
{
	Check(this);
	Check(failure_file);

	int
		range = highestInput - lowestInput,
		hardware_range = hardwareMaximum - hardwareMinimum;

	Verify(hardware_range > 0);

	Scalar
		range_percentage = ((Scalar) range)/hardware_range;
	//---------------------------------
	// Report slippage
	//---------------------------------
	if (range_percentage > (Scalar) 1.1)	// allow 10% overrange
	{
		failure_file->SetEntry(pageName, "Overrange", range_percentage);
	}
	//---------------------------------
	// Report low range
	//---------------------------------
	if (range_percentage < (Scalar) .50)
	{
		failure_file->SetEntry(pageName, "UnderRange", range_percentage);
	}
	Check_Fpu();
}

//########################################################################
//############################### RIO ####################################
//########################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RIO::RIO(Word port, Word intNum, Logical perform_tests):
	PCSerialPacket()
{
	Tell("RIO initialization: variables\n");
	Check_Pointer(this);

	//
	//---------------------------------------------------------------------
	// Initialize values
	//---------------------------------------------------------------------
	//
	TestModeActive = 0;

	Throttle   = (Scalar) 0;
	LeftPedal  = (Scalar) 0;
	RightPedal = (Scalar) 0;
	JoystickX  = (Scalar) 0;
	JoystickY  = (Scalar) 0;

	remoteRetryCount = 0;
	remoteAbandonCount = 0;
	remoteFullBufferCount = 0;
	lineErrorCount = 0;
	overrunCount = 0;
	abandonCount = 0;
	operational = True;
//	discardCount = 0;

	failureFile = NULL;
	failureFileOpenCount = 0;
	//
	//---------------------------------------------------------------------
	// Clear error log
	//---------------------------------------------------------------------
	//
	OpenFailureFile();
	Check(failureFile);
	failureFile->DeletePage("RIOBoardErrors");
	failureFile->DeletePage("RIODeadLamps");
	failureFile->DeletePage("RIOErrors");
	CloseFailureFile();
	//
	//---------------------------------------------------------------------
	// Read value ranges from notation file, initialize rangers
	//
	// Order of integers is: minimum, maximum, end-zone-range, deadband
	//---------------------------------------------------------------------
	//
	{
//		NotationFile
//			stat_file((const char *) "RIO.INI");

		leftPedalRanger = new Ranger("LeftPedal", 0, 470, .0);
		Register_Object(leftPedalRanger);

		rightPedalRanger = new Ranger("RightPedal", 0, 470, .0);
		Register_Object(rightPedalRanger);

		throttleRanger = new Ranger("Throttle", 0, 800, .05);
		Register_Object(throttleRanger);

		joystickXRanger = new Ranger("JoystickX", -96, 96, .05);
		Register_Object(joystickXRanger);

		joystickYRanger = new Ranger("JoystickY", -97, 108, .05);
		Register_Object(joystickYRanger);
	}
	//
	//---------------------------------------------------------------------
	// Start the PCSerialPacket
	//---------------------------------------------------------------------
	//
	int
		status = PCSerialPacket::Initialize(
			PCSP_9600,
			port,
			intNum,
			(Byte *) RIOLengths,
			13,
			(Byte *) RIOLengths,
			13
		);

	switch(status)
	{
	case PCSPAKInitOk:
		// silent if OK
		break;
	case PCSPAKInitErrDPMI:
		DEBUG_STREAM << "RIO::RIO DPMI error!\n";
		break;
	case PCSPAKInitErrAlloc:
		DEBUG_STREAM << "RIO::RIO buffer allocation error!\n";
		break;
	default:
		DEBUG_STREAM << "RIO::RIO unexpected init result=" << status << "!\n";
		break;
	}

#if 0
	extern Byte previousIMR, previousISR, previousIRR;
	extern Byte middleIMR, middleISR, middleIRR;
	extern Byte postIMR, postISR, postIRR;
	extern Byte pcspakLSR, pcspakIER, pcspakIIR;

	DEBUG_STREAM <<
		hex <<
		"previousIMR=" << (int) (previousIMR & 0xFF) <<
		", mid=" << (int) (middleIMR & 0xFF) <<
		", post=" << (int) (postIMR & 0xFF) << "\n" <<

		"previousISR=" << (int) (previousISR & 0xFF) <<
		", mid=" << (int) (middleISR & 0xFF) <<
		", post=" << (int) (postISR & 0xFF) << "\n" <<

		"previousIRR=" << (int) (previousIRR & 0xFF) <<
		", mid=" << (int) (middleIRR & 0xFF) <<
		", post=" << (int) (postIRR & 0xFF) << "\n" <<

		"pcspakLSR=" << (int) (pcspakLSR & 0xFF) <<
		", pcspakIER=" << (int) (pcspakIER & 0xFF) <<
		", pcspakIIR was=" << (int) (pcspakIIR & 0xFF) << "\n" <<
		dec;
#endif

	MajorRevision = 0xFF;
	MinorRevision = 0xFF;

	if (perform_tests)
	{
		//
		//---------------------------------------------------------------------
		// Reset remote I/O board
		//---------------------------------------------------------------------
		//
		Time
			when;
		int
			bomb = 0;

		RIOEvent
			dummy_event;

		Tell("RIO initialization: reset board\n");

		// Reset RIO board
		SetDTR(True);		// Assert reset
		when = Now();
		for(when += .1; Now() < when; );

		SetDTR(False);		// Retract reset
		when = Now();
		for(when += 1.0; Now() < when; );

		//
		//---------------------------------------------------------------------
		// Wait for test mode to start (or bomb out)
		//---------------------------------------------------------------------
		//
		Tell("RIO initialization: check board\n");
		RequestCheck();

		when = Now();
		when += 5.0;
		while (!TestModeActive)
		{
			if (Now() > when)
			{
				bomb = 1;
				DEBUG_STREAM << "RIO never came back from check request!\n";
				break;
			}
			GetNextEvent(&dummy_event);
		}
		//
		//---------------------------------------------------------------------
		// Wait for test mode to finish (or bomb out)
		//---------------------------------------------------------------------
		//
		if (!bomb)
		{
			when = Now();
			when += 5.0;
			while (TestModeActive)
			{
				if (Now() > when)
				{
					bomb = 1;
					DEBUG_STREAM << "RIO never came back from test mode!\n";
					break;
				}
				GetNextEvent(&dummy_event);
			}
		}
		//
		//---------------------------------------------------------------------
		// Get version (or bomb out)
		//---------------------------------------------------------------------
		//

		if (!bomb)
		{
			RequestVersion();
			when = Now();
			when += 5.0;
			while (MajorRevision == 0xFF)
			{
				if (Now() > when)
				{
					DEBUG_STREAM << "RIO never came back from version request!\n";
					break;
				}
				GetNextEvent(&dummy_event);
			}
		}
	}

	Tell("RIO initialization finished\n");
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Destructor
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
extern int historyIndex;
extern Byte history[];

enum
{
	txFlag=1,
	rxFlag=2,
	eventFlag=3
};

enum
{
	IRQ_EVENT=0,
	IRQ_EVENT_RX=1,
	IRQ_EVENT_TX=2,
	IRQ_EVENT_MSR=3,
	IRQ_EVENT_LSR=4,
	IRQ_EVENT_BOGUS=5,

	TX_EVENT_RESTART=10,
	TX_EVENT_ABANDON=11,
	TX_EVENT_DONE=12,
	TX_EVENT_ACK=13,
	TX_EVENT_NAK=14,
	TX_EVENT_EARLY=15,
	TX_EVENT_EMPTY=16,
	TX_EVENT_KICK=17,

	RX_EVENT_FULL=20,
	RX_EVENT_NOT_CMD=21,
	RX_EVENT_FULLBODY=22,
	RX_EVENT_NAK=23,
	RX_EVENT_CKSMERR=24,
	RX_EVENT_OK=25
};

RIO::~RIO()
{
	Check(this);

#if 0
	//---------------------------------------------------------------------
	// Save serial history for debugging
	//---------------------------------------------------------------------
	// If you enable this, set KEEP_HISTORY in PCSPAK.ASM to '1' as well.
	// Otherwise you will be viewing nonexistent history data.
	const char
		*commandName[13] =
			{
			"CheckReq",		//=0x80
			"VersionReq",
			"AnalogReq",
			"ResetReq",
			"LampReq",
			"CheckReply",
			"VersionReply",
			"AnalogReply",
			"ButtonPressed",
			"ButtonReleased",
			"KeyPressed",
			"KeyReleased",
			"TestModeChange"
			};


	int i, type, j;

	cout << flush;
	printf("tx\t\trx\t\tevent\n");
	printf("-----\t-----\t\t---------------------\n");
	for(i=0; i<historyIndex; i+=2)
	{
		j = history[i];		// 80x86 stores as low, high
		type = history[i+1];

		switch(type)
		{
		case txFlag:
			switch(j)
			{
			case 0xFC: printf("ACK\n"); break;
			case 0xFD: printf("NAK\n"); break;
			case 0xFE: printf("RST\n"); break;
			case 0xFF: printf("IDL\n"); break;
			default:
				if ((j >= 0x80) && (j <= 0x8C))
				{
					printf("%s", commandName[j-0x80]);
				}
				else
				{
					printf("%02X", j);
				}
				printf("\n");
				break;
			}
			break;
		case rxFlag:
			printf("\t\t");
			switch(j)
			{
			case 0xFC: printf("ACK\n"); break;
			case 0xFD: printf("NAK\n"); break;
			case 0xFE: printf("RST\n"); break;
			case 0xFF: printf("IDL\n"); break;
			default:
				if ((j >= 0x80) && (j <= 0x8C))
				{
					printf("%s", commandName[j-0x80]);
				}
				else
				{
					printf("%02X", j);
				}
				printf("\n");
				break;
			}
			break;
		case eventFlag:
			printf("\t\t\t\t");
			switch(j)
			{
			case IRQ_EVENT:         printf("IRQ entry\n");         break;
			case IRQ_EVENT_RX:      printf("IRQ_EVENT_RX\n");      break;
			case IRQ_EVENT_TX:      printf("IRQ_EVENT_TX\n");      break;
			case IRQ_EVENT_MSR:     printf("IRQ_EVENT_MSR\n");     break;
			case IRQ_EVENT_LSR:     printf("IRQ_EVENT_LSR\n");     break;
			case IRQ_EVENT_BOGUS:   printf("IRQ_EVENT_BOGUS\n");   break;

			case TX_EVENT_RESTART:  printf("TX_EVENT_RESTART\n");  break;
			case TX_EVENT_ABANDON:  printf("TX_EVENT_ABANDON\n");  break;
			case TX_EVENT_DONE:     printf("TX_EVENT_DONE\n");     break;
			case TX_EVENT_ACK:      printf("TX_EVENT_ACK\n");      break;
			case TX_EVENT_NAK:      printf("TX_EVENT_NAK\n");      break;
			case TX_EVENT_EARLY:    printf("TX_EVENT_EARLY\n");    break;
			case TX_EVENT_EMPTY:    printf("TX_EVENT_EMPTY\n");    break;
			case TX_EVENT_KICK:     printf("TX_EVENT_KICK\n");     break;

			case RX_EVENT_FULL:     printf("RX_EVENT_FULL\n");     break;
			case RX_EVENT_NOT_CMD:  printf("RX_EVENT_NOT_CMD\n");  break;
			case RX_EVENT_FULLBODY: printf("RX_EVENT_FULLBODY\n"); break;
			case RX_EVENT_NAK:  		printf("RX_EVENT_NAK\n");      break;
			case RX_EVENT_CKSMERR:  printf("RX_EVENT_CKSMERR\n");  break;
			case RX_EVENT_OK:       printf("RX_EVENT_OK\n");       break;
			default:   printf("unknown event %02X\n", j); break;
			}
			break;
		}
	}
	fflush(stdout);
#endif

	//---------------------------------------------------------------------
	// Flush data to RIO
	//---------------------------------------------------------------------
	RIOEvent
		dummy_event;

	// This odd sequence of allocation, setting, and adding is necessary:
	// if you try to combine them, the compiler gets confused as to which
	// method to invoke for 'Now()'.
	Time
		then;
	then = Now();
	then += 5.0;
	while (Now() < then)
	{
		//-------------------------------------------------
		// Drop out of loop when all packets have been
		// transmitted and the last character has been sent
		//-------------------------------------------------
		if (TransmitQueueCount() <= 0)                  
		{
			if (!IsActive())
			{
				break;
			}
		}
		GetNextEvent(&dummy_event);
	}

	//---------------------------------------------------------------------
	// Update failure log
	//---------------------------------------------------------------------
#if LOG_RIO_DATA
	OpenFailureFile();
	Check(failureFile);

	failureFile->SetEntry("RIO","lineErrors",lineErrorCount);
	failureFile->SetEntry("RIO","abandonedPackets",abandonCount);
	failureFile->SetEntry("RIO","overruns",overrunCount);
	failureFile->SetEntry("RIO","remoteRetry",remoteRetryCount);
	failureFile->SetEntry("RIO","remoteAbandon",remoteAbandonCount);
	failureFile->SetEntry("RIO","remoteFullBuffer",remoteFullBufferCount);

	CloseFailureFile();
#endif

	//-------------------------------------------
	// Update ranger statistics, delete rangers
	//-------------------------------------------
	{
//		NotationFile
//			stat_file((const char *) "RIO.INI");

		Check(leftPedalRanger);
//		leftPedalRanger->Statistics(&stat_file);
		Unregister_Object(leftPedalRanger);
		delete leftPedalRanger;
		leftPedalRanger = NULL;

		Check(rightPedalRanger);
//		rightPedalRanger->Statistics(&stat_file);
		Unregister_Object(rightPedalRanger);
		delete rightPedalRanger;
		rightPedalRanger = NULL;

		Check(throttleRanger);
//		throttleRanger->Statistics(&stat_file);
		Unregister_Object(throttleRanger);
		delete throttleRanger;
		throttleRanger = NULL;

		Check(joystickXRanger);
//		joystickXRanger->Statistics(&stat_file);
		Unregister_Object(joystickXRanger);
		delete joystickXRanger;
		joystickXRanger = NULL;
	
		Check(joystickYRanger);
//		joystickYRanger->Statistics(&stat_file);
		Unregister_Object(joystickYRanger);
		delete joystickYRanger;
		joystickYRanger = NULL;
	}
	Check_Fpu();
}

Logical
	RIO::TestInstance() const
{
	return True;
}

extern Byte pic_isr, pic_imr, pic_irr;
extern Byte uart_iir;
extern Byte pcspak_active,pcspak_rxState,pcspak_txState;
extern Word pcspak_head,pcspak_tail;
extern Word pcspak_tempHead,pcspak_tempTail,pcspak_count;

extern "C" void
	PCSPAKState(PCSerialPacket *ptr);

Logical
	RIO::GetNextEvent(RIOEvent *destination)
{
	Check(this);

	Byte		receive_buffer[256];
	Logical	reply, looping;
	Word		errors;

//PCSPAKState(this);
//
//cout << flush;
//printf(
//	"RIO isr=%02X imr=%02X irr=%02X, iir=%02X, a=%1d c=%04X ts=%1d rs=%1d e=%X\n",
//	pic_isr, pic_imr, pic_irr,
//	uart_iir,
//	pcspak_active,
//	pcspak_count,
//   pcspak_txState, pcspak_rxState,
//	pcspak_tempHead,pcspak_tempTail,
//	Errors()
//);
//fflush(stdout);

	if (! operational)
	{
		return False;
	}

	errors = Errors();
	if (errors)
	{
		if (errors & PCSerialPacket::initError)
		{
			NotationFile
				failure((const char *) "FAILURE.LOG");

			failure.SetEntry("RIOErrors","error","initialization");
			operational = False;
		}
		else
		{
			if (errors & PCSerialPacket::txAbandonPacket)
			{
//cout << "RIO:: packet abandoned\n";
				++abandonCount;
			}
			if (errors & PCSerialPacket::overrunError)
			{
//cout << "RIO:: overrun\n";
				++overrunCount;
			}
			if (errors &
				(
					PCSerialPacket::parityError |
					PCSerialPacket::framingError |
					PCSerialPacket::overrunError
				)
			)
			{
				++lineErrorCount;
			}
		}
	}

	do
	{
		looping = False;

		reply = ReceivePacket(receive_buffer);
		if (reply)
		{
			switch(receive_buffer[0])
			{
			case CheckReply:
				switch(receive_buffer[1])
				{
				case BoardMissing:
					OpenFailureFile();
					BoardError(failureFile,"missing_board",(int) receive_buffer[2]);
					CloseFailureFile();
					break;

				case BoardBad:
					OpenFailureFile();
					BoardError(failureFile,"dead_board",(int) receive_buffer[2]);
					CloseFailureFile();
					break;

				case LampBad:
					OpenFailureFile();
					Check(failureFile); 
					failureFile->AppendEntry(
						"RIODeadLamps",
						"lamp",
						LBE4ControlsManager::GetLampName((int) receive_buffer[2])
					);
					CloseFailureFile();
					break;

				case RestartCount:
					remoteRetryCount += receive_buffer[2];
					break;
						 
				case AbandonCount: 
					remoteAbandonCount += receive_buffer[2];
					break;
						 
				case FullBufferCount: 
					remoteFullBufferCount += receive_buffer[2];
					break;
				}
				looping = True;		// status events not sent to host
				break;

			case VersionReply:
				destination->Type = RIO::VersionEvent;
				MajorRevision = receive_buffer[1];
				MinorRevision = receive_buffer[2];
				break;

			case AnalogReply:
				destination->Type = RIO::AnalogEvent;
				//
				// NOTE: no data is sent in this packet.
				// The application is expected to directly read
				// the values in the object.
				//

				//----------------------------------------
				// Discard the next N analog packets
				// (set by ForceCenterJoystick)
				//----------------------------------------
//				if (discardCount)
//				{
//					--discardCount;
//				}
//				else
				{
					Check(throttleRanger);
					Throttle = throttleRanger->Update(
							// Note (-): Throttle counts BACKWARDS
							-CombinePair(receive_buffer[1], receive_buffer[2])
						);

					Check(leftPedalRanger);
					LeftPedal = leftPedalRanger->Update(
							CombinePair(receive_buffer[3], receive_buffer[4])
						);

					Check(rightPedalRanger);
					RightPedal = rightPedalRanger->Update(
							CombinePair(receive_buffer[5], receive_buffer[6])
						);

					Check(joystickYRanger);
					JoystickY = joystickYRanger->Update(
							CombinePair(receive_buffer[7], receive_buffer[8])
						);

					Check(joystickXRanger);
					JoystickX = joystickXRanger->Update(
							CombinePair(receive_buffer[9], receive_buffer[10])
						);
				}
				break;

			case ButtonPressed:
				destination->Type = RIO::ButtonPressedEvent;
				destination->Data.Unit = receive_buffer[1];
				break;

			case ButtonReleased:
				destination->Type = RIO::ButtonReleasedEvent;
				destination->Data.Unit = receive_buffer[1];
				break;

			case KeyPressed:
				destination->Type = RIO::KeyEvent;
				destination->Data.Keyboard.Unit = receive_buffer[1];
				destination->Data.Keyboard.Key = receive_buffer[2];
				break;

			case TestModeChange:
				if (receive_buffer[1] != 0)
				{
					Tell("RIO entered test mode\n");
					TestModeActive = 1;

					OpenFailureFile();
					Check(failureFile);
					failureFile->DeletePage("RIOBoardErrors");
					failureFile->DeletePage("RIODeadLamps");
				}
				else
				{
					Tell("RIO exited test mode\n");
					TestModeActive = 0;
					CloseFailureFile();
				}
				looping = True;		// this event not sent to host
				break;          

			default:						// discard unused or bogus packets
				looping = True;
				break;
			}
		}
	}
	while (looping);

	Check_Fpu();
	return reply;
}

void
	RIO::ForceCenterJoystick()
{
	Check(this);

//	ResetVerticalJoystick();
//	ResetHorizontalJoystick();
	//-------------------------------------------
	// Make sure we don't use 'old' data
	// (skip the next two packets)
	// ...why 2? Just to make sure...
	//-------------------------------------------
//	discardCount = 2;

	Check(joystickXRanger);
	joystickXRanger->ForceToZero();

	Check(joystickYRanger);
	joystickYRanger->ForceToZero();

	Check_Fpu();
}

void
	RIO::SetJoystickDeadBand(Scalar dead_band)
{
	Check(this);

	Check(joystickXRanger);
	joystickXRanger->SetDeadBand(dead_band);

	Check(joystickYRanger);
	joystickYRanger->SetDeadBand(dead_band);

	Check_Fpu();
}

void
	RIO::SetThrottleDeadBand(Scalar dead_band)
{
	Check(this);

	Check(throttleRanger);
	throttleRanger->SetDeadBand(dead_band);

	Check_Fpu();
}

void
	RIO::SetPedalsDeadBand(Scalar dead_band)
{
	Check(this);

	Check(leftPedalRanger);
	leftPedalRanger->SetDeadBand(dead_band);

	Check(rightPedalRanger);
	rightPedalRanger->SetDeadBand(dead_band);

	Check_Fpu();
}

void
	RIO::RequestCheck()
{
	Check(this);
	static Byte	request_check_string[] = { RIO::CheckRequest };

	if (operational && !TestModeActive)
	{
		 SendPacket((Byte *) request_check_string);
	}
	Check_Fpu();
}

void
	RIO::RequestVersion()
{
	Check(this);
	static Byte	request_version_string[] = { RIO::VersionRequest };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_version_string);
	}
	Check_Fpu();
}

void
	RIO::RequestAnalogUpdate()
{
	Check(this);
	static Byte	request_analog_string[] = { RIO::AnalogRequest };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_analog_string);
	}
	Check_Fpu();
}

void
	RIO::GeneralReset()
{
	Check(this);
	static Byte	request_reset_string[] = { RIO::ResetRequest, 0 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_reset_string);
	}
	Check_Fpu();
}

void
	RIO::ResetThrottle()
{
	Check(this);
	static Byte	request_throttle_string[] = { RIO::ResetRequest, 1 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_throttle_string);
	}
	Check_Fpu();
}

void
	RIO::ResetLeftPedal()
{
	Check(this);
	static Byte	request_lpedal_string[] = { RIO::ResetRequest, 2 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_lpedal_string);
	}
	Check_Fpu();
}

void
	RIO::ResetRightPedal()
{
	Check(this);
	static Byte	request_rpedal_string[] = { RIO::ResetRequest, 3 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_rpedal_string);
	}
	Check_Fpu();
}

void
	RIO::ResetVerticalJoystick()
{
	Check(this);
	static Byte	request_vstick_string[] = { RIO::ResetRequest, 4 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_vstick_string);
	}
	Check_Fpu();
}

void
	RIO::ResetHorizontalJoystick()
{
	Check(this);
	static Byte	request_hstick_string[] = { RIO::ResetRequest, 5 };

	if (operational && !TestModeActive)
	{
		SendPacket((Byte *) request_hstick_string);
	}
	Check_Fpu();
}

void
	RIO::SetLamp(int lampNumber, int state)
{
	Check(this);
	static Byte	request_lamp_string[] = { RIO::LampRequest, 0, 0 };

	if (operational)
	{
		request_lamp_string[1] = (Byte) (lampNumber & 0x7F);
		request_lamp_string[2] = (Byte) (state & 0x7F);
		SendPacket((Byte *) request_lamp_string);
	}
	Check_Fpu();
}

void
	RIO::OpenFailureFile()
{
	Check(this);

	if (failureFileOpenCount == 0)
	{
		failureFile = new NotationFile("FAILURE.LOG");
		Check(failureFile);
		Register_Object(failureFile);
	}
	++failureFileOpenCount;
	Check_Fpu();
}

void
	RIO::CloseFailureFile()
{
	Check(this);

	--failureFileOpenCount;
	if (failureFileOpenCount <= 0)
	{
		failureFileOpenCount = 0;

		Check(failureFile);
		Unregister_Object(failureFile);
		delete failureFile;
	}
	Check_Fpu();
}

