/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-5
**  FileName: Test10_InUseCounters.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-5 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-5 document in an automated manner, when compiled
**            and used with an SAE J2534-compatible pass-thru device.
**
**            File j1699.c contains information on building and running this test.
**
**  Description:
**
**  Modifications:  03/04/2024  Initial Version
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <string.h>   // C Library character array declarations
#include <time.h>     // C Library time and date declarations
#include <conio.h>    // MS-DOS console input and output declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


STATUS VerifyDTCExtDataList   ( BYTE  ExtDataId );
void   LogDTCExtDataList      ( BYTE  ExtDataId );
STATUS VerifyDTCExtData       ( BYTE  ExtDataID );
STATUS GetDTCExtData          ( BYTE  ExtDataID,
                                BYTE  EcuIdx,
                                DTC  *pDtc );

STATUS SaveDTCIUMPRData       ( BYTE  EcuIdx,
                                WORD *pIndex );
STATUS SaveDTCTestResultData  ( BYTE  EcuIdx,
                                WORD *pIndex );
STATUS SaveDTCSMADData        ( BYTE  EcuIdx,
                                WORD *pIndex );
void   LogDTCIUMPRData        ( BYTE  EcuIdx,
                                WORD  Index );
void   LogDTCTestResultData   ( BYTE  EcuIdx,
                                WORD  Index );
void   LogDTCSMADData         ( BYTE  EcuIdx,
                                WORD  Index );

STATUS VerifyDTCReadinessGroupList ( void );
void   SaveDTCReadinessGroupList   ( BYTE  EcuIdx );
void   LogDTCReadinessGroupList    ( BYTE  EcuIdx,
                                     BYTE  Index );

void GetIUMPR_RGList ( void );
void GetSMAD_RGList  ( void );

boolean isDTCinList           ( BYTE  EcuIdx,
                                DTC  *pID,
                                BYTE  ExtDataId );


extern const char       szEcuId[];
extern unsigned long    gFailureCount;

WORD      gDTCExtDataTotal;
WORD      gDTCExtDataCount;

const char szRG[]                   =  "Readiness Group";
const char szRGListTitle[]          =  "Readiness Group LIST";
const char szRGSize[]               =  "Readiness Group SIZE";
const char szRGId[]                 =  "RGID";
const char szRGName[]               =  "DTC";
const char szRGIgnored[]            =  "Not monitored for changes";

const char szIumpr[]                =  "IUMPR";
const char szIumprListTitle[]       =  "IUMPR LIST";
const char szIumprSize[]            =  "IUMPR SIZE";
const char szIumprName[]            =  "DTC";
const char szIumprData1[]           =  "N";
const char szIumprData2[]           =  "D";

const char szTestResult[]           =  "TestResult";
const char szTestResultListTitle[]  =  "TestResult LIST";
const char szTestResultSize[]       =  "TestResult SIZE";
const char szTestResultName[]       =  "DTC";
const char szTestResultData1[]      =  "Number of Test Results";
const char szTestResultData2[]      =  "Unit and Scaling ID";
const char szTestResultData3[]      =  "Test Value";
const char szTestResultData4[]      =  "Min. Test Limit";
const char szTestResultData5[]      =  "Max. Test Limit";

const char szSmad[]                 =  "SMAD";
const char szSmadListTitle[]        =  "SMAD LIST";
const char szSmadSize[]             =  "SMAD SIZE";
const char szSmadName[]             =  "DTC";
const char szSmadData1[]            =  "MAN";
const char szSmadData2[]            =  "MAD";
const char szSmadData3[]            =  "SMAR";

const char szUnassigned[]  =   "UNASSIGNED";

const char *szReadinessGroupAcronym[] =
{
	"NONE",
	"CAT",
	"HCAT",
	"MIS",
	"EVAP",
	"AIR",
	"FUEL",
	"EGS",
	"EGR",
	"PCV",
	"COOL",
	"CSER",
	"VVT",
	"DOR",
	"CCM",
	"OTH",
	"NMHC",
	"NOx",
	"BP",
	"NOxAbs",
	"PM"
};
const char *szReadinessGroupName[] =
{
	"No Readiness Group",
	"Catalyst Monitor",
	"Heated Catalyst Monitor",
	"Misfire Monitor",
	"Evaporative System Monitoring",
	"Secondary Air System Monitoring",
	"Fuel System Monitoring",
	"Exhaust Gas Sensor Monitoring",
	"Exhaust Gas Recirculation System Monitoring",
	"Positive/Crankcase Ventilation System Monitoring",
	"Engine Cooling System Monitoring",
	"Cold Start Emission Reduction Strategy Monitoring",
	"Variable Valve Timing, Lift, and/or Control System Monitoring",
	"Direct Ozone Reduction System Monitoring",
	"Comprehensive Component Monitoring",
	"Other Emission Control or Source System Monitoring",
	"Non-Methane Hydrocarbon Converting Catalyst Monitoring",
	"Oxides of Nitrogen Converting Catalyst Monitoring",
	"Boost Pressure Control System Monitoring",
	"NOx Adsorber Monitoring",
	"Particulate Matter Filter Monitoring"
};


#define UNDEFINED_SCALE_ITEM    0                // indicates that the associated Unit/Scale ID is not currently used
#define SCALE_UNSIGNED_2B       1                // defines an unsigned 2-byte raw value e.g., 0=0, 0xFFFF=65535
#define SCALE_UNSIGNED_2B_STATE 2                // defines an unsigned 2-byte raw value e.g., 0=0, 0xFFFF=65535 where the UnitString holds a "|" seperated list of Value=State
#define SCALE_SIGNED_2B         3                // defines an signed 2-byte raw value e.g., 0x8000=-32768, 0xFFFF=-1, 0=0, 1=1, 0x7FFF=32767
#define MAX_UNIT_STRING_LENGTH  15               // max length of the unit string

typedef struct
{
	WORD    ID;                                  // Unit / Scale ID
	BYTE    RawDataType;                         // raw data type; SCALE_UNSIGNED_2B, SCALE_SIGNED_2B
	BYTE    DisplayPrecision;                    // decimal places of precision for the display
	double  Scaling;                             // scaling factor
	double  Offset;                              // offset (applied after scaling)
	char    UnitString[MAX_UNIT_STRING_LENGTH];  // unit string
} UNIT_SCALE_ENTRY;


UNIT_SCALE_ENTRY UnitScaletTable[] =             // Unit / Scale table from J1979DA July 11, 2025
{ // ID,     RawDataType,      DP, Scaling,          Offset,  UnitString
	{0x0001, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     ""},
	{0x0002, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     ""},
	{0x0003, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     ""},
	{0x0004, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,     ""},
	{0x0005, SCALE_UNSIGNED_2B, 6, 0.000030517578,   0.0,     ""},
	{0x0006, SCALE_UNSIGNED_2B, 6, 0.000305,         0.0,     ""},
	{0x0007, SCALE_UNSIGNED_2B, 2, 0.25,             0.0,     "rpm"},
	{0x0008, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "km/h"},
	{0x0009, SCALE_UNSIGNED_2B, 2, 1.0,              0.0,     "km/h"},
	{0x000A, SCALE_UNSIGNED_2B, 3, 0.1220703125,     0.0,     "mV"},
	{0x000B, SCALE_UNSIGNED_2B, 3, 1.0,              0.0,     "mV"},   // CAREFUL raw: V --> Eng: mV
	{0x000C, SCALE_UNSIGNED_2B, 2, 10.0,             0.0,     "mV"},   // CAREFUL raw: V --> Eng: mV
	{0x000D, SCALE_UNSIGNED_2B, 8, 0.00390625,       0.0,     "mA"},
	{0x000E, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,     "A"},
	{0x000F, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "A"},
	{0x0010, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,      "s"},   // CAREFUL raw: ms --> Eng: s
	{0x0011, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "s"},    // CAREFUL raw: ms --> Eng: s
	{0x0012, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "s"},
	{0x0013, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "mOhm"},
	{0x0014, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "Ohm"},
	{0x0015, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "kOhm"},
	{0x0016, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "Deg. C"},
	{0x0017, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "kPa"},
	{0x0018, SCALE_UNSIGNED_2B, 4, 0.0117,           0.0,     "kPa"},
	{0x0019, SCALE_UNSIGNED_2B, 3, 0.079,            0.0,     "kPa"},
	{0x001A, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "kPa"},
	{0x001B, SCALE_UNSIGNED_2B, 0, 10,               0.0,     "kPa"},
	{0x001C, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "Deg."},
	{0x001D, SCALE_UNSIGNED_2B, 1, 0.5,              0.0,     "Deg."},
	{0x001E, SCALE_UNSIGNED_2B, 7, 0.000030517578,   0.0,     "lambda"},
	{0x001F, SCALE_UNSIGNED_2B, 2, 0.05,             0.0,     "A/F ratio"},
	{0x0020, SCALE_UNSIGNED_2B, 7, 0.00390625,       0.0,     ""},
	{0x0021, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "mHz"},
	{0x0022, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "Hz"},
	{0x0023, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "kHz"},
	{0x0024, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "counts"},
	{0x0025, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "km"},
	{0x0026, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "mV/ms"},
	{0x0027, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "g/s"},
	{0x0028, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "g/s"},
	{0x0029, SCALE_UNSIGNED_2B, 4, 0.00025,          0.0,     "kPa/s"},  // CAREFUL raw: Pa/s --> Eng: kPa/s
	{0x002A, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,     "kg/hr"},
	{0x002B, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "switches"},
	{0x002C, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "g/cyl"},
	{0x002D, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "mg/stroke"},
	{0x002E, SCALE_UNSIGNED_2B_STATE, 0, 1.0,        0.0,     "0=FALSE|1=TRUE"},  // CAREFUL This is state encoded: 0=FALSE, 1=TRUE
	{0x002F, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "%"},
	{0x0030, SCALE_UNSIGNED_2B, 6, 0.001526,         0.0,     "%"},
	{0x0031, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,     "L"},
	{0x0032, SCALE_UNSIGNED_2B, 6, 0.00003051757813, 0.0,     "inch"},
	{0x0033, SCALE_UNSIGNED_2B, 8, 0.000244140625,   0.0,     "lambda"},
	{0x0034, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "min"},
	{0x0035, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "s"},    // CAREFUL raw: ms --> Eng: s
	{0x0036, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "g"},
	{0x0037, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "g"},
	{0x0038, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "g"},
	{0x0039, SCALE_UNSIGNED_2B, 2, 0.01,             -327.68, "%"},    // this is the only one with an offset! (so far)
	{0x003A, SCALE_UNSIGNED_2B, 3, 0.001,            0.0,     "g"},
	{0x003B, SCALE_UNSIGNED_2B, 4, 0.0001,           0.0,     "g"},
	{0x003C, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "us"},
	{0x003D, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "mA"},
	{0x003E, SCALE_UNSIGNED_2B, 2, 0.00006103516,    0.0,     "mm2"},
	{0x003F, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "L"},
	{0x0040, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "ppm"},
	{0x0041, SCALE_UNSIGNED_2B, 2, 0.01,             0.0,     "uA"},
	{0x0042, SCALE_UNSIGNED_2B, 1, 0.1,              0.0,     "kJ"},
	{0x0043, SCALE_UNSIGNED_2B, 6, 0.000244140625,   0.0,     "g/kWh"},
	{0x0044, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "uS/cm"},
	{0x0045, SCALE_UNSIGNED_2B, 2, 0.1,              0.0,     "%"},
	{0x0046, SCALE_UNSIGNED_2B, 2, 0.00005,          0.0,     "V/% Duty Cycle"},
	{0x0047, SCALE_UNSIGNED_2B, 0, 1.0,              0.0,     "W"},
	{0x0081, SCALE_SIGNED_2B,   0, 1.0,              0.0,     ""},
	{0x0082, SCALE_SIGNED_2B,   1, 0.1,              0.0,     ""},
	{0x0083, SCALE_SIGNED_2B,   2, 0.01,             0.0,     ""},
	{0x0084, SCALE_SIGNED_2B,   3, 0.001,            0.0,     ""},
	{0x0085, SCALE_SIGNED_2B,   7, 0.000030517578,   0.0,     ""},
	{0x0086, SCALE_SIGNED_2B,   6, 0.000305,         0.0,     ""},
	{0x0087, SCALE_SIGNED_2B,   0, 1.0,              0.0,     "ppm"},
	{0x008A, SCALE_SIGNED_2B,   3, 0.1220703125,     0.0,     "mV"},
	{0x008B, SCALE_SIGNED_2B,   3, 0.001,            0.0,     "V"},
	{0x008C, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "V"},
	{0x008D, SCALE_SIGNED_2B,   8, 0.00390625,       0.0,     "mA"},
	{0x008E, SCALE_SIGNED_2B,   3, 0.001,            0.0,     "A"},
	{0x008F, SCALE_SIGNED_2B,   0, 1.0,              0.0,     "us"},
	{0x0090, SCALE_SIGNED_2B,   0, 1.0,              0.0,     "ms"},
	{0x0091, SCALE_SIGNED_2B,   1, 0.1,              0.0,     "s"},
	{0x0092, SCALE_SIGNED_2B,   1, 0.1,              0.0,     "Nm"},
	{0x0093, SCALE_SIGNED_2B,   1, 10.0,             0.0,     "rpm"},
	{0x0096, SCALE_SIGNED_2B,   1, 0.1,              0.0,     "Deg. C"},
	{0x0097, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "Deg. C/s"},
	{0x0098, SCALE_SIGNED_2B,   0, 1.0,              0.0,     "mg/stroke"},
	{0x0099, SCALE_SIGNED_2B,   1, 0.1,              0.0,     "kPa"},
	{0x009C, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "Deg."},
	{0x009D, SCALE_SIGNED_2B,   1, 0.5,              0.0,     "Deg."},
	{0x00A8, SCALE_SIGNED_2B,   0, 1.0,              0.0,     "g/s"},
	{0x00A9, SCALE_SIGNED_2B,   2, 0.25,             0.0,     "Pa/s"},
	{0x00AA, SCALE_SIGNED_2B,   0, 10.0,             0.0,     "g/s"},
	{0x00AB, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "g/s"},    // CAREFUL raw: mg/s --> Eng: g/s
	{0x00AD, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "mg/stroke"},
	{0x00AE, SCALE_SIGNED_2B,   1, 0.1,              0.0,     "mg/stroke"},
	{0x00AF, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "%"},
	{0x00B0, SCALE_SIGNED_2B,   6, 0.003052,         0.0,     "%"},
	{0x00B1, SCALE_SIGNED_2B,   0, 2.0,              0.0,     "mV/s"},
	{0x00B2, SCALE_SIGNED_2B,   6, 0.05,             0.0,     "L/h"},
	{0x00B3, SCALE_SIGNED_2B,   6, 0.015625,         0.0,     "Deg. C/kg/s"},
	{0x00FA, SCALE_SIGNED_2B,   0, 20.0,             0.0,     "kPa"},
	{0x00FB, SCALE_SIGNED_2B,   0, 10.0,             0.0,     "kPa"},
	{0x00FC, SCALE_SIGNED_2B,   2, 0.01,             0.0,     "kPa"},
	{0x00FD, SCALE_SIGNED_2B,   3, 0.001,            0.0,     "kPa"},
	{0x00FE, SCALE_SIGNED_2B,   2, 0.25,             0.0,     "Pa"}
};
WORD UnitScaletTableSize = (sizeof(UnitScaletTable)/sizeof(UNIT_SCALE_ENTRY));
STATUS GetUnitScale           ( UNIT_SCALE_ENTRY *pUnitScaleEntry );


/*******************************************************************************
**
**  Function:  VerifyDTCExtDataList
**
**  Purpose:   
**
*******************************************************************************/
STATUS VerifyDTCExtDataList ( BYTE ExtDataID )
{
	REQ_MSG  stReqMsg;
	STATUS   eRetCode = PASS;


	Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "Request DTC Extended Data list (SID $19 LEV $1A DTCEDRN $%02X) Physically Addressed\n",
	      ExtDataID );

	stReqMsg.SID     = 0x19;
	stReqMsg.NumIds  = 2;
	stReqMsg.u.ID[0] = 0x1A;       // ReportDTCExtDataIdentification
	stReqMsg.u.ID[1] = ExtDataID;  // DTCExtendedDTCDataRecordNumber
	if ( (eRetCode = RequestSID_PhysicallyAddressed_All ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_ALLOW_NO_RESPONSE )) == FAIL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $19 $1A $%02X request failed\n",
		      ExtDataID );
	}

	// Display the responses
	LogDTCExtDataList ( ExtDataID );

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  LogDTCExtDataList
**
**  Purpose:
**
*******************************************************************************/
void LogDTCExtDataList ( BYTE ExtDataId  )
{
	BYTE     EcuIdx;

	char    *pString = NULL;

	boolean  bSupported = FALSE;
	DTCASTR *pDTC = NULL;
	WORD     IndexMax = 0;

	WORD     DTCIndex;


	if ( ExtDataId == 0x91 )
	{
		pString = (char *)&szIumpr;
	}
	else if ( ExtDataId == 0x92 )
	{
		pString = (char *)&szTestResult;
	}
	else if ( ExtDataId == 0x93 )
	{
		pString = (char *)&szSmad;
	}
	
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( ExtDataId == 0x91 )
		{
			bSupported = gstResponse[EcuIdx].bDTCIUMPRSupported;
			IndexMax = gstResponse[EcuIdx].DTCIUMPRCount;
		}
		else if ( ExtDataId == 0x92 )
		{
			bSupported = gstResponse[EcuIdx].bDTCTestResultSupported;
			IndexMax = gstResponse[EcuIdx].DTCTestResultCount;
		}
		else if ( ExtDataId == 0x93 )
		{
			bSupported = gstResponse[EcuIdx].bDTCSMADSupported;
			IndexMax = gstResponse[EcuIdx].DTCSMADCount;
		}

		if ( bSupported != TRUE )
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  supports no DTCs with Extdata $%02X (%s).\n",
			      GetEcuId ( EcuIdx ),
			      ExtDataId,
			      pString );
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  supports the following %d DTCs with Extdata $%02X (%s):\n",
			      GetEcuId ( EcuIdx ),
			      IndexMax,
			      ExtDataId,
			      pString );

			// log Ext Data names and values
			for ( DTCIndex = 0;
			      DTCIndex < IndexMax;
			      DTCIndex++ )
			{
				if ( ExtDataId == 0x91 )
				{
					pDTC = &gstResponse[EcuIdx].DTCIUMPRList[DTCIndex].Record;
				}
				else if ( ExtDataId == 0x92 )
				{
					pDTC = &gstResponse[EcuIdx].DTCTestResultList[DTCIndex].Record;
				}
				else if ( ExtDataId == 0x93 )
				{
					pDTC = &gstResponse[EcuIdx].DTCSMADList[DTCIndex].Record;
				}
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "              %c%02X%02X %02X (Status = $%02X)\n",
				       DTCTypeCharacter[(pDTC->Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
				       pDTC->Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
				       pDTC->Dtc.MidByte,                                   // 4th and 5th characters (0-F)
				       pDTC->Dtc.LowByte,                                   // Failure Type Byte
				       pDTC->Status );                                      // Status of DTC
			}
		}
	}
}


/*******************************************************************************
**
**  Function:  VerifyDTCExtData
**
**  Purpose:   
**
*******************************************************************************/
STATUS VerifyDTCExtData ( BYTE ExtDataId )
{
	BYTE    EcuIdx;
	STATUS  eRetCode = PASS;

	char   *pString = NULL;

	DTC    *pDTC = NULL;
	BYTE    RGId;
	WORD    Index;
	WORD    IndexMax = 0;
	DWORD   FailureCnt = 0;
	WORD    DTCIndex;


	// Start DTC ExtData status display
	gbSuspendScreenOutput = TRUE;

	FailureCnt = gFailureCount;


	// if TEST 10.10, log ExtData list title
	if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
	{
		if ( ExtDataId == 0x91 )
		{
			pString = (char *)&szIumprListTitle;
		}
		else if ( ExtDataId == 0x92 )
		{
			pString = (char *)&szTestResultListTitle;
		}
		else if ( ExtDataId == 0x93 )
		{
			pString = (char *)&szSmadListTitle;
		}

		// log Section Title
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s:\n",
		      pString );
	}

	// Initialize DTC ExtData status display
	// Calculate the total number of DTCs with ExtData assigned to
	// a monitored ReadinessGroup over all ECUs
	gDTCExtDataTotal = 0;
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( ExtDataId == 0x91 )
		{
			gDTCExtDataTotal += gstResponse[EcuIdx].DTCIUMPR_RGTotalCount;
		}
		else if ( ExtDataId == 0x92 )
		{
			gDTCExtDataTotal += gstResponse[EcuIdx].DTCTestResultCount;
		}
		else if ( ExtDataId == 0x93 )
		{
			gDTCExtDataTotal += gstResponse[EcuIdx].DTCSMAD_RGTotalCount;
		}
	}
	gDTCExtDataCount = 0;

	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		// If ECU ID is not setup ($00 - user entered more ECUs than actually respond), don't try DTC ExtData
		if ( GetEcuId ( EcuIdx ) == 0X00000000 )
		{
			continue;
		}

		// get number of data items for this ECU
		if ( ExtDataId == 0x91 )
		{
			IndexMax = gstResponse[EcuIdx].DTCIUMPR_RGTotalCount;
			pString = (char *)&szIumprSize[0];
		}
		else if ( ExtDataId == 0x92 )
		{
			IndexMax = gstResponse[EcuIdx].DTCTestResultCount;
			pString = (char *)&szTestResultSize[0];
		}
		else if ( ExtDataId == 0x93 )
		{
			IndexMax = gstResponse[EcuIdx].DTCSMAD_RGTotalCount;
			pString = (char *)&szSmadSize[0];
 		}
		// if Test 10.10, log number of data items for this ECU
		if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
		{
			// log ECU ID
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%-10s = 0x%X\n",
			      szEcuId,
			      GetEcuId ( EcuIdx ) );

			// log number of data items
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%-10s = %d\n",
			      pString,
			      IndexMax );
		}

		// Request all DTC Extended Data
		for ( Index = 0;
		      Index < IndexMax;
		      Index++ )
		{
			if ( ExtDataId == 0x91 )
			{
				pDTC = &gstResponse[EcuIdx].DTCIUMPR_RGList[Index].Record.Dtc;
				RGId = gstResponse[EcuIdx].DTCIUMPR_RGList[Index].RGID;
				pString = (char *)&szIumpr;
			}
			else if ( ExtDataId == 0x92 )
			{
				pDTC = &gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc;
				pString = (char *)&szTestResult;
			}
			else if ( ExtDataId == 0x93 )
			{
				pDTC = &gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc;
				RGId = gstResponse[EcuIdx].DTCSMAD_RGList[Index].RGID;
				pString = (char *)&szSmad;
			}

			// Print overall ExtData progress to screen
			gDTCExtDataCount++;
			printf ( "INFORMATION: Checking  ECU %-8X  %-5s DTC %c%02X%02X %02X  %4d of %4d  (%3.1f %%)\r",
			         GetEcuId ( EcuIdx ),
			         pString,
			         DTCTypeCharacter[(pDTC->HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
			         pDTC->HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
			         pDTC->MidByte,                                   // 4th and 5th characters (0-F)
			         pDTC->LowByte,
			         gDTCExtDataCount,
			         gDTCExtDataTotal,
			         ((float)gDTCExtDataCount/(float)gDTCExtDataTotal) * (float)100 );

			gbAllECUResetCompleted = FALSE;
			// Request the DTC Extended Data
			eRetCode |= GetDTCExtData ( ExtDataId, EcuIdx, pDTC );

			// if a response was received from this ECU
			if ( gstResponse[EcuIdx].bResponseReceived  == TRUE )
			{
				// Save and log the response
				// if this was an IUMPR request
				if ( ExtDataId == 0x91 &&
				     gstResponse[EcuIdx].bDTCIUMPRSupported == TRUE &&
				     (gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte != 0x00 ||
				      gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte  != 0x00 ||
				      gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte  != 0x00) )
				{
					if ( SaveDTCIUMPRData ( EcuIdx, &DTCIndex ) == PASS )
					{
						// Log, but don't print to screen, individual IUMPRs
						LogDTCIUMPRData ( EcuIdx, DTCIndex );
					}
					else
					{
						eRetCode |= ERRORS;
					}
				}

				// if this was an Test Result request
				else if ( ExtDataId == 0x92 &&
				          gstResponse[EcuIdx].bDTCTestResultSupported  == TRUE &&
				          (gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte != 0x00 ||
				           gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte  != 0x00 ||
				           gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte  != 0x00) )
				{
					if ( SaveDTCTestResultData ( EcuIdx, &DTCIndex ) == PASS )
					{
						// Log, but don't print to screen, individual Test Results
						LogDTCTestResultData ( EcuIdx, DTCIndex );
					}
					else
					{
						eRetCode |= ERRORS;
					}
				}

				// if this was an SMAD request
				else if ( ExtDataId == 0x93 &&
				          gstResponse[EcuIdx].bDTCSMADSupported  == TRUE &&
				          (gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte != 0x00 ||
				           gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte  != 0x00 ||
				           gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte  != 0x00) )
				{
					if ( SaveDTCSMADData ( EcuIdx, &DTCIndex ) == PASS )
					{
						// Log, but don't print to screen, individual SMADS
						LogDTCSMADData ( EcuIdx, DTCIndex );
					}
					else
					{
						eRetCode |= ERRORS;
					}
				}
			}  // end if ( gstResponse[EcuIdx].bResponseReceived  == TRUE )

			else
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %-8X  No response to physically addressed request for DTC %c%02X%02X %02X ExtData $%02X.\n",
				      GetEcuId ( EcuIdx ),
				      DTCTypeCharacter[(pDTC->HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
				      pDTC->HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
				      pDTC->MidByte,                                   // 4th and 5th characters (0-F)
				      pDTC->LowByte,
				      ExtDataId );
				eRetCode |= FAIL;
			}
		}  // end for ( EcuIdx )
	}  // end for ( Index )

	// add FAILURE on-screen, if there was one
	if ( FailureCnt < gFailureCount )
	{
		printf ( "\nFAILURE:  Checking %s DTC data (See Log File for details)",
		         pString );
	}
	// add newline on-screen
	printf ( "\n");

	// End DTC ExtData status display
	gbSuspendScreenOutput = FALSE;

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  GetDTCExtData
**
**  Purpose:  .
**
*******************************************************************************/
STATUS GetDTCExtData ( BYTE  ExtDataId,
                       BYTE  EcuIdx,
                       DTC  *pDtc )
{
	REQ_MSG    stReqMsg;
	STATUS     eRetCode = PASS;

	char      *pString = NULL;


	// Request the DTC data
	stReqMsg.SID     = 0x19;
	stReqMsg.NumIds  = 5;
	stReqMsg.u.ID[0] = 0x06;            // ReportDTCExtDataIdentification
	stReqMsg.u.ID[1] = pDtc->HighByte;  // High byte of DTC
	stReqMsg.u.ID[2] = pDtc->MidByte;   // Mid  byte of DTC
	stReqMsg.u.ID[3] = pDtc->LowByte;   // Low  byte of DTC
	stReqMsg.u.ID[4] = ExtDataId;       // DTCExtendedDTCDataRecordNumber

	if ( ExtDataId == 0x91 )
	{
		pString = (char *)&szIumpr;
	}
	else if ( ExtDataId == 0x92 )
	{
		pString = (char *)&szTestResult;
	}
	else
	{
		pString = (char *)&szSmad;
	}

	if ( (eRetCode = RequestSID_PhysicallyAddressed_Single ( &stReqMsg, REQ_MSG_NO_PERIODIC_DISABLE|REQ_MSG_ALLOW_NO_RESPONSE, gstResponse[EcuIdx].ReqId )) == FAIL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $19 $06 DTC %c%02X%02X %02X ExtData $%02X %s request failed\n",
		       DTCTypeCharacter[(stReqMsg.u.ID[1] & 0xC0) >> 6],  // 1st character (P, C, B, U)
		       stReqMsg.u.ID[1] & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		       stReqMsg.u.ID[2],                                  // 4th and 5th characters (0-F)
		       stReqMsg.u.ID[3],                                  // Failure Type Byte
		       ExtDataId,                                         // Extended Data ID
		       pString );                                         // Extended Data ID type
		return FAIL;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCIUMPRData
**
**  Purpose:   Copy from gstResponse[EcuIdx].DTCIUMPRCurrent into the matching DTC
**             in gstResponse[EcuIdx].DTCIUMPRList.
**
*******************************************************************************/
STATUS SaveDTCIUMPRData ( BYTE  EcuIdx,
                          WORD *pIndex )
{
	STATUS     eRetCode = PASS;  // function result

	WORD       DTCIdx;
	BOOL       bMatch = FALSE;
	DTCIUMPR  *pIUMPR = NULL;


	// find current DTC in DTC IUMPR list and copy the data
	for ( DTCIdx = 0;
	      DTCIdx < gstResponse[EcuIdx].DTCIUMPRCount;
	      DTCIdx++ )
	{
		// if the received DTC is in this ECUs list
		if ( gstResponse[EcuIdx].DTCIUMPRList[DTCIdx].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte &&
		     gstResponse[EcuIdx].DTCIUMPRList[DTCIdx].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte &&
		     gstResponse[EcuIdx].DTCIUMPRList[DTCIdx].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte )
		{
			// save the data to the ECU's IUMPR list
			gstResponse[EcuIdx].DTCIUMPRList[DTCIdx].CompCounts = gstResponse[EcuIdx].DTCIUMPRCurrent.CompCounts;
			gstResponse[EcuIdx].DTCIUMPRList[DTCIdx].CondCounts = gstResponse[EcuIdx].DTCIUMPRCurrent.CondCounts;


			// Only save DTCIUMPR data that has Readiness Group
			// Search for a matching DTC in Readiness Group
			bMatch = FALSE;
			for ( *pIndex = 0;
			      *pIndex < gstResponse[EcuIdx].DTCIUMPR_RGTotalCount;
			      (*pIndex)++ )
			{
				if ( gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte &&
				     gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte  &&
				     gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte )
				{
					bMatch = TRUE;
					break;
				}
			}

			// if there is matching Readiness Group IUMPR DTC save it, otherwise skip this DTCIUMPR
			if ( bMatch == TRUE )
			{
				gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex].CompCounts = gstResponse[EcuIdx].DTCIUMPRCurrent.CompCounts;
				gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex].CondCounts = gstResponse[EcuIdx].DTCIUMPRCurrent.CondCounts;

				// if this is a test phase that needs it's data saved for later testing,
				// also save the data to that phase's copy of the IUMPR list
				if ( (geTestPhase == eTestInUseCounters       && gTestSubsection == 10) ||
				     (geTestPhase == eTestPerformanceCounters && gTestSubsection == 7)  ||
				     (geTestPhase == eTestPerformanceCounters && gTestSubsection == 12) )
				{
					if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
					{
						pIUMPR = &gstResponse[EcuIdx].DTCIUMPR_Test10_10[*pIndex];
					}
					else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 7 )
					{
						pIUMPR = &gstResponse[EcuIdx].DTCIUMPR_Test11_7[*pIndex];
					}
					else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 12 )
					{
						pIUMPR = &gstResponse[EcuIdx].DTCIUMPR_Test11_12[*pIndex];
					}
	
					memcpy ( pIUMPR,
					         &gstResponse[EcuIdx].DTCIUMPR_RGList[*pIndex],
					         sizeof (DTCIUMPR) );
				}
			}
			break;
		}  // end if ( matching DTC found )
	}  // end for ( Index )

	// if the current DTC was not in the DTC IUMPR list, warn
	if ( DTCIdx >= gstResponse[EcuIdx].DTCIUMPRCount )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  received unexpected response to SID $19 $06 DTC %c%02X%02X %02X ExtData $91 (IUMPR) request (Response not stored)\n",
		       GetEcuId ( EcuIdx ),
		       DTCTypeCharacter[(gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		       gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		       gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		       gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte );                                 // Failure Type Byte

		eRetCode = ERRORS;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCTestResultData
**
**  Purpose:   Copy from gstResponse[EcuIdx].DTCTestResultCurrent into the matching DTC
**             in gstResponse[EcuIdx].DTCTestResultList.
**
*******************************************************************************/
STATUS SaveDTCTestResultData ( BYTE  EcuIdx,
                               WORD *pIndex )
{
	STATUS          eRetCode = PASS;  // function result

	DTCTestResult  *pTestResult = NULL;


	// find current DTC in DTC TestResult list and copy the data
	for ( *pIndex = 0;
	      *pIndex < gstResponse[EcuIdx].DTCTestResultCount;
	      (*pIndex)++ )
	{
		// if the received DTC is in this ECUs list
		if ( gstResponse[EcuIdx].DTCTestResultList[*pIndex].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte &&
		     gstResponse[EcuIdx].DTCTestResultList[*pIndex].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte &&
		     gstResponse[EcuIdx].DTCTestResultList[*pIndex].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte )
		{
			// save the data to the ECU's TestResult list
			gstResponse[EcuIdx].DTCTestResultList[*pIndex].NumTstRslts = gstResponse[EcuIdx].DTCTestResultCurrent.NumTstRslts;
			for ( int i = 0; i < gstResponse[EcuIdx].DTCTestResultList[*pIndex].NumTstRslts; i++ )
			{
				gstResponse[EcuIdx].DTCTestResultList[*pIndex].Result[i].UnitScaling = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].UnitScaling;
				gstResponse[EcuIdx].DTCTestResultList[*pIndex].Result[i].TestValue   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].TestValue;
				gstResponse[EcuIdx].DTCTestResultList[*pIndex].Result[i].MinTstLmt   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MinTstLmt;
				gstResponse[EcuIdx].DTCTestResultList[*pIndex].Result[i].MaxTstLmt   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MaxTstLmt;
			}

			// if this is a test phase that needs it's data saved for later testing,
			// also save the data to that phase's copy of the TestResult list
			if ( (geTestPhase == eTestInUseCounters       && gTestSubsection == 10) ||
			     (geTestPhase == eTestPerformanceCounters && gTestSubsection == 7) )
			{
				if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
				{
					pTestResult = &gstResponse[EcuIdx].DTCTestResult_Test10_10[0];
				}
				else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 6 )
				{
					pTestResult = &gstResponse[EcuIdx].DTCTestResult_Test11_6[0];
				}

				if ( pTestResult != NULL )
				{
					pTestResult[*pIndex].Record.Dtc.HighByte = gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte;
					pTestResult[*pIndex].Record.Dtc.MidByte  = gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte;
					pTestResult[*pIndex].Record.Dtc.LowByte  = gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte;

					pTestResult[*pIndex].NumTstRslts = gstResponse[EcuIdx].DTCTestResultCurrent.NumTstRslts;
					for ( int i = 0; i < pTestResult[*pIndex].NumTstRslts; i++ )
					{
						pTestResult[*pIndex].Result[i].UnitScaling = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].UnitScaling;
						pTestResult[*pIndex].Result[i].TestValue   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].TestValue;
						pTestResult[*pIndex].Result[i].MinTstLmt   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MinTstLmt;
						pTestResult[*pIndex].Result[i].MaxTstLmt   = gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MaxTstLmt;
					}
				}
			}
			break;
		}  // end if ( matching DTC found )
	}  // end for ( Index )

	// if the current DTC was not in the DTC TestResult list, warn
	if ( *pIndex >= gstResponse[EcuIdx].DTCTestResultCount )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  received unexpected response to SID $19 $06 DTC %c%02X%02X %02X ExtData $92 (TestResult) request (Response not stored)\n",
		      GetEcuId ( EcuIdx ),
		      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		      gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		      gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		      gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte );                                 // Failure Type Byte

		eRetCode = ERRORS;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCSMADData
**
**  Purpose:   Copy from gstResponse[EcuIdx].DTCSMADCurrent into the matching DTC
**             in gstResponse[EcuIdx].DTCSMADList.
**
*******************************************************************************/
STATUS SaveDTCSMADData ( BYTE  EcuIdx,
                         WORD *pIndex )
{
	STATUS        eRetCode = PASS;  // function result

	WORD       DTCIdx;
	BOOL       bMatch = FALSE;
	DTCSMAD   *pSMAD = NULL;


	// find current DTC in DTC SMAD list and copy the data
	for ( DTCIdx = 0;
	      DTCIdx < gstResponse[EcuIdx].DTCSMADCount;
	      DTCIdx++ )
	{
		// if the received DTC is in this ECUs list
		if ( gstResponse[EcuIdx].DTCSMADList[DTCIdx].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte &&
		     gstResponse[EcuIdx].DTCSMADList[DTCIdx].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte &&
		     gstResponse[EcuIdx].DTCSMADList[DTCIdx].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte )
		{
			// save the data to the main SMAD list
			gstResponse[EcuIdx].DTCSMADList[DTCIdx].NumCounts   = gstResponse[EcuIdx].DTCSMADCurrent.NumCounts;
			gstResponse[EcuIdx].DTCSMADList[DTCIdx].ActRatio    = gstResponse[EcuIdx].DTCSMADCurrent.ActRatio;


			// Only save DTCSMAD data that has Readiness Group
			// Search for a matching DTC in Readiness Group
			bMatch = FALSE;
			for ( *pIndex = 0;
			      *pIndex < gstResponse[EcuIdx].DTCSMAD_RGTotalCount;
				  (*pIndex)++ )
			{
				if ( gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte &&
				     gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte  &&
				     gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte )
				{
					bMatch = TRUE;
					break;
				}
			}
			
			// if there is matching Readiness Group DTC save it, otherwise skip this DTCSMAD
			if ( bMatch == TRUE )
			{
				gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex].NumCounts   = gstResponse[EcuIdx].DTCSMADCurrent.NumCounts;
				gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex].ActRatio    = gstResponse[EcuIdx].DTCSMADCurrent.ActRatio;

				// if this is a test phase that needs it's data saved for later testing,
				// also save the data to that phase's copy of the SMAD list
				if ( (geTestPhase == eTestInUseCounters       && gTestSubsection == 10) ||
				     (geTestPhase == eTestPerformanceCounters && gTestSubsection == 7)  ||
				     (geTestPhase == eTestPerformanceCounters && gTestSubsection == 12) )
				{
					if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
					{
						pSMAD = &gstResponse[EcuIdx].DTCSMAD_Test10_10[*pIndex];
					}
					else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 7 )
					{
						pSMAD = &gstResponse[EcuIdx].DTCSMAD_Test11_7[*pIndex];
					}
					else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 12 )
					{
						pSMAD = &gstResponse[EcuIdx].DTCSMAD_Test11_12[*pIndex];
					}

					memcpy ( pSMAD,
					         &gstResponse[EcuIdx].DTCSMAD_RGList[*pIndex],
					         sizeof (DTCSMAD) );
				}
			}  // end if ( data needed for later testing )
			break;
		}  // end if ( matching DTC found )
	}  // end for ( Index )

	// if the current DTC was not in the DTC SMAD list, warn
	if ( DTCIdx >= gstResponse[EcuIdx].DTCSMADCount )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  received unexpected response to SID $19 $06 DTC %c%02X%02X %02X ExtData $93 (SMAD) request (Response not stored)\n",
		       GetEcuId ( EcuIdx ),
		       DTCTypeCharacter[(gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		       gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		       gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		       gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte );                                 // Failure Type Byte

		eRetCode = ERRORS;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  LogDTCIUMPRData
**
**  Purpose:
**
*******************************************************************************/
void LogDTCIUMPRData ( BYTE  EcuIdx,
                       WORD  Index )
{
	if ( gstResponse[EcuIdx].bDTCIUMPRSupported == TRUE &&
	     gstResponse[EcuIdx].DTCIUMPR_RGTotalCount != 0 )
	{
		// log IUMPR names and values
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  IUMPR DTC %c%02X%02X %02X  %s = %3d  %s = %3d  %s = $%02X\n",
		      GetEcuId ( EcuIdx ) ,
		      DTCTypeCharacter[(gstResponse[EcuIdx].DTCIUMPR_RGList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
		      szIumprData1,
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].CompCounts,
		      szIumprData2,
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].CondCounts,
		      szRGId,
		      gstResponse[EcuIdx].DTCIUMPR_RGList[Index].RGID );
	}
}


/*******************************************************************************
**
**  Function:  LogDTCTestResultData
**
**  Purpose:
**
*******************************************************************************/
void LogDTCTestResultData ( BYTE  EcuIdx,
                            WORD  Index )
{
	UNIT_SCALE_ENTRY UnitScaleEntry;
	double TestValue = 0.0;
	double MinTstLmt = 0.0;
	double MaxTstLmt = 0.0;


	if ( gstResponse[EcuIdx].bDTCTestResultSupported == TRUE &&
	     gstResponse[EcuIdx].DTCTestResultCount != 0 )
	{
		// log TestResult names and values
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  Test Result DTC %c%02X%02X %02X  %-22s = %3d\n",
		      GetEcuId ( EcuIdx ),
		      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
		      szTestResultData1,
		      gstResponse[EcuIdx].DTCTestResultList[Index].NumTstRslts );

		for ( BYTE i = 0; i < gstResponse[EcuIdx].DTCTestResultList[Index].NumTstRslts; i++ )
		{
			UnitScaleEntry.ID = gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].UnitScaling;
			GetUnitScale ( &UnitScaleEntry );

			if ( UnitScaleEntry.RawDataType == SCALE_UNSIGNED_2B ||
			     UnitScaleEntry.RawDataType == SCALE_UNSIGNED_2B_STATE)
			{
				TestValue = (gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].TestValue * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
				MinTstLmt = (gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MinTstLmt * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
				MaxTstLmt = (gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MaxTstLmt * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
			}
			else
			{
				TestValue = ((signed short)gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].TestValue * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
				MinTstLmt = ((signed short)gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MinTstLmt * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
				MaxTstLmt = ((signed short)gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MaxTstLmt * UnitScaleEntry.Scaling) + UnitScaleEntry.Offset;
			}

			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "              Test Result %d:\n"
			      "                                     %-22s = %d\n"
			      "                                     %-22s = %.*f %s\n"
			      "                                     %-22s = %.*f %s\n"
			      "                                     %-22s = %.*f %s\n",
			      i+1,
			      szTestResultData2,
			      gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].UnitScaling,
			      szTestResultData3,
			      UnitScaleEntry.DisplayPrecision,
			      TestValue,
			      UnitScaleEntry.UnitString,
			      szTestResultData4,
			      UnitScaleEntry.DisplayPrecision,
			      MinTstLmt,
			      UnitScaleEntry.UnitString,
			      szTestResultData5,
			      UnitScaleEntry.DisplayPrecision,
			      MaxTstLmt,
			      UnitScaleEntry.UnitString );

			if ( UnitScaleEntry.RawDataType == UNDEFINED_SCALE_ITEM )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Unit Scaling (%d) is undefined. Test Data displays the raw value.\n",
				      gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].UnitScaling );
			}

			if ( geTestPhase == eTestInUseCounters && gTestSubsection == 7 )   // Test 10.7
			{
				if ( gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].TestValue != 0 )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %-8X  Test Result DTC %c%02X%02X %02X  Test Value should be 0\n",
					      GetEcuId ( EcuIdx ),
					      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
				}

				if ( gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MinTstLmt != 0 )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %-8X  Test Result DTC %c%02X%02X %02X  Minimum Test Limit should be 0\n",
					      GetEcuId ( EcuIdx ),
					      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
				}

				if ( gstResponse[EcuIdx].DTCTestResultList[Index].Result[i].MaxTstLmt != 0 )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %-8X  Test Result DTC %c%02X%02X %02X  Maximum Test Limit should be 0\n",
					      GetEcuId ( EcuIdx ),
					      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
				}
			}
			else if ( geTestPhase == eTestPerformanceCounters   && gTestSubsection == 6 )   // Test 11.6
			{
				if ( TestValue < MinTstLmt )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %-8X  Test Result DTC %c%02X%02X %02X  Test Value must be greater than Minimum Test Limit\n",
					      GetEcuId ( EcuIdx ),
					      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
				}

				if ( TestValue > MaxTstLmt )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %-8X  Test Result DTC %c%02X%02X %02X  Test Value must be less than Maximum Test Limit\n",
					      GetEcuId ( EcuIdx ),
					      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
					      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
				}
			}
		}

		if ( !(geTestPhase == eTestInUseCounters && gTestSubsection == 10) &&
		     gstResponse[EcuIdx].DTCTestResultList[Index].NumTstRslts > 1 )
		{
			Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  Test Result DTC %c%02X%02X %02X  has more than 1 test result\n",
			      GetEcuId ( EcuIdx ),
			      DTCTypeCharacter[(gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
			      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
			      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
			      gstResponse[EcuIdx].DTCTestResultList[Index].Record.Dtc.LowByte );                                 // Failure Type Byte
		}
	}
}


/*******************************************************************************
**
**  Function:  LogDTCSMADData
**
**  Purpose:
**
*******************************************************************************/
void LogDTCSMADData ( BYTE  EcuIdx,
                      WORD  Index )
{
	if ( gstResponse[EcuIdx].bDTCSMADSupported == TRUE &&
	     gstResponse[EcuIdx].DTCSMAD_RGTotalCount != 0 )
	{
		// log SMAD names and values
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  SMAD DTC %c%02X%02X %02X  %s = $%02X (%d)  %s = $%02X (%d)  %s = $%02X (%-.3f)  %s = $%02X\n",
		      GetEcuId ( EcuIdx ),
		      DTCTypeCharacter[(gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
		      szSmadData1,
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts,
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts,
		      szSmadData2,
		      gstResponse[EcuIdx].MAD.Data[0],
		      gstResponse[EcuIdx].MAD.Data[0],
		      szSmadData3,
		      (BYTE)(gstResponse[EcuIdx].DTCSMAD_RGList[Index].ActRatio * 256.0),
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].ActRatio,
		      szRGId,
		      gstResponse[EcuIdx].DTCSMAD_RGList[Index].RGID );

		if ( geTestPhase == eTestInUseCounters && gTestSubsection == 15 &&
		     (gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts - gstResponse[EcuIdx].DTCSMAD_Test10_10[Index].NumCounts) > 3 )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  SMAD DTC %c%02X%02X %02X  MAN incremented by %d (should not increment by more than 3)\n",
			      GetEcuId ( EcuIdx ),
			      DTCTypeCharacter[(gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
			      szSmadData1,
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts - gstResponse[EcuIdx].DTCSMAD_Test10_10[Index].NumCounts );
		}
		else if ( geTestPhase == eTestPerformanceCounters && gTestSubsection == 7 &&
		          (gstResponse[EcuIdx].DTCSMAD_RGList[Index].ActRatio == 0 &&
		           gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts < gstResponse[EcuIdx].DTCSMAD_Test10_10[Index].NumCounts) )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  SMAD DTC %c%02X%02X %02X  %s = %d must be greater than or equal to it's value in Test 10.10 (%d) (when the Activity Ratio is 0)\n",
			      GetEcuId ( EcuIdx ),
			      DTCTypeCharacter[(gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
			      szSmadData1,
			      gstResponse[EcuIdx].DTCSMAD_RGList[Index].NumCounts,
			      gstResponse[EcuIdx].DTCSMAD_Test10_10[Index].NumCounts );
		}
	}
}




/*******************************************************************************
**
**  Function:  VerifyDTCReadinessGroupList
**
**  Purpose:   
**
*******************************************************************************/
STATUS VerifyDTCReadinessGroupList ( void )
{
	BYTE     EcuIdx;
	REQ_MSG  stReqMsg;
	STATUS   eRetCode = PASS;

	BYTE RGIdIndex;
	WORD Index;


	// Start DTC ExtData status display
	gbSuspendScreenOutput = TRUE;

	//.clear previous count for all ECUs
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		gstResponse[EcuIdx].DTCReadinessGroupCount = 0;
		for ( RGIdIndex = 0x00;
		      RGIdIndex <= MAX_READINESS_GROUP_ID;
		      RGIdIndex++ )
		{
			gstResponse[EcuIdx].DTCReadinessGroupListCount[RGIdIndex] = 0;
		}
	}

	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		// If ECU ID is not setup ($00 - user entered more ECUs than actually respond), don't try DTC ExtData
		if ( GetEcuId ( EcuIdx ) == 0X00000000 )
		{
			continue;
		}

		for ( RGIdIndex = 0x00;
		      RGIdIndex <= MAX_READINESS_GROUP_ID;
		      RGIdIndex++ )
		{
			printf ( "INFORMATION: Checking  ECU %-8X  Readiness Group $%02X (%-6s)\r",
			         GetEcuId ( EcuIdx ),
			         RGIdIndex,
			         szReadinessGroupAcronym[RGIdIndex] );

			stReqMsg.SID     = 0x19;
			stReqMsg.NumIds  = 3;
			stReqMsg.u.ID[0] = 0x56;   // ReportDTCInformationByDTCReadinessGroupIdentifier
			stReqMsg.u.ID[1] = 0x33;   // FunctionalGroupIdentifier: Emission
			stReqMsg.u.ID[2] = RGIdIndex;  // DTCReadinessGroupIdentifier

			gbAllECUResetCompleted = FALSE;
			// Request the DTCs which support Extended Data $xx
			if ( RequestSID_PhysicallyAddressed_Single ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_ALLOW_NO_RESPONSE, gstResponse[EcuIdx].ReqId ) == FAIL )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "SID $19 $56 $33 $%02X (Readiness Group) request failed\n",
				      RGIdIndex );
				eRetCode = FAIL;
			}
			else
			{
				// Save the responses
				SaveDTCReadinessGroupList ( EcuIdx );

				LogDTCReadinessGroupList ( EcuIdx, RGIdIndex );
			}
		}  // end for RGIdIndex
	}  // end for EcuIdx

	// add FAILURE on-screen, if there was one
	if ( eRetCode == FAIL )
	{
		printf ( "FAILURE:  Checking Readiness Group DTC data (See Log File for details)" );
	}
	// add newline on-screen
	printf ( "\n");


	// if TEST 10.10,for each ECU, log all the Readiness Group data as a group
	if ( geTestPhase == eTestInUseCounters && gTestSubsection == 10 )
	{
		// log Section Title
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s:\n",
		      szRGListTitle,
		      GetEcuId ( EcuIdx ) );

		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// log ECU ID
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%-10s = 0x%X\n",
			      szEcuId,
			      GetEcuId ( EcuIdx ) );

			// log number of data items
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%-10s = %d\n",
			      szRGSize,
			      gstResponse[EcuIdx].DTCReadinessGroupCount );

			for ( Index = 0;
			      Index < gstResponse[EcuIdx].DTCReadinessGroupCount;
			      Index++ )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %-8X  Readiness Group DTC %c%02X%02X %02X  RGID = $%02X (%s)\n",
				      GetEcuId ( EcuIdx ),
				      DTCTypeCharacter[(gstResponse[EcuIdx].DTCReadinessGroupList[Index].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
				      gstResponse[EcuIdx].DTCReadinessGroupList[Index].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
				      gstResponse[EcuIdx].DTCReadinessGroupList[Index].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
				      gstResponse[EcuIdx].DTCReadinessGroupList[Index].Record.Dtc.LowByte,                                   // Failure Type Byte
				      gstResponse[EcuIdx].DTCReadinessGroupList[Index].RGID,
				      szReadinessGroupAcronym[gstResponse[EcuIdx].DTCReadinessGroupList[Index].RGID] );
			}  // end for ( Index )
		}  // end for ( EcuIdx )

		// build the DTC IUMPR/RG List
		GetIUMPR_RGList ();

		// build the DTC SMAD/RG List
		GetSMAD_RGList ();
	}  // end if ( TEST 10.10 )

	// Start DTC ExtData status display
	gbSuspendScreenOutput = FALSE;

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCReadinessGroupList
**
**  Purpose:   Copy from gstResponse[EcuIdx].DTCRGCurrent into the matching DTC
**             in gstResponse[EcuIdx].DTCRGList.
**
*******************************************************************************/
void SaveDTCReadinessGroupList ( BYTE  EcuIdx )
{
	WORD      Index1;
	WORD      Index2;


	// find current DTC in Readiness Group list and copy the data
	for ( Index1 = 0;
	      Index1 < gstResponse[EcuIdx].DTCReadinessGroupCurrentCount;
	      Index1++ )
	{
		// Check if this DTCRG is already in this ECU's List
		for ( Index2 = 0;
		      Index2 < gstResponse[EcuIdx].DTCReadinessGroupCount;
		      Index2++ )
		{
			if ( gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCReadinessGroupCurrent[Index1].Record.Dtc.HighByte &&
			     gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCReadinessGroupCurrent[Index1].Record.Dtc.MidByte  &&
			     gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCReadinessGroupCurrent[Index1].Record.Dtc.LowByte )
			{
				break;
			}
		}  // end for ( Index2 )

		// if the current DTC was already in this ECU's DTCRG list, log the conflict
		if ( Index2 >= gstResponse[EcuIdx].DTCReadinessGroupCount )
		{
			// save the data to the main Readiness Group list
			memcpy ( &gstResponse[EcuIdx].DTCReadinessGroupList[Index2],
			         &gstResponse[EcuIdx].DTCReadinessGroupCurrent[Index1],
			         sizeof ( DTCREADINESSGROUP ) );

			// Increment the number of DTCs for this RGID
			gstResponse[EcuIdx].DTCReadinessGroupListCount[gstResponse[EcuIdx].DTCReadinessGroupCurrent[Index1].RGID]++;

			// Increment the total number of DTCRGIDs
			gstResponse[EcuIdx].DTCReadinessGroupCount++;
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %-8X  DTC %c%02X%02X %02X  has multiple RGID designations\n",
			      GetEcuId ( EcuIdx ) ,
			      DTCTypeCharacter[(gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
			      gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
			      gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
			      gstResponse[EcuIdx].DTCReadinessGroupList[Index2].Record.Dtc.LowByte );                                 // Failure Type Byte
		}
	}  // end for ( Index1 )
}


/*******************************************************************************
**
**  Function:  LogDTCReadinessGroupList
**
**  Purpose:
**
*******************************************************************************/
void LogDTCReadinessGroupList ( BYTE EcuIdx,
                                BYTE Index )
{
	WORD  DTCIndex;


	if ( gstResponse[EcuIdx].bDTCReadinessGroupSupported == TRUE &&
	     gstResponse[EcuIdx].DTCReadinessGroupListCount[Index] > 0 )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  supports the following %d DTCs with Readiness Group $%02X (%s):\n",
		      GetEcuId ( EcuIdx ),
		      gstResponse[EcuIdx].DTCReadinessGroupListCount[Index],
		      Index,
		      szReadinessGroupAcronym[Index] );

		// log Readiness Group names and values
		for ( DTCIndex = 0;
		      DTCIndex < gstResponse[EcuIdx].DTCReadinessGroupCount;
		      DTCIndex++ )
		{
			if ( gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].RGID == Index )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "              %c%02X%02X %02X (Status = $%02X)\n",
				      DTCTypeCharacter[(gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
				      gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
				      gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
				      gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Dtc.LowByte,                                   // Failure Type Byte
				      gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Status );                                      // Status of DTC
			}
		}  // end for ( DTCIndex )
	}  // end if ( bDTCReadinessGroupSupported == TRUE )
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %-8X  supports no DTCs with Readiness Group $%02X (%s)\n",
		      GetEcuId ( EcuIdx ),
		      Index,
		      szReadinessGroupAcronym[Index] );
	}
	
}




/*******************************************************************************
**
**  Function:  isDTCinList
**
**  Purpose:
**
*******************************************************************************/
boolean isDTCinList           ( BYTE  EcuIdx,
                                DTC  *pID,
                                BYTE  ExtDataID )
{
	WORD     DTCIndex;
	DTC     *pDTC = NULL ;
	WORD     DTCCount;


	// Display the responses
	if ( ExtDataID == 0x91 )
	{
		DTCCount = gstResponse[EcuIdx].DTCIUMPR_RGTotalCount;
	}
	else if ( ExtDataID == 0x92 )
	{
		DTCCount = gstResponse[EcuIdx].DTCTestResultCount;
	}
	else if ( ExtDataID == 0x93 )
	{
		DTCCount = gstResponse[EcuIdx].DTCSMADCount;
	}
	else if ( ExtDataID == 0x00 )
	{
		DTCCount = gstResponse[EcuIdx].DTCReadinessGroupCount;
	}

	for ( DTCIndex = 0;
	      DTCIndex < DTCCount;
	      DTCIndex++ )
	{
		if ( ExtDataID == 0x91 )
		{
			pDTC = &gstResponse[EcuIdx].DTCIUMPR_RGList[DTCIndex].Record.Dtc;
		}
		else if ( ExtDataID == 0x92 )
		{
			pDTC = &gstResponse[EcuIdx].DTCTestResultList[DTCIndex].Record.Dtc;
		}
		else if ( ExtDataID == 0x93 )
		{
			pDTC = &gstResponse[EcuIdx].DTCSMADList[DTCIndex].Record.Dtc;
		}
		else if ( ExtDataID == 0x00 )
		{
			pDTC = &gstResponse[EcuIdx].DTCReadinessGroupList[DTCIndex].Record.Dtc;
		}

		if ( pID->HighByte == pDTC->HighByte &&
		     pID->MidByte  == pDTC->MidByte  &&
		     pID->LowByte  == pDTC->LowByte )
		{
			return TRUE;
		}
	}

	return FALSE;
}


/*******************************************************************************
**
**  Function:  GetIUMPR_RGList
**
**  Purpose:   Build the ECU lists of DTCIUMPR with Readiness Group.
**
*******************************************************************************/
void GetIUMPR_RGList ( void )
{
	BYTE      EcuIdx;
	WORD      IUMPRIdx;
	WORD      RGIdx;
	DTCIUMPR *pIUMPR = NULL;

	WORD      ListDTCIndex;


	// Build per-ECU List
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		for ( IUMPRIdx = 0;
		      IUMPRIdx < gstResponse[EcuIdx].DTCIUMPRCount;
		      IUMPRIdx++ )
		{
			// Search for a matching DTC in ECU's Readiness Group List
			for ( RGIdx = 0;
			      RGIdx < gstResponse[EcuIdx].DTCReadinessGroupCount;
			      RGIdx++ )
			{
				if ( gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.HighByte &&
				     gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.MidByte  &&
				     gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.LowByte  )
				{
					break;
				}
			}
			// if there is matching Readiness Group DTC,
			// check if it needs to be added,
			// otherwise skip this DTCIUMPR
			if ( RGIdx < gstResponse[EcuIdx].DTCReadinessGroupCount )
			{
				// Do not add DTCs in Readiness Groups which are not evaluated in Test 11.7
				if ( gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x0E &&  // CCM
				     gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x10 &&  // NMHC
				     gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x14 )   // PM
				{
					// check if the DTC is already in the IUMPR/RG list
					for ( ListDTCIndex = 0;
					      ListDTCIndex < gstResponse[EcuIdx].DTCIUMPR_RGTotalCount;
					      ListDTCIndex++ )
					{
						if ( gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.HighByte &&
						     gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.MidByte &&
						     gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCIUMPRList[IUMPRIdx].Record.Dtc.LowByte )
						{
							break;
						}
					}
			
					// if the DTC isn't in the IUMPR/RG list, add it
					if ( ListDTCIndex == gstResponse[EcuIdx].DTCIUMPR_RGTotalCount )
					{
						if ( gstResponse[EcuIdx].DTCIUMPR_RGTotalCount < MAX_DTCIUMPR_COUNT )
						{
							gstResponse[EcuIdx].DTCIUMPR_RGList[gstResponse[EcuIdx].DTCIUMPR_RGTotalCount].Record.Dtc.HighByte = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.HighByte;
							gstResponse[EcuIdx].DTCIUMPR_RGList[gstResponse[EcuIdx].DTCIUMPR_RGTotalCount].Record.Dtc.MidByte  = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.MidByte;
							gstResponse[EcuIdx].DTCIUMPR_RGList[gstResponse[EcuIdx].DTCIUMPR_RGTotalCount].Record.Dtc.LowByte  = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.LowByte;
							gstResponse[EcuIdx].DTCIUMPR_RGList[gstResponse[EcuIdx].DTCIUMPR_RGTotalCount].RGID                = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID;

							gstResponse[EcuIdx].DTCIUMPR_RGTotalCount++;
			
							gstResponse[EcuIdx].DTCIUMPR_RGListCount[gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID]++;
						}
						else
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "More than the allocated number of DTCs with Extdata $91 with Readiness Group\n" );
						}
					}
					else
					{
						Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %-8X  IUMPR DTC %c%02X%02X %02X  has multiple RGID designations\n",
						      GetEcuId ( EcuIdx ) ,
						      DTCTypeCharacter[(gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
						      gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
						      gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
						      gstResponse[EcuIdx].DTCIUMPR_RGList[ListDTCIndex].Record.Dtc.LowByte );                                 // Failure Type Byte
					}
				} // end if RGID not 0E, 10, 14
			}  // end if DTC match
		}  // end for (IUMPRIdx)
	}  // end for (EcuIdx)
}


/*******************************************************************************
**
**  Function:  GetSMAD_RGList
**
**  Purpose:   Build the ECU lists of DTCSMAD with Readiness Group.
**
*******************************************************************************/
void GetSMAD_RGList ( void )
{
	BYTE      EcuIdx;
	WORD      SMADIdx;
	WORD      RGIdx;
	DTCSMAD  *pSMAD = NULL;

	WORD      ListDTCIndex;


	// Build per-ECU List
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		for ( SMADIdx = 0;
		      SMADIdx < gstResponse[EcuIdx].DTCSMADCount;
		      SMADIdx++ )
		{
			// Search for a matching DTC in ECU's Readiness Group List
			for ( RGIdx = 0;
			      RGIdx < gstResponse[EcuIdx].DTCReadinessGroupCount;
			      RGIdx++ )
			{
				if ( gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.HighByte &&
				     gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.MidByte  &&
				     gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.LowByte )
				{
					break;
				}
			}
			// if there is matching Readiness Group DTC,
			// check if it needs to be added,
			// otherwise skip this DTCSMAD
			if ( RGIdx < gstResponse[EcuIdx].DTCReadinessGroupCount )
			{
				// Do not add DTCs in Readiness Groups which are not evaluated in Test 11.7
				if ( gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x0E &&  // CCM
				     gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x10 &&  // NMHC
				     gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID != 0x14 )   // PM
				{
					// check if the DTC is already in the SMAD/RG list
					for ( ListDTCIndex = 0;
					      ListDTCIndex < gstResponse[EcuIdx].DTCSMAD_RGTotalCount;
					      ListDTCIndex++ )
					{
						if ( gstResponse[EcuIdx].DTCSMAD_RGList[ListDTCIndex].Record.Dtc.HighByte == gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.HighByte &&
						     gstResponse[EcuIdx].DTCSMAD_RGList[ListDTCIndex].Record.Dtc.MidByte  == gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.MidByte &&
						     gstResponse[EcuIdx].DTCSMAD_RGList[ListDTCIndex].Record.Dtc.LowByte  == gstResponse[EcuIdx].DTCSMADList[SMADIdx].Record.Dtc.LowByte )
						{
							break;
						}
					}
			
					// if the DTC isn't in the SMAD/RG list, add it
					if ( ListDTCIndex == gstResponse[EcuIdx].DTCSMAD_RGTotalCount )
					{
						if ( gstResponse[EcuIdx].DTCSMAD_RGTotalCount < MAX_DTCSMAD_COUNT )
						{
							gstResponse[EcuIdx].DTCSMAD_RGList[gstResponse[EcuIdx].DTCSMAD_RGTotalCount].Record.Dtc.HighByte = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.HighByte;
							gstResponse[EcuIdx].DTCSMAD_RGList[gstResponse[EcuIdx].DTCSMAD_RGTotalCount].Record.Dtc.MidByte  = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.MidByte;
							gstResponse[EcuIdx].DTCSMAD_RGList[gstResponse[EcuIdx].DTCSMAD_RGTotalCount].Record.Dtc.LowByte  = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].Record.Dtc.LowByte;
							gstResponse[EcuIdx].DTCSMAD_RGList[gstResponse[EcuIdx].DTCSMAD_RGTotalCount].RGID                = gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID;
			
							gstResponse[EcuIdx].DTCSMAD_RGTotalCount++;

							gstResponse[EcuIdx].DTCSMAD_RGListCount[gstResponse[EcuIdx].DTCReadinessGroupList[RGIdx].RGID]++;
						}
						else
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "More than the allocated number of DTCs with Extdata $91 with Readiness Group\n" );
						}
					}
				}  // end if RGID not 0E, 10, 14
			}  // end if DTC match
		}  // end for (SMADIdx)
	}  // end for (EcuIdx)
}





/*******************************************************************************
**
**  Function:  GetUnitScale
**
**  Purpose:   Get the units and scaling for the designated ID.
**
*******************************************************************************/
STATUS GetUnitScale ( UNIT_SCALE_ENTRY *pUnitScaleEntry )
{
	STATUS eRetCode = FAIL;  // function result
	WORD   Index;            // loop control
	
	pUnitScaleEntry->RawDataType = UNDEFINED_SCALE_ITEM;
	pUnitScaleEntry->DisplayPrecision = 0;
	pUnitScaleEntry->Scaling = 1.0;
	pUnitScaleEntry->Offset = 0.0;
	strcpy_s ( pUnitScaleEntry->UnitString, MAX_UNIT_STRING_LENGTH, "" );

	for ( Index = 0;
	      Index < UnitScaletTableSize;
	      Index++ )
	{
		if ( UnitScaletTable[Index].ID == pUnitScaleEntry->ID )
		{
			pUnitScaleEntry->RawDataType = UnitScaletTable[Index].RawDataType;
			pUnitScaleEntry->DisplayPrecision = UnitScaletTable[Index].DisplayPrecision;
			pUnitScaleEntry->Scaling = UnitScaletTable[Index].Scaling;
			pUnitScaleEntry->Offset = UnitScaletTable[Index].Offset;
			if ( strcmp ( UnitScaletTable[Index].UnitString, "" ) != 0 )
			{
				strcpy_s ( pUnitScaleEntry->UnitString, MAX_UNIT_STRING_LENGTH, UnitScaletTable[Index].UnitString );
			}
			eRetCode = PASS;
			break;
		}
	}
	return eRetCode;
}
