//---------------------------------------------------------------------------
/*
Remote Train Control Program for Windows

© Copyright 2019 by Mark DiVecchio
                               
    This file is part of Remote Train Control.

    Remote Train Control is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Remote Train Control is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Remote Train Control.  If not, see <http://www.gnu.org/licenses/>.

*/
// Home Page : http://www.silogic.com/trains/RTC_Running.html
//---------------------------------------------------------------------------
/*
Encapsulates the packet format and response for the TIU

Only one thread of this routine runs

For details of the packet to and from the TIU, look at:

    Serial Data Stream Decoding.doc

Use of message pump:

ICS supplies it own message pump which should be used instead of
Application->ProcessMessages();

        WTIUTXRX[TIUNo - 1]->ProcessMessages();

This is described in WSocket.pas starting around line 4155 (icsv5r20)
*/
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

#pragma package(smart_init)

#include "Train_Control.h"
#include "Tserial_event.h"
#include "Comm_Thread.h"
#include "Setup_Control.h"
#include "Debug_Controls.h"
#include "RTCMegaMaster.h"

#include <time.h>
#if __BORLANDC__ >= 0x720
#include <algorithm>	// STL - needed for lower_bound()
#else
#include <algorithm.h>	// STL - needed for lower_bound()
#endif

// Rocksoft™ Model CRC Algorithm
#include "crcmodel.h"

// AKA TSerialEventThread
extern Tserial_event * Serial_port;
extern Tserial_event * RFID_serial_port;

// AIU Send port
static TWSocket * AIUSend = NULL;
// WTIU WiFi Send and Receive ports - TIU Number used as index
extern TWSocket * WTIUTXRX[TIUMAX];
extern volatile bool WTIUSessionsConnected;  // set to true on all TCP connection to the WTIUs successful
extern volatile bool WTIUSessionsAttemptComplete;  // set to true on all WTIU connection attempts complete

// global vars that might be used in an ISR should be tagged volatile
volatile static int iTIUbufCount;
volatile int iRFIDbufCount;
volatile byte cTIUbuf[CMDMAX];				// data from the TIU
volatile byte cRFIDbuf[CMDMAX];				// data from the RFID Detectors

// global vars that might be used in an ISR should be tagged volatile
extern volatile bool bRadioCOMConnected;	// Radio Serial Port Connection Complete Flag
extern volatile bool bRFIDCOMConnected;	    // RFID Serial Port Connection Complete Flag
extern volatile bool bWTIUWiFiConnected;	// WTIU WiFi Port Connection Complete Flag
extern volatile bool bRFIDWiFiConnected;	// RFID WiFi Port Connection Complete Flag
volatile bool RadioDataSent;				// Indicates that the Radio packet was sent either via COM or WiFi
// AIU Emulator
extern volatile bool bAIUWiFiTXConnected;	// AIU WiFi TX Port Connection Complete Flag
volatile bool AIUDataSent;					// Indicates that the AIU packet was sent via WiFi
//extern struct AIU_EMU AIUEmulator[TIUMAX];				// Flags to indicate an AIU Emulator is attached to an emulated TIU
//extern struct MSG_DISPLAY MessageDisplay[100];          // Flags to indicate a message display ( 1 - 100 ) is available

extern volatile bool bTIUConnected;	        // set to true when the TIU acknowledged a Get AIU Count 'x' Command

extern bool Ignore_FF_Char;
extern byte Sequence;			// Sequence number for TX packets
extern bool RequestyCommand;       // When true during WIU_WiFi_Mode, generate a 'y' command

void __fastcall byteEncodeAString(Cmd * CSource, Cmd * CDest);
String __fastcall byteHextoString(byte * str);

extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern int TimeStampMode;                           // Debug messages time stamping
extern int PC_Tenths;								// RunTimeinTenths counter (system wide)
extern String sEncodeAString(Cmd CS);

extern int AIUCount[TIUMAX];				   // Number of AIU connected to each TIU
extern int TIUVersion[TIUMAX];                 // TIU version number as an int * 100 (eg v1.06 will have a value of 106)
extern String TIUVersionString[TIUMAX];        // TIU Version number as a string

extern volatile int SelectedWTIUPortCount;      // Count of TIU connected

extern AnsiString J13HexString;            // All Engine Operation
extern bool TIUSelection[TIUMAX];                       // Flags to indicate that a TIU is present
extern bool SuperTIUMode, TIUSuperMode[TIUMAX];         // Flags to indicate if Super TIU Mode is active
extern bool bEmergencyStop;

HANDLE CommReqEvent;		// for event communications with CommThread

// 64 bit Speck Cipher key
uint16_t key[4];
// 32 bit plain text message
uint16_t plain[2];
// 32 bit encrypted message
uint16_t enc[2];

//---------------------------------------------------------------------------
#define RAMIDLE 0	// delay (milliseconds) in case we are hitting the TIU too hard with 'q' commands
                    // Values of 25 through 250 had no effect on "TIU response failure" errors
//---------------------------------------------------------------------------
//
// check that d is a valid Encoded digit, that is, is 0x5,0x6 0x9, or 0xA.
//
inline unsigned char ValidDigit(unsigned char d)
{
if (d == 0x05 || d == 0x06 || d == 0x09 || d == 0x0A) return d;
return 0;
}
//---------------------------------------------------------------------------
//
// check that c is a valid encoded byte, that is, is "0x4D" or contains only digits 0x5,0x6 0x9, and 0xA.
//
// W8027 Functions containing some if statements are not expanded inline.
// W8070 Function should return a value
inline unsigned char ValidByte(unsigned char c)
{
if (c == 0x4D) return c;
if (ValidDigit(c & 0x0F))
  if (ValidDigit((c >> 4) & 0x0F))
    return c;
return '\0';
}
/* ======================================================== */
/* ===============  OnCharArrival     ===================== */
/* ======================================================== */

void SerialEventManager(uint32 object, uint32 event)
{
char txt[512];
Tserial_event *com = (Tserial_event *) object;
if (com!=0)
    {
    switch(event)
        {
        default			:	// Handles CTS, DSR, and CD along with Error and Break
            SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, 0, event);
            break;

        case  SERIAL_CONNECTED  :
            bRadioCOMConnected = true;	// this is the only spot where this is set true
            SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, 0, event);
            break;

        case  SERIAL_DISCONNECTED  :
            bRadioCOMConnected = false;	// this is the only spot where this is set false
            // pass the disconnect reason to the MainForm
            SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, com->DoneReason, event);
            break;

        case  SERIAL_DATA_SENT  :
	        RadioDataSent = true;
            // Don't have to do anything here
            break;

        case  SERIAL_ERR			:
            // look at ClearCommError() for values
            SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, com->getCommError(), event);
            break;

        case  SERIAL_DATA_ARRIVAL  :
			int size   = com->getDataInSize();
            const char * buffer = com->getDataInBuffer();
            if (size > 511) size = 511;	// don't allow buffer overflow
            // Get the Data
            memcpy(txt, buffer, size);
            com->dataHasBeenRead();
            txt[size] = NULL;	// Null terminate
            //
            // Don't take strlen(txt) as the string may contain one
            // character of 0x00 and that appears as an empty string
			//
            // Display as Hex
            for(int i = 0 ; i < size; i ++ ) {
                // get the character from our buffer and convert it to an integer
                const unsigned char Char1 = static_cast<unsigned char>(txt[i]) & 0x00FF ;
                // skip certain invalid characters
                if (Ignore_FF_Char && (Char1 == 0xFF)) {
                    continue;
                    }
                if (!ValidByte(Char1)) {
                    continue;
                    }
                if (iTIUbufCount > (CMDMAX - 10)) {
                	iTIUbufCount = 0;	// dont' allow buffer overflow
                    }
                // State machine to detect the 55554D prefix on each packet returned
                // from the TIU. Code added July 2018
                switch(iTIUbufCount) {
                    case 0:
                        // wait until a 0x55 is detected
                        if (Char1 == 0x55) {
                            cTIUbuf[iTIUbufCount++] = Char1;
                            // Send CM_TIUCHAR message to MainForm which will move Char1 into the TIU Buffer
                            if (DEBUGGING) (void) PostMessage(MainForm->Handle, CM_TIUCHAR, Char1, iTIUbufCount);
                            } else iTIUbufCount = 0;
                        break;
                    case 1:
                        // second character must also be a 0x55
                        if (Char1 == 0x55) {
                            cTIUbuf[iTIUbufCount++] = Char1;
                            // Send CM_TIUCHAR message to MainForm which will move Char1 into the TIU Buffer
                            if (DEBUGGING) (void) PostMessage(MainForm->Handle, CM_TIUCHAR, Char1, iTIUbufCount);
                            } else iTIUbufCount = 0;
                        break;
                    case 2:
                        // Third character must be a 0x4D
                        if (Char1 == 0x4D) {
                            // found the prefix
                            cTIUbuf[iTIUbufCount++] = Char1;
                            // Send CM_TIUCHAR message to MainForm which will move Char1 into the TIU Buffer
                            if (DEBUGGING) (void) PostMessage(MainForm->Handle, CM_TIUCHAR, Char1, iTIUbufCount);
							} else iTIUbufCount = 0;
                        break;
                    default:
                        // once the prefix is found, process all characters
                        // until the RX is turned off
                        cTIUbuf[iTIUbufCount++] = Char1;
                        // Send CM_TIUCHAR message to MainForm which will move Char1 into the TIU Buffer
                        if (DEBUGGING) (void) PostMessage(MainForm->Handle, CM_TIUCHAR, Char1, iTIUbufCount);
                        break;
                    }
                }
            break;
        }
    }
}
//---------------------------------------------------------------------------
/* ======================================================== */
/* ===============  OnCharArrival 2  ====================== */
/* ==== Runs in the context of the TSerial_event Thread === */
/* ========NO VCL calls in this routine =================== */
/* ======================================================== */
static void SerialEventManager2(uint32 object, uint32 event)
{
char txt[512];
//static DWORD CE;
Tserial_event *com = (Tserial_event *) object;
if (com!=0) {
    switch(event) {
        default			:
            SendMessage(MainForm->Handle, CM_RFIDCOMSTATUS, com->DoneReason, event);
            break;

        case  SERIAL_CONNECTED  :
            bRFIDCOMConnected = true;	// this is the only spot where this is set true
            iRFIDbufCount = 0;	// RFID Port Byte Counter
            cRFIDbuf[0] = '\0';
            SendMessage(MainForm->Handle, CM_RFIDCOMSTATUS, 0, event);
            break;

        case  SERIAL_DISCONNECTED  :
            bRFIDCOMConnected = false;	// this is the only spot where this is set false
			iRFIDbufCount = 0;	// RFID Port Byte Counter
            cRFIDbuf[0] = '\0';           
            // pass the disconnect reason to the MainForm
            SendMessage(MainForm->Handle, CM_RFIDCOMSTATUS, com->DoneReason, event);
            break;

        case  SERIAL_ERR		:
            // look at ClearCommError() for values
            SendMessage(MainForm->Handle, CM_RFIDCOMSTATUS, com->getCommError(), event);
            assert(false);
            break;

        case  SERIAL_DATA_ARRIVAL  :
            int size   = com->getDataInSize();
            const char * buffer = com->getDataInBuffer();
            if (size > 511) size = 511; // don't allow buffer overflow
            // Get the Data
            memcpy(txt, buffer, size);
            txt[size] = NULL;     // NULL terminator
            com->dataHasBeenRead();
            //
            for(int i = 0 ; i < size; i ++ ) {
                // get the character from our buffer and convert it to an integer
                const unsigned char Char1 = static_cast<unsigned char>(txt[i]) & 0x00FF ;
                if (Char1 == '\r') continue;	// ignore Carriage Return 0x0D CR character
                if (iRFIDbufCount > (CMDMAX - 10)) iRFIDbufCount = 0;	// dont' allow buffer overflow
                if (Char1 != '\n') cRFIDbuf[iRFIDbufCount++] = Char1;	// buffer all non 0x0A LF characters
                assert(iRFIDbufCount < 70);
                // Send CM_RFIDASCIICHAR message to MainForm which will move Char1 into the RFID Buffer
                // Not necessary as CM_PROCESSRFIDDATA message will display the complete incoming packet
                //if (DEBUGGING) (void) PostMessage(MainForm->Handle, CM_RFIDASCIICHAR, Char1, iRFIDbufCount);
                if (Char1 == '\n') {  // on Line Feed 0x0A LF character
                    cRFIDbuf[iRFIDbufCount] = '\0';	// null terminated
					//
                    // send tag to MainForm which relays it to all PC threads.
                    char * pBuffer = new char[(CMDMAX - 10)];	// NOTE: buffer is delete[]d in the CM_PROCESSRFIDDATA message handler CMProcessRFIDData()
                    assert(pBuffer != NULL);	// make sure constructor worked
                    strncpy(pBuffer, (char *) cRFIDbuf, (CMDMAX - 10));
                    // Send CM_PROCESSRFIDDATA message to MainForm which will handle the incoming data in pBuffer
                    PostMessage(MainForm->Handle, CM_PROCESSRFIDDATA, Char1, (long)pBuffer);    // Serial RFID Full Packet
                    // Message handler sends RFIDReqEvents to all PC threads.
					// pBuffer is deleted in the MainForm Message Handler
                    // clear the buffer
                    cRFIDbuf[0] = '\0';
                    iRFIDbufCount = 0;
                    }
                }
            break;
        }
    }
}
//---------------------------------------------------------------------------
//   Important: Methods and properties of objects in VCL can only be
//   used in a method called using Synchronize, for example:
//
//      Synchronize(UpdateCaption);
//
//   where UpdateCaption could look like:
//
//      void __fastcall TCommThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TCommThread::TCommThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------
__fastcall TCommThread::TCommThread(bool CreateSuspended, TForm * Caller, HWND Parent, String PortName, int BaudRate, int ParitySel, char BitsSel, int StopSel,
			bool Radio_COM_Mode, bool WTIU_WiFi_Mode, bool RFID_COM_Mode, bool RFID_WiFi_Mode, bool AIU_WiFi_Mode, bool Signals_WiFi_Mode, int * ResultValue)
	: TThread(CreateSuspended)
{
// Calling Form and Handle
MyCaller = Caller;
ParentHandle = Parent;

MyResultValue = ResultValue;

Finished = false;

// free memory on terminate so that parent does not have to worry about it
FreeOnTerminate = true;

// termination
OnTerminate = &RunOnTerminate;

// Priority
Priority = tpHighest;
//Priority = tpTimeCritical;

// Operation Modes
My_Radio_COM_Mode = Radio_COM_Mode;
My_WTIU_WiFi_Mode = WTIU_WiFi_Mode;
assert(My_Radio_COM_Mode || My_WTIU_WiFi_Mode);	// one of the two TIU modes must be selected

My_AIU_WiFi_Mode = AIU_WiFi_Mode;
My_RFID_COM_Mode = RFID_COM_Mode;
My_RFID_WiFi_Mode = RFID_WiFi_Mode;

// Radio Serial port if needed
if (My_Radio_COM_Mode) {
    if (!Serial_port) Serial_port = new Tserial_event;
    assert(Serial_port);	// make sure constructor worked
    // Set Event Manager
    Serial_port->setManager(SerialEventManager);
    //
    SetLED(MainForm->StatusLED5, clLime, true, false);
    } else {
    Serial_port = NULL;
    }
// RFID Serial port if needed
if (My_RFID_COM_Mode) {
    if (!RFID_serial_port) RFID_serial_port = new Tserial_event;
    assert(RFID_serial_port);	// make sure constructor worked
    // Set Event Manager
    RFID_serial_port->setManager(SerialEventManager2);
    SetLED(MainForm->StatusLED6, clLime, true, false);
    } else {
    RFID_serial_port = NULL;
    }

// RFID Port
iRFIDbufCount = 0;	// RFID Port Byte Counter
SelectedWTIUPortCount = 0;                  // clear the global TIU counter

Initialize();

// indicate data buffer is empty
RadioDataSent = true;
AIUDataSent = true;
*MyResultValue = 0;
for (int TIUNo = 1 ; TIUNo <= TIUMAX ; TIUNo++) {
    LastEngine[TIUNo - 1] = 127;     // last engine number used by TIU
    }

for (int idx = 0; idx < 256 ; idx++) TIUCommands[idx] = false;
//
// Lashup Commands contain the list of Engines and follow the Super TIU Mode rules
//
// Radio mode all Lashup commands follow the Super TIU Mode rules
// WiFi mode all Lashup commands are prepended with a 'V' and follow the Super TIU Mode rules
//
// All Engine mode does not need an engine number as all engines respond. Command prepended with a '|' and follow the Super TIU Mode rules
//

// 64 bit Speck key found using dnSpy
key[0] =  5196;
key[1] =  46084;
key[2] =  38013;
key[3] =  32838;
if (My_WTIU_WiFi_Mode && DEBUGGING) PrintItem("Speck Key : 0x" + IntToHex((uint16_t)key[3], 4) + IntToHex((uint16_t)key[2], 4) +
    IntToHex((uint16_t)key[1], 4) + IntToHex((uint16_t)key[0], 4));
}
//---------------------------------------------------------------------------

__fastcall TCommThread::~TCommThread() {
// Destructor
if (DEBUGGINGX(1)) {
	PrintItem("TCommThread::~TCommThread ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    if (My_Radio_COM_Mode)
    	PrintItem("TSerialEventThread:~TSerialEventThread Serial Port ThreadID " + IntToHex((int)Serial_port->getThreadId(), 8), (TColor) clGreen);
    if (My_RFID_COM_Mode)
    	PrintItem("TSerialEventThread:~TSerialEventThread RFID Port ThreadID " + IntToHex((int)RFID_serial_port->getThreadId(), 8), (TColor) clGreen);
	}
CloseHandle(CommReqEvent);		// not really needed - the system closes the handle automatically when the process terminates
// close RFID serial port
if (RFID_serial_port) {
    delete RFID_serial_port;
    RFID_serial_port = NULL;
    SetLED(MainForm->StatusLED6, clLime, false, false);
    };
// close Radio serial port
if (Serial_port) {
    delete Serial_port;
    Serial_port = NULL;
    SetLED(MainForm->StatusLED5, clLime, false, false);
    }

Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::Initialize(void)     // called using Synchronize() or by constructor
{
NumRetries = Setup->NumRetries->Value;			// 0-9 with 10 meaning to disable sending packets
Speed_Mode = Setup->Speed_Mode->Checked;
TXOnlyComm = Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked;
MyListBoxCount = Setup->WTIUListBox->Items->Count;
Echo = Setup->Echo->Checked;
Ignore_FF_Char = Setup->Ignore_FF->Checked;
// AIU Emulators WiFi Port
AIUIP = Setup->AIUIP->Text;
AIUPort = Setup->AIUPort->Text;
AIUMultiCast = Setup->AIU_WiFi_Multicast->Checked;
if (Setup->IPComboBox->Items->Count >  0 && Setup->IPComboBox->ItemIndex != -1) {
    FServerAddr.S_un.S_addr = WSocket_inet_addr(Setup->IPComboBox->Items->Strings[Setup->IPComboBox->ItemIndex].c_str());
    }
}

//---------------------------------------------------------------------------

void __fastcall TCommThread::CleanUp(void)     // called using Synchronize()
{
// not used
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::AIUSendChangeState(TObject *Sender,
      TSocketState OldState, TSocketState NewState)
{
// handle change of state
if (DEBUGGINGX(9)) PrintItem("AIU TX WiFi Change State = " + String(OldState) + " to " + String(NewState));
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::AIUSendSendData(TObject *Sender,
      int BytesSent)
{
//AIUDataSent = true;		// indicate that Winsock send buffer is empty, will be filled again by internal send buffer if data available to send.
if (DEBUGGINGX(7)) PrintItem("AIU TX WiFi Send Data");
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::AIUSendDataSent(TObject *Sender, WORD ErrCode)
{
AIUDataSent = true;		// indicate that Internal sent buffer is completely delivered to winsock.
if (ErrCode > 0) {
	if (DEBUGGING) PrintItem("AIU TX WiFi Data Sent - ErrCode = " + String(ErrCode));
	} else {
	if (DEBUGGINGX(7)) PrintItem("AIU TX WiFi Send Data Sent");
	}
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::AIUSendSessionClosed(TObject *Sender,
      WORD ErrCode)
{
bAIUWiFiTXConnected = false;
AIUWiFiTXConnected = false;
if (DEBUGGING) PrintItem("AIU TX WiFi disconnected from " + AIUSend->Addr + " - Port " + AIUSend->Port);
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::AIUSendSessionConnected(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGING) PrintItem("Can't connect to AIU TX WiFi. Error #" + String(ErrCode));
	} else {
	bAIUWiFiTXConnected = true;
	AIUWiFiTXConnected = true;
	if (DEBUGGING) PrintItem("AIU TX WiFi connected to " + AIUSend->Addr + " - Port " + AIUSend->Port);
	}
}
//---------------------------------------------------------------------------
/* From winsock2.h
//
struct in_addr {
  union {
    struct {
      u_char s_b1;
      u_char s_b2;
      u_char s_b3;
      u_char s_b4;
    } S_un_b;
    struct {
      u_short s_w1;
      u_short s_w2;
    } S_un_w;
    u_long S_addr;
  } S_un;
};
*/
//---------------------------------------------------------------------------

void __fastcall TCommThread::Execute()
{
//
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread
//

if (DEBUGGINGX(1)) {
	// print the ThreadId of the Communications Thread
	PrintItem("TCommThread: ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), (TColor) clGreen);
   	// print the Serial port ThreadIds if they are running --  NOTE It takes a few milliseconds for the serial threads to start
    // otherwise calls to getThreadId() return zero.
    Sleep(50);
    if (My_Radio_COM_Mode) {
    	PrintItem("TSerialEventThread: Serial Port ThreadID " + IntToHex((int)Serial_port->getThreadId(), 8), (TColor) clGreen);
        }
    if (My_RFID_COM_Mode) {
    	PrintItem("TSerialEventThread: RFID Port ThreadID " + IntToHex((int)RFID_serial_port->getThreadId(), 8), (TColor) clGreen);
        }
    }

// this has no effect in BCB4.0 and Thread view not available in CB10.1
// it does work on BCB6.0
// http://docwiki.embarcadero.com/RADStudio/Rio/en/Converting_an_Unnamed_Thread_to_a_Named_Thread
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = "Comm_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
	RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (DWORD*)&info);
	}
__except (EXCEPTION_CONTINUE_EXECUTION) {
	}

if (My_Radio_COM_Mode) {
    info.dwType = 0x1000;
    info.szName = "Radio_Serial_Thread";
    info.dwThreadID = Serial_port->getThreadId();
    info.dwFlags = 0;
    __try {
        RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (DWORD*)&info);
        }
    __except (EXCEPTION_CONTINUE_EXECUTION) {
        }
    }

if (My_RFID_COM_Mode) {
    info.dwType = 0x1000;
    info.szName = "RFID_Serial_Thread";
    info.dwThreadID = RFID_serial_port->getThreadId();
    info.dwFlags = 0;
    __try {
        RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (DWORD*)&info);
        }
    __except (EXCEPTION_CONTINUE_EXECUTION) {
        }
    }
// Construct the WTIU WiFi Ports
HANDLE MyTimer = NULL;
//
// Handle the Comm in a try{} block for ease of exit
try {
    if (My_WTIU_WiFi_Mode) {
        WTIUSessionsAttemptComplete = false;
        // Get up to MyListBoxCount IPaddress/PortNumbers from the Setup TIUWTIUListBox
        for (int TCPSlot = 0; TCPSlot < MyListBoxCount; TCPSlot++) {
            //
            // only create TCP connections for selected TIU
            // but only keep the connection if the TIU number is selected
            // on the "Select Active TIU" window
            //
            TCHAR IP[50];
            TCHAR WTIUName[50];
            String ListBoxString;
            int Port;
            GetListBoxText(Setup->WTIUListBox, ListBoxString, TCPSlot); // can't call the VCL directly
            if (!ListBoxString.IsEmpty()) {
                int retv = _stscanf(ListBoxString.c_str(), TEXT("%s %d %s"), IP, &Port, WTIUName);
                if (retv != 3) {    // must be three items in the ListBox string
                    continue;
                    }
                // ------------------------------------------------
                // Construct one WTIU WiFi Port
                TWSocket * Socket = new TWSocket(NULL);
                assert(Socket);	// make sure constructor worked
                // ------------------------------------------------
                //
                //---------------------- WTIU WiFi TCP ---------------------------
                Socket->Proto       = "tcp";      // Use TCP protocol
                Socket->Port        = Port;
                Socket->Addr        = IP;
                Socket->ReuseAddr = true;
                Socket->OnDataAvailable = WTIUTXRXDataAvailable;
                Socket->OnSessionClosed = WTIUTXRXSessionClosed;
                Socket->OnSessionConnected = WTIUTXRXSessionConnected;
                Socket->OnDataSent = WTIUTXRXDataSent;
                Socket->OnSendData = WTIUTXRXSendData;
                Socket->OnSessionAvailable = WTIUTXRXSessionAvailable;
                Socket->MultiThreaded = true;              // Tell TWSocket that it is running outside of main thread
                //
                Socket->Connect();

    #ifdef OLDWAY
                not used
                // Connect is asynchronous (non-blocking)-> We will wait while the
                // session is connecting or terminated
                while (Socket->State == wsConnecting) {
                    Socket->ProcessMessages();
                    if (Terminated)
                        return;
                    }
    #else
                // Connect is asynchronous (non-blocking)-> We will wait while the
                // session is connecting or terminated
                // TODO This failure results in spinning cursor and need for taskmgr to kill the program
                if (!WTIUWaitforConnection(Socket)) {
                    String sStr = "WTIU WiFi TCP failed to connect " + String(WTIUName) + " IP " + String(IP) + " Port = " + String(Port);
                    if (DEBUGGING) PrintItem(sStr);
                    PopupItem(sStr, UIPOPUPTIMEOUT);
                    Sleep(5000);    // let the message stay on the screen
                    throw 102;
                    }
    #endif
                if (DEBUGGING) {
                    PrintItem("WTIU WiFi TCP connected " + String(WTIUName) + " IP " + String(IP) + " Port = " + String(Port));
                    }
                //
                // Use Socket here since we don't know the TIU number
                Handshake(Socket);   // unlock the interface and sets/clears bWTIUWiFiConnected
                //
                // Once we are unlocked, get the TIU Number (1-5) and AIU Count
                int NewTIUNo, NewAIUCount;
                GetWIUInfo(Socket, &NewTIUNo, &NewAIUCount);
                // we then check if the TIU has been selected on the "Select Active TIU" window
                if (TIUSelection[NewTIUNo - 1] == true) {
                    // if so, save the socket in the WTIUTXRX[] array
                    WTIUTXRX[NewTIUNo - 1] = Socket;
					Bump(MainForm->WiFiTIUCount);
					SelectedWTIUPortCount++;
					} else {
					// otherwise, the socket is not needed and can be closed
                    Socket->Close();
                    WTIUTXRX[NewTIUNo - 1] = NULL;
                    }
                }   // if (!ListBoxString.IsEmpty()) 
            }   // for (int TCPSlot = 0; TCPSlot < MyListBoxCount; TCPSlot++)

        // We should have at least 1 WTIU selected
        if (SelectedWTIUPortCount == 0) {
            PopupItem("No Selected WTIU WiFi Devices found. RTC requires at least one WTIU", UIPOPUPTIMEOUT * 2, "(Check that 'Setup->Select Active TIU' is set correctly)");
            Sleep(5000);
            throw 100;
            }
        // Every Active WTIU should have been found and should correctly handshake
        //
        for (int WTIUNo = 0 ; WTIUNo < TIUMAX ; WTIUNo++) {
            if (TIUSelection[WTIUNo] && WTIUTXRX[WTIUNo] == NULL) {
                PopupItem("Active WTIU #" + String(WTIUNo + 1) + " not found", UIPOPUPTIMEOUT * 2, "(Check that 'Setup->Select Active TIU' is set correctly)");
                Sleep(5000);    // let the message stay on the screen
                throw 101;
                }
            }
        }   // if (My_WTIU_WiFi_Mode)
    //
    // At this point, all Active WTIU are connect with TCP sessions
    WTIUSessionsConnected = true;    // indicate all WTIU sessions connected
    bTIUConnected = true;	// indicate successful communications with the TIU
    WTIUSessionsAttemptComplete = true;  // we tried

    if (My_AIU_WiFi_Mode) {
        // Construct the AIU TX WiFi Port
        AIUSend = new TWSocket(NULL);
        assert(AIUSend);	// make sure constructor worked
        //
        AIUSend->OnDataSent = AIUSendDataSent;
        AIUSend->OnSessionClosed = AIUSendSessionClosed;
        AIUSend->OnSessionConnected = AIUSendSessionConnected;

        AIUSend->ReuseAddr 			= true;
        AIUSend->Proto 				= "udp";
        AIUSend->Addr  				= AIUIP;		// use AIU IP from Setup
        AIUSend->Port  				= AIUPort;		// use AIU Port from Setup
        AIUSend->MultiThreaded = true;              // Tell TWSocket that is running outside of main thread

        // http://wiki.overbyte.eu/wiki/index.php/TWSocket.MultiCast
        // https://en.delphipraxis.net/topic/1429-multicast-configuration/
        // https://tldp.org/HOWTO/Multicast-HOWTO-6.html
        // https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/udp-examples.html
        if (AIUMultiCast) {	// Use Multicasting when sending to one/many RTCAIUWiFi devices (they all reply unicast)
            AIUSend->MultiCast 		= true;
            //AIUSend->MultiCastAddrStr = AIUIP;	// not used for TX, use Addr
            if (DEBUGGING) PrintItem("AIU TX WiFi Connect() (Multicast) to IP " + AIUSend->Addr + " - Port " + AIUSend->Port);
            } else {
            AIUSend->MultiCast 		= false;	// Use Unicasting when sending only to one RTCAIUWiFi device - debug mode only
            if (DEBUGGING) PrintItem("AIU TX WiFi Connect() (Unicast) to IP " + AIUSend->Addr + " - Port " + AIUSend->Port);
            }
        //
        AIUSend->Connect();

        // Connect is asynchronous (non-blocking)-> We will wait while the
        // session is connecting or application terminated
        while (AIUWiFiTXConnected == false) {
        // TODO : when this is used, consider changing to monitor AIUSend->State
            Sleep(10);
            AIUSend->ProcessMessages();
            if (Terminated)
                return;
            }
        SetLED(MainForm->StatusLED8, clLime, true, false);
        }

    // ----------------------------------------------------------------------------------
    //
    // FIFO - Thread safe
    //
    // ----------------------------------------------------------------------------------
    FIFO = new TThreadList();	// - Thread Safe
    assert(FIFO != NULL);	// make sure constructor worked
    TList *myList = FIFO->LockList();
    try {
        myList->Capacity = 100;
        }
    __finally {
        FIFO->UnlockList();
        }
    // ----------------------------------------------------------------------------------
    //
    // Main Execute Loop
    //
    // ----------------------------------------------------------------------------------
    String PrintLine;
    //
    // create Comm Request event
    CommReqEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
    assert(CommReqEvent != NULL);	// make sure constructor worked
    //
    // https://docs.microsoft.com/en-us/windows/desktop/sync/using-waitable-timer-objects
    //
    LARGE_INTEGER liDueTime;
    liDueTime.QuadPart = 0L;

    // Create an unnamed waitable timer.
    // use this timer to periodically check for Terminated.
    MyTimer = CreateWaitableTimer(NULL, false, NULL);	// Autoreset --> the timer is a synchronization time
    if (MyTimer == NULL) {
        assert(false);	// make sure constructor worked
        }

    HANDLE handles[] = { MyTimer, CommReqEvent, MainForm->TerminationCTEvent };

    // start the waitable Timer for 100 milliseconds and continue on if it starts up OK.
    bool bretv = SetWaitableTimer(MyTimer, &liDueTime, 100, NULL, NULL, false);
    if (bretv) {
        // Timer is running OK
        struct FIFOItem *MyFIFOItem;
        while (!Terminated) {
            // check the FIFO to see if anything is there - alerted by CommReqEvent
            DWORD dwretv = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
            //------------------------  Termination Event -----------------------------------
            if (dwretv == WAIT_OBJECT_0 + 2 || dwretv == WAIT_FAILED) {	// TerminationCTEvent Event Signaled or unknown failure?
                if (DEBUGGINGX(1)) PrintItem("TCommThread: TerminationCTEvent");
                Terminate();
                break;	// time to terminate
                }
            if (Terminated) {
                break;				// in case we are terminated
                }
            //------------------------  Timer Event -----------------------------------
            if (dwretv == WAIT_OBJECT_0 + 0) {	// MyTimer Event Signaled ?
                continue;		// continue to wait
                }
            if (dwretv != WAIT_OBJECT_0 + 1) continue;
            //------------------------  Communications Request Event -----------------------------------
            // The remaining event is the CommReqEvent (WAIT_OBJECT_0 + 1)
            while (!Terminated) {
                TList *myList = FIFO->LockList();
                //
                if (myList->Count > 0) {
                    // FIFO has a command ready to send
                    MyFIFOItem = reinterpret_cast<struct FIFOItem *>(myList->Items[0]); // Thread Safe
                    // remove(MyFIFOItem) the item pointer from the FIFO
                    // from Help: Alternative: Delete by Index:
                    // While Remove is generally preferred when you have a pointer to the item, TList also has a Delete(Index: Integer) method if you need to remove an item by its position. This also needs to be performed within a locked section.
                    myList->Delete(0);
                    //if (MyFIFOItem != NULL) FIFO->Remove(MyFIFOItem);
                    FIFO->UnlockList();
                    //
                    // Send the command and wait for its completion
                    //
                    bool bretv = false;
                    switch(MyFIFOItem->Type) {
                        case CMD_Type_SendString:
                            if (DEBUGGINGX(4)) {
                                // print the values in the struct pointed to by FIFOItem
                                int ConsistNo = MyFIFOItem->ConsistNo;
                                PrintLine = "TCommThread: Type DL_Type_SendString Command " + String((char *)MyFIFOItem->Command) + " for Consist " + String(ConsistNo);
                                PrintItem(PrintLine, clBlack);
                                }
                            // all commands execpt the 'V' command go out the port
                            switch(MyFIFOItem->Command[0]) {
                              case 'V':
                                if (DEBUGGINGX(3)) {
                                    PrintLine = "Sending Virtual Command: " + String(MyFIFOItem->Command);
                                    PrintItem(PrintLine);
                                    }
                                bretv = SendReadRAMCommand(MyFIFOItem);
                                // retry the 'V' command once if it fails
                                if (!bretv && NumRetries > 0) {
                                    // retry
                                    if (DEBUGGINGX(3)) {
                                        PrintLine = "Retrying Virtual Command: " + String(MyFIFOItem->Command);
                                        PrintItem(PrintLine);
                                        }
                                    bretv = SendReadRAMCommand(MyFIFOItem);
                                    }
                                break;
                              default:
                                //
                                bretv = Send_String(MyFIFOItem->ConsistNo, MyFIFOItem->Command,
                                    MyFIFOItem->TIUNo, MyFIFOItem->Remote, MyFIFOItem->ExpectedCount, MyFIFOItem->WaitTime, MyFIFOItem->Record_Flag,
                                    MyFIFOItem->cLashUpEngineList,
                                    MyFIFOItem->ptrRespCode, MyFIFOItem->ResponseCount, MyFIFOItem->ResponseBuffer);
                                break;
                              }
                            if (!bretv) {	// the send string call failed
                                if (MyFIFOItem->ptrRespCode) {	// return a failure code as a response - unless a code is already present
                                    if (*MyFIFOItem->ptrRespCode < UNDETERMINED) *MyFIFOItem->ptrRespCode = UNDETERMINED;
                                    }
                                }
                            break;
                        case CMD_Type_WaitForResponse:
                            if (DEBUGGINGX(4)) {
                                // print the values in the struct pointed to by FIFOItem
                                int ConsistNo = MyFIFOItem->ConsistNo;
                                PrintLine = "TCommThread: Type DL_Type_WaitForResponse Command " + byteHextoString(MyFIFOItem->Command) + " for Consist " + String(ConsistNo);
                                PrintItem(PrintLine, clBlack);
                                }
                            bretv = TCommThread::TIUSendWaitforResponseRetry(MyFIFOItem->ConsistNo, MyFIFOItem->Command,
                                MyFIFOItem->ExpectedCount, MyFIFOItem->WaitTime, MyFIFOItem->Remote, MyFIFOItem->TIUNo, MyFIFOItem->ptrRespCode,
                                MyFIFOItem->ResponseCount, MyFIFOItem->ResponseBuffer);
                            if (!bretv) {	// the send string call failed
                                if (MyFIFOItem->ptrRespCode) {	// return a failure code as a response - unless a code is already present
                                    if (*MyFIFOItem->ptrRespCode < UNDETERMINED) *MyFIFOItem->ptrRespCode = UNDETERMINED;
                                    }
                                }
                            break;
                        case CMD_Type_SendHex:
                            if (DEBUGGINGX(4)) {
                                // print the values in the struct pointed to by FIFOItem
                                int ConsistNo = MyFIFOItem->ConsistNo;
                                PrintLine = "TCommThread: Type DL_Type_SendHex Command " + byteHextoString(MyFIFOItem->Command) + " for Consist " + String(ConsistNo);
                                PrintItem(PrintLine, clBlack);
                                }
                            bretv = TCommThread::SendHexStr(MyFIFOItem->Command, MyFIFOItem->Len, MyFIFOItem->ExpectedCount, MyFIFOItem->WaitTime, MyFIFOItem->TIUNo);  // Len is actual bytes to send
                            if (!bretv) {	// the send string call failed
                                if (MyFIFOItem->ptrRespCode) {	// return a failure code as a response - unless a code is already present
                                    if (*MyFIFOItem->ptrRespCode < UNDETERMINED) *MyFIFOItem->ptrRespCode = UNDETERMINED;
                                    }
                                }
                            break;
                       case CMD_Type_DelayedCommand:
                            // This type of message must always converted to DL_Type_SendString
                            // before being put in the FIFO. Therefore, you should never see this type of
                            // command coming out of the FIFO.
                            assert(false);
                            break;
                        default:
                            // ignore bad commands but return an error code
                            if (MyFIFOItem->ptrRespCode) *MyFIFOItem->ptrRespCode = ILLEGALCOMMAND;
                            int ConsistNo = MyFIFOItem->ConsistNo;
                            PrintLine = "TCommThread: Unknown Type Command " + byteHextoString(MyFIFOItem->Command) + " for Consist " + String(ConsistNo);
                            PrintItem(PrintLine, clRed);
                            break;
                        }

                    HANDLE CommEvent = MyFIFOItem->CommEvent;   // save Event before deleting the request
                    // Free the command's memory
                    delete MyFIFOItem;
                    if (CommEvent != NULL) SetEvent(CommEvent);		// signal complete
                    // go and try to process another command
                    } else {
                    // FIFO is empty
                    FIFO->UnlockList();	// all commands have been sent from the FIFO
                    break;
                    }
                }
            }
        } else {
        // SetWaitableTimer failed
        assert(false);
        }
    }
catch(int e) {
    // Come here if the necessary WTIU are not found
    // 100 no WTIU found
    // 101 all needed WTIU not found
    // 102 WTIU connection timeout
    WTIUSessionsConnected = false;    // indicate session not connected
    WTIUSessionsAttemptComplete = true;  // we tried
    bTIUConnected = false;	// indicate unsuccessful communications with the TIU
    }

// Shut Down the AIU WiFi
// http://wiki.overbyte.eu/wiki/index.php/TWSocket.HowTo.Close
//
if (AIUSend) {
	AIUSend->Close();
	delete AIUSend;
	AIUSend = NULL;
    SetLED(MainForm->StatusLED8, clLime, false, false);
    }
// close the WTIU ports
for (int PortNo = 0; PortNo < TIUMAX; PortNo++) {
    if (WTIUTXRX[PortNo]) {
        WTIUTXRX[PortNo]->Close();
        delete WTIUTXRX[PortNo];
        WTIUTXRX[PortNo] = NULL;
		}
    }
SetNumEditValue(MainForm->WiFiTIUCount, 0); // clear the WiFi TCP Count
SelectedWTIUPortCount = 0;                  // clear the global TIU counter
//
if (MyTimer) {
	CancelWaitableTimer(MyTimer);
	CloseHandle(MyTimer);
    MyTimer = NULL;
    }

if (FIFO) {
	delete FIFO;     // Thread Safe
    FIFO = NULL;
    }
//Synchronize(CleanUp);	// not used
}
//---------------------------------------------------------------------------

bool __fastcall TCommThread::Send_String(unsigned char Consist, byte * Command_String,
	unsigned char TIU, unsigned char Remote, int Expected, int WaitTime, bool Record_Flag, byte * ptrLashUpEngineList,
    int * ptrRespCode, int * SSResponseCount, byte * SSResponseBuffer)
{
// return true on success, false on failure
if (TIU == 0) return true;  // Engine not found on any active TIU
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Command_String != NULL);
assert (Remote >= 1 && Remote <= REMOTEMAX);
// if TIU Serial Port or WiFi port is not connected fail
// replaced by next line if (!MainForm->IsRadioConnected()) return false;	// Radio must be connected either by COM or WTIU WiFi
if (!(bRadioCOMConnected || bWTIUWiFiConnected)) return false;	// Radio must be connected either by COM or WTIU WiFi
// TIU may not be connected at this point as we may still be executing the 'x' and '!' commands to
// verify that the TIU is responding
//
int LocalRespCode = UNDETERMINED;	// must have a response code even if the caller doesn't care so create a local spot
// *ptrRespCode is set from LocalRespCode just before the function exits
//
String TextValue;
String PrintLine;
//
bool bFlag;
//
//----------------------------
// WTIU WiFi Handling
//----------------------------
//
// setup the list of commands which operate on or are executed by a specific TIU
// and do not follow the Super TIU Mode rules
//
/*
DCS Commands

950870:     f   jk   o    t   xy ABC EF HIJKLMNOPQRSTU WXYZ@
950902:abcde ghi  lmnopqrs uvw  z   DEFG IJKL N PQR  UVW    #|

TIU         f   jk        t   xy ABC    H    M O   ST   XYZ@
Super                o               EF  IJKL N PQR  U W

??     abcde ghi  lmn pqrs uvw ?z   D  G              V      |

951069:abcde ghi  lmn p rs uvw yz DGV
951092:abcde ghi  lmn pqrs uvw yz DGV|
*/
TIUCommands['f'] = true;    // Unknown
TIUCommands['j'] = true;    // Unknown
TIUCommands['k'] = true;    // Unknown

TIUCommands['o'] = true;    // Emergency Stop (direct to specific TIU)
TIUCommands['t'] = true;    // Track Control (direct to specific TIU)
TIUCommands['x'] = true;    // Read TIU number and number of AIU (direct to specific TIU and return data)
//TIUCommands['y'] = true;    // I don't understand this. The 'y' command must go to all TIU in Super mode

TIUCommands['!'] = true;    // TIU Version (direct to specific TIU and return data)
TIUCommands['A'] = true;    // AIU Control (direct to specific TIU)
TIUCommands['B'] = true;	// Change TIU Number and set TIU into Super TIU Mode (direct to specific TIU)
TIUCommands['C'] = true;    // Unknown

TIUCommands['H'] = true;    // Handshake (direct to specific TIU and return data and not used in Radio mode)
TIUCommands['I'] = true;    // Interrogate (direct to specific TIU and return data)
// NOTE : RTCSnoop showed that the 'I0' and 'Ixxx' commands are always sent to one TIU
//TIUCommands['J'] = true;    // All Engine Operation (direct to every TIU)
TIUCommands['M'] = true;    // Unknown (direct to TIU)
TIUCommands['O'] = true;	// AC Input Frequency/DCS Setup (direct to specific TIU)
TIUCommands['S'] = true;    // Custom Sounds (direct to specific TIU)
TIUCommands['T'] = true;    // TMCC (direct to specific TIU)
TIUCommands['X'] = true;    // Unknown (direct to TIU)
TIUCommands['Y'] = true;    // Unknown (direct to TIU)
TIUCommands['Z'] = true;    // Unknown (direct to TIU)
TIUCommands['@'] = true;    // Unknown (direct to TIU)
//----------------------------

if (My_WTIU_WiFi_Mode) {
    AnsiString FinalAnswer;     // must be AnsiString because a c_str() is taken when requires char string
    String sCommand_String;
    byte FirstChar = Command_String[0];     // NOTE: character strings are 0-based
    //----------------------------
    // Generate the 'y' command if needed but always when in SuperTIUMode because we never know where the engine will be
    //----------------------------
    if (RequestyCommand || SuperTIUMode) LastEngine[TIU - 1] = 127;  // Generate a 'y' command when requested or when in SuperTIUMode
    //
    // if its a new engine number (and not all engine or LashUp), send a 'y' command or
    // or if requested by RequestyCommand
    // but a 'y' command should never generate a 'y' command
    if (FirstChar != 'y') {
        // In addition, those commands which operate on or by a TIU and do not need to generate a 'y' command
        if (((RequestyCommand && (Consist < ALLENGINE)) ||
            ((Consist != 0) && (Consist != LastEngine[TIU - 1]) && (Consist < ALLENGINE) && (TIUCommands[FirstChar] == false)))) {    	// skip the 'y' command for TIU specific commands
            // Send the 'y' command
            FinalAnswer = SendWTIUString(TIU, "y" + String(Consist + 1), Consist, SHORT_WAIT);
            LastEngine[TIU - 1] = Consist;
            if (MainForm->bRecording) {	// bRecording is a bool and its always OK to record a 'y' command
                // The Engine/Consist number followed by the Command exactly as sent to the WTIU
                // recording : time(NULL) = The number of seconds since January 1, 1970
                SetRecordingTextValue(String(time(NULL)) + " " + String(Consist) + " y" + String(Consist + 1) + " " + String(TIU) + "      // " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4));
                }
            }
        }
    RequestyCommand = false;
    sCommand_String = String((char *)Command_String);
    // in All Engine mode, send the 'J' string before every(!) engine command except for 'J' and 'y' commands
    // in All Engine mode, prepend the 'V' except for 'J' and 'y' commands
    // Looking at the App communications with the WTIU shows that the App does send a "Vy4" in all
    // engine mode and the WTIU responds with "Vy4 input error".
    if ((Consist == ALLENGINE) &&
        (TIUCommands[FirstChar] == false) &&
        (FirstChar != 'J') &&   // Join
        (FirstChar != 'y')) {   // Set working engine number
        // Send 'J' command
        FinalAnswer = SendWTIUString(TIU, J13HexString, Consist, SHORT_WAIT);
        if (MainForm->bRecording) {	// bRecording is a bool and its always OK to record a 'J' command
		    // The Engine/Consist number followed by the Command exactly as sent to the WTIU
            // recording : time(NULL) = The number of seconds since January 1, 1970
            SetRecordingTextValue(String(time(NULL)) + " 100 " + J13HexString + " " + String(TIU) + "     //  " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4));
            }
        sCommand_String = "V" + sCommand_String;    // note that this not the 'V' command that I use to access RAM via a 'q' command
        }

    // append the lashup list if it's any Lashup Command (101)
    // prepend the '|' (bar) if it is any Lashup command (101) except the 'U' command
    // Note that the 'U' command is never sent using Lashup consist number (101)
    if (Consist >= LASHUPMIN) {
        sCommand_String = sCommand_String + String((char *)ptrLashUpEngineList);
        if (FirstChar != 'U') {
            sCommand_String = "|" + sCommand_String;
            }
        }

    for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++) {	// always executes the loop once
        // Check for certain error codes and if found, send the command again
        // Comm_Thread retries are always done ONLY in this function.
        // 0 = no retries (original send packet only)
        // 1 = 1 retry
        // etc.
        //
        if (Terminated)
            return false;
        // loop for NumRetries set on Setup Form (0-9) : 10 is "ignore serial ports" mode
        //
		bFlag = true;
        LocalRespCode = UNDETERMINED;       // set no error condition
        // All Normal commands are sent here
		FinalAnswer = SendWTIUString(TIU, sCommand_String, Consist, WaitTime);
        if (DEBUGGINGX(4)) SetTextValue(MainForm->Last_Response, FinalAnswer);	// VCL Reference
        // if Recording, we create lines that look like this:
        //          1717161219 101 |u4,9103˙ 2  // |u4,9103˙ okay
        //          1717161231 101 |u5,9103˙ 2  // |u5,9103˙ okay
        //          1717161354 2 y3 2      // y3 okay
        //          1717161354 2 d0 2  // d0 okay
        //          1717161355 2 ab3 2  // ab3 okay
        //          1717161355 2 v0100 2  // v0100 okay
        //          1717161611 100 J00000000000000000000010004 2     //  J00000000000000000000010004 okay
        //          1717161611 100 Vd0 2  // Vd0 okay
        //          1717161611 100 J00000000000000000000010004 2     //  J00000000000000000000010004 okay
        //          1717161611 100 Vu4 2  // Vu4 okay
        //          1717161611 100 J00000000000000000000010004 2     //  J00000000000000000000010004 okay
        //          1717161611 100 Vs0 2  // Vs0 okay
        //
        if (MainForm->bRecording && (Record_Flag == RECORD_OK)) {	// bRecording is a bool
		    // The Engine/Consist number followed by the Command exactly as sent to the WTIU
            // recording : time(NULL) = The number of seconds since January 1, 1970
            SetRecordingTextValue(String(time(NULL)) + " " + String(Consist) + " " + sCommand_String + " " + String(TIU) + "  // " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4));
            }

        /*  String.SubString(index, count)
            Returns a specified substring of the AnsiStringT.
            SubString returns a new AnsiStringT that is a substring of this AnsiStringT.
            The substring contains count characters beginning at index. Here index is 1-based,
            meaning that 1 stands for the first character, 2 for the second character, and so on.
        */
        if (bFlag && FinalAnswer.Pos("failure")) {  // note: I've seen "TIU response failure" returned from the WTIU
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
            LocalRespCode = WTIUFAILURE;
            Bump(MainForm->sendDataBusy);// increment Send Data Busy as "failure"
            bFlag = false;              // retry
			}
		if (bFlag && FinalAnswer.Pos("timeout")) {
			// in SuperMode, don't retry timeouts - TIU's respond with timeout message if they don't have the engine on their track
            // in All Engine Mode, don't retry timeouts - TIU's respond with timeout message if they don't have the engine on their track
			if (!(SuperTIUMode && TIUSuperMode[TIU - 1]) && !(Consist == ALLENGINE)) {
       			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
    			LocalRespCode = TIMEDOUT;
	    		Bump(MainForm->NAKLen);     // increment NAK Length error as "timeout"
				bFlag = false;          // retry
				}
			// otherwise, don't retry
			}
        /*
        Since I started using the WIU all the time now, I am seeing cases where
        the whistle off command is missed! Its happened twice over the last month.
        For me, twice is significant.
        What I see is the command being sent:
         bFFFD
        but the WIU/TIU echoes back:
         bFF okay
        and the whistle does not turn off.
        Somewhere, this command is getting corrupted but the WIU/TIU does
        not realize it and sends back the "okay".
        I've also seen an "s15" command become "s" which was effectivly "s0" and the engine stopped.
		I added code to check for this and retry the command.
        */

        //
        // WTIU must echo back the entire command as sent
        // Compare sent string with response (only the command as sent) case sensitive
        //
        // this will detect dropped characters in the response - though I don't know why
        // this could ever happen with TCP. It is possible that the problem is over the
        // serial line between the WIU and the TIU. Will have to see if the problem continues
        // with the WTIU. Example:
        // 293919.328 Send WTIU #1 Engine #3 : q7FA6
        // 293919.328 Response : qA614 00 00 00  okay (note: the'7F' was dropped)
        // 293919.328 WiFi error : Incorrect command echo : qA614 00 00 00  okay
        //
        // This has a failure mode:  a plain 'q' command will match with
        // a response to an out of sequence 'qXXXX' command
        //
        //AnsiString sEchoed_String = LastResponse.SubString(1, sCommand_String.Length());
        if (bFlag && (sCommand_String != FinalAnswer.SubString(1, sCommand_String.Length()))) {
			if (DEBUGGING) PrintItem("WiFi error : Incorrect command echo : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
			LocalRespCode = WTIUECHO;  // simulate an input error though it actually is a transmission error
			Bump(MainForm->EngNotFound);// increment NF as "echo"
			bFlag = false;              // retry
            }
        //
		// WTIU does not always respond with "okay" especially noted for 'x', 'B' and "Dq" commands
        // So if a response is received without "okay" and without any other error, RTC assumes
        // that it worked correctly.
        //
        if (bFlag && FinalAnswer.Pos("okay")) {
            break;  // with bFlag == true;
            }

		if (bFlag && FinalAnswer.Pos("checksum")) {
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
            Bump(MainForm->CKSum);      // increment as "checksum error"
            LocalRespCode = UNDETERMINED;
            Sleep(100);
            bFlag = false;              // retry
            }

        //
        // Look in LastResponse for error words and set bFlag to false
        //
        // TODO : WTIU responds with "input error" for some 'B' commands so we need to ignore it
        //
		if (bFlag && (sCommand_String[1] != 'B') && FinalAnswer.Pos("error")) {    // aka "input error"
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
			Bump(MainForm->NAK0T);      // increment NAK 0T as "input error"
            LocalRespCode = WTIUERROR;
            bFlag = false;              // retry
			}

        if (bFlag && FinalAnswer.Pos("reset")) {
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
            LocalRespCode = WTIURESET;
            Bump(MainForm->NAKCRC);     // increment CRC error counter as "reset"
            bFlag = false;              // retry
            }

		if (bFlag && FinalAnswer.Pos("disconnected")) {
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer.SubString(1, FinalAnswer.Length() - 4), clRed);
            LocalRespCode = WTIUDISCONNECT;
			bTIUConnected = false;      // lost TCP connection to WTIU
            bFlag = false;              // retry
            }

        if (bFlag && FinalAnswer.Pos("available")) {    // "PC connection not available"
			if (DEBUGGING) PrintItem("WiFi error : " + FinalAnswer, clRed);
            Handshake(WTIUTXRX[TIU - 1]);       // Handshake and retry
            Bump(MainForm->NAKShort);	// increment No Response counter as PC connection not available
            LocalRespCode = WTIUNOTAVAILABLE;
            bFlag = false;              // retry
            }

        if (bFlag) break;       // no "okay" or error response so assume success
		Bump(MainForm->Retries);// increment retries counter (WTIU) and retry
        }
    // display error popup message as necessary
    if (NumRetries > 0) {
        switch (LocalRespCode) {
            case UNDETERMINED:  // no error detected
                break;
            case WTIUFAILURE:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " response failure", UIPOPUPTIMEOUT);
                break;
            case TIMEDOUT:
                // timeouts are ignored in Super TIU Mode
                if (!(SuperTIUMode && TIUSuperMode[TIU - 1])) PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " not on track", UIPOPUPTIMEOUT);
                break;
            case WTIUECHO:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " incorrect TIU command echo", UIPOPUPTIMEOUT);
                break;
            case WTIUERROR:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " TIU input error", UIPOPUPTIMEOUT);
                break;
            case WTIURESET:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " TIU reset", UIPOPUPTIMEOUT);
                break;
            case WTIUDISCONNECT:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " TIU disconnect", UIPOPUPTIMEOUT);
                break;
            case WTIUNOTAVAILABLE:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " PC Connection not Available", UIPOPUPTIMEOUT);
                break;
            default:
                PopupItem(String("Error - TIU ") + String(TIU) + " : " + ((Consist >= 101) ? "Lashup " : "Engine ") + String(Consist >= 101 ? Consist - 100 : Consist) + " unknown error", UIPOPUPTIMEOUT);
                break;
            }
        }

	// Copy local response to global response
	if (SSResponseCount) *SSResponseCount = FinalAnswer.Length();
	if (SSResponseBuffer) strncpy(SSResponseBuffer, FinalAnswer.c_str(), CMDMAX);
    // Create a good response code
    LocalRespCode = 0x00 | (TIU - 1);

    // *ptrRespCode is set from LocalRespCode just before the function exits
	if (ptrRespCode) *ptrRespCode = LocalRespCode;

    return bFlag;		// with flag indicating success/failure
    }

//----------------------------
// TIU Radio COM Handling
//----------------------------
//
// check pointers
if (Record_Flag == RECORD_OK) {
    assert(!SSResponseCount);	// recorded command cannot have a response
    assert(!SSResponseBuffer);
	}

Cmd Cmd_S1 = {"", 0, false};    // ASCII Command
Cmd Cmd_S2 = {"", 0, false};    // Encoded Command

// if the Consist is a LashUp, use #101 (DCS #102 (0x66) always)
if (Consist >= LASHUPMIN) Consist = LASHUPMIN;
//
Cmd_S1.idx = 0;  // Create the command in (unsigned char *)Cmd_S1
// Create command string for record function and do a Debug Print of it
// Some commands could be long, longest I know of is the "J" all engine mode command
String OSS = "";
// Consist
const unsigned char I_Consist = Consist + 1;  // In spinner, engine is 1 to 99, // in command it's 2 to 100
                                      // Engine number in command of 0 (DCS #1) is for factory reset engine
                                      // #100 (DCS #101) is used for "all engine operation"
                                      // Consist number for LashUps is 101 to 120 although #101 (DCS 102 (0x66))
                                      // is always sent to the TIU
Cmd_S1.Str[Cmd_S1.idx++] = I_Consist;
//
// insert Command_String (contains Command and Data)
// NOTE: Command_String is always ASCII so strlen() is OK.
const int StrLCS = strlen((char *)Command_String);
if (StrLCS > 0)	// If no string is supplied, skip this
	for (int i = 0; i < StrLCS ; i++) {
    	Cmd_S1.Str[Cmd_S1.idx++] = Command_String[i];
		}

	// DCS 5.00 : LashUpEngineList NOTE: LashUpEngineList will never
	// contain binary zero (because DCS engine 0 does not exist)
	// but could be any other value from 1 to 255 in DCS ver 5.00 or earlier.

    // DCS 6.00 : In ver 6.00, the engine numbers are sent as hex values but as ASCII characters.
    // LashUpEngineList starts with a ',' 0x2C, followed by the engine numbers.

    // When sent to the TIU the engine numbers are sent as ASCII Characters, for
    // example Engine 14 would show as DCS Engine 15 or 0x0F, which would be sent
    // as two ASCII characters '0' and 'F' or 0x30 0x46.
    // If it were reverse running, it would show with the high order bit
    // on or 0x8F and be sent as two ASCII characters '8' and 'F' or 0x38 0x46


if (ptrLashUpEngineList != NULL && (Consist >= LASHUPMIN && Consist <= LASHUPMAX)) // ignore ptrLashUpEngineList if not a Lashup
  if (ptrLashUpEngineList[0] != '\0') {
	assert(Consist >= LASHUPMIN && Consist <= LASHUPMAX);	// must be a LashUp
    // process lashup engine list
    const int StrLLU = strlen((char*) ptrLashUpEngineList);	// never contains 0x00 except the \0 at the end of the string
    // Find the end of the LashUpEngineList as denoted by 0xFF
    int StrLength;
    for(StrLength = 0 ; StrLength < LASHUP_ENGINE_LIST_LENGTH ; StrLength++) {
    	byte zz = ptrLashUpEngineList[StrLength];
        if (zz == 0xFF) break;
    	}
    StrLength++;
    assert((StrLLU == StrLength) && (StrLength != LASHUP_ENGINE_LIST_LENGTH));	// length from strlen() must be the same as determined programmatically
    /*
    As of V0.3.24, I require that you upgrade all of your Remotes
	and TIUs to DCS v6.xx. MTH made some dramatic changes to the command
	format with v6.xx and TIU with that version will not accept all commands
	from a Remote running the older DCS version.
	*/

    // LashUpEngineList is a null terminated char string of engine numbers already in ASCII
    for (int i = 0; i < StrLLU ; i++) {
        Cmd_S1.Str[Cmd_S1.idx++] =  ptrLashUpEngineList[i]; 	// Build Command to TIU
        }
	}

//
// Sequence Number
Cmd_S1.Str[Cmd_S1.idx++] = Sequence;	// Sequence is an unsigned char

// TIU/remote
unsigned char I_TIU = TIU - 1;	// in spinner it's 1 to 5, in commands it's 0 to 4

// Super TIU Mode 0x08 is added only for Engine commands not for TIU/AIU commands or a few other commands
if (SuperTIUMode && TIUSuperMode[TIU - 1]) {
    byte FirstChar = Command_String[0];
    if (TIUCommands[FirstChar] == false) {     // is it an Engine Command?
        // if we are in Radio mode and TIU is in Super Mode, turn on the 0x08 bit in the TIU number
		I_TIU |= 0x08;	// TIUSuperMode is a bool- in commands it's 0x08 or'ed into the TIU number
	    } else {
        // For TIU Commands, we do NOT 'OR' in this bit
        }
    }
   
const unsigned char I_Remote = Remote;   // in spinner and in commands it is 0 to 15  (0x00 to 0x0F)
const unsigned char I_TIU_Remote = ((I_TIU << 4) | I_Remote);
Cmd_S1.Str[Cmd_S1.idx++] = I_TIU_Remote;
// NULL terminate
Cmd_S1.Str[Cmd_S1.idx]  = NULL;

if (DEBUGGINGX(2) || MainForm->bRecording) {	// bRecording is a bool
	// create printable command string for debugging and for recording
    // Note: uses actual Engine Number, TIU Number and Remote Number
    // In Super TIU Mode, prints "S" instead of the TIU Number
	OSS = OSS + String(Consist) + " ";		// Consist Number
	//
	// insert Command_String (contains Command and Data)
	// NOTE: Command_String is always ASCII
	if (StrLCS > 0)	// If no string is supplied, skip this
		for (int i = 0; i < StrLCS ; i++) {
	        OSS = OSS + String((char)Command_String[i]); 	// ASCII Command String
			}
	OSS = OSS + " ";

	if (ptrLashUpEngineList != NULL) if (ptrLashUpEngineList[0] != '\0') {
	    OSS += "(";	// start with open parenthesis
	    // process lashup engine list
	    const int StrLLU = strlen((char*) ptrLashUpEngineList);
	    // Lashup is a null terminated char string of two digit ASCII engine numbers in hex with H.O. bit for reverse running
	    for (int i = 0; i < StrLLU ; ) {
            if (ptrLashUpEngineList[i] == ',') {
                i++;
                continue;	// skip comma
                }
            if ((ptrLashUpEngineList[i] == (unsigned char) 0xFF)) break; 	// end with 0xFF
            // Dislplay engine number in decimal, negative for reverse running
            int t1;
            sscanf(&ptrLashUpEngineList[i], "%2X", &t1);
            if (t1 & 0x00000080) {
                t1 &= 0x7f;
                t1 -= 1;
                t1 = -t1;
                } else {
                t1 -= 1;
                }
            OSS += String(t1) + ",";
            i += 2;
            }
	    // remove last comma
	    OSS.SetLength(OSS.Length()-1);
	    OSS += ") ";	// add close parenthesis
		}
    if (I_TIU & 0x08) {	        // Check for Super TIU Mode bit on in TIU number
        OSS = OSS + "S ";       // display Super TIU Mode not the TIU number
        } else {
    	OSS = OSS + String(TIU) + " ";	// TIU Number
        }
	OSS = OSS + String(Remote) + " ";	// Remote Number
	}

//  For this routine, we  put the information into the Display Boxes only for debugging >= 4
if (DEBUGGINGX(4) && (StrLCS > 0)) {
    SetTextValue(MainForm->H_EngNo, IntToHex(I_Consist, 2));
    // command letter & first 4 data chars
    SetTextValue(MainForm->H_Command, IntToHex(Command_String[0], 2));
    if (StrLCS > 1) TextValue = IntToHex(Command_String[1], 2); else TextValue = "-";
    SetTextValue(MainForm->H_Data1, TextValue);
    if (StrLCS > 2) TextValue = IntToHex(Command_String[2], 2); else TextValue = "-";
    SetTextValue(MainForm->H_Data2, TextValue);
    if (StrLCS > 3) TextValue = IntToHex(Command_String[3], 2); else TextValue = "-";
    SetTextValue(MainForm->H_Data3, TextValue);
    if (StrLCS > 4) TextValue = IntToHex(Command_String[4], 2); else TextValue = "-";
    SetTextValue(MainForm->H_Data4, TextValue);
    SetTextValue(MainForm->H_Seq_Count, IntToHex(Sequence, 2));
    SetTextValue(MainForm->H_TIU_Remote, IntToHex(I_TIU_Remote, 2));
	}

if (DEBUGGINGX(2)) {
	PrintLine = "Sending String: (#" + String(Sequence) + ") "+ OSS;
    PrintItem(PrintLine);
    }

//
// Generate the CRC from Cmd_S1 and insert it into the correct spot in Cmd_S1
(void) CRCGen(&Cmd_S1, 0, Cmd_S1.idx - 1, true);	// from Cmd_S1 - generate CRC's - quietly
Cmd_S1.idx += 2;	// add two CRC bytes to the length for the CRC

// NOTE: Cmd_S1 may contain binary zero so don't take StrLen() of it

// encode command from Cmd_String into Encoded_CommandA
if (MainForm->bRecording && (Record_Flag == RECORD_OK)) {	// bRecording is a bool
    // TextValue is used in the "if" statement a few lines down and must be preserved until then
    TextValue = sEncodeAString(Cmd_S1);
    }

byteEncodeAString(&Cmd_S1, &Cmd_S2);	// result of encode of Cmd_S1 is put into Cmd_S2

// recording : time(NULL) = The number of seconds since January 1, 1970
if (MainForm->bRecording && (Record_Flag == RECORD_OK)) {	// bRecording is a bool
	if (MainForm->EncodedRecordFormat) {
		// Use Plain Text command format as a comment because it is easier to read
    	// 1439353496 5555595566A965696565666A656595A55A5A55A59A664D // 7 v4070 1 15
	    // 1439353517 5555669966A66565695956595955AAAA99A65A5A699556AA4D // 101 s0 (5,7) 1 15
        SetRecordingTextValue(String(time(NULL)) + " " + TextValue + " // " + OSS);
	    }
	if (!MainForm->EncodedRecordFormat) {		// DEFAULT format - Plain Text w/encoded comment
		// Use Encoded format as a comment because we can  - Plain Text is the default format
	    // 1439352676 7 v4070 1 15  // 5555595566A965696565666A656595A65A5A955A55594D
        // 1439352704 101 s0 (5,7) 1 15  // 5555669966A66565695956595955AAAA9AA55A5AA96599AA4D
        // 1717163655 101 u4 (-16,2) 2 15  // 5555669965AA656969596966656665656666AAAA65595A6A9655AA6A4D

        // 1717163462 100 d0 2 15  // 5555659A659965655A565A6A55669A694D
        // 1717163462 100 v0100 2 15  // 5555659A66A9656565666565656559595A6A955A66A54D
        // 1717163463 100 u4 2 15  // 5555659A65AA656955665A6A66696A994D
        // 1717163463 100 d0 2 15  // 5555659A6599656556655A6A96A6A5594D
        // 1717163463 100 s0 2 15  // 5555659A66A6656556665A6AA5A665954D

        SetRecordingTextValue(String(time(NULL)) + " " + OSS + " // " + TextValue);
        }
    }

// TIU == 0 means don't send or receive anything. This is not the normal case and
// many parts of RTC will not function with a TIU number of 0.

if (TIU != 0) {
	// if running in Speed_mode, clear that mode temporarily for commands which require a response
	bool save_Speed_Mode = Speed_Mode;
	//
	if (Record_Flag == NO_RECORD) { 	// commands which are set to NO_RECORD cannot run in Speed_Mode
		Speed_Mode = false;	// turn off Speed_Mode
	    }
	// Send the encoded command
	// In All Engine Operation or if this is a LashUp, give the TIU three times as much time
	// Set bFlag with success(true)/failure(false), LocalRespCode with error code
    // SSresponseCount and SSResponseBuffer with response to Cmd_S2
    //
	bFlag = TIUSendWaitforResponseRetry(Consist, Cmd_S2.Str, Expected, ((Consist >= ALLENGINE) ? (WaitTime * 3) : WaitTime),
    	Remote, TIU, &LocalRespCode, SSResponseCount, SSResponseBuffer);

    if (DEBUGGINGX(6)) {
	    PrintLine = "Sent String: (#" + String(Sequence) + ") "+ OSS + " Response Code = " + IntToHex(LocalRespCode,2);
        // respCode == 0x00 is actually an error as the L.O. nibble must be the TIU number
    	if ((Expected == MINRESPONSE && (LocalRespCode & 0xF0) == 0x00) ||  // NAK
            (Expected != MINRESPONSE && (LocalRespCode & 0xF0) == 0x10) ||  // NAK
            LocalRespCode >= UNDETERMINED)
        	PrintItem(PrintLine, clRed);    // NAK
            else
            PrintItem(PrintLine, clGreen);  // ACK
    	}

    //
	// Never retry Radio Modes outside of TIUSendWaitforResponseRetry()
	Speed_Mode = save_Speed_Mode;	// restore Speed_Mode
    } else {
    // Debug use only
    // for TIU == 0 always return success, many parts of RTC will not work correctly with TIU == 0
    LocalRespCode = 0x00;   // not really a valid success response code but this is debug use only
    bFlag = true;
    }	// if (TIU != 0) 

// Bump the sequence number on each command send
Sequence++;		// Sequence is an unsigned char
// *ptrRespCode is set from LocalRespCode just before the function exits
if (ptrRespCode) *ptrRespCode = LocalRespCode;
return bFlag;		// with flag indicating if the call to TIUSendWaitforResponseRetry() succeeded
}	// TCommThread::Send_String
//---------------------------------------------------------------------------

bool __fastcall TCommThread::TIUSendWaitforResponseRetry(unsigned char Consist, byte * Command,
	int ExpectedCount, int WaitTime, unsigned char Remote, unsigned char TIU, int * ptrRespCode,
    int * SWResponseCount, byte * SWResponseBuffer)
{
//
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread
//
// This routine does not record, only Send_String() records
//
// Send Command string out TIU Port or the AIU Port and wait for a response of ExpectedCount bytes
// in WaitTime ms - NOTE: the ExpectedCount should include the 3 bytes of the "55554D" plus
// the number of bytes returned by the actual command. The TIU always responds with
// OKRESPONSE bytes when it receives the command, then more bytes up to MINRESPONSE bytes
// or more for some commands (ie 'x', '!', 'I', 'q', etc).
//
// Command is in binary and may contain 0x00.
//
// WaitTime in milliseconds and must be less than 20,000 (20 seconds).
//
// the encoded string received is decoded and placed into byteResp[]. Does not include the "55554D"
//
// This routine does NOT record. If needed, record must be performed before calling this routine.
//
// Return true on success, false on failure
//
// Examine value returned in *ptrRespCode for more details (see defines in Train_Control.h)
// ------
//
String PrintLine;
byte byteResp[CMDMAX]; 				// Array for decoded binary response
int LocalRespCode = UNDETERMINED;	// must have a response code even if the caller doesn't care so create a local spot
char FirstChar = Command[0];        // first character of the command

if (DEBUGGINGX(5)) {
	PrintLine =     "TIU: " + byteHextoString(Command) + " (Wait " + String(WaitTime) + "ms for " + String(ExpectedCount) + " bytes)";
    PrintItem(PrintLine, clBlack);
    // print decoded command
    if (DEBUGGINGX(7)) {
   		PrintLine = "                      : " + byteDecodeBinaryString(Command, strlen(Command) - 1, false);
    	PrintItem(PrintLine);
    	}
    }

//
// Retries are always done ONLY in this function TIUSendWaitforResponseRetry()
// 0 = no retries (original send packet only)
// 1 = 1 retry
// etc.
//
// loop for NumRetries set on Setup Form (0-9) : 10 is "ignore serial ports" mode
//
for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++) {	// always executes the loop once
	//
    if (DEBUGGINGX(4)) {
        SetTextValue(MainForm->Last_Response, "none\r\n->");	// VCL Reference
        SetTextValue(MainForm->TIU_Char_Count, "0");
        }
	// send the command, SendHexStr() does not record
    // Command may contain 0x00 so use strcspn() to find the end of the command
    int Len = strcspn(Command, "M") + 1;	// locate the 0x4D at the end of the command
    //---------------------------------------------------------
    RadioDataSent = false;
    AIUDataSent = false;
    memset((byte *) cTIUbuf, NULL, CMDMAX);	// clear the receive buffer
    iTIUbufCount = 0;						// clear the receive byte counter
    bool SendOK = SendHexStr(Command, Len, ExpectedCount, WaitTime, TIU);
    //
    while (!RadioDataSent && !AIUDataSent) {
    	Sleep(10);
		if (AIUSend) {
			AIUSend->ProcessMessages();
			} else {
    		Application->ProcessMessages();
            }
		if (Terminated)
            return false;
        }	// wait until the command is sent out the COM port or the WiFi port
    //---------------------------------------------------------
    //
    // no data response expected or don't wait at all for response
    //
	if (ExpectedCount == 0 || WaitTime == NO_WAIT) {            // assume that the command normally did not return data
        LocalRespCode = 0x10 | (TIU - 1);						// no error (see note above, this byte is 0x1T and could be 0x10 to 0x17)
    	if (DEBUGGINGX(8)) {
        	PrintLine = "TIU: Not waiting for a response from TIU";
            PrintItem(PrintLine, clGreen);
            }
		// Copy local response to global response
        if (SWResponseCount) *SWResponseCount = 0;
        if (SWResponseBuffer) SWResponseBuffer[0] = '\0';
        if (ptrRespCode) *ptrRespCode = LocalRespCode;
		return true;	// a Debugging mode is active - Success
        }

    // Debugging - don't wait for response:
    // 1. in Speed_Mode
    // 2. in ignore serial ports mode
    // 3. if Emergency Stop was pressed
    // 4. in TX Only Mode
    //
    // in all Debugging cases, fake a response so we might get fewer error messages.
	if (Speed_Mode || (NumRetries == 10) || bEmergencyStop || TXOnlyComm) {	// bEmergency is a bool
        // NumRetries == 10 is a debug mode which disables a lot of error checking
        iTIUbufCount = 173;
        //
    	if (DEBUGGINGX(4)) SetTextValue(MainForm->TIU_Char_Count, "173");
        // set bTIUbuf byteResp to some default
        memset((byte *) cTIUbuf, '5', CMDMAX);
		memset(byteResp, 0x55, sizeof(byteResp));
		if (!TXOnlyComm)  {
            // set TIU box color to yellow indicating test mode
            SetColor(MainForm->TIUSpin, clYellow);
            SetColor(MainForm->AIUSpin, clYellow);
            } else {
            // - only done in MainForm - otherwise set to green indicating good communications
            //SetColor(MainForm->TIUSpin, clLime);
            //SetColor(MainForm->AIUSpin, clLime);
            }
        if (DEBUGGINGX(3)) {
        	PrintLine = "TIU: Speed or debug mode, Not waiting for response from TIU";
            PrintItem(PrintLine, clGreen);
            }
        if (ExpectedCount == 11) {
            // no error (see note above, this byte is 0x1T and could be 0x10 to 0x17)
            LocalRespCode = 0x10 | TIU;
            } else {
            // no error (see note above, this byte is 0x0T and could be 0x00 to 0x07)
            LocalRespCode = 0x00 | TIU;
            }

		// Copy local response to global response
        if (SWResponseCount) *SWResponseCount = iTIUbufCount;
        if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
		if (ptrRespCode) *ptrRespCode = LocalRespCode;
    	return true;	// a Debugging mode is active - Success
        }
    //
    if (!SendOK) {	// sendData failed with busy indication (the only error possible to make it to here)
    	Sleep(100);	// wait 100 ms
       	Bump(MainForm->sendDataBusy);	// increment busy counter
		Bump(MainForm->Retries);        // increment retries counter (Radio Busy)
    	if (DEBUGGING) {
            PrintLine = "TIU: Busy Retry";
            PrintItem(PrintLine, clRed);
            }
        continue; // the "for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++)" loop
        }

    // -----------------------------------------------------------------------------------------
    //
	// wait for <WaitTime> for <ExpectedCount> bytes (eg: "55554D" + "A595696655555565")
	// if we don't get them within <WaitTime>, retry <NumRetries> times
    //
    int step;
	for (step = WaitTime/DELAYSTEP; step > 0 ; step--) {
        //
        // Here we wait for the serial port thread to send the packet and receive the response
        // Debug PrintItem("Delay " + String(DELAYSTEP) + " - " + String(step), clBlack);
    	Sleep(DELAYSTEP);	// delay for DELAYSTEP ms
        Application->ProcessMessages();
        if (Terminated)
            return false;
        //--------------------------------------------------------
        // see if we got the expected number of bytes from the TIU
        // 11 for commands that don't return data and >11 for commands that return data
		if (iTIUbufCount == ExpectedCount) {   			// Number of characters returned depends on command - given as "ExpectedCount"
            // --------------------------Scenario 1 and 2 ---------------------------------------
    		// Got correct number of bytes in response 	// got "55554D" followed by rest of response
            //
            if (bRadioCOMConnected && DEBUGGING) {
                String TextValue = "";
                // convert byte string to a character String type for display in Buffer
                for (int i = 0 ; i < iTIUbufCount ; i++) {
                    TextValue += IntToHex(cTIUbuf[i] & 0xFF,2);
                    }
                // display it in the TIU buffer (in Red)
                ShowinTIUBuffer(TextValue, clRed);
                }
            //
            // use cTIUbuf loaded by SerialEventManager
            LocalRespCode = RXLookUp(cTIUbuf[9] << 8 | cTIUbuf[10]); // set from 10th and 11th bytes of response
            //
        	if (DEBUGGINGX(4)) {
	        	// VCL Reference
    			SetTextValue(MainForm->Last_Response, byteHextoString((byte *) &cTIUbuf[3]));
                }
            if (DEBUGGINGX(4)) {
			    PrintLine = "TIU: Response after " + String(((WaitTime/DELAYSTEP) - step) * DELAYSTEP) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
    	        PrintItem(PrintLine, clGreen);
            	}
            if (DEBUGGINGX(7)) {
                // SetTextValue() is a VCL Reference using messages to the main thread
                if (iTIUbufCount > 3) {
	    			SetTextValue(MainForm->Last_Response_Decoded, byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false));
                    }
                if (iTIUbufCount > 3) {
				    PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x" + IntToHex(LocalRespCode, 2) + ")";
    		        PrintItem(PrintLine);
				    PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
            		PrintItem(PrintLine);
                    }
	            }
            //
            // -----------------------------------------------------------------
            //
			// go through each 4 characters in bTIUbuf and build byteResp in binary
            //
            // response is encoded binary
			// decode it. NOTE: byteResp may contain 0x00 so you can't do a strlen() call on it
            memset(byteResp, NULL, sizeof(byteResp));
 		 	// convert 4 encoded ASCII characters into a binary value for the response starting at the 3rd byte
            int iLen = (iTIUbufCount - 3) / 2;
			for (int i = 0 ; i < iLen; i++) {
				// Note: "55554D" part of response is not converted
		    	byteResp[i] = RXLookUp(cTIUbuf[(i * 2) + 3] << 8 | cTIUbuf[(i * 2) + 4]);
				}
            // -----------------------------------------------------------------
            //
            // check the remote number in the response and make sure the response packet is for us.
            //
            if ((byteResp[2] & 0x0F) != Remote) {
                // packet was not for us, so just ignore it
				if (DEBUGGINGX(4)) {
				    SetTextValue(MainForm->TIU_Char_Count, "0");
                	PrintLine = "TIU: Foreign Packet - for Remote " + String(byteResp[2] & 0x0F);
                    PrintItem(PrintLine, clRed);
                    }
                //
                //clear packet area
                LocalRespCode = UNDETERMINED;	// undetermined response
    			memset((byte *) cTIUbuf, NULL, CMDMAX);
                iTIUbufCount = 0;
                // Response packet was for some other Remote
                continue; // "for (step = WaitTime/DELAYSTEP; step > 0 ; step--)" loop and wait for another response
            	}
            // -----------------------------------------------------------------
			/*
			 * Check CRC
			 *
			 * A9A995955666555555595555555556555955
			 *   FC  C0  13  00  04  00  00  02  08
			 * reveng -c -m CRC-16/MCRF4XX 13000400000208c0fc
			 *			returns 0000
			 */
			// evaulate the received CRC - it should be 0x0000 (evaluates case where iTIUbufCount == ExpectedCount)
            unsigned int crc_result = CRCEvaluation((Cmd *) &byteResp[0], 0, iLen - 1, true);	// from bTIUBuf - Check CRC as received from TIU - Quietly
			if (crc_result != 0x0000) {
				// check the CRC and increment the error count
               	Bump(MainForm->NAKCRC);		// increment NAKCRC (NAK4) counter
				Bump(MainForm->Retries);    // increment retries counter (Radio CRC)
               	if (DEBUGGING) {
    			    PrintLine = "TIU: (0x84) Command Retrying - CRC Error";	// Note: post CRC popup message only if CRC error persists
        	        PrintItem(PrintLine, clRed);
                    // Not Thread Safe : (void) new TMessageDisplayThread(false, MyCaller, ParentHandle, String("Error - TIU ") + String(TIU), " CRC Error", "", UIPOPUPTIMEOUT);
                    if (NumRetries > 0) PopupItem(String("Error - TIU ") + String(TIU) + " : CRC Error", UIPOPUPTIMEOUT);
                    }
                LocalRespCode = CRCERROR;	// CRC Error
                break; // out of "for (step = WaitTime/DELAYSTEP; step > 0 ; step--)" loop and retry
				}
            if ((ExpectedCount == MINRESPONSE) && (iTIUbufCount == MINRESPONSE)) {
                // --------------------------Scenario 1 ---------------------------------------
	            //
	            // If we are waiting for 11 bytes, and we got 11 bytes, check the response code
                //
    	        // 0x1T is a good response and 0x0T is a bad response (for ExpectedCount == 11)
                // "T" is the TIU number 0-4
				//
                //  66A569A55A5A5566
                //  72  78  0F  11
                //          ^           TIU nybble is 0
                //           ^			Remote nybble here is 15
                //              ^^		Good Response Status byte is 0x1T indicating ACK where T is the TIU Number 0-4
				//
         	    if ((LocalRespCode & 0xF0) == 0x00) {	// NAK?  Did we get a 0x0T for the status byte?
            		// yes, something went wrong - but I don't know what. Its a NAK
                    // NOTE: 0x0T is a typical good response for a command that returns data - x, !, I0, Ix, Dq, etc.
                    //	   For a command which does not return data (the case here), it's a NAK
                    //
    				// increment NAK0T counter
                    // Translate to an error code
                    LocalRespCode = NAKRESPONSE;
                   	Bump(MainForm->NAK0T);	// Count NAK0T errors and display
					Bump(MainForm->EngNotFound);
                    if (DEBUGGINGX(4)) {
    	               	// I believe that a response code of 0x00 for an 11 expected count means engine was not found
			        	PrintLine = "TIU: (0x0T) NAK after " + String(((WaitTime/DELAYSTEP) - step) * DELAYSTEP) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
			        	PrintItem(PrintLine, clRed);
                    	}
            		// Message and then retry - no "not on track" message until the retries fail
					//if (NumRetries >= 1) (void) new TMessageDisplayThread(false, MyCaller, ParentHandle, "Warning", "Engine " + String(Consist) + " not on Track (NAK==0x00)");

        			if (DEBUGGINGX(7)) {
            			// SetTextValue() is a VCL Reference using messages to the main thread
   	        			if (iTIUbufCount > 3) {
						    SetTextValue(MainForm->Last_Response_Decoded, byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false));
							}
				        if (iTIUbufCount > 3) {
					    	PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x" + IntToHex(LocalRespCode, 2) + ")";
 			       			PrintItem(PrintLine);
				    		PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
				        	PrintItem(PrintLine);
 			               }
                       }
			        // -----------------------------------------------------------------
			        // Packet analysis complete
			        // - only done in MainForm - set TIU box color to green indicating good comm with the TIU (but this is a NAK)
        		    //SetColor(MainForm->TIUSpin, clLime);
                    //SetColor(MainForm->AIUSpin, clLime);
                    //
                    if (SWResponseCount) *SWResponseCount = iTIUbufCount;
                    if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
#define CASE2
                    #ifdef CASE1
                    // TODO : Failed with a 0x0T NAK - just retry on a 0x0T NAK
                    break; // out of "for (step = WaitTime/DELAYSTEP; step > 0 ; step--)"  loop and retry
                    #endif
                    #ifdef CASE2
					// TODO : consider setting response and exiting with false - that is, don't retry on a 0x0T NAK
		            if (ptrRespCode) *ptrRespCode = LocalRespCode;
					return false;    // Failed with a 0x0T NAK
                    #endif
                    #ifdef CASE3
					// TODO : consider setting response and exiting with true - that is, don't retry on a 0x0T NAK and pretend it worked
		            if (ptrRespCode) *ptrRespCode = 0x00 | (TIU - 1);
					return true;    // pretend command worked with a 0x0T NAK response
                    #endif
                    // End of 0x0T NAK processing for 11 byte responses.
   	                } else {
                    //
                    // status byte is 0x1T which is an ACK (good) response for 11 byte responses.
                    // 
                    }
                }   // if ((ExpectedCount == MINRESPONSE) && (iTIUbufCount == MINRESPONSE))
            // -----------------------------------------------------------------
            //
            // Packet analysis complete - Got Expected Count of any number of bytes, good response code 0x1T and good CRC
            //
            // - Only done in MainForm - Set TIU box color to green indicating good comm with the TIU
            //SetColor(MainForm->TIUSpin, clLime);
            //SetColor(MainForm->AIUSpin, clLime);
            // This is the normal (good response) exit
			// Copy local response to global response
    	    if (SWResponseCount) *SWResponseCount = iTIUbufCount;
        	if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
            if (DEBUGGINGX(2)) {
    			if ((ExpectedCount == MINRESPONSE && (LocalRespCode & 0xF0) == 0x00) ||     // NAK
                    (ExpectedCount != MINRESPONSE && (LocalRespCode & 0xF0) == 0x10) ||     // NAK
                    LocalRespCode >= UNDETERMINED)
                    // TODO : Check UNDETERMINED Status
                    //
        			PrintItem("TIU: ACK   Response Code = 0x" + IntToHex(LocalRespCode,2), clRed);  // NAK
            		else
                    //
            		PrintItem("TIU: ACK   Response Code = 0x" + IntToHex(LocalRespCode,2), clGreen);// ACK
                }

            if (ptrRespCode) *ptrRespCode = LocalRespCode;
            // This is the primary success exit from this function -- it may be ACK or NAK
			return true;    // Success - Got ExpectedCount bytes and the CRC was good
            //
        	}  // if (TIUbufCount == ExpectedCount)
        //--------------------------------------------------------
        // "break" statements come out here
        // Go back and keep on waiting for complete response (until WaitTime) since
        // we did not get the ExpectedCount number of bytes yet
		}	// for (int step = WaitTime/DELAYSTEP; step > 0 ; step--)
    //
    // ----------------------- Response Timed Out ------------------------------------------
    //
	// At this point, we have waited for one "WaitTime" in steps of WaitTime/DELAYSTEP
	//
	// If we expected more than 11 characters but we got exactly 11 (MINRESPONSE) characters
	// that probably means we got a NAK for a "non Engine" command - x, !, I0, Ix, etc.
	//
    // A "0x00" is a error response, 0x1T is some kind of good response
    //
    // DCS v5.0 note: The TIU now sends 0x11 as the response byte. Seems to indicate success.
    // DCS v5.0 note: The TIU sends the TIU number in the response only for commands that return a value.
    //		Simple ACK or NAK responses don't include the TIU number, they just return the remote number, TIU nybble set to zero
	//
	//  A995699555565566
	//    E8  68  01  1T
    //            ^             TIU nybble is always == 0
    //             ^			Remote nybble here is 1
    //                ^^		Status byte is 0x1T indicating ACK
	//  66A665A55A5A5555
	//    73  70  0F  0T
    //            ^             TIU nybble is always == 0
    //             ^			Remote nybble here is 15
    //                ^^		Status byte is 0x0T indicating NAK
	//
    // Display the packet
	//
    if (bRadioCOMConnected && DEBUGGING) {
        String TextValue = "";
        // convert byte string to a character String type for display in Buffer
        for (int i = 0 ; i < iTIUbufCount ; i++) {
            TextValue += IntToHex(cTIUbuf[i] & 0xFF,2);
            }
        // display it in the TIU buffer (in Red)
        ShowinTIUBuffer(TextValue, clRed);
        }
	//
	if ((ExpectedCount > MINRESPONSE) && (iTIUbufCount == MINRESPONSE)) {
        // --------------------------Scenario 3 ---------------------------------------
	    //
   		// Got a short response.
        // I think that when this occurs, the response is ALWAYS a NAK
        //
        if (DEBUGGINGX(4) && (iTIUbufCount > 3)) {		// VCL Response
		    SetTextValue(MainForm->Last_Response, byteHextoString((byte *) &cTIUbuf[3]));
            }
        //
        // -----------------------------------------------------------------
		// go through each 4 characters in bTIUbuf and build byteResp in binary
        //
	 	// convert 4 encoded ASCII characters into a binary value for the entire response
        memset(byteResp, NULL, sizeof(byteResp));
		// convert 4 encoded ASCII characters into a binary value for the response starting at the 3rd byte
        // NOTE: byteResp may contain 0x00 so you can't do a strlen() call on it
        int iLen = (iTIUbufCount - 3) / 2;
		for (int i = 0 ; i < iLen; i++) {
			// Note: "5554D" part of response is not converted
	    	byteResp[i] = RXLookUp(cTIUbuf[(i * 2) + 3] << 8 | cTIUbuf[(i * 2) + 4]);
			}

        // -----------------------------------------------------------------
		/*
		 * Check CRC
		 *
		 * A9A995955666555555595555555556555955
		 *   FC  C0  13  00  04  00  00  02  08
		 * reveng -c -m CRC-16/MCRF4XX 13000400000208c0fc
		 *			returns 0000
		 */
		// evaulate the received CRC - it should be 0x0000 (evaluates when a timeout occured)
        // This determines if we have, at lease, received a complete response from the TIU
		unsigned int crc_result =  CRCEvaluation((Cmd *)&byteResp[0], 0, iLen - 1, true);	// from bTIUbuf - Check CRC as received from TIU - Quietly
		if (crc_result != 0x0000) {
			// check the CRC and increment the error count
			Bump(MainForm->NAKCRC);	// increment NAKCRC counter
			Bump(MainForm->Retries);// increment retries counter  (Radio CRC)
			if (DEBUGGING) {
			    PrintLine = "TIU: (0x84) Command Retrying - CRC Error";	// Note: post CRC popup message only if CRC error persists
    	        PrintItem(PrintLine, clRed);
                // Not Thread Safe : new TMessageDisplayThread(false, MyCaller, ParentHandle, String("Error - TIU ") + String(TIU), " CRC Error", "", UIPOPUPTIMEOUT);
                if (NumRetries > 0) PopupItem(String("Error - TIU ") + String(TIU) + " : CRC Error", UIPOPUPTIMEOUT);
         		}
            LocalRespCode = CRCERROR;	// CRC Error
            continue; // the "for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++)" loop
		 	}
        // -----------------------------------------------------------------
        //
        // check the remote number in the response and make sure the response packet is for us.
        //
        if ((byteResp[2] & 0x0F) != Remote) {
            // packet was not for us, so just ignore it
			if (DEBUGGINGX(4)) {
	            SetTextValue(MainForm->TIU_Char_Count, "0");
            	PrintLine = "TIU: Foreign Packet - Remote " + String(Remote);
                PrintItem(PrintLine, clBlack);
                }
			//
            // clear packet area
            LocalRespCode = UNDETERMINED;	// undetermined response
   			memset((byte *) cTIUbuf, NULL, CMDMAX);
            RetryCount--;	// this packet does not count as a retry
            iTIUbufCount = 0;
            continue; // the "for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++)" loop
           	}
		// -----------------------------------------------------------------
        //
        // use cTIUbuf loaded from ISR
        LocalRespCode = RXLookUp(cTIUbuf[9] << 8 | cTIUbuf[10]); // set from 10th and 11th bytes of response
        //
        if (DEBUGGING) {
        	PrintLine = "TIU: Short NAK after " + String(((WaitTime/DELAYSTEP) - step) * DELAYSTEP) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
        	PrintItem(PrintLine, clRed);
        	}
        if (DEBUGGINGX(7)) {
            // SetTextValue() is a VCL Reference using messages to the main thread
   	        if (iTIUbufCount > 3) {
			    SetTextValue(MainForm->Last_Response_Decoded, byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false));
                }
	        if (iTIUbufCount > 3) {
		    	PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x" + IntToHex(LocalRespCode, 2) + ")";
        		PrintItem(PrintLine);
	    		PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
	        	PrintItem(PrintLine);
                }
            }
        // -----------------------------------------------------------------
		//
		// A Short NAK will never recover no matter how many retries you do until the sequence number is incremented
        //
#define CASE20
		#ifdef CASE20
        Bump(MainForm->NAK0T);	// increment NAK0T counter
        // don't increment EngNotFound or Retries counters
        return false;
		#endif
		#ifdef CASE21
        //
        if ((ExpectedCount > MINRESPONSE) && (iTIUbufCount == MINRESPONSE)) {
			// did we get a 0x0T for the status byte?
        	// this is case where a NAK was returned for a command which expected more than MINRESPONSE (11 bytes)
        	// but actually got MINRESPONSE (11 bytes)
        	// yes, something went wrong - but I don't know what
   	       	Bump(MainForm->NAK0T);	// increment NAK0T counter
       		// I believe that a response code of 0x00 for an 11 expected count means engine was not found
			Bump(MainForm->EngNotFound);
			Bump(MainForm->Retries);	// increment retries counter (Radio NAK)
       		// Show Message and then retry
			if (DEBUGGING && NumRetries > 0) {
            	PrintLine = "TIU: (0x00) Engine " + String(Consist) + " not on track (NAK==0x00 expected long response)";
                PrintItem(PrintLine, clRed);
                }
            continue; // the "for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++)" loop
			}
		#endif
        // -----------------------------------------------------------------
        //
        #ifdef NOTNEEDED
        not used
        // Packet analysis complete - ACK response to what was probably a duplicate packet number
        // In this case, we don't want to retry because it will just fail again.
        //
        // - Only done in MainForm - set TIU box color to green indicating good comm with the TIU
        //SetColor(MainForm->TIUSpin, clLime);
        //SetColor(MainForm->AIUSpin, clLime);
		// Copy local response to global response
        if (SWResponseCount) *SWResponseCount = iTIUbufCount;
        if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
		if (DEBUGGING) {
        	PrintLine = "TIU: (0x00) NAK (no retry)";
            PrintItem(PrintLine, clRed);
            }
        if (ptrRespCode) *ptrRespCode = LocalRespCode;
		return false;    // Got MINRESPONSE and the CRC was good - but expected more bytes, comm to TIU is good
        #endif
       	}	// 	if (ExpectedCount > MINRESPONSE && TIUbufCount == MINRESPONSE)
    // --------------------------Scenario 4 ---------------------------------------
	//
    // Finished processing packet with expected Byte Count or with shorter (MINRESPONSE) Byte Count
    //
    // Now its an unknown problem, display error and retry
    //
    // increment retries counter
	Bump(MainForm->Retries); // increment retries counter (Radio catch all)

    if (DEBUGGINGX(4) && iTIUbufCount > 3) {		// get whatever response bytes are available above the "55554D"
	    // VCL Reference
	    SetTextValue(MainForm->Last_Response, byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false));
		}
	//if (NumRetries > 0) new TMessageDisplayThread(false, MyCaller, ParentHandle, "Error", "Engine " + String(Consist) + " Timeout", "", UIPOPUPTIMEOUT);
    // TODO : debugging the failure of the TIU to response in Super TIU Mode using the Radio
    //PrintItem("Raw TIU Response : " + String(iTIUbufCount) + " bytes : " + String((char *)cTIUbuf), clRed);
    //PrintItem("Decoded TIU Response : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false), clRed);

    if (DEBUGGING && NumRetries > 0) {
    	PrintLine = "TIU: Retrying after " + String(((WaitTime/DELAYSTEP) - step) * DELAYSTEP) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
    	PrintItem(PrintLine, clRed);
    	}

    // Don't try to decode the response because it is most likely garbage
    // go back and try again until "NumRetries" retry count is reached
	}	// // the "for (int RetryCount = 0 ; RetryCount <= NumRetries ; RetryCount++)" loop

//
// If we didn't get the expected number of bytes in return (after "WaitTime" timeout), give
// an error depending on how many bytes were actually returned. If it was some other error, display
// the appropriate error message. All requested retries also failed.
//
// ALL ERRORS COME THROUGH HERE
//
// TODO : Debugging Super TIU Mode - the TIU is not responding to a command but
// it appears that the TIU has processed that command. For now, just ignore
// this lack of response. Most of the failures are on the 's' and 'v' commands.
if (SuperTIUMode && (FirstChar == 's' || FirstChar == 'v' || FirstChar == 'n')) {
    PrintItem("Ignoring missing TIU response in SuperTIUMode", clRed);
    // fake good response  once
    iTIUbufCount = ExpectedCount;
    // Copy local response to global response
    if (SWResponseCount) *SWResponseCount = iTIUbufCount;
    if (SWResponseBuffer) memcpy(SWResponseBuffer, "55554D5555555555555555", CMDMAX);
    if (ptrRespCode) *ptrRespCode = 0x10 | (TIU - 1);
    return true;	// a Debugging mode is active - Success
    }

// Every command to the TIU should respond with an OK - "55554D"
if (iTIUbufCount < OKRESPONSE) {
	// we didn't get the OK from the TIU
	// set TIU box color to red indicating bad comm with the TIU but we are still connected
	SetColor(MainForm->TIUSpin, clYellow);
    SetColor(MainForm->AIUSpin, clYellow);
	Bump(MainForm->NAKShort);	// increment NAK counter #1
    // post message "Error - TIUx not responding"
    if (NumRetries > 0) PopupItem(String("Error - TIU ") + String(TIU) + " : not responding", UIPOPUPTIMEOUT);
    //
    if (iTIUbufCount > 0) {
	    LocalRespCode = OKMISSED;	// OK response not received
	    if (DEBUGGING) {
    		PrintLine = "TIU: (0x82) Check Track - NAK - OK response not received after " + String(WaitTime) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
    		PrintItem(PrintLine, clRed);
	        if (iTIUbufCount > 3) {
		    	PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x82)";
    			PrintItem(PrintLine);
	    		PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
		    	PrintItem(PrintLine);
        	    }
	    	}
        } else {
	    LocalRespCode = TIMEDOUT;	// response not received
	    if (DEBUGGING) {
    		PrintLine = "TIU: (0x85) Check Track - No Response after " + String(WaitTime) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
    		PrintItem(PrintLine, clRed);
	        if (iTIUbufCount > 3) {
		    	PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x85)";
    			PrintItem(PrintLine);
	    		PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
		    	PrintItem(PrintLine);
        	    }
	    	}
        }
	// Copy local response to global response
    if (SWResponseCount) *SWResponseCount = iTIUbufCount;
    if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
	if (ptrRespCode) *ptrRespCode = LocalRespCode;
    // come through here if the engine is not on track
    return false;	// failure - OK missed or timed-out
	}
//
// see if we got more or fewer than the expected number of bytes back from the TIU
//
if (iTIUbufCount != ExpectedCount) {
	// More or Fewer than "ExpectedCount" bytes returned
	// set TIU box color to yellow indicating unknown comm with the TIU but we are still connected
	SetColor(MainForm->TIUSpin, clYellow);
  	SetColor(MainForm->AIUSpin, clYellow);
    Bump(MainForm->NAKLen);	// increment NAK counter #2
    // post message for 3 seconds as long as NumRetries is > 0
    // Not Thread Safe : if (NumRetries >  0 && iTIUbufCount != MINRESPONSE) new TMessageDisplayThread(false, MyCaller, ParentHandle, String("Error - TIU ") + String(TIU), String("Check Track") + String(" - Engine ") + String(Consist), "", UIPOPUPTIMEOUT);
    if (NumRetries >  0 && iTIUbufCount != MINRESPONSE) PopupItem(String("Error - TIU ") + String(TIU) + " : Check Track - Engine " + String(Consist), UIPOPUPTIMEOUT);
    //
    if (DEBUGGING) {
    	PrintLine = "TIU: (0x81/0x83) Wrong # of Bytes Received - NAK Length  after " + String(WaitTime) + " milliseconds - Expected: " + String(ExpectedCount) + " Got: " + String(iTIUbufCount);
    	PrintItem(PrintLine, clRed);
        if (iTIUbufCount > 3) {
	    	PrintLine = "     Last Response  : " + byteHextoString((byte *) &cTIUbuf[3]) + " (0x81/0x83)";
    		PrintItem(PrintLine);
    		PrintLine = "                    : " + byteDecodeBinaryString((byte *) &cTIUbuf[3], iTIUbufCount - 3, false);
	    	PrintItem(PrintLine);
            }
    	}
    if (iTIUbufCount > ExpectedCount)
	    LocalRespCode = TOMANYBYTES;	// too many bytes returned
        else
		LocalRespCode = TOOFEWBYTES;	// too few bytes received
	// Copy local response to global response
    if (SWResponseCount) *SWResponseCount = iTIUbufCount;
	if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
	if (ptrRespCode) *ptrRespCode = LocalRespCode;
    return false;	// failure - either too many bytes or too few bytes
	}
// if the code gets here, some other error has occured - LocalRespCode is set with the error
// show error popup and exit
// Not Thread Safe : if (NumRetries > 0) new TMessageDisplayThread(false, MyCaller, ParentHandle, String("Error - TIU ") + String(TIU), String(" Unknown Error") + String(" - Engine ") + String(Consist), "", UIPOPUPTIMEOUT);
if (NumRetries > 0) PopupItem(String("Error - TIU ") + String(TIU) + " : Unknown Error - Engine " + String(Consist), UIPOPUPTIMEOUT);
// Copy local response to global response
if (SWResponseCount) *SWResponseCount = iTIUbufCount;
if (SWResponseBuffer) memcpy(SWResponseBuffer, byteResp, CMDMAX);
if (ptrRespCode) *ptrRespCode = LocalRespCode;
return false;	// failure - unknown error
}	// TIUSendWaitforResponseRetry
//---------------------------------------------------------------------------

bool __fastcall TCommThread::SendHexStr(byte * Cmd_In, int Len, int ExpectedByteCount, int Timeout, char cTIULocal)  // Len is actual bytes to send
{
//
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread
//
assert(Cmd_In != NULL);
assert(Len > 0);
//
// Send to TIU Port or AIU port, don't wait for response, simple error handling
// This is the only routine that actually sends to the TIU and AIU ports
//
// Cmd_In is in binary and might contain 0x00 - use Len
//
if (!MainForm->IsRadioConnected()) return false;	// Radio must be connected either by COM or WiFi
//
String PrintLine;
String TextValue;

// No recording, if needed it must be done prior to calling this routine

if (DEBUGGING) {
	TextValue = "";
    // convert byte string to a character String type for display in Buffer
	for (int i = 0 ; i < Len ; i++) {
        TextValue += IntToHex(Cmd_In[i] & 0xFF,2);
    	}
	// display it in the TIU buffer
    ShowinTIUBuffer(TextValue);
    }

// ignore serial port mode (don't send and don't receive)
if (NumRetries == 10) {
    // NumRetries == 10 is a debug mode which disables a lot of error checking
	return true;
	}
//
// TODO : - Should I do some kind of retry if busy here?
// Return true on sucess, false on failure
// Right now, busy is handled at a higher level with a retry
//
// either bRadioCOMConnected or bRadioWiFiConnected or bWTIUWiFiConnected must be true to send a packet
//
/*
 Truth Table

	USB			WiFi      	AIU       	AIU			Send
    Radio       Radio       WiFi TX     Emulator    Using
    Connected   Connection  Connection  [TIU -1 ]
                                                 ||
    0			0			0 			0           X (can't occur)
    0           0           0 			1           X (can't occur)
    0           0           1 			0           X (can't occur)
    0           0           1 			1           X (can't occur)
    0           1           0 			0			WiFi Radio
    0           1           0 			1			WiFi Radio
    0           1           1 			0			WiFi Radio
    0           1           1 			1			AIU WiFi
    1           0           0 			0			USB
    1           0           0 			1			USB
    1           0           1 			0			USB
    1           0           1 			1			AIU WiFi
    1           1           0 			0           X (can't occur)
    1           1           0 			1           X (can't occur)
    1           1           1 			0           X (can't occur)
    1           1           1 			1           X (can't occur)

 */

if (bRadioCOMConnected && !(bAIUWiFiTXConnected /* && (AIUEmulator[cTIULocal-1].Found == true)*/)) {
	//
    // Send a packet to a Radio over the USB Serial port -------------------
    //
    if (DEBUGGINGX(9)) {
        PrintItem("SendHexStr: Sending over Radio TX USB Serial");
        }
	// Returns 0 on sucess, 1 on bad parameters, 2 on busy
	int retv = Serial_port->sendData(Cmd_In, Len);  	// ******* THIS IS THE ONLY CALL TO Serial_port.sendData() *************

	// error trap
	assert(retv != 1);	// bad parameters
	assert(retv != 2);	// port busy
    if (retv) {	// failure
        if (DEBUGGING) {
            PrintLine = "SendHexStr: Error: " + MainForm->ConnectedWiFiRadioLED->Caption + " sendData failed : " + String(retv);
            PrintItem(PrintLine, clRed);
            }
        // Error retv == 1 - Bad Parameters
        // Error retv == 2 - Port Busy
        return false;	// indicate failure
        }
    }
/*
if (bAIUWiFiTXConnected) {
	//
    // Send a packet to a AIU Emulator over WiFi -------------------
    //
    if (DEBUGGINGX(9)) {
		PrintLine = "SendHexStr: WiFi Sending";
    	PrintItem(PrintLine);
        }
    // Build up the message by prepending the Expected Byte Count, Timeout (in tenths of a second) and Checksum to the command
    // The Expected Byte Count and Timeout can both be zero. The Checksum always has the 0x80 bit set.
    // I do a 1's complement ('~') to the byte count and timeout before they are inserted in the message. Then the
    // code in the Modem has to do the same 1's complement to get the original bytes. This does mean that Expected Byte Count
    // and Timeout can never have a value of 255. A 0x80 is or'ed into the Checksum to be sure that it is never zero.
    #define PACKETSTART 3
    Cmd CommandOut;
    CommandOut.Str[0] = ~ExpectedByteCount;
    CommandOut.Str[1] = ~(Timeout/100);	// convert to tenths of a second from ms
    CommandOut.Str[2] =  0x80 || (ExpectedByteCount + Timeout);
    strncpy(&CommandOut.Str[PACKETSTART], Cmd_In, Len + 1);	// plus one to copy the NULL terminator

    String LocalStr = String((char *)CommandOut.Str);
    // if an AIU Emulator is running for this TIU, send the packet using AIUSend
    //

    if (bAIUWiFiTXConnected && (AIUEmulator[cTIULocal-1].Found == true)) {
		AIUSend->SendStr(LocalStr);     // AIU Emulator
	    if (DEBUGGINGX(7)) PrintItem("SendHexStr: Sending over AIU TX WiFi");
    	}
	}
    */
//
if (DEBUGGINGX(8)) {
	PrintLine = "SendHexStr: Sent";
    PrintItem(PrintLine);
    }

// in Speed_Mode, ignore the response completely (Speed_Mode is turned off for commands which REQUIRE a response.)
if (Speed_Mode) {
	Sleep(300);		// originally this was too speedy for the TIU so I had to add the delay
    return true;	// always indicate success
    }
if (TXOnlyComm) {
	Sleep(500);		// originally this was too speedy for the TIU so I had to add the delay
    return true;	// always indicate success
    }
//
return true; // indicate success
}	// SendHexStr(byte *)
//---------------------------------------------------------------------------
void __fastcall TCommThread::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
// not used
}
//---------------------------------------------------------------------------
// Beginning of Interface Routines
// These routines are the way that threads other than the main thread
// communicate with the VCL
//---------------------------------------------------------------------------
void __fastcall TCommThread::PrintItem(String PrintLine, TColor PrintColor) {
UI_Struct * Message1;
// This routine does the equivalent of the next line but without calling the VCL directly
//if (DEBUGGING) DB->Debug_Print(PrintLine, PrintColor);
if (!DEBUGGING) return;	// only print the PrintLine if DEBUGGING is true
						// because the output goes to the Debug_Messages buffer
// type 1 message
Message1 = new UI_Struct;
Message1->PrintColor = PrintColor;
Message1->Line = PrintLine;
//-----------------------------------
if (TimeStampMode == 0 || (TimeStampMode == 1 && PC_Tenths == 0)) {
    // https://www.drdobbs.com/windows/win32-performance-measurement-options/184416651
    // https://stackoverflow.com/questions/1695288/getting-the-current-time-in-milliseconds-from-the-system-clock-in-windows
    SYSTEMTIME st;
    GetLocalTime(&st);
    char currentTime[24];
    snprintf(currentTime, sizeof currentTime, "%02d:%02d:%02d.%03d", st.wHour, st.wMinute, st.wSecond , st.wMilliseconds);
    Message1->Line = String(currentTime) + " " + Message1->Line;
    } else {
    if (TimeStampMode == 1) {
        Message1->Line = Format(String("%9.1f"), ARRAYOFCONST((PC_Tenths/10.0))) + " " + Message1->Line;
        } else {
        if (TimeStampMode == 2) {
            float GTC = GetTickCount();
            Message1->Line = Format(String("%9.3f"), ARRAYOFCONST((GTC/1000.0))) + " " + Message1->Line;
            } else {
            Message1->Line = Format(String("%7d"), ARRAYOFCONST((MainForm->MsgNum))) + " " + Message1->Line;
            MainForm->MsgNum++;
            }
        }
    }
//-----------------------------------
//
// Post CM_UI message to MainForm for direct print to Debug_Messages RichEdit Control
(void) PostMessage(MainForm->Handle, CM_UI, M_PrintItem, (LPARAM) Message1);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::Bump(TAbNumEdit * Countr) {
UI_Struct * Message2;
// Countr->Value++;
// type 2 message
Message2 = new UI_Struct;
Message2->Countr = Countr;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_Bump, (LPARAM) Message2);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetColor(TAbNumSpin * Spinner, TColor ColorDefault) {
UI_Struct * Message3;
// Spinner->ColorDefault = ColorDefault;
// type 3 message
Message3 = new UI_Struct;
Message3->Spinner = Spinner;
Message3->ColorDefault = ColorDefault;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetColor, (LPARAM) Message3);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetTextValue(TEdit * EditBox, String TextValue) {
UI_Struct * Message4;
// EditBox->Text = TextValue;
// type 4 message
Message4 = new UI_Struct;
Message4->EditBox = EditBox;
Message4->Line = TextValue;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetTextValue, (LPARAM) Message4);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetTextValue(TEdit * EditBox, String &TextValue) {
UI_Struct * Message5;
// TextValue = EditBox->Text;
// type 5 message
Message5 = new UI_Struct;
Message5->EditBox = EditBox;
Message5->Line = "";

// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetTextValue, (LPARAM) Message5);	// wait for response

TextValue = Message5->Line;
delete Message5;	// delete the message after processing the return value
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetRecordingTextValue(String TextValue) {
UI_Struct * Message6;
// RecordingBuffer->SelStart = RecordingBuffer->GetTextLen();
// RecordingBuffer->SelText = TextValue + TEXT("\r\n");
// type 6 message
Message6 = new UI_Struct;
Message6->Line = TextValue;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetRecordingTextValue, (LPARAM) Message6);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::ShowinTIUBuffer(String TextValue, TColor Color) {
UI_Struct * Message7;
// TIUBuffer->SelAttributes->Color = Color;
// TIUBuffer->SelStart = TIUBuffer->GetTextLen();
// TIUBuffer->SelText = String(MessageX->Line) + TEXT("\r\n");
// type 7 message
Message7 = new UI_Struct;
Message7->Line = TextValue;
Message7->ColorDefault = Color;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_ShowinTIUBuffer, (LPARAM) Message7);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::PopupItem(String PrintLine, int Duration, String PrintLine2) {
// duration in milliseconds (optional - defaults to 1000)
// PrintLine2 (optional - default to "")
UI_Struct * Message8;
// type 8 message
Message8 = new UI_Struct;
Message8->Line = PrintLine;
Message8->iValue = Duration;		// Duration in milliseconds
Message8->Status = PrintLine2;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_PopupItem, (LPARAM) Message8);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::ShowinBuffer(TRichEdit * Buf, String PrintLine, TColor PrintColor, bool Scroll) {
UI_Struct * Message9;
//Buf->SelAttributes->Color = PrintColor;
//Buf->SelStart = Buf->GetTextLen();
//Buf->SelText = String(PrintLine) + TEXT("\n");
// type 9 message
Message9 = new UI_Struct;
Message9->Buffer = Buf;
Message9->Line = PrintLine;
Message9->PrintColor = PrintColor;
Message9->bValue = Scroll;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_ShowinBuffer, (LPARAM) Message9);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetSpinnerValue(TAbNumSpin * Spinner, int Value) {
UI_Struct * Message10;
// Spinner->Value = Value;
// type 10 message
Message10 = new UI_Struct;
Message10->Spinner = Spinner;
Message10->iValue = Value;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetSpinnerValue, (LPARAM) Message10);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetNumEditValue(TAbNumEdit * Countr, int Value) {
UI_Struct * Message11;
// Countr->ValueAsInt = Value;
// type 11 message
Message11 = new UI_Struct;
Message11->Countr = Countr;
Message11->iValue = Value;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetNumEditValue, (LPARAM) Message11);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::Dec(TAbNumEdit * Countr) {
UI_Struct * Message12;
// Countr->Value--;
// type 12 message
Message12 = new UI_Struct;
Message12->Countr = Countr;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_Dec, (LPARAM) Message12);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetCounterValue(TAbNumEdit * Countr, int * CntrVal) {
UI_Struct * Message13;
// CntrValue = Countr->Value;
// type 13 message
Message13 = new UI_Struct;
Message13->Countr = Countr;
Message13->Line = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetCntrValue, (LPARAM) Message13);	// wait for response

*CntrVal = Message13->Line.ToIntDef(0);
delete Message13;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetPCLED(TAbLED * LED, int iState, String Line) {
UI_Struct * Message14;
// LED->Visible = true
// LED->Checked = bState
// type 14 message
Message14 = new UI_Struct;
Message14->LED = LED;
Message14->iState = iState;
Message14->Line = Line;
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetPCLED, (LPARAM) Message14);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetNumEditColor(TAbNumEdit * Countr, TColor Color) {
UI_Struct * Message15;
// Countr->ValueAsInt = Value;
// type 15 message
Message15 = new UI_Struct;
Message15->Countr = Countr;
Message15->ColorDefault = Color;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetNumEditColor, (LPARAM) Message15);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetComboBoxText(TComboBox * ComboBox, AnsiString &TextValue, int Index) {
UI_Struct * Message16;
// TextValue = ComboBox->Items[Index] ->Text;
// type 16 message
Message16 = new UI_Struct;
Message16->ComboBox = ComboBox;
Message16->iValue = Index;
Message16->Line = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetComboBoxText, (LPARAM) Message16);	// wait for response

TextValue = Message16->Line;
delete Message16;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::ClearCounter(TAbNumEdit * Countr) {
UI_Struct * Message17;
// Countr->Value = 0;
// type 17 message
Message17 = new UI_Struct;
Message17->Countr = Countr;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_ClearCntr, (LPARAM) Message17);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetCounter(TAbNumEdit * Countr, float value) {
UI_Struct * Message18;
// Countr->Value = value;
// type 18 message
Message18 = new UI_Struct;
Message18->Countr = Countr;
Message18->fValue = value;
//
// Send CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetCntr, (LPARAM) Message18);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::OpenAIUWindow(int TIU, int AIU) {
UI_Struct * Message19;
// type 19 message
Message19 = new UI_Struct;
Message19->iTIU = TIU;
Message19->iAIU = AIU;
//
// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the window construction before proceeding
(void) SendMessage(MainForm->Handle, CM_UI, M_OpenAIUWindow, (LPARAM) Message19);	// wait for response
// MessageX deleted in Train_Control.cpp
}

//---------------------------------------------------------------------------
void __fastcall TCommThread::GetAIUSwitchState(int iChan, int iAIU, int iTIU, bool &bState, int MyPCNo) {
UI_Struct * Message20;
// type 20 message
Message20 = new UI_Struct;
Message20->iTIU = iTIU;
Message20->iAIU = iAIU;
Message20->iChan = iChan;
Message20->PCNo = MyPCNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetAIUSwitchState, (LPARAM) Message20);	// wait for response

bState = Message20->bValue;
delete Message20;	// delete the message after processing it
}

//---------------------------------------------------------------------------
void __fastcall TCommThread::GetAIUAccState(int iChan, int iAIU, int iTIU, bool &bState, int MyPCNo) {
UI_Struct * Message21;
// type 21 message
Message21 = new UI_Struct;
Message21->iTIU = iTIU;
Message21->iAIU = iAIU;
Message21->iChan = iChan;
Message21->PCNo = MyPCNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetAIUAccState, (LPARAM) Message21);	// wait for response

bState = Message21->bValue;
delete Message21;	// delete the message after processing it
}

//---------------------------------------------------------------------------
void __fastcall TCommThread::GetAIUSwitchName(int iChan, int iAIU, int iTIU, AnsiString &TextValue, int MyPCNo) {
UI_Struct * Message22;
// type 22 message
Message22 = new UI_Struct;
Message22->iTIU = iTIU;
Message22->iAIU = iAIU;
Message22->iChan = iChan;
Message22->PCNo = MyPCNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetAIUSwitchName, (LPARAM) Message22);	// wait for response

TextValue = Message22->Line;
delete Message22;	// delete the message after processing it
}

//---------------------------------------------------------------------------
void __fastcall TCommThread::GetAIUAccName(int iChan, int iAIU, int iTIU, AnsiString &TextValue, int MyPCNo) {
UI_Struct * Message23;
// type 23 message
Message23 = new UI_Struct;
Message23->iTIU = iTIU;
Message23->iAIU = iAIU;
Message23->iChan = iChan;
Message23->PCNo = MyPCNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetAIUAccName, (LPARAM) Message23);	// wait for response

TextValue = Message23->Line;
delete Message23;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::MakeItSo(int iAIU, int iTIU, int MyPCNo) {
UI_Struct * Message24;
// type 24 message
Message24 = new UI_Struct;
Message24->iTIU = iTIU;
Message24->iAIU = iAIU;
Message24->PCNo = MyPCNo;

// Send CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_MakeItSo, (LPARAM) Message24);	// wait for response
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetVirtualAIUAccState(bool bState, int iChan, int iAIU, int iTIU, int MyPCNo) {
UI_Struct * Message25;
// type 25 message
Message25 = new UI_Struct;
Message25->iTIU = iTIU;
Message25->iAIU = iAIU;
Message25->iChan = iChan;
Message25->PCNo = MyPCNo;
Message25->bValue = bState;

// Send CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetVirtualAIUAccState, (LPARAM) Message25);	// wait for response
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetStringGridText(TStringGrid * StringGrid, AnsiString &TextValue, int &PSLevel, int Index) {
UI_Struct * Message26;
// TextValue = ComboBox->Items[Index] ->Text;
// type 26 message
Message26 = new UI_Struct;
Message26->StringGrid = StringGrid;
Message26->iValue = Index;
Message26->Line = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetStringGridText, (LPARAM) Message26);	// wait for response

TextValue = Message26->Line;
PSLevel = Message26->iValue;
delete Message26;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetLabelText(TLabel * Label, String TextValue) {
UI_Struct * Message27;
// Label->Caption = TextValue;
// type 27 message
Message27 = new UI_Struct;
Message27->Label = Label;
Message27->Line = TextValue;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetLabelText, (LPARAM) Message27);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::PopupMessage(String Message, String Caption, String Status, int dly) {
UI_Struct * Message28;
// Message displayed in a popup window
// type 28 message
Message28 = new UI_Struct;
Message28->Line = Message;
Message28->Caption = Caption;
Message28->Status = Status;
Message28->iValue = dly;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_PopupMessage, (LPARAM) Message28);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetFlag(int FlagNo, int &iState) {
// each thread that calls this must protect with Global_Flag_CS critical section
UI_Struct * Message29;
// bValue = FLag->Checked
// type 29 message
Message29 = new UI_Struct;
Message29->iValue = FlagNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetFlag, (LPARAM) Message29);	// wait for response
// SendMessage used in SetFlag, GetFlag, GetFlagName and SetFlagName
iState = Message29->iState;
delete Message29;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetFlag(int FlagNo, int &iState) {
// each thread that calls this must protect with Global_Flag_CS critical section
UI_Struct * Message30;
// Label->Caption = TextValue;
// type 30 message
Message30 = new UI_Struct;
Message30->iValue = FlagNo;
Message30->iState = iState;
//
// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_SetFlag, (LPARAM) Message30);
// SendMessage used in SetFlag, GetFlag, GetFlagName and SetFlagName
iState = Message30->iState;
delete Message30;    // delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetFlagName(int FlagNo, String &FlagName) {
// each thread that calls this must protect with Global_Flag_CS critical section
UI_Struct * Message31;
// FlagName = FLag->Caption
// type 31 message
Message31 = new UI_Struct;
Message31->iValue = FlagNo;
Message31->Line = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetFlagName, (LPARAM) Message31);	// wait for response
// SendMessage used in SetFlag, GetFlag, GetFlagName and SetFlagName
FlagName = Message31->Line;
delete Message31;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetFlagName(int FlagNo, String FlagName, int &iState) {
// each thread that calls this must protect with Global_Flag_CS critical section
UI_Struct * Message32;
// Label->Caption = TextValue;
// type 32 message
Message32 = new UI_Struct;
Message32->iValue = FlagNo;
Message32->Line = FlagName;
//
// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_SetFlagName, (LPARAM) Message32);
// SendMessage used in SetFlag, GetFlag, GetFlagName and SetFlagName
if (iState != NULL) iState = Message32->iState;
delete Message32;	// delete the message after processing it
}
//---------------------------------------------------------------------------
// Message 33 deleted - it was a duplicate of Message 38
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetAspect(int SignalNo, int &iAspect, int &iMode, int &iType) {
// each thread that calls this must protect with Signals_CS critical section
UI_Struct * Message34;
// bValue = FLag->Checked
// type 34 message
Message34 = new UI_Struct;
Message34->iSignalNo = SignalNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetAspect, (LPARAM) Message34);	// wait for response
// SendMessage used in SetAspect, GetAspect, GetSignalName and CreateSignal
iAspect = Message34->iAspect;		// return Aspect
iMode = Message34->iMode;	// return mode number of the signal head
iType = Message34->iType;
delete Message34;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetAspect(int SignalNo, int &iAspect) {
// each thread that calls this must protect with Signals_CS critical section
UI_Struct * Message35;
// type 35 message
Message35 = new UI_Struct;
Message35->iSignalNo = SignalNo;
Message35->iAspect = iAspect;
//
// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_SetAspect, (LPARAM) Message35);
// SendMessage used in SetAspect, GetAspect, GetSignalName and CreateSignal
iAspect = Message35->iAspect;	// return Aspect
delete Message35;    // delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetSignalName(int SignalNo, String &SignalName, int &NumberPlate, int &iType) {
// each thread that calls this must protect with Signals_CS critical section
UI_Struct * Message36;
// SignalName = FLag->Caption
// type 36 message
Message36 = new UI_Struct;
Message36->iSignalNo = SignalNo;
Message36->Line = "";
Message36->iNumberPlate = 0;
Message36->iType = 0;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetSignalName, (LPARAM) Message36);	// wait for response
// SendMessage used in SetAspect, GetAspect, GetSignalName and CreateSignal
SignalName = Message36->Line;			// return signal name
NumberPlate = Message36->iNumberPlate;	// return Number Plate value
iType = Message36->iType;				// return Signal type
delete Message36;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::CreateSignal(int iSignalNo, int iDir, int iType, int iCol, int iRow, String SignalName, int &iAspect, int iMode, int NumberPlate) {
// each thread that calls this must protect with Signals_CS critical section
UI_Struct * Message37;;
// type 37 message
Message37 = new UI_Struct;
Message37->iSignalNo = iSignalNo;
Message37->iDir = iDir;
Message37->Line = SignalName;
Message37->iAspect = iAspect;
Message37->iMode = iMode;
Message37->iCol = iCol;
Message37->iRow= iRow;
Message37->iNumberPlate = NumberPlate;
Message37->iType = iType;
//
// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_CreateSignal, (LPARAM) Message37);
// SendMessage used in SetAspectl, GetAspect, GetSignalName and CreateSignal
iAspect = Message37->iAspect;	// return Aspect
delete Message37;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::DeleteSignal(int iSignalNo) {
// Called with a signal number
// each thread that calls this must protect with Signals_CS critical section
int iDeleted;
CreateSignal(-iSignalNo, 0, 0, 0, 0, "", iDeleted, 0, 0);	// negative signal number tells CreateSignal() to delete it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::ShowLayout(void) {
UI_Struct * Message38;
// type 38 message
Message38 = new UI_Struct;
// no operands
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_ShowLayout, (LPARAM) Message38);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::DeleteAllSignals(void) {
UI_Struct * Message39;
// type 39 message
Message39 = new UI_Struct;
// no operands
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_DeleteAllSignals, (LPARAM) Message39);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetCabAspect(int iSignal, int Aspect, int MyPCNo) {
UI_Struct * Message40;
// type 40 message

Message40 = new UI_Struct;
Message40->PCNo = MyPCNo;
// head
Message40->iSignalNo = iSignal;
Message40->iAspect = Aspect;
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_SetCabAspect, (LPARAM) Message40);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetCabLabels(int iSignal, String sName, int iNumberPlate, int iType, int MyPCNo) {
UI_Struct * Message41;
// type 41 message

Message41 = new UI_Struct;
Message41->PCNo = MyPCNo;
// head
Message41->iSignalNo = iSignal;
Message41->Line = sName;
Message41->iNumberPlate = iNumberPlate;
Message41->iAspect = -1;	// -1 means don't set the Aspect (use SetCabAspect())
Message41->iType = iType;
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_SetCabLabels, (LPARAM) Message41);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::CreateCabSignals(int iSignal, int iDistant, int MyPCNo) {
UI_Struct * Message42;
// type 42 message

Message42 = new UI_Struct;
Message42->PCNo = MyPCNo;
// head
Message42->iSignalNo = iSignal; // can be zero to skip creating Home signal
Message42->iDistant = iDistant;	// can be zero to skip creating Distant signal
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_CreateCab, (LPARAM) Message42);
// Message42 deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetCabAspect(int iSignal, int &iAspect, int &iMode, int &iType, int MyPCNo) {	// and Type
UI_Struct * Message43;

// type 43 message
Message43 = new UI_Struct;
Message43->PCNo = MyPCNo;
// head
Message43->iValue = iSignal;
// Post CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetCabAspect, (LPARAM) Message43);
iAspect = Message43->iAspect;
iMode = Message43->iMode;
iType = Message43->iType;
delete Message43;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetLED(TAbLED * LED, TColor ColorOn, bool Checked, bool Flashing, String Line) {
UI_Struct * Message44;
//
// type 44 message
Message44 = new UI_Struct;
Message44->LED = LED;
Message44->iState = (int) ColorOn;
Message44->iValue = Checked;
Message44->iType = Flashing;
Message44->Line = Line;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetLED, (LPARAM) Message44);
// Message44 deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------
void __fastcall TCommThread::AddCntr(TAbNumEdit * Countr, int Val) {
UI_Struct * Message45;
// Countr->Value += Val;
// type 45 message
Message45 = new UI_Struct;
Message45->Countr = Countr;
Message45->iValue = Val;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_AddCntr, (LPARAM) Message45);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetListBoxText(TListBox * ListBox, String &TextValue, int Index) {
UI_Struct * Message46;
// TextValue = ListBox->Items->Strings[Index];
// type 46 message
Message46 = new UI_Struct;
Message46->ListBox = ListBox;
Message46->iValue = Index;
Message46->Line = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetListBoxText, (LPARAM) Message46);	// wait for response

TextValue = Message46->Line;
delete Message46;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetTIU(int EngineNo, int &TIUNo) {
UI_Struct * Message47;
// type 47 message
Message47 = new UI_Struct;
Message47->iEngineNo = EngineNo;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetTIUNo, (LPARAM) Message47);	// wait for response

TIUNo =  Message47->iTIU;
delete Message47;	// delete the message after processing it}
}
//---------------------------------------------------------------------------
void __fastcall TCommThread::SetRockerSwitch(TAbRockerSwitch * RockerSwitch, bool bValue) {
UI_Struct * Message48;
// RockerSwitch->Checked = bValue;
// type 48 message
Message48 = new UI_Struct;
Message48->RockerSwitch = RockerSwitch;
Message48->bValue = bValue;

// Send CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_RockerSwitch, (LPARAM) Message48);	// wait for response
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::SetButtonCaption(TButton * But, String Caption) {
UI_Struct * Message49;
// But->Caption = Caption;
// type 49 message
Message49 = new UI_Struct;
Message49->But = But;
Message49->Line = Caption;
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_SetButtonCaption, (LPARAM) Message49);
// MessageX deleted in Train_Control.cpp
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetSpinnerValue(TAbNumSpin * Spinner, int * SpinnerVal) {
UI_Struct * Message50;
// CntrValue = Countr->Value;
// type 50 message
Message50 = new UI_Struct;
Message50->Spinner = Spinner;
Message50->iValue = 0;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetSpinnerValue, (LPARAM) Message50);	// wait for response

*SpinnerVal = Message50->iValue;
delete Message50;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::ProcessRS(int RSNo, int iFunc) {
UI_Struct * Message51;
//
// type 51 message
Message51 = new UI_Struct;
Message51->iRSNo = RSNo;
Message51->iValue = iFunc ; // one of 5 functions
//
// Post CM_UI message to MainForm
(void) PostMessage(MainForm->Handle, CM_UI, M_RouteScene, (LPARAM) Message51);
// Message51 deleted in Train_Control.cpp
}

//---------------------------------------------------------------------------
void __fastcall TCommThread::ReadEngines(int * Count) {
UI_Struct * Message52;
// type 52 message
Message52 = new UI_Struct;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_ReadEngines, (LPARAM) Message52);	// wait for response

*Count = Message52->iValue;
delete Message52;	// delete the message after processing it
}
//---------------------------------------------------------------------------
void __fastcall TCommThread::FindEngines(int * Count) {
UI_Struct * Message53;
// type 53 message
Message53 = new UI_Struct;

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_FindEngines, (LPARAM) Message53);	// wait for response

*Count = Message53->iValue;
delete Message53;	// delete the message after processing it
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::GetStringGridLashup(TStringGrid * StringGrid, AnsiString &TextValue, AnsiString &EngList, int Index) {
UI_Struct * Message;
// TextValue = ComboBox->Items[Index] ->Text;
// type 54 message
Message = new UI_Struct;
Message->StringGrid = StringGrid;
Message->iValue = Index;
Message->Line = "";
Message->EngineList = "";

// Send CM_UI message to MainForm - Use SendMessage() so that we wait for the message processing to complete
(void) SendMessage(MainForm->Handle, CM_UI, M_GetStringGridLashup, (LPARAM) Message);	// wait for response

TextValue = Message->Line;
EngList = Message->EngineList;
delete Message;	// delete the message after processing it
}
//---------------------------------------------------------------------------
// End of Interface Routines
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

unsigned int __fastcall TCommThread::CRCGen(Cmd * Command, int StartChar, int EndChar, bool Quiet)
{
//
// This function generates CRC for packets to sent to the TIU for CRC-16/MCRF4XX
//
// NOTE: Cmd_String may contain binary zero
//
cm_t var;
p_cm_t p_cm = &var;
p_cm->cm_width = 16;
p_cm->cm_poly  = 0x1021L;
p_cm->cm_init  = 0x0000FFFFL;
p_cm->cm_refin = TRUE;
p_cm->cm_refot = TRUE;
p_cm->cm_xorot = 0L;

cm_ini(p_cm);

// feed in the data bytes in reverse order
for (int ii = EndChar ; ii >= StartChar ; ii--) {
	cm_nxt(p_cm, Command->Str[ii]);
    }
//
ulong const crc_result = cm_crc(p_cm) & 0xFFFF;

// Insert the CRC byte in the command string - LSB then MSB
Command->Str[EndChar+1] = (crc_result & 0x00FF);
Command->Str[EndChar+2] = (crc_result & 0xFF00) >> 8;
// Insert the NULL
Command->Str[EndChar+3] = NULL;	    // NULL Terminated

// then put it in Command Decode area in preparation for sending
if (DEBUGGINGX(4)) {
    SetTextValue(MainForm->H_CRC_LSB, IntToHex(Command->Str[EndChar+1], 2));
    SetTextValue(MainForm->H_CRC_MSB, IntToHex(Command->Str[EndChar+2], 2));
    }


return crc_result;
}
//---------------------------------------------------------------------------

unsigned int __fastcall TCommThread::CRCEvaluation(Cmd * Cmd_String, int StartChar, int EndChar, bool Quiet)
{
//
// This function checks packets received from the TIU for CRC for CRC-16/MCRF4XX
//
// NOTE: Cmd_String may contain binary zero
//
//   A9A995955666555555595555555556555955
//     FC  C0  13  00  04  00  00  02  08
// reveng -c -m CRC-16/MCRF4XX 13000400000208c0fc
//
cm_t var;
p_cm_t p_cm = &var;
p_cm->cm_width = 16;
p_cm->cm_poly  = 0x1021L;
p_cm->cm_init  = 0x0000FFFFL;
p_cm->cm_refin = TRUE;
p_cm->cm_refot = TRUE;
p_cm->cm_xorot = 0L;

cm_ini(p_cm);

// feed in the data bytes in forward order    *******
for (int ii = StartChar + 2 ; ii <= EndChar ; ii++) {
	cm_nxt(p_cm, Cmd_String->Str[ii]);
    }
// feed in the CRC bytes - LSB then MSB     ******
cm_nxt(p_cm, Cmd_String->Str[StartChar+1]);
cm_nxt(p_cm, Cmd_String->Str[StartChar]);
//
ulong const crc_result = cm_crc(p_cm) & 0xFFFF;

//  ******   crc_result must be zero

return crc_result;
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::charstringtobinary(char * Command, char * Packet, int Len)
{
//
// Input is Command, a (char *) to an array of hexidecimal digits in ASCII. Null Terminated. Must
//	always be an even number of digits and be equal to (Len * 2).
//	eg: "55555A566A99656966669996696A5A55695A4D"
// Input is Len, an integer count of the number of bytes in the Packet array.
//	eg:	Len = strlen(Command)/2;
// Output is Packet, a (char *) to an array of the binary representation of Command, converting/
//	two hexidecimal digits into one 8 bit byte. It will contain Len bytes. It may contain
//	a 0x00 byte.
//	eg: "UUZVjeiffijZuiZM" where the 'M' is the 0x4D End of Packet
//
assert(Command);
assert(Packet);
assert(Len < 254);

static const char* const lut = "0123456789ABCDEF";
// Convert Hex character string to Hex
for (int i = 0; i < (Len * 2); i += 2) {
    char a = Command[i];
   	const char* p = std::lower_bound(lut, lut + 16, a);
    if (*p != a) {
        return;
        }
    char b = Command[i + 1];
    const char* q = std::lower_bound(lut, lut + 16, b);
    if (*q != b) {
        return;
        }
	Packet[i/2] = (((p - lut) << 4) | (q - lut));
	}
}
//---------------------------------------------------------------------------
// Set/Reset RTS on TIU Port
void __fastcall TCommThread::RTSSwitch(bool Checked)
{
if (bRadioCOMConnected)
	Serial_port->RTS(Checked);
}
//---------------------------------------------------------------------------
// Set/Reset DTR on TIU Port
void __fastcall TCommThread::DTRSwitch(bool Checked)
{
if (bRadioCOMConnected)
	Serial_port->DTR(Checked);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Beginning of FIFO Routines
// These routines run in the context of the calling thread, not TCommThread
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Send_String_FIFO()
// Enqueue the Command_String and associated data in the FIFO
// Wait for CommDoneEvent
// Runs in the context of the calling thread, not TCommThread
// Called by almost all UI windows, PC_Thread.cpp, DL_Thread.cpp and Engine_Status_Thread.cpp
// Typical usage:
/* from TProg_Control_Thread::FetchByte()
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, CommandString, TIUNo, MyRemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, FBResp);
if (!bretv) {	// retry once
   	if (DEBUGGING) ptrTIUCommThread->PrintItem("Send_String_FIFO() Retrying " + String(CommandString));
	bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, CommandString, TIUNo, MyRemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, FBResp);
	if (!bretv) return false;	// return false with value == 0 if we can't read RAM
	}
*/
////---------------------------------------------------------------------------
bool __fastcall TCommThread::Send_String_FIFO(unsigned char Consist, byte * Command_String,
	unsigned char TIU, unsigned char Remote, int Expected, int WaitTime, bool Record_Flag, byte * ptrLashUpEngineList,
    int * ptrRespCode, int * SSResponseCount, byte * SSResponseBuffer)
{
if (TIU == 0) return true;  // Engine not found on any active TIU
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Command_String != NULL);
assert (Remote >= 1 && Remote <= REMOTEMAX);
//
String PrintLine;
bool bretv = false;
int LocalRespCode = UNDETERMINED;		// must have a response code even if the caller doesn't care so create a local spot
//
// runs in the context of the thread
//
// if CommThread is created, use it
if (ptrTIUCommThread) {	// make sure the comm thread is running
    //  Wait for response using signal event - for event communications with CommThread
	HANDLE LocalCommDoneEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
	assert(LocalCommDoneEvent != NULL);

	// allocate the FIFO list structure for one command
	struct FIFOItem * pFItem;
	assert (sizeof (struct FIFOItem) > 8);
    pFItem = new struct FIFOItem;				// deleted in TCommThread->Execute()
    pFItem->Type = CMD_Type_SendString;			// type CMD_Type_SendString command into the list
	pFItem->TVal = 0;							// execute now when calling any of the *_FIFO routines
    pFItem->ConsistNo = Consist;
    pFItem->TIUNo = TIU;
    (void) strncpy(pFItem->Command, Command_String, 128);
    pFItem->Remote = Remote;
    //pFItem->Flag = (int *) NULL;				// not used
    //pFItem->FlagValue = 0;					// not used
    pFItem->ExpectedCount = Expected;
    pFItem->WaitTime = WaitTime;
    pFItem->Record_Flag = Record_Flag;
    pFItem->ptrRespCode = &LocalRespCode;

    pFItem->ResponseCount = SSResponseCount;
	pFItem->ResponseBuffer = SSResponseBuffer;
    // use the Lashup Engine List only if the Consist number is > 101
    if ((Consist>= 101) && (ptrLashUpEngineList != NULL)) {
    	(void) strncpy(pFItem->cLashUpEngineList, ptrLashUpEngineList, LASHUP_ENGINE_LIST_LENGTH);
        } else {
		pFItem->cLashUpEngineList[0] = '\0';
        }

    pFItem->CommEvent = LocalCommDoneEvent;

    if (!FIFO) {
    	// if the FIFO does not exists, toss the command
        PrintLine = "Send_String_FIFO: FIFO does not exist";
        PrintItem(PrintLine);
    	return false;
        }

    // add to the queue
    TList *myList = ptrTIUCommThread->FIFO->LockList();
    assert(myList);
    ptrTIUCommThread->FIFO->Add(pFItem); // Thread Safe
    // Signal Comm_Thread that a command is ready
    SetEvent(CommReqEvent);
    ptrTIUCommThread->FIFO->UnlockList();
    Bump(MainForm->FIFO_Count);
    //  Wait for response using signal event
    if (DEBUGGINGX(9)) {
        PrintLine = "Send_String_FIFO: WaitForObject(LocalCommDoneEvent)";
        PrintItem(PrintLine);
        }
    DWORD dwWait;
    //
    while (true) {
        // Wait for the event to show up
        dwWait = WaitForSingleObject(LocalCommDoneEvent, DWDELAY);
        if (bEmergencyStop) {
            bretv = false;	// failure
            break;	// get out in a hurry
			}
        //
        if (dwWait == WAIT_TIMEOUT || dwWait == WAIT_FAILED) {	// DWDELAY timed out or other failure?
            // terminate Comm_Thread because of unknown failure
            // assert(false);	// this may execute if single stepping through the communications code
            // This failure mode does occur under some yet unknown circumstances.
            // With the assert() call, the program terminates. It seems to me to be
            // better to just return a failure than to terminate.
            // Caller should evaluate the failure and do something.
            bretv = false;	// failure
            break;
            }
        if (dwWait == WAIT_OBJECT_0) {	// Event Signaled ?
        	bretv = true;		// possible success
            if (DEBUGGINGX(7)) {
                PrintLine = "Send_String_FIFO() : CommDoneEvent Signaled";
                PrintItem(PrintLine, clBlack);
                }
            // TODO : is this response code check correct?
            if (LocalRespCode >= UNDETERMINED) {
                // TODO : Check UNDETERMINED Status
                bretv = false;	// failed
                }
            if (ptrRespCode) *ptrRespCode = LocalRespCode;
            break;
            }
        if (bEmergencyStop) break;	// get out in a hurry
        // keep on waiting
        }
    CloseHandle(LocalCommDoneEvent);
	return bretv;
    }
// May reach this point if the port closes before all commands are sent
// Evidenced by the comm thread destroyed (ptrTIUCommThread == NULL)
// TODO : save status info before terminating - this should never happen
SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, __LINE__ + 1, SERIAL_ERR);
Sleep(2000);
assert(false);					// but its a hard failure if it occurs
return false;
}

//---------------------------------------------------------------------------
// TIUSendWaitforResponse_FIFO()
// Enqueue the Command and associated data in the FIFO
// Wait for CommDoneEvent
// Runs in the context of the calling thread, not TCommThread
// Called only by PBFileThread.cpp
//---------------------------------------------------------------------------
bool __fastcall TCommThread::TIUSendWaitforResponse_FIFO(unsigned char Consist, byte * Command,
	int ExpectedCount, int WaitTime, unsigned char Remote, unsigned char TIU, int * ptrRespCode,
    int * SWResponseCount, byte * SWResponseBuffer)
{
assert(ExpectedCount == 0 || ExpectedCount >= MINRESPONSE); 	// ExpectedCount must be 0 or 11 or more
assert(WaitTime >= 0 && WaitTime <= 20000);	// WaitTime must be positive and less than 20 seconds
assert(Remote <= REMOTEMAX);			// Remote must be 15 or less
assert(Command != NULL);				// Command must be present
//
String PrintLine;
bool bretv = false;
int LocalRespCode = UNDETERMINED;	// must have a response code even if the caller doesn't care so create a local spot

// if CommThread is created, use it
if (ptrTIUCommThread) {	// right now, pressing the debug tab's "Connect" button creates this thread

	//  Wait for response using signal event - for event communications with CommThread
    HANDLE LocalCommDoneEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
    assert(LocalCommDoneEvent != NULL);	// make sure constructor worked

	// allocate the FIFO list structure for one command
	struct FIFOItem * pFItem;
    assert (sizeof (struct FIFOItem) > 8);
    pFItem = new struct FIFOItem;					// deleted in TCommThread->Execute()
    pFItem->Type = CMD_Type_WaitForResponse;		// type CMD_Type_WaitForResponse command into the list
	pFItem->TVal = 0;	// execute now
    pFItem->ConsistNo = Consist;
    pFItem->TIUNo = TIU;
    (void) strncpy(pFItem->Command, Command, 128);
    pFItem->Remote = Remote;
    pFItem->ExpectedCount = ExpectedCount;
    pFItem->WaitTime = WaitTime;
	pFItem->ptrRespCode = &LocalRespCode;
    pFItem->ResponseCount = SWResponseCount;
    pFItem->ResponseBuffer = SWResponseBuffer;

    pFItem->CommEvent = LocalCommDoneEvent;

    if (!FIFO) {
    	// if the FIFO does not exists, toss the command
        PrintLine = "TIUSendWaitforResponse_FIFO: FIFO does not exist";
        PrintItem(PrintLine);
    	return false;
        }

    // add to the queue
    TList *myList = ptrTIUCommThread->FIFO->LockList();
    assert(myList);
    ptrTIUCommThread->FIFO->Add(pFItem); // Thread Safe
    // Signal Comm_Thread that a command is ready
    SetEvent(CommReqEvent);
    ptrTIUCommThread->FIFO->UnlockList();
    Bump(MainForm->FIFO_Count);
    //  Wait for response using signal event
    if (DEBUGGINGX(7)) {
        PrintLine = "TIUSendWaitforResponse_FIFO: WaitForObject(CommDoneEvent)";
        PrintItem(PrintLine, clBlack);
        }
    DWORD dwWait;
    //
    for (;;) {
        // Wait for the CommDoneEvent
        dwWait = WaitForSingleObject(LocalCommDoneEvent, DWDELAY);
        if (bEmergencyStop) break;	// get out in a hurry
        //
        if (dwWait == WAIT_TIMEOUT || dwWait == WAIT_FAILED) {	// DWDELAY timed out or other failure?
            // terminate Comm_Thread because of unknown failure
            assert(false);
            bretv = false;	// failure
			break;
            }
        if (dwWait == WAIT_OBJECT_0) {	// Event Signaled ?
	        bretv = true;	// possible success
            if (DEBUGGINGX(7)) {
                PrintLine = "TIUSendWaitforResponse_FIFO() : CommDoneEvent Signaled";
                PrintItem(PrintLine, clBlack);
                }
			// *ptrRespCode is set from LocalRespCode just before the function exits
            // TODO : Check UNDETERMINED Status
            if (LocalRespCode >= UNDETERMINED) bretv = false;	// failed
			if (ptrRespCode) *ptrRespCode = LocalRespCode;
            break;
            }
        // process all messages
        bool MsgAvailable;
        MSG msg;
        while (true) {
            MsgAvailable = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);	// message available ?
            if (!MsgAvailable) break;	// no messages available
            // translate and dispatch the message
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            // check for another message
            }
        if (bEmergencyStop) break;	// get out in a hurry
        // keep on waiting
        }
    CloseHandle(LocalCommDoneEvent);
    return bretv;
    }
// May reach this point if the port closes before all commands are sent
// Evidenced by the comm thread destroyed (ptrTIUCommThread == NULL)
// TODO : save status info before terminating - this should never happen
SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, __LINE__ + 1, SERIAL_ERR);
Sleep(2000);
assert(false);					// but its a hard failure if it occurs
return false;
}

//---------------------------------------------------------------------------
// SendHexStrStr_FIFO()
// FiFO version of SendHexStr() which uses a String as input
// Runs in the context of the calling thread, not TCommThread
// Called by Train_Control.cpp
//---------------------------------------------------------------------------

bool __fastcall TCommThread::SendHexStrStr_FIFO(int Port, String Cmd_In, int Len)  // Len is actual bytes to send
{
assert(Port == TIU_PORT);
assert(Cmd_In.Length() > 0);
assert(Len > 0);
//
// convert Cmd_In from a String to unsigned char[] and pass it to SendHexStr_FIFO()
unsigned char Packet[CMDMAX];
// this routine is used the CB10.1 compile where a wchar_t needs to be converted to char
// aCmd_In must be AnsiString because the c_str() taken needs to be a char type.
AnsiString aCmd_In = Cmd_In;
charstringtobinary(aCmd_In.c_str(), Packet, Len);	// convert ASCII char string to binary
return SendHexStr_FIFO(Port, Packet, Len);
}	// SendHexStrStr(String)

//---------------------------------------------------------------------------
// SendHexStr_FIFO()
// FIFO version of SendHexStr() which uses a binary hex string as input
// Runs in the context of the calling thread, not TCommThread
//---------------------------------------------------------------------------

bool __fastcall TCommThread::SendHexStr_FIFO(int Port, byte * Cmd_In, int Len)  // Len is actual bytes to send
{
assert(Port == TIU_PORT);
assert(Cmd_In != NULL);
assert(Len > 0);
//
// Enqueue the Cmd_In and associated data in the FIFO
// Wait for CommDoneEvent
//
String PrintLine;
bool retv = false;

// Send to TIU Port only, don't wait for packet response, simple error handling
// This routine sends the command to the FIFO and waits for a CommDoneEvent

// Cmd_In is in binary and might contain 0x00 so don't take length() but use Len

// if CommThread is created, use it
if (ptrTIUCommThread && (Port == TIU_PORT)) {	// right now, pressing the debug tab's "Connect" button creates this thread

    //  Wait for response using signal event - for event communications with CommThread
    HANDLE LocalCommDoneEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
    assert(LocalCommDoneEvent != NULL);	// make sure constructor worked

	// allocate the FIFO list structure for one command
	struct FIFOItem * pFItem;
	assert (sizeof (struct FIFOItem) > 8);
	pFItem = new struct FIFOItem;				// deleted in TCommThread->Execute()
    pFItem->Type = CMD_Type_SendHex;			// type CMD_Type_SendHex command into the list
	pFItem->TVal = 0;							// execute now
    pFItem->ExpectedCount = 0;
    pFItem->WaitTime = 0;
    (void) strncpy(pFItem->Command, Cmd_In, Len);
    pFItem->Len = Len;

    pFItem->CommEvent = LocalCommDoneEvent;

    // other variables in the FIFOItem structure are not used

    if (!FIFO) {
    	// if the FIFO does not exists, toss the command
        PrintLine = "SendHexStr_FIFO: FIFO does not exist";
        PrintItem(PrintLine);
    	return false;
        }

    // add to the queue
    TList *myList = ptrTIUCommThread->FIFO->LockList();
    assert(myList);
    ptrTIUCommThread->FIFO->Add(pFItem); // Thread Safe
    // Signal Comm_Thread that a command is ready
    SetEvent(CommReqEvent);
    ptrTIUCommThread->FIFO->UnlockList();
    Bump(MainForm->FIFO_Count);
    //  Wait for response using signal event
    if (DEBUGGINGX(7)) {
        PrintLine = "SendHexStr_FIFO: WaitForObject(CommDoneEvent)";
        PrintItem(PrintLine, clBlack);
        }

    DWORD dwDelay = 1000; 	// absolute time out value of 1 second (not the same as WaitTime)
    DWORD dwWait;
    //
    for (;;) {
        // Wait for our event to show up
        dwWait = WaitForSingleObject(LocalCommDoneEvent, dwDelay);
        if (bEmergencyStop) break;	// get out in a hurry
        //
        if (dwWait == WAIT_TIMEOUT || dwWait == WAIT_FAILED) {	// dwDelay timed out or other failure?
            // terminate Comm_Thread because of unknown failure
            assert(false);
            retv = false;	// failure
            break;
            }
        if (dwWait == WAIT_OBJECT_0) {	// Event Signaled ?
			if (DEBUGGINGX(7)) {
                PrintLine = "SendHexStr_FIFO: CommDoneEvent Signaled";
                PrintItem(PrintLine, clBlack);
                }
            retv = true; 	// success
            break;
            }
        if (bEmergencyStop) break;	// get out in a hurry
        // keep on waiting
        }
    CloseHandle(LocalCommDoneEvent);
    return retv;
    }
// May reach this point if the port closes before all commands are sent
// Evidenced by the comm thread destroyed (ptrTIUCommThread == NULL)
// TODO : save status info before terminating - this should never happen
SendMessage(MainForm->Handle, CM_RADIOCOMSTATUS, __LINE__ + 1, SERIAL_ERR);
Sleep(2000);
assert(false);					// but its a hard failure if it occurs
return false;
}
//---------------------------------------------------------------------------

bool __fastcall TCommThread::SendReadRAMCommand(struct FIFOItem * PItem) {
//
// The 'V' command gets broken into 2-5 'q' commands to be sent as a group to the TIU
// It is very important that the group go to the TIU one after the other.
//
// The 'V' command is a special command made up for RTC
// Vbaaa
// where:
//    b = number of bytes to read 1,2,4
//    aaa = lowest byte address (we read up big endian so only 1, 2, or 4 big endian bytes are valid)
//
int NumBytes;
unsigned int addr, TranslatedAddr;
char cmd[15];
(void) sscanf(PItem->Command, QUERYATOM, &NumBytes, &addr);
//assert(NumBytes == 1 || NumBytes == 2 || NumBytes == 4); // this is tested in the below switch statement
assert(addr <= 0x3FF);
bool bretv = true;	// starts at true and set to false if any of the Send_String() calls fails
switch(NumBytes) {
	case 1:
		#if RAMIDLE > 0
        Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif
        TranslatedAddr = RAMTOQ(addr, 0);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv =  Send_String(PItem->ConsistNo, cmd,
            PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
            PItem->cLashUpEngineList,
            NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		break;
	case 2:
		#if RAMIDLE > 0
		Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif
		TranslatedAddr = RAMTOQ(addr, 0);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		if (!bretv) break;	// don't continue on error as the RAM value will be wrong
		//
		#if RAMIDLE > 0
		Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif
		TranslatedAddr = RAMTOQ(addr+1, 1);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		break;
	case 4:
		#if RAMIDLE > 0
		Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif
		TranslatedAddr = RAMTOQ(addr, 0);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		if (!bretv) break;	// don't continue on error as the RAM value will be wrong
		#if RAMIDLE > 0
		Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif        //
		TranslatedAddr = RAMTOQ(addr+1, 1);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		if (!bretv) break;	// don't continue on error as the RAM value will be wrong
		#if RAMIDLE > 0
        Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
        #endif        //
		TranslatedAddr = RAMTOQ(addr+2, 2);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		if (!bretv) break;	// don't continue on error as the RAM value will be wrong
		#if RAMIDLE > 0
        Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
		#endif        //
		TranslatedAddr = RAMTOQ(addr+3, 3);
		snprintf(cmd, sizeof cmd, QUERY, TranslatedAddr);
		bretv = Send_String(PItem->ConsistNo, cmd,
			PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
			PItem->cLashUpEngineList,
			NULL, NULL, NULL);	// we don't care about the response until the 'q' command
		break;
	default:
		assert(false);	// NumBytes can only be 1, 2, or 4, otherwise, it is a programming error
		return false;
	}

if (!bretv) return false;	// don't continue on error as the RAM value will be wrong
//
#if RAMIDLE > 0
Sleep(RAMIDLE);	// delay in case we are hitting the TIU too hard with 'q' commands
#endif
// Now send a 'q' command to actually get the data
bretv = Send_String(PItem->ConsistNo, "q",
	PItem->TIUNo, PItem->Remote, PItem->ExpectedCount, PItem->WaitTime, PItem->Record_Flag,
	PItem->cLashUpEngineList,
    PItem->ptrRespCode, PItem->ResponseCount, PItem->ResponseBuffer);
//
return bretv;	// all calls to Send_String must return true for this function to return true
}
//---------------------------------------------------------------------------


void speck_expand(SPECK_TYPE const K[ SPECK_KEY_LEN], SPECK_TYPE S[ SPECK_ROUNDS])
{
  SPECK_TYPE i, b = K[0];
  SPECK_TYPE a[SPECK_KEY_LEN - 1];

  for (i = 0; i < (SPECK_KEY_LEN - 1); i++)
  {
    a[i] = K[i + 1];
  }
  S[0] = b;
  for (i = 0; i < SPECK_ROUNDS - 1; i++) {
    R(a[i % (SPECK_KEY_LEN - 1)], b, i);
    S[i + 1] = b;
  }
}
//---------------------------------------------------------------------------

void speck_encrypt(SPECK_TYPE const pt[ 2], SPECK_TYPE ct[ 2], SPECK_TYPE const K[ SPECK_ROUNDS])
{
  SPECK_TYPE i;
  ct[0]=pt[0]; ct[1]=pt[1];

  for(i = 0; i < SPECK_ROUNDS; i++){
	R(ct[1], ct[0], K[i]);
  }
}
//---------------------------------------------------------------------------

void speck_decrypt(SPECK_TYPE const ct[ 2], SPECK_TYPE pt[ 2], SPECK_TYPE const K[ SPECK_ROUNDS])
{
  SPECK_TYPE i;
  pt[0]=ct[0]; pt[1]=ct[1];

  for(i = 0; i < SPECK_ROUNDS; i++){
	RR(pt[1], pt[0], K[(SPECK_ROUNDS - 1) - i]);
  }
}
//---------------------------------------------------------------------------

void speck_encrypt_combined(SPECK_TYPE const pt[ 2], SPECK_TYPE ct[ 2], SPECK_TYPE const K[ SPECK_KEY_LEN])
{
  SPECK_TYPE i, b = K[0];
  SPECK_TYPE a[SPECK_KEY_LEN - 1];
  ct[0]=pt[0]; ct[1]=pt[1];

  for (i = 0; i < (SPECK_KEY_LEN - 1); i++)
  {
	a[i] = K[i + 1];
  }

  R(ct[1], ct[0], b);
  for(i = 0; i < SPECK_ROUNDS - 1; i++){
	R(a[i % (SPECK_KEY_LEN - 1)], b, i);
	R(ct[1], ct[0], b);
  }
}
//---------------------------------------------------------------------------

void speck_decrypt_combined(SPECK_TYPE const ct[ 2], SPECK_TYPE pt[ 2], SPECK_TYPE const K[ SPECK_KEY_LEN])
{
  int i;
  SPECK_TYPE b = K[0];
  SPECK_TYPE a[SPECK_KEY_LEN - 1];
  pt[0]=ct[0]; pt[1]=ct[1];

  for (i = 0; i < (SPECK_KEY_LEN - 1); i++)
  {
	a[i] = K[i + 1];
  }

  for (i = 0; i < SPECK_ROUNDS - 1; i++)
  {
	R(a[i % (SPECK_KEY_LEN - 1)], b, i);
  }

  for(i = 0; i < SPECK_ROUNDS; i++){
	RR(pt[1], pt[0], b);
	RR(a[((SPECK_ROUNDS - 2) - i) % (SPECK_KEY_LEN - 1)], b, ((SPECK_ROUNDS - 2) - i));
  }
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXSessionAvailable(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGING) PrintItem("WTIUTXRX Session Available Error");
	return;
	}
if (DEBUGGING) PrintItem("WTIUTXRX Session Available");
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXDataAvailable(TObject *Sender,
      WORD ErrCode)
{
//
// Wait for data to be read from the WiFi connection.
// Set ResponseReady to true when all data has been received
//
// data returned in global RawRXData
//
char        cBuffer[1024];
int         Len;

TWSocket * Socket = static_cast<TWSocket *>(Sender);
Len    = Socket->Receive(cBuffer, sizeof(cBuffer));
if (DEBUGGINGX(6)) {
	PrintItem("WTIU Sin Port = " + String(Socket->PeerPort), clBlack);
	PrintItem("WTIU Dotted Sin Address = " + Socket->PeerAddr, clBlack);
    }
if (ErrCode != 0) {
	return;
	}
if (Len == 0) {
	PrintItem("WTIUTXRX Connection Closed in OnDataAvaiable()");
    return;
    }
if (Len <  0) {
	PrintItem("WTIUTXRX OnDataAvailable Last Error: " + Socket->LastError);
    return;
    }
if (Len > 0) {
    cBuffer[Len] = 0;           // Null terminate received data
    RawRXData = cBuffer;        // Return WiFi packet recevied in RawRXData
    RawRXDataReady = true;      // Indicate data is available
	if (DEBUGGINGX(5)) PrintItem("WTIUTXRX Data Available (" + String(Len) + ") : " + RawRXData.SubString(1, RawRXData.Length() - 4));
	}
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXDataSent(TObject *Sender, WORD ErrCode)
{
// Data Sent - possibly due to a bug in ICS, this method is never called
if (ErrCode != 0) {
	if (DEBUGGING) PrintItem("WTIUTXRX Data Sent Error");
	return;
	}
if (DEBUGGINGX(5)) PrintItem("WTIUTXRX Data Sent");
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXSendData(TObject *Sender, int BytesSent)
{
if (DEBUGGINGX(5)) PrintItem("WTIUTXRX Send Data " + String(BytesSent) + " bytes being sent");
RawRXData = "";         // data returned via WTIUTXRXDataAvailable() in global RawRXData
RawRXDataReady = false;  // WTIUTXRXDataAvailable() sets RawRXDataReady to true when all data has been received
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXSessionConnected(TObject *Sender,
      WORD ErrCode)
{
ConnectError = ErrCode;
TWSocket * Port = static_cast<TWSocket *>(Sender);
if (ErrCode != 0) {
	if (DEBUGGING) PrintItem("WTIUTXRX Session Connected Error #" + IntToStr(ErrCode));
    //WTIUSessionConnected = false;    // indicate session not connected
    //WTIUSessionAttemptComplete = true;  // we tried
	return;
	}
if (DEBUGGING) {
	PrintItem("WTIUTXRX TCP Sending Local Port = " + Port->GetXPort());
	PrintItem("WTIUTXRX Session Connected");
    }
//WTIUSessionConnected = true;    // indicate session connected
//WTIUSessionAttemptComplete = true;  // we tried
}
//---------------------------------------------------------------------------

void __fastcall TCommThread::WTIUTXRXSessionClosed(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGING) PrintItem("WTIUTXRX Session Closed Error");
	return;
	}
if (DEBUGGING) PrintItem("WTIUTXRX Session Closed");
}
//---------------------------------------------------------------------------

bool __fastcall TCommThread::Handshake(TWSocket * Socket)
{
assert(My_WTIU_WiFi_Mode);
bWTIUWiFiConnected = false;
// send H5 command and return the Speck cipher encryption with the H6 command

// Start 32 bit Handshake

// increment packets sent counter
Bump(MainForm->PacketsSent);

int retv = Socket->SendStr((String)"H5\r\n");	// this string is in ASCII
// data returned in global RawRXData
if(retv != 4) {
    return false;
    }

while (!RawRXDataReady) {    // wait for response from the TIU
    Socket->ProcessMessages();
    if (Terminated)
        return false;
    }
//
// response in the form "H5 1234ABCD okay\r\n"
//
if (DEBUGGING) PrintItem("H5 Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
// scan for "okay" in response return false if not seen
AnsiString OKString = RawRXData.SubString(RawRXData.Length() - 7, 4);
if (OKString != "okay") {
	bWTIUWiFiConnected = false;     // "okay" is missing
	return false;
	}

SPECK_TYPE encrypt[2] = {0};
SPECK_TYPE exp[SPECK_ROUNDS];

// get Plain Text 32 bit input from LastResponse into plain[]
// LastResponse must be AnsiString because the c_str() taken needs to be a char type.
char H5Format[] = "H5 %4hx%4hx";
retv = sscanf(RawRXData.c_str(), H5Format, &plain[1], &plain[0]);
if(retv != 2) {
    return false;
    }

speck_expand(key, exp);
speck_encrypt(plain, encrypt, exp);

if (DEBUGGING) PrintItem("Speck Cipher Encrypted : {0x" + IntToHex((uint16_t)encrypt[0], 4) + ", 0x" + IntToHex((uint16_t)encrypt[1], 4) + "}");

// increment packets sent counter
Bump(MainForm->PacketsSent);

retv = Socket->SendStr("H6" + IntToHex((uint16_t)encrypt[1], 4) + IntToHex((uint16_t)encrypt[0], 4) + "\r\n");	// this string is in ASCII
// data returned in global RawRXData
if(retv != 12) {
    return false;
    }

while (!RawRXDataReady) {    // wait for response from the TIU
    Socket->ProcessMessages();
    if (Terminated)
        return false;
    }
//
// response in the form "H61234ABCD okay\r\n"
//    
if (DEBUGGING) PrintItem("H6 Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));

// scan for "okay" in response and set/clear bWTIUWiFiConnected
OKString = RawRXData.SubString(RawRXData.Length() - 7, 4);
bWTIUWiFiConnected = (OKString == "okay");

return bWTIUWiFiConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TCommThread::GetWIUInfo(TWSocket * Socket, int * iTIU, int * iAIUCount)
{
// Returns the TIU Number 1-5 and the AIU Count 0-5
// send 'x' command

// increment packets sent counter
Bump(MainForm->PacketsSent);

int retv = Socket->SendStr((String)"x\r\n");	// this string is in ASCII
// data returned in global RawRXData
if(retv != 3) {
    return false;
    }

while (!RawRXDataReady) {    // wait for response from the TIU
    Socket->ProcessMessages();
    if (Terminated)
        return false;
    }
//
// response in the form "xXY". It will not include the "okay"
//
if (DEBUGGING) PrintItem("x Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
//
retv = sscanf(RawRXData.c_str(), "x%1d%1d", iTIU, iAIUCount);
if (retv != 2) {
	return false;
	}

// adjust TIU number for 1-base
*iTIU = *iTIU + 1;
// Save AIU Count
AIUCount[*iTIU - 1] = *iAIUCount;

// send '!' command

// increment packets sent counter
Bump(MainForm->PacketsSent);

retv = Socket->SendStr((String)"!\r\n");	// this string is in ASCII
// data returned in global RawRXData
if(retv != 3) {
    return false;
    }

while (!RawRXDataReady) {    // wait for response from the TIU
    Socket->ProcessMessages();
    if (Terminated)
        return false;
    }
//
// response
//
if (DEBUGGING) PrintItem("! Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
//

// pull out the digits - pre WTIU Format
//!30 36 31 30  okay
// ^^ ^^ ^^ ^^  <---- TIU Version -- returned by the TIU and WIU/TIU
// pull out the version string - WTIU Format
//!v1.03-0-f0ee3b4c okay\r\n->
//  ^^^^^^         <---- TIU Version -- returned by older WTIU
//!v1.3.0-20250814-1255
//  ^^^^^          <---- TIU Version -- returned by newer WTIU
int c1, c2, c3, c4;
String D1, D2, D3, D4;
String Ver;
retv = sscanf(RawRXData.c_str(), "!%x %x %x %x", &c1, &c2, &c3, &c4);   // %x requires &int
if (retv == 4) {    // old format version number
    D1 = (char) c1;
    D2 = (char) c2;
    D3 = (char) c3;
    D4 = (char) c4;

    // delete leading zero
    if (D1 == "0") D1 =   " ";
    Ver = D1 + D2 + "." + D3 + D4;
    // Save TIU Version
    TIUVersionString[*iTIU - 1] = "v" + String(Ver);
    if (TIUSelection[*iTIU - 1]) {
        SetButtonCaption(MainForm->ConnectButton, TIUVersionString[*iTIU - 1]);
        }
    //set internal TIUVersion[] array
    String sTIUVer = D1 + D2 + D3 + D4;
    TIUVersion[*iTIU - 1] = sTIUVer.ToInt();
    if (TIUVersion[*iTIU - 1] < 600) {     // DCS 5.0 supported the WIU/TIU but we require DCS 6.0 or higher
        PopupItem(MainForm->TIUVerMsg, UIPOPUPTIMEOUT, "(RTC will run but some functionality will not work)");
        }
    } else {        // new format version number
    // Early versions of the WTIU used:          1.06-0-gfd16006
    //                                           1.03-0-g0ee3b4c
    // Starting with version 1.3.0 WTIU uses:    1.3.0-20250814-1255
    char VerChar[10];
    //int retv = sscanf(RawRXData.c_str(), "!v%[0-9.]", &VerChar);
    // suggested by ChatGPT
    int retv = sscanf(RawRXData.c_str(), "!v%9[0-9.]", VerChar);
    //
    if (retv == 1 ) {
        TIUVersionString[*iTIU - 1] = "v" + String(VerChar);
        if (TIUSelection[*iTIU - 1]) {
            SetButtonCaption(MainForm->ConnectButton, TIUVersionString[*iTIU - 1]);
            }
        // Convert version number from string to integer or use 6.20
        // WTIU version numbers follow an odd sequence
        // Original WTIU was 1.02 -> 1.04 -> 1.06  -> 1.3.1   -> x.y.z
        //       converts to 102  -> 104  -> 106   -> 10301   -> xxyyzz
        if (strlen(VerChar) == 4) {
            // Early versions of the WTIU used:          1.06-0-gfd16006
            char * ptrVerChar =  strchr(VerChar, '.') + 1;
            TIUVersion[*iTIU - 1] = (atoi(&VerChar[0]) * 100) + atoi(ptrVerChar);
            } else if (strlen(VerChar) > 4) {
                // Starting with version 1.3.0 WTIU uses:    1.3.0-20250814-1255
                char * ptrVerChar =  strchr(VerChar, '.') + 1;
                char * ptrVerChar1 =  strchr(ptrVerChar, '.') + 1;
                TIUVersion[*iTIU - 1] = (atoi(&VerChar[0]) * 10000) +  (atoi(ptrVerChar) * 100) + atoi(ptrVerChar1);
                }
        } else {
        // invalid response shown by bad response format
        TIUVersionString[*iTIU - 1] = "&Connect";
        SetButtonCaption(MainForm->ConnectButton, TIUVersionString[*iTIU - 1]);
        TIUVersion[*iTIU - 1] = 0;
        }
    }
if (DEBUGGING) PrintItem("GetWIUInfo : TIU# = " + String(*iTIU) + " Version# " + TIUVersionString[*iTIU - 1] + "  Number of AIU = " + String(*iAIUCount));
assert(*iTIU >= 1 && *iTIU <= TIUMAX);
assert(*iAIUCount >= 0 && *iAIUCount <= AIUMAX);
return true;
}
//---------------------------------------------------------------------------

String __fastcall TCommThread::SendWTIUString(int TIU, String Command, int ConsistNo, int WaitTime)
{
assert(My_WTIU_WiFi_Mode);

// WaitTime (milliseconds) is the maximum time to wait for a TCP response
//
// Send Command to one or more WTIU in normal or SuperTIU mode
//
String FinalResponse = Command + " okay\r\n";   // default response
try {
    //  For this routine, we put the information into the Display Boxes only for debugging
    if (DEBUGGINGX(4)) {
        String TextValue;
        int StrLCS = Command.Length();
        SetTextValue(MainForm->H_EngNo, IntToHex(ConsistNo, 2));
        // command letter & first 4 data chars    // NOTE: String are 1-based !!
        SetTextValue(MainForm->H_Command, IntToHex(Command[1], 2));
        if (StrLCS > 1) TextValue = IntToHex(Command[2], 2); else TextValue = "-";
        SetTextValue(MainForm->H_Data1, TextValue);
        if (StrLCS > 2) TextValue = IntToHex(Command[3], 2); else TextValue = "-";
        SetTextValue(MainForm->H_Data2, TextValue);
        if (StrLCS > 3) TextValue = IntToHex(Command[4], 2); else TextValue = "-";
        SetTextValue(MainForm->H_Data3, TextValue);
        if (StrLCS > 4) TextValue = IntToHex(Command[5], 2); else TextValue = "-";
        SetTextValue(MainForm->H_Data4, TextValue);
        }

    // Some commands never run in SuperTIUMode or AllEngineMode
    byte FirstChar = Command[1];    // NOTE: Strings are 1-based
    //---------------------------------------------------------------------------
    if (TIUCommands[FirstChar]) {
        // Commands which do not run in SuperMode or AllEngineMode
        // are sent Directly to the addressed WTIU and acted on or by the WTIU

        // Send command as normal command to a specific WTIU
        if (WTIUTXRX[TIU - 1]) {
            if (DEBUGGINGX(3)) PrintItem("Send (WTIU Command) WTIU #" + String(TIU) + " Engine #" + String(ConsistNo >= 101 ? ConsistNo - 100 : ConsistNo) + " : " + Command, clGreen);
            bool bretv = WTIUSendWaitforResponse(WTIUTXRX[TIU - 1], Command, WaitTime); // data returned in global RawRXData
            if (!bretv) {
                FinalResponse = "TCP Connection timeout->\r\n";
                } else {
                if (DEBUGGINGX(3)) PrintItem("Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
                FinalResponse = RawRXData;
                }
            }

        } else
        //---------------------------------------------------------------------------
        // if we are in SuperMode and the WTIU is in TIUSuperMode[], send the command to all WTIU in TIUSuperMode[]
        if (SuperTIUMode && TIUSuperMode[TIU - 1]) {
            // SuperMode
            // Send the command to all WTIU selected for TIUSuperMode[]
            //
            // TODO : in SuperMode or AllEngineMode, when we send out multiple packets to multiple WTIU,
            // we don't make sure that the one TWIU which responds "okay" is the response
            // we return in LastResponse. Possibily a check for this should be added.
            //
            // SuperMode
            // if WTIU is in SuperMode, send the command to each WTIU
            for (int LocalTIU = 1 ; LocalTIU <= TIUMAX ; LocalTIU++) {
                if (WTIUTXRX[LocalTIU - 1] && TIUSuperMode[LocalTIU - 1]) {
                    if (DEBUGGINGX(3)) PrintItem("Send (SuperTIUMode) WTIU #" + String(LocalTIU) + ((ConsistNo >= 101) ? " Lashup " : " Engine ") + String(ConsistNo >= 101 ? ConsistNo - 100 : ConsistNo) + " : " + Command, clGreen);
                    bool bretv = WTIUSendWaitforResponse(WTIUTXRX[LocalTIU - 1], Command, WaitTime); // data returned in global RawRXData
                    if (!bretv) {
                        FinalResponse = "TCP Connection timeout->\r\n";
                        } else {
                        if (DEBUGGINGX(3)) PrintItem("Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
                        //
                        // in SuperMode, all of the WTIU will respond. One will respond as it should and
                        // the others will timeout. This is code is so we save the good response and
                        // return that as the FinalResponse
                        //
                        // TODO : A problem occurs if all of the WTIU timeout. When that happens,
                        // FinalReponse returns NULL. I've seen all of the WTIU timeout in
                        // reponse to a 'U' command. As a workaround, I initialize FinalResponse
                        // as shown above with a known good response. If all WTIU timeout
                        // in SuperMode, that known good response will be returned.
                        //
                        if (RawRXData.Pos("timeout") == 0) {
                            FinalResponse = RawRXData;   // In SuperMode, ignore timeout responses
                            }
                        }
                    }
                }
            } else
            //---------------------------------------------------------------------------
            if ((ConsistNo == ALLENGINE)) {
                //
                // AllEngineMode
                // Send the command to each active WTIU
                //
                for (int LocalTIU = 1 ; LocalTIU <= TIUMAX ; LocalTIU++) {
                    if (WTIUTXRX[LocalTIU - 1]) {
                        if (DEBUGGINGX(3)) PrintItem("Send (AllEngineMode) WTIU #" + String(LocalTIU) + ((ConsistNo >= 101) ? " Lashup " : " Engine ") + String(ConsistNo >= 101 ? ConsistNo - 100 : ConsistNo) + " : " + Command, clGreen);
                        bool bretv = WTIUSendWaitforResponse(WTIUTXRX[LocalTIU - 1], Command, WaitTime); // data returned in global RawRXData
                        if (!bretv) {
                            FinalResponse = "TCP Connection timeout->\r\n";
                            } else {
                            if (DEBUGGINGX(3)) PrintItem("Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
                            FinalResponse = RawRXData;
                            }
                        }
                    }
                } else
                //---------------------------------------------------------------------------
                // Normal Mode - send packet to one WTIU
                // if the WTIU is not in SuperMode, send the command directly to that one WTIU
                if (WTIUTXRX[TIU - 1]) {
                    if (DEBUGGINGX(3)) PrintItem("Send WTIU #" + String(TIU) + ((ConsistNo >= 101) ? " Lashup " : " Engine ") + String(ConsistNo) + " : " + Command, clGreen);
                    bool bretv = WTIUSendWaitforResponse(WTIUTXRX[TIU - 1], Command, WaitTime);  // data returned in global RawRXData
                    if (!bretv) {
                        FinalResponse = "TCP Connection timeout->\r\n";
                        } else {
                        if (DEBUGGINGX(3)) PrintItem("Response : " + RawRXData.SubString(1, RawRXData.Length() - 4));
                        FinalResponse = RawRXData;
                        }
                    }
	}
__finally {
    // increment bytes received counter
    AddCntr(MainForm->TIU_Total_Byte_Count, FinalResponse.Length());
    }

// Calling routine is responsible for checking for error indications in LastResponse
return FinalResponse;
}
//---------------------------------------------------------------------------

bool __fastcall TCommThread::WTIUSendWaitforResponse(TWSocket * socket, String Command, int WaitTime)
{
//
// Send a command to the WTIU and wait for a response with timeout
//
// data returned in global RawRXData which is filled by RXDataAvailable()
//
DWORD StartTime, NowTime;

try {
    // increment packets sent counter
    Bump(MainForm->PacketsSent);

	socket->SendStr(Command + "\r\n");	// WTIUSendWaitforResponse() - this string is in ASCII
    // data returned in global RawRXData
    //
    //if (DEBUGGINGX(3)) PrintItem("SendStr byte count " + String(retv));   // showed correct byte count
#ifndef COMPLICATEDWAY
    // periodcount was 5, increasing to 6 and 10 did not reduce timeout errors
    // Only once I set the periodcount to 100 did it show that 72 timeout periods of 200ms were
    // occuring. That was about 15 seconds. The response packet showed up
    // 15 seconds after the command was sent. The response was "TIU response failure", not the expected response.
    // At that point, RTC retried the command the the retry would work correctly within WaitTime.
    // This occurs predominately with the 'q' or 'qXXXX' commands
    // Example:
    /*
62844.560 TCommThread: Type DL_Type_SendString Command V1060 for Consist 3
62844.560 Sending Virtual Command: V1060
62844.560 Send WTIU #1 Engine #3 : q1FB0
62844.560 WTIUTXRX Send Data 7 bytes being sent
62844.764 TCP timeout period #100 of 200 ms has elapsed
62844.968 TCP timeout period #99 of 200 ms has elapsed
62845.172 TCP timeout period #98 of 200 ms has elapsed
62845.376 TCP timeout period #97 of 200 ms has elapsed
..... repeats
62859.188 TCP timeout period #29 of 200 ms has elapsed
62859.392 TCP timeout period #28 of 200 ms has elapsed
62859.576 WTIUTXRX Data Available (24) : TIU response failure
62859.576 Response : TIU response failure
62859.576 WiFi error : TIU response failure
62859.576 Send WTIU #1 Engine #3 : q1FB0
62859.576 WTIUTXRX Send Data 7 bytes being sent
62859.592 WTIUTXRX Data Available (26) : q1FB000 00 00 00  okay
62859.592 Response : q1FB000 00 00 00  okay
    */

    // The other case is where the correct response, which usually takes less than WaitTime to arrive,
    // suddenly takes 3x - 5x WaitTime. I call this an 800 millisecond timeout. This requires
    // periodcount to be at least 10 for the program to work (with a margin). Again, the response
    // is correct so a retry is not necessary.
    // Example:
    /*
149496.752 TCommThread: Type DL_Type_SendString Command V2062 for Consist 3
149496.752 Sending Virtual Command: V2062
149496.752 Send WTIU #3 Engine #3 : q1FB1
149496.752 WTIUTXRX Send Data 7 bytes being sent
149496.768 WTIUTXRX Data Available (26) : q1FB182 0F 00 00  okay
149496.768 Response : q1FB182 0F 00 00  okay
149496.768 Send WTIU #3 Engine #3 : q7FB1
149496.768 WTIUTXRX Send Data 7 bytes being sent
149496.784 WTIUTXRX Data Available (26) : q7FB100 0F 00 00  okay
149496.784 Response : q7FB100 0F 00 00  okay
149496.784 Send WTIU #3 Engine #3 : q
149496.784 WTIUTXRX Send Data 3 bytes being sent
149496.992 TCP timeout period #0 of 200 ms has elapsed
149497.184 TCP timeout period #1 of 200 ms has elapsed
149497.392 TCP timeout period #2 of 200 ms has elapsed
149497.408 WTIUTXRX Data Available (22) : q00 00 00 00  okay
149497.408 Response : q00 00 00 00  okay
    */
    //
    // 15 second and 800 millisecond Time out errors occur here.
    // Both occur on my trainroom layout with WIU 1456 with Rev K TIU connected by USB cable
    // 15 second timeout does not occur on my den test layout with WIU 18D4 and Rev I3A TIU connected by USB-Serial adapter cable.
    // 15 second timeout does not occur on my trainroom layout with WIU 18D4 and Rev I3A TIU connected by USB-Serial adapter cable.
    //
    // A command (1) is sent
    // It appears that a response is missed (ResponseReady never occurs)
    // a TCP Connection timeout is posted
    // After the timeout, the command is resent (2)
    // Then the response (1) to the first command (1) comes in
    // It is accepted
    // Next command (3) is sent
    // Then the response (2) to the second command (2) comes in
    // that is the wrong response for the command (3)
    // An Incorrect command echo error is posted
    // This repeats and continues as now every command gets the response
    // generated by the previous command
    //
    // Conclusion
    //
    // The 15 second timeout appears to be a bad WIU/TIU combination
    // The 800 millisecond timeout appears to happen with both WIU/TIU combinations
    //
	StartTime = GetTickCount(); // start timeout period
    #define NUMPERIODS 100
    int periodcount = 0;    // wait for NUMPERIODS timeout periods for response from the TIU
    //
	while (!RawRXDataReady) {    // wait for response from the WTIU
		NowTime = GetTickCount();
		if (NowTime - StartTime >= (unsigned)WaitTime) {
            // TODO : Adding the print of the periodcount showed that 72 periods of 200ms (about 15 seconds)
            // elapse before the TIU responds
            // TODO : The TIU always responds and responds correctly.
			if (DEBUGGINGX(1)) PrintItem("TCP timeout period #" + String(periodcount) + " of " + String(WaitTime) + " ms has elapsed");
            Bump(MainForm->WiFiTO);
            // Next line just showed that StartTime ane NowTime were correct
			//if (DEBUGGINGX(3)) PrintItem("NowTime " + String(NowTime) + " StartTime " + String(StartTime) + " WaitTime " + String((unsigned)WaitTime));
			StartTime = NowTime;   // reset timeout period
            periodcount++;
            if (periodcount == NUMPERIODS) {
    			throw 5;    // throw an error to the catch() statement
                }
		    }
		socket->ProcessMessages();
        SleepButHandleMessagesandRawRXDataReady(WaitTime/16); 	// release CPU time for other threads
        if (Terminated)
			return false;
        }
#else
    not used
    // SIMPLEWAY
    while (!ResponseReady) {    // wait for response from the TIU
        socket->ProcessMessages();
        if (Terminated)
            return false;
        Sleep(WaitTime/16);	    // release CPU time for other threads
        }
#endif
    }

catch(int e) {
	if (DEBUGGINGX(3)) PrintItem(Command + " TCP failure " + String(e) + " in WTIUSendWaitforResponse()");
 	return false;   // Timeout Error
	}

catch(...) {
	if (DEBUGGINGX(3)) PrintItem(Command + " TCP error in WTIUSendWaitforResponse()");
	return false;
	}    
return true;
}
//---------------------------------------------------------------------------
// https://stackoverflow.com/questions/77852665/is-there-a-windows-api-function-like-sleep-that-works-like-messageboxa
// by Remy Lebeau
//
void __fastcall TCommThread::SleepButHandleMessagesandRawRXDataReady(DWORD sleepMs)
{
    while ((sleepMs != 0) &&
           (MsgWaitForMultipleObjects(0, NULL, FALSE, sleepMs, QS_ALLINPUT) == WAIT_OBJECT_0))
    {
        DWORD startTime = GetTickCount();
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            if (RawRXDataReady) return; // exit when data is ready
        }
        if (sleepMs != INFINITE)
            sleepMs -= (GetTickCount() - startTime);
    }
}

void __fastcall TCommThread::SleepButHandleMessages(DWORD sleepMs)
{
    while ((sleepMs != 0) &&
           (MsgWaitForMultipleObjects(0, NULL, FALSE, sleepMs, QS_ALLINPUT) == WAIT_OBJECT_0))
    {
        DWORD startTime = GetTickCount();
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if (sleepMs != INFINITE)
            sleepMs -= (GetTickCount() - startTime);
    }
}
//---------------------------------------------------------------------------
bool __fastcall TCommThread::WTIUWaitforConnection(TWSocket * socket) {
//
// Connect is asynchronous (non-blocking)-> We will wait while the
// session is connecting or terminated
//
DWORD StartTime, NowTime;
try {
	StartTime = GetTickCount(); // start timeout period
	while (socket->State == wsConnecting) {    // wait for connection to the WTIU
		NowTime = GetTickCount();
		if (NowTime - StartTime >= 5000) {
   			throw 5;    // timeout, throw an error to the catch() statement
			}
		socket->ProcessMessages();
        if (Terminated)
			return false;
        }
	if (socket->State != wsConnected) throw 6;	// failed to connect, throw an error to the catch() statement
    }

catch(int e) {
    if (DEBUGGINGX(3)) {
    	if (e == 5) PrintItem("TCP Connection timeout");
	    if (e == 6) PrintItem("TCP Connection did not return connected status");
        }
	return false;
	}

catch(...) {
	if (DEBUGGINGX(3)) PrintItem("TCP Connection unknown failure");
	return false;
	}    
return true;
}
//---------------------------------------------------------------------------

