Files
BT412/engine/MUNGA_L4/L4RIO.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

1675 lines
38 KiB
C++

#include "mungal4.h"
#include "..\munga\controls.h"
#pragma hdrstop
#include "l4rio.h"
#include "l4ctrl.h"
#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
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Win32 Serial support: ADB 02/13/07
//RIO::RIO(Word port, Word intNum, Logical perform_tests):
RIO::RIO(const char* port, 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
//---------------------------------------------------------------------
//
//Win32 Serial support: ADB 02/13/07
//int status = PCSerialPacket::Initialize(
// PCSP_9600,
// port,
// intNum,
// (Byte *) RIOLengths,
// 13,
// (Byte *) RIOLengths,
// 13
// );
int status = PCSerialPacket::Initialize(PCS_9600, PCS_N81, port, (BYTE*)RIOLengths, 13, (BYTE*)RIOLengths, 13);
switch(status)
{
case PCSPAKInitOk:
// silent if OK
DEBUG_STREAM << "RIO successfully initialized!\n";
break;
//Win32 Serial Support: ADB 02/24/2007
//case PCSPAKInitErrDPMI:
// DEBUG_STREAM << "RIO::RIO DPMI error!\n";
// break;
//case PCSPAKInitErrAlloc:
// DEBUG_STREAM << "RIO::RIO buffer allocation error!\n";
// break;
case PCSPAKWin32Err:
DEBUG_STREAM << "RIO::RIO Win32 Serial Initalization 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 += .1f; Now() < when; );
SetDTR(False); // Retract reset
when = Now();
for(when += 1.0f; Now() < when; );
//
//---------------------------------------------------------------------
// Wait for test mode to start (or bomb out)
//---------------------------------------------------------------------
//
Tell("RIO initialization: check board\n");
RequestCheck();
when = Now();
when += 5.0f;
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.0f;
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.0f;
while (MajorRevision == 0xFF)
{
if (Now() > when)
{
DEBUG_STREAM << "RIO never came back from version request!\n";
break;
}
GetNextEvent(&dummy_event);
}
}
if(!bomb)
{
DEBUG_STREAM<<"RIO initialization finished\n";
}
else
{
DEBUG_STREAM<<"RIO initialization failed! Shutting Down Serial Port!\n";
ShutdownRxThread();
}
}
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.0f;
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()
{
//DEBUG_STREAM << "Analog Update Requested: " << GetTickCount() << std::endl << std::flush;
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();
}